From 0c554e096cf2bf0a2bb91397143f524219693a3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20R=C3=BC=C3=9Fler?= Date: Tue, 15 Oct 2024 11:06:28 +0200 Subject: [PATCH 1/2] feat: add first 'debug' version of `gix diff tree`. It's primarily meant to better understand `gix blame`. --- gitoxide-core/src/repository/diff.rs | 82 ++++++++++++++++++++++++++++ gitoxide-core/src/repository/mod.rs | 1 + src/plumbing/main.rs | 16 ++++++ src/plumbing/options/mod.rs | 25 +++++++++ 4 files changed, 124 insertions(+) create mode 100644 gitoxide-core/src/repository/diff.rs diff --git a/gitoxide-core/src/repository/diff.rs b/gitoxide-core/src/repository/diff.rs new file mode 100644 index 00000000000..f6b51568207 --- /dev/null +++ b/gitoxide-core/src/repository/diff.rs @@ -0,0 +1,82 @@ +use gix::bstr::{BString, ByteSlice}; + +pub fn tree( + repo: gix::Repository, + out: &mut dyn std::io::Write, + old_treeish: BString, + new_treeish: BString, +) -> anyhow::Result<()> { + let old_tree_id = repo.rev_parse_single(old_treeish.as_bstr())?; + let new_tree_id = repo.rev_parse_single(new_treeish.as_bstr())?; + + let old_tree = old_tree_id.object()?.peel_to_kind(gix::object::Kind::Tree)?.into_tree(); + let new_tree = new_tree_id.object()?.peel_to_kind(gix::object::Kind::Tree)?.into_tree(); + + let changes = repo.diff_tree_to_tree(&old_tree, &new_tree, None)?; + + writeln!( + out, + "Diffing trees `{old_treeish}` ({old_tree_id}) -> `{new_treeish}` ({new_tree_id})" + )?; + writeln!(out)?; + + write_changes(out, changes)?; + + Ok(()) +} + +fn write_changes( + mut out: impl std::io::Write, + changes: Vec, +) -> Result<(), std::io::Error> { + for change in changes { + match change { + gix::diff::tree_with_rewrites::Change::Addition { + location, + id, + entry_mode, + .. + } => { + writeln!(out, "A: {location}")?; + writeln!(out, " {id}")?; + writeln!(out, " -> {:o}", entry_mode.0)?; + } + gix::diff::tree_with_rewrites::Change::Deletion { + location, + id, + entry_mode, + .. + } => { + writeln!(out, "D: {location}")?; + writeln!(out, " {id}")?; + writeln!(out, " {:o} ->", entry_mode.0)?; + } + gix::diff::tree_with_rewrites::Change::Modification { + location, + previous_id, + id, + previous_entry_mode, + entry_mode, + } => { + writeln!(out, "M: {location}")?; + writeln!(out, " {previous_id} -> {id}")?; + writeln!(out, " {:o} -> {:o}", previous_entry_mode.0, entry_mode.0)?; + } + gix::diff::tree_with_rewrites::Change::Rewrite { + source_location, + source_id, + id, + location, + source_entry_mode, + entry_mode, + .. + } => { + writeln!(out, "R: {source_location} -> {location}")?; + writeln!(out, " {source_id} -> {id}")?; + writeln!(out, " {:o} -> {:o}", source_entry_mode.0, entry_mode.0)?; + } + }; + } + + Ok(()) +} diff --git a/gitoxide-core/src/repository/mod.rs b/gitoxide-core/src/repository/mod.rs index ba8c35ef083..489d5c32e66 100644 --- a/gitoxide-core/src/repository/mod.rs +++ b/gitoxide-core/src/repository/mod.rs @@ -28,6 +28,7 @@ pub use credential::function as credential; pub mod attributes; #[cfg(feature = "clean")] pub mod clean; +pub mod diff; pub mod dirty; #[cfg(feature = "clean")] pub use clean::function::clean; diff --git a/src/plumbing/main.rs b/src/plumbing/main.rs index 983faea77dd..59df7c431e2 100644 --- a/src/plumbing/main.rs +++ b/src/plumbing/main.rs @@ -189,6 +189,22 @@ pub fn main() -> Result<()> { core::repository::merge_base(repository(Mode::Lenient)?, first, others, out, format) }, ), + Subcommands::Diff(crate::plumbing::options::diff::Platform { cmd }) => match cmd { + crate::plumbing::options::diff::SubCommands::Tree { + old_treeish, + new_treeish, + } => prepare_and_run( + "diff-tree", + trace, + verbose, + progress, + progress_keep_open, + None, + move |_progress, out, _err| { + core::repository::diff::tree(repository(Mode::Lenient)?, out, old_treeish, new_treeish) + }, + ), + }, Subcommands::Worktree(crate::plumbing::options::worktree::Platform { cmd }) => match cmd { crate::plumbing::options::worktree::SubCommands::List => prepare_and_run( "worktree-list", diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index c1813336b69..b038d3ca091 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -145,6 +145,7 @@ pub enum Subcommands { Corpus(corpus::Platform), MergeBase(merge_base::Command), Merge(merge::Platform), + Diff(diff::Platform), Worktree(worktree::Platform), /// Subcommands that need no git repository to run. #[clap(subcommand)] @@ -384,6 +385,30 @@ pub mod merge { } } +pub mod diff { + use gix::bstr::BString; + + /// Print all changes between two objects + #[derive(Debug, clap::Parser)] + pub struct Platform { + #[clap(subcommand)] + pub cmd: SubCommands, + } + + #[derive(Debug, clap::Subcommand)] + pub enum SubCommands { + /// Diff two trees by specifying their revspecs. + Tree { + /// A revspec representing the before or old tree + #[clap(value_parser = crate::shared::AsBString)] + old_treeish: BString, + /// A revspec representing the after or new tree + #[clap(value_parser = crate::shared::AsBString)] + new_treeish: BString, + }, + } +} + pub mod config { use gix::bstr::BString; From 6777ecb99306830a3353a0db24caaa69e348ca74 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 15 Oct 2024 11:08:12 +0200 Subject: [PATCH 2/2] Some minor modifications --- gitoxide-core/src/repository/diff.rs | 63 ++++++++++++++++++++-------- src/plumbing/options/mod.rs | 12 +++--- 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/gitoxide-core/src/repository/diff.rs b/gitoxide-core/src/repository/diff.rs index f6b51568207..18ca60f9e88 100644 --- a/gitoxide-core/src/repository/diff.rs +++ b/gitoxide-core/src/repository/diff.rs @@ -1,31 +1,34 @@ use gix::bstr::{BString, ByteSlice}; +use gix::objs::tree::EntryMode; +use gix::prelude::ObjectIdExt; pub fn tree( - repo: gix::Repository, + mut repo: gix::Repository, out: &mut dyn std::io::Write, old_treeish: BString, new_treeish: BString, ) -> anyhow::Result<()> { + repo.object_cache_size_if_unset(repo.compute_object_cache_size_for_tree_diffs(&**repo.index_or_empty()?)); + let old_tree_id = repo.rev_parse_single(old_treeish.as_bstr())?; let new_tree_id = repo.rev_parse_single(new_treeish.as_bstr())?; - let old_tree = old_tree_id.object()?.peel_to_kind(gix::object::Kind::Tree)?.into_tree(); - let new_tree = new_tree_id.object()?.peel_to_kind(gix::object::Kind::Tree)?.into_tree(); + let old_tree = old_tree_id.object()?.peel_to_tree()?; + let new_tree = new_tree_id.object()?.peel_to_tree()?; let changes = repo.diff_tree_to_tree(&old_tree, &new_tree, None)?; writeln!( out, - "Diffing trees `{old_treeish}` ({old_tree_id}) -> `{new_treeish}` ({new_tree_id})" + "Diffing trees `{old_treeish}` ({old_tree_id}) -> `{new_treeish}` ({new_tree_id})\n" )?; - writeln!(out)?; - - write_changes(out, changes)?; + write_changes(&repo, out, changes)?; Ok(()) } fn write_changes( + repo: &gix::Repository, mut out: impl std::io::Write, changes: Vec, ) -> Result<(), std::io::Error> { @@ -37,8 +40,8 @@ fn write_changes( entry_mode, .. } => { - writeln!(out, "A: {location}")?; - writeln!(out, " {id}")?; + writeln!(out, "A: {}", typed_location(location, entry_mode))?; + writeln!(out, " {}", id.attach(repo).shorten_or_id())?; writeln!(out, " -> {:o}", entry_mode.0)?; } gix::diff::tree_with_rewrites::Change::Deletion { @@ -47,8 +50,8 @@ fn write_changes( entry_mode, .. } => { - writeln!(out, "D: {location}")?; - writeln!(out, " {id}")?; + writeln!(out, "D: {}", typed_location(location, entry_mode))?; + writeln!(out, " {}", id.attach(repo).shorten_or_id())?; writeln!(out, " {:o} ->", entry_mode.0)?; } gix::diff::tree_with_rewrites::Change::Modification { @@ -58,9 +61,16 @@ fn write_changes( previous_entry_mode, entry_mode, } => { - writeln!(out, "M: {location}")?; - writeln!(out, " {previous_id} -> {id}")?; - writeln!(out, " {:o} -> {:o}", previous_entry_mode.0, entry_mode.0)?; + writeln!(out, "M: {}", typed_location(location, entry_mode))?; + writeln!( + out, + " {previous_id} -> {id}", + previous_id = previous_id.attach(repo).shorten_or_id(), + id = id.attach(repo).shorten_or_id() + )?; + if previous_entry_mode != entry_mode { + writeln!(out, " {:o} -> {:o}", previous_entry_mode.0, entry_mode.0)?; + } } gix::diff::tree_with_rewrites::Change::Rewrite { source_location, @@ -71,12 +81,31 @@ fn write_changes( entry_mode, .. } => { - writeln!(out, "R: {source_location} -> {location}")?; - writeln!(out, " {source_id} -> {id}")?; - writeln!(out, " {:o} -> {:o}", source_entry_mode.0, entry_mode.0)?; + writeln!( + out, + "R: {source} -> {dest}", + source = typed_location(source_location, source_entry_mode), + dest = typed_location(location, entry_mode) + )?; + writeln!( + out, + " {source_id} -> {id}", + source_id = source_id.attach(repo).shorten_or_id(), + id = id.attach(repo).shorten_or_id() + )?; + if source_entry_mode != entry_mode { + writeln!(out, " {:o} -> {:o}", source_entry_mode.0, entry_mode.0)?; + } } }; } Ok(()) } + +fn typed_location(mut location: BString, mode: EntryMode) -> BString { + if mode.is_tree() { + location.push(b'/'); + } + location +} diff --git a/src/plumbing/options/mod.rs b/src/plumbing/options/mod.rs index b038d3ca091..b6b1b005f11 100644 --- a/src/plumbing/options/mod.rs +++ b/src/plumbing/options/mod.rs @@ -372,13 +372,13 @@ pub mod merge { #[clap(long, short = 'c')] resolve_with: Option, - /// A path or revspec to our file + /// A path or revspec to our file. #[clap(value_name = "OURS", value_parser = crate::shared::AsBString)] ours: BString, - /// A path or revspec to the base for both ours and theirs + /// A path or revspec to the base for both ours and theirs. #[clap(value_name = "BASE", value_parser = crate::shared::AsBString)] base: BString, - /// A path or revspec to their file + /// A path or revspec to their file. #[clap(value_name = "OURS", value_parser = crate::shared::AsBString)] theirs: BString, }, @@ -397,12 +397,12 @@ pub mod diff { #[derive(Debug, clap::Subcommand)] pub enum SubCommands { - /// Diff two trees by specifying their revspecs. + /// Diff two trees. Tree { - /// A revspec representing the before or old tree + /// A rev-spec representing the 'before' or old tree. #[clap(value_parser = crate::shared::AsBString)] old_treeish: BString, - /// A revspec representing the after or new tree + /// A rev-spec representing the 'after' or new tree. #[clap(value_parser = crate::shared::AsBString)] new_treeish: BString, },