cli: Support working copy files in diff
Previously we only syntax highlighted files that were blobs inside the git repo. This doesn't work when diffing files in the working copy. We solve that by looking for files in the working copy if they aren't found in the ODB.
This commit is contained in:
parent
bb40347121
commit
b896a7fbeb
|
|
@ -1,3 +1,6 @@
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use radicle::git;
|
use radicle::git;
|
||||||
use radicle_surf::diff;
|
use radicle_surf::diff;
|
||||||
use radicle_surf::diff::{Diff, DiffContent, FileDiff, Hunk, Modification};
|
use radicle_surf::diff::{Diff, DiffContent, FileDiff, Hunk, Modification};
|
||||||
|
|
@ -9,15 +12,44 @@ use crate::terminal::highlight::{Highlighter, Theme};
|
||||||
|
|
||||||
use super::unified_diff::{Decode, HunkHeader};
|
use super::unified_diff::{Decode, HunkHeader};
|
||||||
|
|
||||||
|
/// Blob returned by the [`Repo`] trait.
|
||||||
|
pub enum Blob {
|
||||||
|
Binary,
|
||||||
|
Plain(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
/// A repository of Git blobs.
|
/// A repository of Git blobs.
|
||||||
pub trait Repo {
|
pub trait Repo {
|
||||||
/// Lookup a blob from the repo.
|
/// Lookup a blob from the repo.
|
||||||
fn blob(&self, oid: git::Oid) -> Result<git::raw::Blob, git::raw::Error>;
|
fn blob(&self, oid: git::Oid) -> Result<Blob, git::raw::Error>;
|
||||||
|
/// Lookup a file in the workdir.
|
||||||
|
fn file(&self, path: &Path) -> Option<Blob>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Repo for git::raw::Repository {
|
impl Repo for git::raw::Repository {
|
||||||
fn blob(&self, oid: git::Oid) -> Result<git::raw::Blob, git::raw::Error> {
|
fn blob(&self, oid: git::Oid) -> Result<Blob, git::raw::Error> {
|
||||||
self.find_blob(*oid)
|
let blob = self.find_blob(*oid)?;
|
||||||
|
|
||||||
|
if blob.is_binary() {
|
||||||
|
Ok(Blob::Binary)
|
||||||
|
} else {
|
||||||
|
Ok(Blob::Plain(blob.content().to_vec()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file(&self, path: &Path) -> Option<Blob> {
|
||||||
|
self.workdir()
|
||||||
|
.and_then(|dir| fs::read(dir.join(path)).ok())
|
||||||
|
.map(|content| {
|
||||||
|
// A file is considered binary if there is a zero byte in the first 8 kilobytes
|
||||||
|
// of the file. This is the same heuristic Git uses.
|
||||||
|
let binary = content.iter().take(8192).any(|b| *b == 0);
|
||||||
|
if binary {
|
||||||
|
Blob::Binary
|
||||||
|
} else {
|
||||||
|
Blob::Plain(content)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,10 +150,16 @@ impl ToPretty for DiffContent {
|
||||||
let theme = Theme::default();
|
let theme = Theme::default();
|
||||||
|
|
||||||
let (old, new) = match context {
|
let (old, new) = match context {
|
||||||
FileDiff::Added(f) => (None, Some(f.new.oid)),
|
FileDiff::Added(f) => (None, Some((f.new.oid, f.path.clone()))),
|
||||||
FileDiff::Moved(f) => (Some(f.old.oid), Some(f.new.oid)),
|
FileDiff::Moved(f) => (
|
||||||
FileDiff::Deleted(f) => (Some(f.old.oid), None),
|
Some((f.old.oid, f.old_path.clone())),
|
||||||
FileDiff::Modified(f) => (Some(f.old.oid), Some(f.new.oid)),
|
Some((f.new.oid, f.new_path.clone())),
|
||||||
|
),
|
||||||
|
FileDiff::Deleted(f) => (Some((f.old.oid, f.path.clone())), None),
|
||||||
|
FileDiff::Modified(f) => (
|
||||||
|
Some((f.old.oid, f.path.clone())),
|
||||||
|
Some((f.new.oid, f.path.clone())),
|
||||||
|
),
|
||||||
FileDiff::Copied(_) => {
|
FileDiff::Copied(_) => {
|
||||||
// This is due to not having `oid`s for copied files yet.
|
// This is due to not having `oid`s for copied files yet.
|
||||||
unimplemented!("DiffContent::pretty: copied files are not supported in diffs")
|
unimplemented!("DiffContent::pretty: copied files are not supported in diffs")
|
||||||
|
|
@ -154,19 +192,15 @@ impl ToPretty for DiffContent {
|
||||||
header.push(term::label(format!(" +{additions}")).fg(theme.color("positive.light")));
|
header.push(term::label(format!(" +{additions}")).fg(theme.color("positive.light")));
|
||||||
}
|
}
|
||||||
|
|
||||||
let old = old.and_then(|oid| repo.blob(oid).ok());
|
let old = old.and_then(|(oid, path)| repo.blob(oid).ok().or_else(|| repo.file(&path)));
|
||||||
let new = new.and_then(|oid| repo.blob(oid).ok());
|
let new = new.and_then(|(oid, path)| repo.blob(oid).ok().or_else(|| repo.file(&path)));
|
||||||
let mut blobs = Blobs::default();
|
let mut blobs = Blobs::default();
|
||||||
|
|
||||||
if let Some(blob) = old {
|
if let Some(Blob::Plain(content)) = old {
|
||||||
if !blob.is_binary() {
|
blobs.old = hi.highlight(context.path(), &content).ok().flatten();
|
||||||
blobs.old = hi.highlight(context.path(), blob.content()).ok().flatten();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if let Some(blob) = new {
|
if let Some(Blob::Plain(content)) = new {
|
||||||
if !blob.is_binary() {
|
blobs.new = hi.highlight(context.path(), &content).ok().flatten();
|
||||||
blobs.new = hi.highlight(context.path(), blob.content()).ok().flatten();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
let mut vstack = term::VStack::default()
|
let mut vstack = term::VStack::default()
|
||||||
.border(Some(term::colors::FAINT))
|
.border(Some(term::colors::FAINT))
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue