diff --git a/gitoxide-core/src/hours/core.rs b/gitoxide-core/src/hours/core.rs index 1fa073b325..7563e64f62 100644 --- a/gitoxide-core/src/hours/core.rs +++ b/gitoxide-core/src/hours/core.rs @@ -128,19 +128,19 @@ pub fn spawn_tree_delta_threads<'scope>( .track_filename() .track_rewrites(None) .for_each_to_obtain_tree(&to, |change| { - use gix::object::tree::diff::change::Event::*; + use gix::object::tree::diff::Change::*; changes.fetch_add(1, Ordering::Relaxed); - match change.event { + match change { Rewrite { .. } => { unreachable!("we turned that off") } - Addition { entry_mode, id } => { + Addition { entry_mode, id, .. } => { if entry_mode.is_no_tree() { files.added += 1; add_lines(line_stats, &lines_count, &mut lines, id); } } - Deletion { entry_mode, id } => { + Deletion { entry_mode, id, .. } => { if entry_mode.is_no_tree() { files.removed += 1; remove_lines(line_stats, &lines_count, &mut lines, id); @@ -151,6 +151,7 @@ pub fn spawn_tree_delta_threads<'scope>( previous_entry_mode, id, previous_id, + .. } => match (previous_entry_mode.is_blob(), entry_mode.is_blob()) { (false, false) => {} (false, true) => { diff --git a/gitoxide-core/src/query/engine/update.rs b/gitoxide-core/src/query/engine/update.rs index da332ea1b1..060a87f6e1 100644 --- a/gitoxide-core/src/query/engine/update.rs +++ b/gitoxide-core/src/query/engine/update.rs @@ -210,12 +210,17 @@ pub fn update( .track_path() .track_rewrites(Some(rewrites)) .for_each_to_obtain_tree_with_cache(&to, &mut rewrite_cache, |change| { - use gix::object::tree::diff::change::Event::*; + use gix::object::tree::diff::Change::*; change_counter.fetch_add(1, Ordering::SeqCst); - match change.event { - Addition { entry_mode, id } => { + match change { + Addition { + entry_mode, + id, + location, + .. + } => { if entry_mode.is_blob_or_symlink() { - add_lines(&mut out, change.location, &lines_counter, id); + add_lines(&mut out, location, &lines_counter, id); } } Modification { @@ -223,18 +228,14 @@ pub fn update( previous_entry_mode, id, previous_id, + location, } => match (previous_entry_mode.is_blob(), entry_mode.is_blob()) { (false, false) => {} (false, true) => { - add_lines(&mut out, change.location, &lines_counter, id); + add_lines(&mut out, location, &lines_counter, id); } (true, false) => { - add_lines( - &mut out, - change.location, - &lines_counter, - previous_id, - ); + add_lines(&mut out, location, &lines_counter, previous_id); } (true, true) => { if let Ok(cache) = @@ -266,7 +267,7 @@ pub fn update( lines_counter .fetch_add(nl, Ordering::SeqCst); out.push(FileChange { - relpath: change.location.to_owned(), + relpath: location.to_owned(), mode: FileMode::Modified, source_relpath: None, lines: Some(lines), @@ -281,19 +282,25 @@ pub fn update( } } }, - Deletion { entry_mode, id } => { + Deletion { + entry_mode, + id, + location, + .. + } => { if entry_mode.is_blob_or_symlink() { - remove_lines(&mut out, change.location, &lines_counter, id); + remove_lines(&mut out, location, &lines_counter, id); } } Rewrite { source_location, diff, copy, + location, .. } => { out.push(FileChange { - relpath: change.location.to_owned(), + relpath: location.to_owned(), source_relpath: Some(source_location.to_owned()), mode: if copy { FileMode::Copy } else { FileMode::Rename }, lines: diff.map(|d| LineStats { diff --git a/gix/src/ext/mod.rs b/gix/src/ext/mod.rs index ad69fec071..4f1818e874 100644 --- a/gix/src/ext/mod.rs +++ b/gix/src/ext/mod.rs @@ -2,7 +2,7 @@ pub use object_id::ObjectIdExt; pub use reference::ReferenceExt; #[cfg(feature = "revision")] pub use rev_spec::RevSpecExt; -pub use tree::{TreeEntryExt, TreeEntryRefExt, TreeIterExt}; +pub use tree::{TreeDiffChangeExt, TreeEntryExt, TreeEntryRefExt, TreeIterExt}; mod object_id; mod reference; diff --git a/gix/src/ext/tree.rs b/gix/src/ext/tree.rs index 72922e31b2..dc357c2cc5 100644 --- a/gix/src/ext/tree.rs +++ b/gix/src/ext/tree.rs @@ -42,9 +42,9 @@ impl TreeIterExt for TreeRefIter<'_> { } } -/// Extensions for [EntryRef][gix_object::tree::EntryRef]. +/// Extensions for [EntryRef](gix_object::tree::EntryRef). pub trait TreeEntryRefExt<'a>: 'a { - /// Attach [`Repository`][crate::Repository] to the given tree entry. It can be detached later with `detach()`. + /// Attach [`repo`](crate::Repository) to the given tree entry. It can be detached later with `detach()`. fn attach<'repo>(self, repo: &'repo crate::Repository) -> crate::object::tree::EntryRef<'repo, 'a>; } @@ -54,9 +54,9 @@ impl<'a> TreeEntryRefExt<'a> for gix_object::tree::EntryRef<'a> { } } -/// Extensions for [Entry][gix_object::tree::Entry]. +/// Extensions for [Entry](gix_object::tree::Entry). pub trait TreeEntryExt { - /// Attach [`Repository`][crate::Repository] to the given tree entry. It can be detached later with `detach()`. + /// Attach [`repo`](crate::Repository) to the given tree entry. It can be detached later with `detach()`. fn attach(self, repo: &crate::Repository) -> crate::object::tree::Entry<'_>; } @@ -65,3 +65,15 @@ impl TreeEntryExt for gix_object::tree::Entry { crate::object::tree::Entry { inner: self, repo } } } + +/// Extensions for [Change](gix_diff::tree_with_rewrites::Change). +#[cfg(feature = "blob-diff")] +pub trait TreeDiffChangeExt { + /// Attach [`old_repo`](crate::Repository) and `new_repo` to current instance. It can be detached later with `detach()`. + /// Note that both repositories are usually the same. + fn attach<'old, 'new>( + &self, + old_repo: &'old crate::Repository, + new_repo: &'new crate::Repository, + ) -> crate::object::tree::diff::Change<'_, 'old, 'new>; +} diff --git a/gix/src/object/blob.rs b/gix/src/object/blob.rs index 8d189a3bd3..c07fd31983 100644 --- a/gix/src/object/blob.rs +++ b/gix/src/object/blob.rs @@ -5,12 +5,9 @@ use crate::{Blob, ObjectDetached}; pub mod diff { use std::ops::Range; - use gix_diff::blob::{platform::prepare_diff::Operation, ResourceKind}; + use gix_diff::blob::platform::prepare_diff::Operation; - use crate::{ - bstr::ByteSlice, - object::{blob::diff::lines::Change, tree::diff::change::Event}, - }; + use crate::bstr::ByteSlice; /// A platform to keep temporary information to perform line diffs on modified blobs. /// @@ -21,99 +18,10 @@ pub mod diff { /// pub mod init { - /// The error returned by [`Platform::from_tree_change()`][super::Platform::from_tree_change()]. + /// The error returned by [`object::tree::diff::Change::diff`](crate::object::tree::diff::Change::diff()). pub type Error = gix_diff::blob::platform::set_resource::Error; } - impl<'a> Platform<'a> { - /// Produce a platform for performing various diffs after obtaining the data from a single `tree_change`. - pub fn from_tree_change( - tree_change: &crate::object::tree::diff::Change<'_, '_, '_>, - resource_cache: &'a mut gix_diff::blob::Platform, - ) -> Result, init::Error> { - match tree_change.event { - Event::Addition { entry_mode, id } => { - resource_cache.set_resource( - id.repo.object_hash().null(), - entry_mode.kind(), - tree_change.location, - ResourceKind::OldOrSource, - &id.repo.objects, - )?; - resource_cache.set_resource( - id.inner, - entry_mode.kind(), - tree_change.location, - ResourceKind::NewOrDestination, - &id.repo.objects, - )?; - } - Event::Deletion { entry_mode, id } => { - resource_cache.set_resource( - id.inner, - entry_mode.kind(), - tree_change.location, - ResourceKind::OldOrSource, - &id.repo.objects, - )?; - resource_cache.set_resource( - id.repo.object_hash().null(), - entry_mode.kind(), - tree_change.location, - ResourceKind::NewOrDestination, - &id.repo.objects, - )?; - } - Event::Modification { - previous_entry_mode, - previous_id, - entry_mode, - id, - } => { - resource_cache.set_resource( - previous_id.inner, - previous_entry_mode.kind(), - tree_change.location, - ResourceKind::OldOrSource, - &previous_id.repo.objects, - )?; - resource_cache.set_resource( - id.inner, - entry_mode.kind(), - tree_change.location, - ResourceKind::NewOrDestination, - &id.repo.objects, - )?; - } - Event::Rewrite { - source_location, - source_entry_mode, - source_id, - entry_mode, - id, - diff: _, - copy: _, - } => { - resource_cache.set_resource( - source_id.inner, - source_entry_mode.kind(), - source_location, - ResourceKind::OldOrSource, - &source_id.repo.objects, - )?; - resource_cache.set_resource( - id.inner, - entry_mode.kind(), - tree_change.location, - ResourceKind::NewOrDestination, - &id.repo.objects, - )?; - } - } - Ok(Self { resource_cache }) - } - } - /// pub mod lines { use crate::bstr::BStr; @@ -195,11 +103,11 @@ pub mod diff { let hunk_before = &lines[..end_of_before]; let hunk_after = &lines[end_of_before..]; if hunk_after.is_empty() { - err = process_hunk(Change::Deletion { lines: hunk_before }).err(); + err = process_hunk(lines::Change::Deletion { lines: hunk_before }).err(); } else if hunk_before.is_empty() { - err = process_hunk(Change::Addition { lines: hunk_after }).err(); + err = process_hunk(lines::Change::Addition { lines: hunk_after }).err(); } else { - err = process_hunk(Change::Modification { + err = process_hunk(lines::Change::Modification { lines_before: hunk_before, lines_after: hunk_after, }) diff --git a/gix/src/object/tree/diff/change.rs b/gix/src/object/tree/diff/change.rs index 9d8265ea7a..2c5e1b9c5a 100644 --- a/gix/src/object/tree/diff/change.rs +++ b/gix/src/object/tree/diff/change.rs @@ -1,173 +1,14 @@ -use crate::bstr::BString; +use crate::bstr::{BStr, ByteSlice}; use crate::ext::ObjectIdExt; -use crate::object::tree::diff::ChangeDetached; -use crate::{bstr::BStr, diff::blob::DiffLineStats, Id, Repository}; +use crate::object::tree::diff::Change; +use crate::{bstr, Repository}; +use gix_diff::tree_with_rewrites::Change as ChangeDetached; -/// An event emitted when finding differences between two trees. -#[derive(Debug, Clone, Copy)] -pub enum Event<'a, 'old, 'new> { - /// An entry was added, like the addition of a file or directory. - Addition { - /// The mode of the added entry. - entry_mode: gix_object::tree::EntryMode, - /// The object id of the added entry. - id: Id<'new>, - }, - /// An entry was deleted, like the deletion of a file or directory. - Deletion { - /// The mode of the deleted entry. - entry_mode: gix_object::tree::EntryMode, - /// The object id of the deleted entry. - id: Id<'old>, - }, - /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning - /// a file into a symbolic link adjusts its mode. - Modification { - /// The mode of the entry before the modification. - previous_entry_mode: gix_object::tree::EntryMode, - /// The object id of the entry before the modification. - previous_id: Id<'old>, - - /// The mode of the entry after the modification. - entry_mode: gix_object::tree::EntryMode, - /// The object id after the modification. - id: Id<'new>, - }, - /// Entries are considered rewritten if they are not trees and they, according to some understanding of identity, were renamed - /// or copied. - /// In case of renames, this means they originally appeared as [`Deletion`][Event::Deletion] signalling their source as well as an - /// [`Addition`][Event::Addition] acting as destination. - /// - /// In case of copies, the `copy` flag is true and typically represents a perfect copy of a source was made. - /// - /// This variant can only be encountered if [rewrite tracking][super::Platform::track_rewrites()] is enabled. - /// - /// Note that mode changes may have occurred as well, i.e. changes from executable to non-executable or vice-versa. - Rewrite { - /// The location of the source of the rename operation. - /// - /// It may be empty if neither [file names][super::Platform::track_filename()] nor [file paths][super::Platform::track_path()] - /// are tracked. - source_location: &'a BStr, - /// The mode of the entry before the rename. - source_entry_mode: gix_object::tree::EntryMode, - /// The object id of the entry before the rename. - /// - /// Note that this is the same as `id` if we require the [similarity to be 100%][super::Rewrites::percentage], but may - /// be different otherwise. - source_id: Id<'old>, - /// Information about the diff we performed to detect similarity and match the `source_id` with the current state at `id`. - /// It's `None` if `source_id` is equal to `id`, as identity made an actual diff computation unnecessary. - diff: Option, - /// The mode of the entry after the rename. - /// It could differ but still be considered a rename as we are concerned only about content. - entry_mode: gix_object::tree::EntryMode, - /// The object id after the rename. - id: Id<'new>, - /// If true, this rewrite is created by copy, and `source_id` is pointing to its source. Otherwise, it's a rename, and `source_id` - /// points to a deleted object, as renames are tracked as deletions and additions of the same or similar content. - copy: bool, - }, -} - -/// An event emitted when finding differences between two trees. -#[derive(Debug, Clone)] -pub enum EventDetached { - /// An entry was added, like the addition of a file or directory. - Addition { - /// The mode of the added entry. - entry_mode: gix_object::tree::EntryMode, - /// The object id of the added entry. - id: gix_hash::ObjectId, - }, - /// An entry was deleted, like the deletion of a file or directory. - Deletion { - /// The mode of the deleted entry. - entry_mode: gix_object::tree::EntryMode, - /// The object id of the deleted entry. - id: gix_hash::ObjectId, - }, - /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning - /// a file into a symbolic link adjusts its mode. - Modification { - /// The mode of the entry before the modification. - previous_entry_mode: gix_object::tree::EntryMode, - /// The object id of the entry before the modification. - previous_id: gix_hash::ObjectId, - - /// The mode of the entry after the modification. - entry_mode: gix_object::tree::EntryMode, - /// The object id after the modification. - id: gix_hash::ObjectId, - }, - /// Entries are considered rewritten if they are not trees and they, according to some understanding of identity, were renamed - /// or copied. - /// In case of renames, this means they originally appeared as [`Deletion`][Event::Deletion] signalling their source as well as an - /// [`Addition`][Event::Addition] acting as destination. - /// - /// In case of copies, the `copy` flag is true and typically represents a perfect copy of a source was made. - /// - /// This variant can only be encountered if [rewrite tracking][super::Platform::track_rewrites()] is enabled. - /// - /// Note that mode changes may have occurred as well, i.e. changes from executable to non-executable or vice-versa. - Rewrite { - /// The location of the source of the rename operation. - /// - /// It may be empty if neither [file names][super::Platform::track_filename()] nor [file paths][super::Platform::track_path()] - /// are tracked. - source_location: BString, - /// The mode of the entry before the rename. - source_entry_mode: gix_object::tree::EntryMode, - /// The object id of the entry before the rename. - /// - /// Note that this is the same as `id` if we require the [similarity to be 100%][super::Rewrites::percentage], but may - /// be different otherwise. - source_id: gix_hash::ObjectId, - /// Information about the diff we performed to detect similarity and match the `source_id` with the current state at `id`. - /// It's `None` if `source_id` is equal to `id`, as identity made an actual diff computation unnecessary. - diff: Option, - /// The mode of the entry after the rename. - /// It could differ but still be considered a rename as we are concerned only about content. - entry_mode: gix_object::tree::EntryMode, - /// The object id after the rename. - id: gix_hash::ObjectId, - /// If true, this rewrite is created by copy, and `source_id` is pointing to its source. Otherwise, it's a rename, and `source_id` - /// points to a deleted object, as renames are tracked as deletions and additions of the same or similar content. - copy: bool, - }, -} - -/// Lifecycle -impl super::Change<'_, '_, '_> { - /// Detach the repository instance to obtain a fully-owned version - pub fn detach(self) -> ChangeDetached { - ChangeDetached { - location: self.location.to_owned(), - event: self.event.detach(), - } - } -} - -/// Lifecycle -impl ChangeDetached { - /// Return an attached version of this instance that uses `old_repo` for previous values and `new_repo` for current values. - pub fn attach<'old, 'new>( - &self, - old_repo: &'old Repository, - new_repo: &'new Repository, - ) -> super::Change<'_, 'old, 'new> { - super::Change { - location: self.location.as_ref(), - event: self.event.attach(old_repo, new_repo), - } - } -} - -impl super::Change<'_, '_, '_> { - /// Produce a platform for performing a line-diff no matter whether the underlying [Event] is an addition, modification, +impl Change<'_, '_, '_> { + /// Produce a platform for performing a line-diff no matter whether the underlying [Change] is an addition, modification, /// deletion or rewrite. /// Use `resource_cache` to store the diffable data and possibly reuse previously stored data, usually obtained with - /// [crate::Repository::diff_resource_cache()]. + /// [Repository::diff_resource_cache()]. /// Afterward the platform, which holds on to `resource_cache`, can be used to perform ready-made operations on the /// pre-set resources. /// @@ -178,107 +19,310 @@ impl super::Change<'_, '_, '_> { &self, resource_cache: &'b mut gix_diff::blob::Platform, ) -> Result, crate::object::blob::diff::init::Error> { - crate::object::blob::diff::Platform::from_tree_change(self, resource_cache) + resource_cache.set_resource_by_change((*self).into(), &self.id().repo.objects)?; + Ok(crate::object::blob::diff::Platform { resource_cache }) + } +} + +impl<'a> From> for gix_diff::tree_with_rewrites::ChangeRef<'a> { + fn from(value: Change<'a, '_, '_>) -> Self { + use gix_diff::tree_with_rewrites::ChangeRef; + match value { + Change::Addition { + location, + entry_mode, + relation, + id, + } => ChangeRef::Addition { + location, + entry_mode, + relation, + id: id.detach(), + }, + Change::Deletion { + location, + entry_mode, + relation, + id, + } => ChangeRef::Deletion { + location, + entry_mode, + relation, + id: id.detach(), + }, + Change::Modification { + location, + previous_entry_mode, + previous_id, + entry_mode, + id, + } => ChangeRef::Modification { + location, + previous_entry_mode, + previous_id: previous_id.detach(), + entry_mode, + id: id.detach(), + }, + Change::Rewrite { + source_location, + source_relation, + source_entry_mode, + source_id, + diff, + entry_mode, + location, + id, + relation, + copy, + } => ChangeRef::Rewrite { + source_location, + source_entry_mode, + source_relation, + source_id: source_id.detach(), + diff, + entry_mode, + id: id.detach(), + location, + relation, + copy, + }, + } + } +} + +impl<'a, 'old, 'new> Change<'a, 'old, 'new> { + /// Convert `change` into this instance type, attaching the `old_repo` and `new_repo` to each side respectively. + /// Note that both repos are typically the same. + pub fn from_change_ref( + change: gix_diff::tree_with_rewrites::ChangeRef<'a>, + old_repo: &'old Repository, + new_repo: &'new Repository, + ) -> Self { + use gix_diff::tree_with_rewrites::ChangeRef; + match change { + ChangeRef::Addition { + location, + entry_mode, + relation, + id, + } => Change::Addition { + location, + entry_mode, + relation, + id: id.attach(new_repo), + }, + ChangeRef::Deletion { + location, + entry_mode, + relation, + id, + } => Change::Deletion { + location, + entry_mode, + relation, + id: id.attach(old_repo), + }, + ChangeRef::Modification { + location, + previous_entry_mode, + previous_id, + entry_mode, + id, + } => Change::Modification { + location, + previous_entry_mode, + entry_mode, + previous_id: previous_id.attach(old_repo), + id: id.attach(new_repo), + }, + ChangeRef::Rewrite { + source_location, + source_entry_mode, + source_relation, + source_id, + diff, + entry_mode, + id, + location, + relation, + copy, + } => Change::Rewrite { + source_location, + source_relation, + source_entry_mode, + source_id: source_id.attach(old_repo), + diff, + entry_mode, + location, + id: id.attach(new_repo), + relation, + copy, + }, + } } } /// Lifecycle -impl Event<'_, '_, '_> { +impl Change<'_, '_, '_> { /// Detach the repository instance to obtain a fully-owned version - pub fn detach(self) -> EventDetached { + pub fn detach(self) -> ChangeDetached { match self { - Event::Addition { entry_mode, id } => EventDetached::Addition { + Change::Addition { + entry_mode, + id, + location, + relation, + } => ChangeDetached::Addition { entry_mode, id: id.detach(), + location: location.to_owned(), + relation, }, - Event::Deletion { entry_mode, id } => EventDetached::Deletion { + Change::Deletion { + entry_mode, + id, + location, + relation, + } => ChangeDetached::Deletion { entry_mode, id: id.detach(), + location: location.to_owned(), + relation, }, - Event::Modification { + Change::Modification { previous_entry_mode, previous_id, entry_mode, id, - } => EventDetached::Modification { + location, + } => ChangeDetached::Modification { previous_entry_mode, previous_id: previous_id.detach(), entry_mode, id: id.detach(), + location: location.to_owned(), }, - Event::Rewrite { + Change::Rewrite { source_location, + source_relation, source_entry_mode, source_id, diff, entry_mode, id, + relation, copy, - } => EventDetached::Rewrite { + location, + } => ChangeDetached::Rewrite { source_location: source_location.to_owned(), source_entry_mode, + source_relation, source_id: source_id.detach(), diff, entry_mode, id: id.detach(), copy, + location: location.to_owned(), + relation, }, } } } -impl EventDetached { - /// Return an attached version of this instance that uses `old_repo` for previous values and `new_repo` for current values. - pub fn attach<'old, 'new>(&self, old_repo: &'old Repository, new_repo: &'new Repository) -> Event<'_, 'old, 'new> { +impl crate::ext::TreeDiffChangeExt for gix_diff::tree_with_rewrites::Change { + fn attach<'old, 'new>(&self, old_repo: &'old Repository, new_repo: &'new Repository) -> Change<'_, 'old, 'new> { + use bstr::ByteSlice; match self { - EventDetached::Addition { entry_mode, id } => Event::Addition { + ChangeDetached::Addition { + entry_mode, + id, + location, + relation, + } => Change::Addition { entry_mode: *entry_mode, id: id.attach(new_repo), + location: location.as_bstr(), + relation: *relation, }, - EventDetached::Deletion { entry_mode, id } => Event::Deletion { + ChangeDetached::Deletion { + entry_mode, + id, + location, + relation, + } => Change::Deletion { entry_mode: *entry_mode, id: id.attach(old_repo), + location: location.as_bstr(), + relation: *relation, }, - EventDetached::Modification { + ChangeDetached::Modification { previous_entry_mode, previous_id, entry_mode, id, - } => Event::Modification { + location, + } => Change::Modification { previous_entry_mode: *previous_entry_mode, previous_id: previous_id.attach(old_repo), entry_mode: *entry_mode, id: id.attach(new_repo), + location: location.as_bstr(), }, - EventDetached::Rewrite { + ChangeDetached::Rewrite { source_location, + source_relation, source_entry_mode, source_id, diff, entry_mode, id, copy, - } => Event::Rewrite { + location, + relation, + } => Change::Rewrite { source_location: source_location.as_ref(), + source_relation: *source_relation, source_entry_mode: *source_entry_mode, source_id: source_id.attach(old_repo), diff: *diff, entry_mode: *entry_mode, id: id.attach(new_repo), copy: *copy, + relation: *relation, + location: location.as_bstr(), }, } } } -impl Event<'_, '_, '_> { +impl Change<'_, '_, '_> { + /// Return the current ID of the change. + pub fn id(&self) -> crate::Id<'_> { + match self { + Change::Addition { id, .. } + | Change::Deletion { id, .. } + | Change::Modification { id, .. } + | Change::Rewrite { id, .. } => *id, + } + } + + /// Return the location of this instance. + pub fn location(&self) -> &BStr { + match self { + Change::Addition { location, .. } + | Change::Deletion { location, .. } + | Change::Modification { location, .. } + | Change::Rewrite { location, .. } => location.as_bstr(), + } + } + /// Return the current mode of this instance. pub fn entry_mode(&self) -> gix_object::tree::EntryMode { match self { - Event::Addition { entry_mode, .. } - | Event::Deletion { entry_mode, .. } - | Event::Modification { entry_mode, .. } - | Event::Rewrite { entry_mode, .. } => *entry_mode, + Change::Addition { entry_mode, .. } + | Change::Deletion { entry_mode, .. } + | Change::Modification { entry_mode, .. } + | Change::Rewrite { entry_mode, .. } => *entry_mode, } } } diff --git a/gix/src/object/tree/diff/for_each.rs b/gix/src/object/tree/diff/for_each.rs index 2582f76576..1fce28ea2e 100644 --- a/gix/src/object/tree/diff/for_each.rs +++ b/gix/src/object/tree/diff/for_each.rs @@ -1,20 +1,14 @@ use gix_object::TreeRefIter; -use super::{change, Action, Change, Platform}; -use crate::{ - bstr::BStr, - diff::{rewrites, rewrites::tracker}, - ext::ObjectIdExt, - object::tree::diff, - Repository, Tree, -}; +use super::{Action, Change, Platform}; +use crate::{diff::rewrites::tracker, Tree}; /// The error return by methods on the [diff platform][Platform]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] - Diff(#[from] gix_diff::tree::Error), + Diff(#[from] gix_diff::tree_with_rewrites::Error), #[error("The user-provided callback failed")] ForEach(#[source] Box), #[error(transparent)] @@ -23,13 +17,6 @@ pub enum Error { RenameTracking(#[from] tracker::emit::Error), } -/// -#[derive(Clone, Debug, Copy, PartialEq)] -pub struct Outcome { - /// Available only if [rewrite-tracking was enabled][Platform::track_rewrites()]. - pub rewrites: Option, -} - /// Add the item to compare to. impl<'old> Platform<'_, 'old> { /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`. @@ -40,7 +27,7 @@ impl<'old> Platform<'_, 'old> { &mut self, other: &Tree<'new>, for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result, - ) -> Result + ) -> Result, Error> where E: Into>, { @@ -62,7 +49,7 @@ impl<'old> Platform<'_, 'old> { other: &Tree<'new>, resource_cache: &mut gix_diff::blob::Platform, for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result, - ) -> Result + ) -> Result, Error> where E: Into>, { @@ -72,289 +59,37 @@ impl<'old> Platform<'_, 'old> { fn for_each_to_obtain_tree_inner<'new, E>( &mut self, other: &Tree<'new>, - for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result, + mut for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result, resource_cache: Option<&mut gix_diff::blob::Platform>, - ) -> Result + ) -> Result, Error> where E: Into>, { let repo = self.lhs.repo; - let mut delegate = Delegate { - src_tree: self.lhs, - other_repo: other.repo, - recorder: gix_diff::tree::Recorder::default().track_location(self.tracking), - visit: for_each, - location: self.tracking, - tracked: self.rewrites.map(rewrites::Tracker::new), - err: None, - }; - match gix_diff::tree( - TreeRefIter::from_bytes(&self.lhs.data), - TreeRefIter::from_bytes(&other.data), - &mut self.state, - &repo.objects, - &mut delegate, - ) { - Ok(()) => { - let outcome = Outcome { - rewrites: delegate.process_tracked_changes(resource_cache)?, - }; - match delegate.err { - Some(err) => Err(Error::ForEach(err.into())), - None => Ok(outcome), - } - } - Err(gix_diff::tree::Error::Cancelled) => delegate - .err - .map_or(Err(Error::Diff(gix_diff::tree::Error::Cancelled)), |err| { - Err(Error::ForEach(err.into())) - }), - Err(err) => Err(err.into()), - } - } -} - -struct Delegate<'a, 'old, 'new, VisitFn, E> { - src_tree: &'a Tree<'old>, - other_repo: &'new Repository, - recorder: gix_diff::tree::Recorder, - visit: VisitFn, - tracked: Option>, - location: Option, - err: Option, -} - -impl<'old, 'new, VisitFn, E> Delegate<'_, 'old, 'new, VisitFn, E> -where - VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result, - E: Into>, -{ - /// Call `visit` on an attached version of `change`. - fn emit_change( - change: gix_diff::tree::visit::Change, - location: &BStr, - visit: &mut VisitFn, - repo: &'old Repository, - other_repo: &'new Repository, - stored_err: &mut Option, - ) -> gix_diff::tree::visit::Action { - use gix_diff::tree::visit::Change::*; - let event = match change { - Addition { - entry_mode, - oid, - relation: _, - } => change::Event::Addition { - entry_mode, - id: oid.attach(other_repo), - }, - Deletion { - entry_mode, - oid, - relation: _, - } => change::Event::Deletion { - entry_mode, - id: oid.attach(repo), - }, - Modification { - previous_entry_mode, - previous_oid, - entry_mode, - oid, - } => change::Event::Modification { - previous_entry_mode, - entry_mode, - previous_id: previous_oid.attach(repo), - id: oid.attach(other_repo), - }, - }; - match visit(Change { event, location }) { - Ok(Action::Cancel) => gix_diff::tree::visit::Action::Cancel, - Ok(Action::Continue) => gix_diff::tree::visit::Action::Continue, - Err(err) => { - *stored_err = Some(err); - gix_diff::tree::visit::Action::Cancel - } - } - } - - fn process_tracked_changes( - &mut self, - diff_cache: Option<&mut gix_diff::blob::Platform>, - ) -> Result, Error> { - let tracked = match self.tracked.as_mut() { - Some(t) => t, - None => return Ok(None), - }; - - let repo = self.src_tree.repo; let mut storage; - let diff_cache = match diff_cache { - Some(cache) => cache, + let cache = match resource_cache { None => { storage = repo.diff_resource_cache(gix_diff::blob::pipeline::Mode::ToGit, Default::default())?; &mut storage } + Some(cache) => cache, }; - - let outcome = tracked.emit( - |dest, source| match source { - Some(source) => { - let (oid, mode) = dest.change.oid_and_entry_mode(); - let change = diff::Change { - location: dest.location, - event: diff::change::Event::Rewrite { - source_location: source.location, - source_entry_mode: source.entry_mode, - source_id: source.id.attach(self.src_tree.repo), - entry_mode: mode, - id: oid.to_owned().attach(self.other_repo), - diff: source.diff, - copy: match source.kind { - tracker::visit::SourceKind::Rename => false, - tracker::visit::SourceKind::Copy => true, - }, - }, - }; - match (self.visit)(change) { - Ok(Action::Cancel) => gix_diff::tree::visit::Action::Cancel, - Ok(Action::Continue) => gix_diff::tree::visit::Action::Continue, - Err(err) => { - self.err = Some(err); - gix_diff::tree::visit::Action::Cancel - } - } - } - None => Self::emit_change( - dest.change, - dest.location, - &mut self.visit, - self.src_tree.repo, - self.other_repo, - &mut self.err, - ), + Ok(gix_diff::tree_with_rewrites( + TreeRefIter::from_bytes(&self.lhs.data), + TreeRefIter::from_bytes(&other.data), + cache, + &mut self.state, + &repo.objects, + |change| { + for_each(Change::from_change_ref(change, repo, other.repo)).map(|action| match action { + Action::Continue => gix_diff::tree_with_rewrites::Action::Continue, + Action::Cancel => gix_diff::tree_with_rewrites::Action::Cancel, + }) }, - diff_cache, - &self.src_tree.repo.objects, - |push| { - self.src_tree - .traverse() - .breadthfirst(&mut tree_to_changes::Delegate::new(push, self.location)) + gix_diff::tree_with_rewrites::Options { + location: self.location, + rewrites: self.rewrites, }, - )?; - Ok(Some(outcome)) - } -} - -impl<'old, 'new, VisitFn, E> gix_diff::tree::Visit for Delegate<'_, 'old, 'new, VisitFn, E> -where - VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result, - E: Into>, -{ - fn pop_front_tracked_path_and_set_current(&mut self) { - self.recorder.pop_front_tracked_path_and_set_current(); - } - - fn push_back_tracked_path_component(&mut self, component: &BStr) { - self.recorder.push_back_tracked_path_component(component); - } - - fn push_path_component(&mut self, component: &BStr) { - self.recorder.push_path_component(component); - } - - fn pop_path_component(&mut self) { - self.recorder.pop_path_component(); - } - - fn visit(&mut self, change: gix_diff::tree::visit::Change) -> gix_diff::tree::visit::Action { - match self.tracked.as_mut() { - Some(tracked) => tracked.try_push_change(change, self.recorder.path()).map_or( - gix_diff::tree::visit::Action::Continue, - |change| { - Self::emit_change( - change, - self.recorder.path(), - &mut self.visit, - self.src_tree.repo, - self.other_repo, - &mut self.err, - ) - }, - ), - None => Self::emit_change( - change, - self.recorder.path(), - &mut self.visit, - self.src_tree.repo, - self.other_repo, - &mut self.err, - ), - } - } -} - -mod tree_to_changes { - use gix_diff::tree::visit::Change; - use gix_object::tree::EntryRef; - - use crate::bstr::BStr; - - pub struct Delegate<'a> { - push: &'a mut dyn FnMut(Change, &BStr), - recorder: gix_traverse::tree::Recorder, - } - - impl<'a> Delegate<'a> { - pub fn new( - push: &'a mut dyn FnMut(Change, &BStr), - location: Option, - ) -> Self { - let location = location.map(|t| match t { - gix_diff::tree::recorder::Location::FileName => gix_traverse::tree::recorder::Location::FileName, - gix_diff::tree::recorder::Location::Path => gix_traverse::tree::recorder::Location::Path, - }); - Self { - push, - recorder: gix_traverse::tree::Recorder::default().track_location(location), - } - } - } - - impl gix_traverse::tree::Visit for Delegate<'_> { - fn pop_front_tracked_path_and_set_current(&mut self) { - self.recorder.pop_front_tracked_path_and_set_current(); - } - - fn push_back_tracked_path_component(&mut self, component: &BStr) { - self.recorder.push_back_tracked_path_component(component); - } - - fn push_path_component(&mut self, component: &BStr) { - self.recorder.push_path_component(component); - } - - fn pop_path_component(&mut self) { - self.recorder.pop_path_component(); - } - - fn visit_tree(&mut self, _entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action { - gix_traverse::tree::visit::Action::Continue - } - - fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action { - if entry.mode.is_blob() { - (self.push)( - Change::Modification { - previous_entry_mode: entry.mode, - previous_oid: gix_hash::ObjectId::null(entry.oid.kind()), - entry_mode: entry.mode, - oid: entry.oid.to_owned(), - }, - self.recorder.path(), - ); - } - gix_traverse::tree::visit::Action::Continue - } + )?) } } diff --git a/gix/src/object/tree/diff/mod.rs b/gix/src/object/tree/diff/mod.rs index 354315cbbc..72dd48f404 100644 --- a/gix/src/object/tree/diff/mod.rs +++ b/gix/src/object/tree/diff/mod.rs @@ -1,7 +1,7 @@ +use gix_diff::tree; use gix_diff::tree::recorder::Location; -use crate::bstr::BString; -use crate::{bstr::BStr, diff::Rewrites, Tree}; +use crate::{bstr::BStr, diff::Rewrites, Id, Tree}; /// Returned by the `for_each` function to control flow. #[derive(Default, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] @@ -15,25 +15,100 @@ pub enum Action { /// Represents any possible change in order to turn one tree into another. #[derive(Debug, Clone, Copy)] -pub struct Change<'a, 'old, 'new> { - /// The location of the file or directory described by `event`, if tracking was enabled. +pub enum Change<'a, 'old, 'new> { + /// An entry was added, like the addition of a file or directory. + Addition { + /// The location of the file or directory, if [tracking](Platform::track_path) was enabled. + /// + /// It may be empty if neither [file names](Platform::track_filename()) nor [file paths](Platform::track_path()) + /// are tracked. + location: &'a BStr, + /// The mode of the added entry. + entry_mode: gix_object::tree::EntryMode, + /// Identifies a relationship between this instance and another one, + /// making it easy to reconstruct the top-level of directory changes. + relation: Option, + /// The object id of the added entry. + id: Id<'new>, + }, + /// An entry was deleted, like the deletion of a file or directory. + Deletion { + /// The location of the file or directory, if [tracking](Platform::track_path) was enabled. + /// + /// Otherwise, this value is always an empty path. + location: &'a BStr, + /// The mode of the deleted entry. + entry_mode: gix_object::tree::EntryMode, + /// Identifies a relationship between this instance and another one, + /// making it easy to reconstruct the top-level of directory changes. + relation: Option, + /// The object id of the deleted entry. + id: Id<'old>, + }, + /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning + /// a file into a symbolic link adjusts its mode. + Modification { + /// The location of the file or directory, if [tracking](Platform::track_path) was enabled. + /// + /// It may be empty if neither [file names](Platform::track_filename()) nor [file paths](Platform::track_path()) + /// are tracked. + location: &'a BStr, + /// The mode of the entry before the modification. + previous_entry_mode: gix_object::tree::EntryMode, + /// The object id of the entry before the modification. + previous_id: Id<'old>, + + /// The mode of the entry after the modification. + entry_mode: gix_object::tree::EntryMode, + /// The object id after the modification. + id: Id<'new>, + }, + /// Entries are considered rewritten if they are not trees and they, according to some understanding of identity, were renamed + /// or copied. + /// In case of renames, this means they originally appeared as [`Deletion`](Change::Deletion) signalling their source as well as an + /// [`Addition`](Change::Addition) acting as destination. /// - /// Otherwise, this value is always an empty path. - pub location: &'a BStr, - /// The diff event itself to provide information about what would need to change. - pub event: change::Event<'a, 'old, 'new>, -} - -/// Represents any possible change in order to turn one tree into another, the fully owned version -/// of [`Change`]. -#[derive(Debug, Clone)] -pub struct ChangeDetached { - /// The location of the file or directory described by `event`, if tracking was enabled. + /// In case of copies, the `copy` flag is true and typically represents a perfect copy of a source was made. + /// + /// This variant can only be encountered if [rewrite tracking](Platform::track_rewrites()) is enabled. /// - /// Otherwise, this value is always an empty path. - pub location: BString, - /// The diff event itself to provide information about what would need to change. - pub event: change::EventDetached, + /// Note that mode changes may have occurred as well, i.e. changes from executable to non-executable or vice-versa. + Rewrite { + /// The location of the source of the rename operation. + /// + /// It may be empty if neither [file names](Platform::track_filename()) nor [file paths](Platform::track_path()) + /// are tracked. + source_location: &'a BStr, + /// Identifies a relationship between the source and another source, + /// making it easy to reconstruct the top-level of directory changes. + source_relation: Option, + /// The mode of the entry before the rename. + source_entry_mode: gix_object::tree::EntryMode, + /// The object id of the entry before the rename. + /// + /// Note that this is the same as `id` if we require the [similarity to be 100%](Rewrites::percentage), but may + /// be different otherwise. + source_id: Id<'old>, + /// Information about the diff we performed to detect similarity and match the `source_id` with the current state at `id`. + /// It's `None` if `source_id` is equal to `id`, as identity made an actual diff computation unnecessary. + diff: Option, + /// The mode of the entry after the rename. + /// It could differ but still be considered a rename as we are concerned only about content. + entry_mode: gix_object::tree::EntryMode, + /// The location of the destination file or directory, if [tracking](Platform::track_path) was enabled. + /// + /// It may be empty if neither [file names](Platform::track_filename()) nor [file paths](Platform::track_path()) + /// are tracked. + location: &'a BStr, + /// The object id after the rename. + id: Id<'new>, + /// Identifies a relationship between this destination and another destination, + /// making it easy to reconstruct the top-level of directory changes. + relation: Option, + /// If true, this rewrite is created by copy, and `source_id` is pointing to its source. Otherwise, it's a rename, and `source_id` + /// points to a deleted object, as renames are tracked as deletions and additions of the same or similar content. + copy: bool, + }, } /// @@ -58,7 +133,7 @@ impl<'repo> Tree<'repo> { Ok(Platform { state: Default::default(), lhs: self, - tracking: None, + location: None, rewrites: self.repo.config.diff_renames()?.unwrap_or_default().into(), }) } @@ -69,7 +144,7 @@ impl<'repo> Tree<'repo> { pub struct Platform<'a, 'repo> { state: gix_diff::tree::State, lhs: &'a Tree<'repo>, - tracking: Option, + location: Option, rewrites: Option, } @@ -77,7 +152,7 @@ pub struct Platform<'a, 'repo> { impl Platform<'_, '_> { /// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item. pub fn track_filename(&mut self) -> &mut Self { - self.tracking = Some(Location::FileName); + self.location = Some(Location::FileName); self } @@ -85,7 +160,7 @@ impl Platform<'_, '_> { /// /// This makes the [`location`][Change::location] field usable. pub fn track_path(&mut self) -> &mut Self { - self.tracking = Some(Location::Path); + self.location = Some(Location::Path); self } diff --git a/gix/tests/object/tree/diff.rs b/gix/tests/object/tree/diff.rs index 0fc92644d9..477356b3bd 100644 --- a/gix/tests/object/tree/diff.rs +++ b/gix/tests/object/tree/diff.rs @@ -1,9 +1,6 @@ use std::convert::Infallible; -use gix::{ - bstr::BString, - object::{blob::diff::lines::Change, tree::diff::change::Event}, -}; +use gix::object::{blob::diff::lines, tree::diff::Change}; use gix_object::{bstr::ByteSlice, tree::EntryKind}; use crate::named_repo; @@ -27,13 +24,18 @@ fn changes_against_tree_modified() -> crate::Result { let (expected_previous_entry_mode, expected_previous_data, expected_entry_mode, expected_data) = expected_modifications[i]; - assert_eq!(change.location, "", "without configuration the location field is empty"); - match change.event { - Event::Modification { + assert_eq!( + change.location(), + "", + "without configuration the location field is empty" + ); + match change { + Change::Modification { previous_entry_mode, previous_id, entry_mode, id, + .. } => { assert_eq!(previous_entry_mode.kind(), expected_previous_entry_mode); assert_eq!(entry_mode.kind(), expected_entry_mode); @@ -46,7 +48,7 @@ fn changes_against_tree_modified() -> crate::Result { assert_eq!(previous_id.object().unwrap().data.as_bstr(), expected_previous_data); assert_eq!(id.object().unwrap().data.as_bstr(), expected_data); } - Event::Rewrite { .. } | Event::Deletion { .. } | Event::Addition { .. } => { + Change::Rewrite { .. } | Change::Deletion { .. } | Change::Addition { .. } => { unreachable!("only modification is expected") } }; @@ -57,12 +59,12 @@ fn changes_against_tree_modified() -> crate::Result { assert_eq!(count.removals, 0); diff.lines(|hunk| { match hunk { - Change::Deletion { .. } => unreachable!("there was no deletion"), - Change::Addition { lines } => assert_eq!( + lines::Change::Deletion { .. } => unreachable!("there was no deletion"), + lines::Change::Addition { lines } => assert_eq!( lines, vec![expected_data[expected_previous_data.len()..].as_bytes().as_bstr()] ), - Change::Modification { .. } => unreachable!("there was no modification"), + lines::Change::Modification { .. } => unreachable!("there was no modification"), }; Ok::<_, Infallible>(()) }) @@ -86,310 +88,21 @@ fn changes_against_tree_modified() -> crate::Result { Ok(()) } -#[test] -fn changes_against_tree_with_filename_tracking() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = repo.empty_tree(); - let to = tree_named(&repo, ":/c1 - initial"); - - let mut expected = vec!["a", "b", "c", "d"]; - from.changes()? - .track_filename() - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - expected.retain(|name| name != change.location); - Ok(Default::default()) - })?; - assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen"); - - let mut expected = vec!["a", "b", "dir/c", "d"]; - from.changes()? - .track_path() - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - expected.retain(|name| name != change.location); - Ok(Default::default()) - })?; - assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen"); - - let err = from - .changes()? - .track_path() - .for_each_to_obtain_tree(&to, |_change| { - Err(std::io::Error::new(std::io::ErrorKind::Other, "custom error")) - }) - .unwrap_err(); - assert_eq!( - err.to_string(), - "The user-provided callback failed", - "custom errors made visible and not squelched" - ); - Ok(()) -} - -#[test] -fn changes_against_modified_tree_with_filename_tracking() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/c3-modification}~1"); - let to = tree_named(&repo, ":/c3-modification"); - - let mut expected = vec!["a", "dir", "c"]; - from.changes()? - .track_filename() - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - expected.retain(|name| name != change.location); - Ok(Default::default()) - })?; - assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen"); - - let mut expected = vec!["a", "dir", "dir/c"]; - from.changes()? - .track_path() - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - expected.retain(|name| name != change.location); - Ok(Default::default()) - })?; - assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen"); - - let err = from - .changes()? - .track_path() - .for_each_to_obtain_tree(&to, |_change| { - Err(std::io::Error::new(std::io::ErrorKind::Other, "custom error")) - }) - .unwrap_err(); - assert_eq!( - err.to_string(), - "The user-provided callback failed", - "custom errors made visible and not squelched" - ); - Ok(()) -} - -fn tree_named(repo: &gix::Repository, rev_spec: impl AsRef) -> gix::Tree { - repo.rev_parse_single(rev_spec.as_ref()) - .unwrap() - .object() - .unwrap() - .peel_to_kind(gix::object::Kind::Tree) - .unwrap() - .into_tree() -} - mod track_rewrites { use std::collections::HashMap; use std::convert::Infallible; use gix::{ diff::{ - blob::DiffLineStats, rewrites::{Copies, CopySource}, Rewrites, }, - object::tree::diff::change::Event, + object::tree::diff::Change, }; use gix_ref::bstr::BStr; + use crate::object::tree::diff::tree_named; use crate::util::named_subrepo_opts; - use crate::{ - object::tree::diff::{added, deleted, modified, store, tree_named}, - util::named_repo, - }; - - #[test] - fn renames_by_identity() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - for (commit_msg, expected, assert_msg) in [ - ( - "r1-identity", - vec![BStr::new("a"), "dir/a-moved".into()], - "one rename and nothing else", - ), - ( - "r2-ambiguous", - vec![ - "s1".into(), - "b1".into(), - "s2".into(), - "b2".into(), - "s3".into(), - "z".into(), - ], - "multiple possible sources decide by ordering everything lexicographically", - ), - ( - "r4-symlinks", - vec!["link-1".into(), "renamed-link-1".into()], - "symlinks are only tracked by identity", - ), - ( - "c4 - add identical files", - vec![], - "not having any renames is OK as well", - ), - ("tc1-identity", vec![], "copy tracking is off by default"), - ] { - let from = tree_named(&repo, format!("@^{{/{commit_msg}}}~1")); - let to = tree_named(&repo, format!(":/{commit_msg}")); - - for percentage in [None, Some(0.5)] { - let mut actual = Vec::new(); - #[cfg_attr(windows, allow(unused_variables))] - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - percentage, - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - source_location, copy, .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - assert!(!copy); - } - } - Ok(Default::default()) - })?; - assert_eq!(actual, expected, "{assert_msg}"); - #[cfg(not(windows))] - assert_eq!( - out.rewrites.expect("present as its configured").num_similarity_checks, - 0, - "even though fuzzy checks are enabled, we don't end up using them" - ); - } - } - Ok(()) - } - - #[test] - fn rename_by_similarity() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/r3-simple}~1"); - let to = tree_named(&repo, ":/r3-simple"); - - for percentage in [ - None, - Some(0.76), /*cutoff point where git stops seeing it as equal */ - ] { - let mut actual = Vec::new(); - let mut rewrite_count = 0; - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - percentage, - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { .. } = change.event { - rewrite_count += 0; - } else { - actual.push(change.location.to_owned()); - } - } - Ok(Default::default()) - })?; - assert_eq!( - actual, - vec![BStr::new("b"), "dir/c".into(), "dir/c-moved".into()], - "these items include no rewrite as the cut-off is chosen accordingly" - ); - if percentage.is_some() { - assert_eq!( - out.rewrites - .expect("always set as rewrite tracking is configured") - .num_similarity_checks, - 1 - ); - } - } - - let mut actual = Vec::new(); - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - percentage: Some(0.6), - limit: 1, // has no effect as it's just one item here. - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - source_location, copy, .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - assert!(!copy); - } - } - Ok(Default::default()) - })?; - assert_eq!( - actual, - vec![BStr::new("dir/c"), "dir/c-moved".into()], - "it found all items at the cut-off point, similar to git" - ); - let out = out.rewrites.expect("tracking enabled"); - assert_eq!(out.num_similarity_checks, 1); - assert_eq!(out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 0); - assert_eq!(out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, 0); - - Ok(()) - } - - #[test] - fn renames_by_similarity_with_limit() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/r5}~1"); - let to = tree_named(&repo, ":/r5"); - - let mut actual = Vec::new(); - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - limit: 1, // prevent fuzzy tracking from happening - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { .. } = change.event { - unreachable!("fuzzy tracking is effecitively disabled due to limit"); - } - actual.push(change.location.to_owned()); - } - Ok(Default::default()) - })?; - assert_eq!( - actual, - vec![BStr::new("f1"), "f1-renamed".into(), "f2".into(), "f2-renamed".into()], - ); - let out = out.rewrites.expect("tracking enabled"); - assert_eq!(out.num_similarity_checks, 0); - assert_eq!(out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 4); - assert_eq!(out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, 0); - - Ok(()) - } #[test] #[cfg_attr( @@ -434,22 +147,23 @@ mod track_rewrites { .into(), ) .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if let Event::Rewrite { + if let Change::Rewrite { source_location, diff: Some(diff), + location, .. - } = change.event + } = change { // Round to percentage points to avoid floating point error. let similarity = (diff.similarity * 100.0) as u32; - let v = expected.remove(change.location); + let v = expected.remove(location); assert_eq!(v, Some((source_location, similarity))); } Ok(Default::default()) })?; assert_eq!(expected, HashMap::new()); - let out = out.rewrites.expect("tracking enabled"); + let out = out.expect("tracking enabled"); assert_eq!( out.num_similarity_checks, 21, "this probably increases once the algorithm improves" @@ -465,842 +179,14 @@ mod track_rewrites { Ok(()) } - - #[test] - fn copies_by_identity() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/tc1-identity}~1"); - let to = tree_named(&repo, ":/tc1-identity"); - - let mut actual = Vec::new(); - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - copies: Some(Copies { - source: CopySource::FromSetOfModifiedFiles, - percentage: None, - }), - limit: 1, // the limit isn't actually used for identity based checks - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - source_location, copy, .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - assert!(copy); - } - } - Ok(Default::default()) - })?; - assert_eq!( - actual, - vec![ - BStr::new("base"), - "c1".into(), - "base".into(), - "c2".into(), - "base".into(), - "dir/c3".into() - ], - ); - let out = out.rewrites.expect("tracking enabled"); - assert_eq!(out.num_similarity_checks, 0); - assert_eq!(out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 0); - assert_eq!(out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, 0); - - Ok(()) - } - - #[test] - fn copies_by_similarity() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/tc2-similarity}~1"); - let to = tree_named(&repo, ":/tc2-similarity"); - - let mut actual = Vec::new(); - let mut stat = None; - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - copies: Some(Copies::default()), - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - source_location, - copy, - diff, - .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - stat = diff; - assert!(copy); - } - } - Ok(Default::default()) - })?; - assert_eq!( - actual, - vec![ - BStr::new("base"), - "c4".into(), - "base".into(), - "c5".into(), - "base".into(), - "dir/c6".into() - ], - ); - assert_eq!( - stat, - Some(DiffLineStats { - removals: 0, - insertions: 1, - before: 11, - after: 12, - similarity: 0.8888889 - }), - "by similarity there is a diff" - ); - - let out = out.rewrites.expect("tracking enabled"); - assert_eq!( - out.num_similarity_checks, 2, - "two are similar, the other one is identical" - ); - assert_eq!(out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 0); - assert_eq!(out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, 0); - - Ok(()) - } - - #[test] - fn copies_in_entire_tree_by_similarity() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/tc3-find-harder}~1"); - let to = tree_named(&repo, ":/tc3-find-harder"); - - let mut actual = Vec::new(); - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - copies: Some(Copies::default()), - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { .. } = change.event { - unreachable!("needs --find-copies-harder to detect them here") - } - actual.push(change.location.to_owned()); - } - Ok(Default::default()) - })?; - assert_eq!( - actual, - vec![BStr::new("b"), "c6".into(), "c7".into(), "newly-added".into(),], - ); - - let out = out.rewrites.expect("tracking enabled"); - assert_eq!( - out.num_similarity_checks, 3, - "it does have some candidates, probably for rename tracking" - ); - assert_eq!( - out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 0, - "no limit configured" - ); - assert_eq!(out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, 0); - - let mut actual = Vec::new(); - let mut stat = None; - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - copies: Some(Copies { - source: CopySource::FromSetOfModifiedFilesAndAllSources, - ..Default::default() - }), - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - copy, - diff, - source_location, - .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - stat = diff; - assert!(copy); - } - } - Ok(Default::default()) - })?; - assert_eq!( - actual, - vec![ - BStr::new("base"), - "c6".into(), - "dir/c6".into(), - "c7".into(), - "c5".into(), - "newly-added".into() - ] - ); - - let out = out.rewrites.expect("tracking enabled"); - assert_eq!( - stat, - Some(DiffLineStats { - removals: 0, - insertions: 3, - before: 12, - after: 15, - similarity: 0.75 - }), - "by similarity there is a diff" - ); - assert_eq!(out.num_similarity_checks, 4); - assert_eq!( - out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 0, - "no limit configured" - ); - assert_eq!(out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, 0); - - Ok(()) - } - - #[test] - fn copies_in_entire_tree_by_similarity_with_limit() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/tc3-find-harder}~1"); - let to = tree_named(&repo, ":/tc3-find-harder"); - - let mut actual = Vec::new(); - let mut stat = None; - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - copies: Some(Copies { - source: CopySource::FromSetOfModifiedFilesAndAllSources, - ..Default::default() - }), - limit: 2, // similarity checks can't be made that way - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - copy, - diff, - source_location, - .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - stat = diff; - assert!(copy); - } - } - Ok(Default::default()) - })?; - assert_eq!( - actual, - vec![BStr::new("base"), "c6".into(), "dir/c6".into(), "c7".into(),], - "identification by identity, which is fast due to binary search" - ); - - let out = out.rewrites.expect("tracking enabled"); - assert_eq!(stat, None, "similarity can't run"); - assert_eq!(out.num_similarity_checks, 0); - assert_eq!( - out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 0, - "no limit configured" - ); - assert_eq!(out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, 19); - - Ok(()) - } - - #[test] - fn copies_by_similarity_with_limit() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/tc2-similarity}~1"); - let to = tree_named(&repo, ":/tc2-similarity"); - - let mut actual = Vec::new(); - let mut stat = None; - let out = from - .changes()? - .track_path() - .track_rewrites( - Rewrites { - copies: Some(Copies::default()), - limit: 1, - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - source_location, - copy, - diff, - .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - stat = diff; - assert!(copy); - } - } - Ok(Default::default()) - })?; - assert_eq!( - actual, - vec![BStr::new("base"), "c4".into()], - "the limit prevents any similarity check from being performed, and identity fails everywhere" - ); - assert_eq!(stat, None, "by identity there is no diff"); - - let out = out.rewrites.expect("tracking enabled"); - assert_eq!(out.num_similarity_checks, 0); - assert_eq!(out.num_similarity_checks_skipped_for_rename_tracking_due_to_limit, 0); - assert_eq!(out.num_similarity_checks_skipped_for_copy_tracking_due_to_limit, 2); - - Ok(()) - } - - #[test] - fn realistic_renames() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/r1-change}~1"); - let to = tree_named(&repo, ":/r1-change"); - - let mut actual = Vec::new(); - let mut other = Vec::new(); - from.changes()? - .track_path() - .track_rewrites( - Rewrites { - copies: Some(Copies::default()), - limit: 1, - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - source_location, copy, .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - assert!(!copy); - } else { - other.push(store(&change)); - } - } - Ok(Default::default()) - })?; - - assert_eq!(actual, vec!["git-index/src/file.rs", "git-index/src/file/mod.rs"]); - assert_eq!( - other, - vec![ - added("git-index/tests/index/file/access.rs"), - modified("git-index/tests/index/file/mod.rs") - ] - ); - - #[cfg(not(windows))] - { - let actual = std::fs::read_to_string(repo.work_dir().expect("non-bare").join("baseline.with-renames"))?; - let expected = r#"commit 0231f5093bd3d760e7ee82984e0453da80e05c87 -Author: author -Date: Sat Jan 1 00:00:00 2000 +0000 - - r1-change - -diff --git a/git-index/src/file.rs b/git-index/src/file/mod.rs -similarity index 100% -rename from git-index/src/file.rs -rename to git-index/src/file/mod.rs -diff --git a/git-index/tests/index/file/access.rs b/git-index/tests/index/file/access.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/git-index/tests/index/file/mod.rs b/git-index/tests/index/file/mod.rs -index e69de29..8ba3a16 100644 ---- a/git-index/tests/index/file/mod.rs -+++ b/git-index/tests/index/file/mod.rs -@@ -0,0 +1 @@ -+n -"#; - assert_eq!(actual, expected); - } - - Ok(()) - } - - #[test] - fn realistic_renames_disabled() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/r1-change}~1"); - let to = tree_named(&repo, ":/r1-change"); - - let mut actual = Vec::new(); - from.changes()? - .track_path() - .track_rewrites(None) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - actual.push(store(&change)); - if let Event::Rewrite { .. } = change.event { - unreachable!("it's disabled, so cannot happen") - } - } - Ok(Default::default()) - })?; - - assert_eq!( - actual, - vec![ - deleted("git-index/src/file.rs"), - added("git-index/src/file/mod.rs"), - added("git-index/tests/index/file/access.rs"), - modified("git-index/tests/index/file/mod.rs") - ] - ); - - #[cfg(not(windows))] - { - let actual = std::fs::read_to_string(repo.work_dir().expect("non-bare").join("baseline.no-renames"))?; - let expected = r#"commit 0231f5093bd3d760e7ee82984e0453da80e05c87 -Author: author -Date: Sat Jan 1 00:00:00 2000 +0000 - - r1-change - -diff --git a/git-index/src/file.rs b/git-index/src/file.rs -deleted file mode 100644 -index e69de29..0000000 -diff --git a/git-index/src/file/mod.rs b/git-index/src/file/mod.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/git-index/tests/index/file/access.rs b/git-index/tests/index/file/access.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/git-index/tests/index/file/mod.rs b/git-index/tests/index/file/mod.rs -index e69de29..8ba3a16 100644 ---- a/git-index/tests/index/file/mod.rs -+++ b/git-index/tests/index/file/mod.rs -@@ -0,0 +1 @@ -+n -"#; - assert_eq!(actual, expected); - } - - Ok(()) - } - - #[test] - fn realistic_renames_disabled_2() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/r2-change}~1"); - let to = tree_named(&repo, ":/r2-change"); - - let mut actual = Vec::new(); - from.changes()? - .track_path() - .track_rewrites(None) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - actual.push(store(&change)); - if let Event::Rewrite { .. } = change.event { - unreachable!("it's disabled, so cannot happen") - } - } - Ok(Default::default()) - })?; - - #[cfg(not(windows))] - { - let expected = r#"commit d78c63c5ea3149040767e4387e7fc743cda118fd -Author: author -Date: Sat Jan 1 00:00:00 2000 +0000 - - r2-change - -diff --git a/git-sec/CHANGELOG.md b/git-sec/CHANGELOG.md -deleted file mode 100644 -index e69de29..0000000 -diff --git a/git-sec/Cargo.toml b/git-sec/Cargo.toml -deleted file mode 100644 -index e69de29..0000000 -diff --git a/git-sec/src/identity.rs b/git-sec/src/identity.rs -deleted file mode 100644 -index e69de29..0000000 -diff --git a/git-sec/src/lib.rs b/git-sec/src/lib.rs -deleted file mode 100644 -index e69de29..0000000 -diff --git a/git-sec/src/permission.rs b/git-sec/src/permission.rs -deleted file mode 100644 -index e69de29..0000000 -diff --git a/git-sec/src/trust.rs b/git-sec/src/trust.rs -deleted file mode 100644 -index e69de29..0000000 -diff --git a/git-sec/tests/identity/mod.rs b/git-sec/tests/identity/mod.rs -deleted file mode 100644 -index e69de29..0000000 -diff --git a/git-sec/tests/sec.rs b/git-sec/tests/sec.rs -deleted file mode 100644 -index e69de29..0000000 -diff --git a/gix-sec/CHANGELOG.md b/gix-sec/CHANGELOG.md -new file mode 100644 -index 0000000..e69de29 -diff --git a/gix-sec/Cargo.toml b/gix-sec/Cargo.toml -new file mode 100644 -index 0000000..e69de29 -diff --git a/gix-sec/src/identity.rs b/gix-sec/src/identity.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/gix-sec/src/lib.rs b/gix-sec/src/lib.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/gix-sec/src/permission.rs b/gix-sec/src/permission.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/gix-sec/src/trust.rs b/gix-sec/src/trust.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/gix-sec/tests/identity/mod.rs b/gix-sec/tests/identity/mod.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/gix-sec/tests/sec.rs b/gix-sec/tests/sec.rs -new file mode 100644 -index 0000000..e69de29 -"#; - assert_eq!( - std::fs::read_to_string(repo.work_dir().expect("non-bare").join("baseline-2.no-renames"))?, - expected - ); - } - - assert_eq!( - actual, - vec![ - deleted("git-sec/CHANGELOG.md"), - deleted("git-sec/Cargo.toml"), - added("gix-sec/CHANGELOG.md"), - added("gix-sec/Cargo.toml"), - deleted("git-sec/src/identity.rs"), - deleted("git-sec/src/lib.rs"), - deleted("git-sec/src/permission.rs"), - deleted("git-sec/src/trust.rs"), - deleted("git-sec/tests/sec.rs"), - added("gix-sec/src/identity.rs"), - added("gix-sec/src/lib.rs"), - added("gix-sec/src/permission.rs"), - added("gix-sec/src/trust.rs"), - added("gix-sec/tests/sec.rs"), - deleted("git-sec/tests/identity/mod.rs"), - added("gix-sec/tests/identity/mod.rs"), - ] - ); - - Ok(()) - } - - #[test] - fn realistic_renames_disabled_3() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/r3-change}~1"); - let to = tree_named(&repo, ":/r3-change"); - - let mut actual = Vec::new(); - from.changes()? - .track_path() - .track_rewrites(None) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - actual.push(store(&change)); - if let Event::Rewrite { .. } = change.event { - unreachable!("it's disabled, so cannot happen") - } - } - Ok(Default::default()) - })?; - - #[cfg(not(windows))] - { - let expected = r#"commit 0cf7a4fe3ad6c49ae7beb394a1c1df7cc5173ce4 -Author: author -Date: Sat Jan 1 00:00:00 2000 +0000 - - r3-change - -diff --git a/src/ein.rs b/src/ein.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/src/gix.rs b/src/gix.rs -new file mode 100644 -index 0000000..e69de29 -diff --git a/src/plumbing-cli.rs b/src/plumbing-cli.rs -deleted file mode 100644 -index e69de29..0000000 -diff --git a/src/porcelain-cli.rs b/src/porcelain-cli.rs -deleted file mode 100644 -index e69de29..0000000 -"#; - - assert_eq!( - std::fs::read_to_string(repo.work_dir().expect("non-bare").join("baseline-3.no-renames"))?, - expected - ); - } - assert_eq!( - actual, - vec![ - added("src/ein.rs"), - added("src/gix.rs"), - deleted("src/plumbing-cli.rs"), - deleted("src/porcelain-cli.rs"), - ] - ); - - Ok(()) - } - - #[test] - fn realistic_renames_3() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/r3-change}~1"); - let to = tree_named(&repo, ":/r3-change"); - - let mut actual = Vec::new(); - let mut other = Vec::new(); - from.changes()? - .track_path() - .track_rewrites( - Rewrites { - copies: Some(Copies::default()), - limit: 1, - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - source_location, copy, .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - assert!(!copy); - } else { - other.push(store(&change)); - } - } - Ok(Default::default()) - })?; - - #[cfg(not(windows))] - { - let expected = r#"commit 0cf7a4fe3ad6c49ae7beb394a1c1df7cc5173ce4 -Author: author -Date: Sat Jan 1 00:00:00 2000 +0000 - - r3-change - -diff --git a/src/plumbing-cli.rs b/src/ein.rs -similarity index 100% -rename from src/plumbing-cli.rs -rename to src/ein.rs -diff --git a/src/porcelain-cli.rs b/src/gix.rs -similarity index 100% -rename from src/porcelain-cli.rs -rename to src/gix.rs -"#; - assert_eq!( - std::fs::read_to_string(repo.work_dir().expect("non-bare").join("baseline-3.with-renames"))?, - expected - ); - } - assert_eq!( - actual, - vec![ - "src/plumbing-cli.rs", - "src/ein.rs", - "src/porcelain-cli.rs", - "src/gix.rs" - ] - ); - assert!(other.is_empty()); - - Ok(()) - } - - #[test] - fn realistic_renames_2() -> crate::Result { - let repo = named_repo("make_diff_repo.sh")?; - let from = tree_named(&repo, "@^{/r2-change}~1"); - let to = tree_named(&repo, ":/r2-change"); - - let mut actual = Vec::new(); - from.changes()? - .track_path() - .track_rewrites( - Rewrites { - copies: Some(Copies::default()), - limit: 1, - ..Default::default() - } - .into(), - ) - .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { - if !change.event.entry_mode().is_tree() { - if let Event::Rewrite { - source_location, copy, .. - } = change.event - { - actual.push(source_location.to_owned()); - actual.push(change.location.to_owned()); - assert!(!copy); - } else { - unreachable!("everything is a rewrite"); - } - } - Ok(Default::default()) - })?; - - #[cfg(not(windows))] - { - let expected = r#"commit d78c63c5ea3149040767e4387e7fc743cda118fd -Author: author -Date: Sat Jan 1 00:00:00 2000 +0000 - - r2-change - -diff --git a/git-sec/CHANGELOG.md b/gix-sec/CHANGELOG.md -similarity index 100% -rename from git-sec/CHANGELOG.md -rename to gix-sec/CHANGELOG.md -diff --git a/git-sec/Cargo.toml b/gix-sec/Cargo.toml -similarity index 100% -rename from git-sec/Cargo.toml -rename to gix-sec/Cargo.toml -diff --git a/git-sec/src/identity.rs b/gix-sec/src/identity.rs -similarity index 100% -rename from git-sec/src/identity.rs -rename to gix-sec/src/identity.rs -diff --git a/git-sec/src/lib.rs b/gix-sec/src/lib.rs -similarity index 100% -rename from git-sec/src/lib.rs -rename to gix-sec/src/lib.rs -diff --git a/git-sec/src/permission.rs b/gix-sec/src/permission.rs -similarity index 100% -rename from git-sec/src/permission.rs -rename to gix-sec/src/permission.rs -diff --git a/git-sec/src/trust.rs b/gix-sec/src/trust.rs -similarity index 100% -rename from git-sec/src/trust.rs -rename to gix-sec/src/trust.rs -diff --git a/git-sec/tests/identity/mod.rs b/gix-sec/tests/identity/mod.rs -similarity index 100% -rename from git-sec/tests/identity/mod.rs -rename to gix-sec/tests/identity/mod.rs -diff --git a/git-sec/tests/sec.rs b/gix-sec/tests/sec.rs -similarity index 100% -rename from git-sec/tests/sec.rs -rename to gix-sec/tests/sec.rs -"#; - assert_eq!( - std::fs::read_to_string(repo.work_dir().expect("non-bare").join("baseline-2.with-renames"))?, - expected - ); - } - - assert_eq!( - actual, - vec![ - "git-sec/CHANGELOG.md", - "gix-sec/CHANGELOG.md", - "git-sec/Cargo.toml", - "gix-sec/Cargo.toml", - "git-sec/src/identity.rs", - "gix-sec/src/identity.rs", - "git-sec/src/lib.rs", - "gix-sec/src/lib.rs", - "git-sec/src/permission.rs", - "gix-sec/src/permission.rs", - "git-sec/src/trust.rs", - "gix-sec/src/trust.rs", - "git-sec/tests/sec.rs", - "gix-sec/tests/sec.rs", - "git-sec/tests/identity/mod.rs", - "gix-sec/tests/identity/mod.rs", - ] - ); - - Ok(()) - } -} -fn store(change: &gix::object::tree::diff::Change<'_, '_, '_>) -> (char, BString) { - (shorthand(&change.event), change.location.to_owned()) } -fn added(path: &str) -> (char, BString) { - ('A', path.into()) -} - -fn deleted(path: &str) -> (char, BString) { - ('D', path.into()) -} - -fn modified(path: &str) -> (char, BString) { - ('M', path.into()) -} - -fn shorthand(change: &Event) -> char { - match change { - Event::Addition { .. } => 'A', - Event::Deletion { .. } => 'D', - Event::Modification { .. } => 'M', - Event::Rewrite { .. } => 'R', - } +fn tree_named(repo: &gix::Repository, rev_spec: impl AsRef) -> gix::Tree { + repo.rev_parse_single(rev_spec.as_ref()) + .unwrap() + .object() + .unwrap() + .peel_to_kind(gix::object::Kind::Tree) + .unwrap() + .into_tree() }