From b896a7fbeb0efca0b97de02b921cd51d3c2ad3d0 Mon Sep 17 00:00:00 2001 From: cloudhead Date: Wed, 27 Sep 2023 15:26:57 +0200 Subject: [PATCH] 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. --- radicle-cli/src/git/pretty_diff.rs | 68 ++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 17 deletions(-) diff --git a/radicle-cli/src/git/pretty_diff.rs b/radicle-cli/src/git/pretty_diff.rs index 976d9bd9..56345a99 100644 --- a/radicle-cli/src/git/pretty_diff.rs +++ b/radicle-cli/src/git/pretty_diff.rs @@ -1,3 +1,6 @@ +use std::fs; +use std::path::Path; + use radicle::git; use radicle_surf::diff; 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}; +/// Blob returned by the [`Repo`] trait. +pub enum Blob { + Binary, + Plain(Vec), +} + /// A repository of Git blobs. pub trait Repo { /// Lookup a blob from the repo. - fn blob(&self, oid: git::Oid) -> Result; + fn blob(&self, oid: git::Oid) -> Result; + /// Lookup a file in the workdir. + fn file(&self, path: &Path) -> Option; } impl Repo for git::raw::Repository { - fn blob(&self, oid: git::Oid) -> Result { - self.find_blob(*oid) + fn blob(&self, oid: git::Oid) -> Result { + 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 { + 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 (old, new) = match context { - FileDiff::Added(f) => (None, Some(f.new.oid)), - FileDiff::Moved(f) => (Some(f.old.oid), Some(f.new.oid)), - FileDiff::Deleted(f) => (Some(f.old.oid), None), - FileDiff::Modified(f) => (Some(f.old.oid), Some(f.new.oid)), + FileDiff::Added(f) => (None, Some((f.new.oid, f.path.clone()))), + FileDiff::Moved(f) => ( + Some((f.old.oid, f.old_path.clone())), + 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(_) => { // This is due to not having `oid`s for copied files yet. 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"))); } - let old = old.and_then(|oid| repo.blob(oid).ok()); - let new = new.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, path)| repo.blob(oid).ok().or_else(|| repo.file(&path))); let mut blobs = Blobs::default(); - if let Some(blob) = old { - if !blob.is_binary() { - blobs.old = hi.highlight(context.path(), blob.content()).ok().flatten(); - } + if let Some(Blob::Plain(content)) = old { + blobs.old = hi.highlight(context.path(), &content).ok().flatten(); } - if let Some(blob) = new { - if !blob.is_binary() { - blobs.new = hi.highlight(context.path(), blob.content()).ok().flatten(); - } + if let Some(Blob::Plain(content)) = new { + blobs.new = hi.highlight(context.path(), &content).ok().flatten(); } let mut vstack = term::VStack::default() .border(Some(term::colors::FAINT))