From 90b9c906bc3779d62b0317ea318c07693fda0d3c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 19 Sep 2022 20:01:05 +0800 Subject: [PATCH] refactor (#470) --- git-repository/src/object/tree/diff.rs | 209 ++++++++++++++++++++++++ git-repository/src/object/tree/mod.rs | 213 +------------------------ 2 files changed, 210 insertions(+), 212 deletions(-) create mode 100644 git-repository/src/object/tree/diff.rs diff --git a/git-repository/src/object/tree/diff.rs b/git-repository/src/object/tree/diff.rs new file mode 100644 index 0000000000..c316db4dc3 --- /dev/null +++ b/git-repository/src/object/tree/diff.rs @@ -0,0 +1,209 @@ +use crate::bstr::{BStr, BString, ByteVec}; +use crate::ext::ObjectIdExt; +use crate::{Id, Repository, Tree}; +use git_object::TreeRefIter; +use git_odb::FindExt; + +/// The error return by methods on the [diff platform][Platform]. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error(transparent)] + Diff(#[from] git_diff::tree::changes::Error), + #[error("The user-provided callback failed")] + ForEach(#[source] Box), +} + +/// Returned by the `for_each` function to control flow. +#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub enum Action { + /// Continue the traversal of changes. + Continue, + /// Stop the traversal of changes and stop calling this function. + Cancel, +} + +impl Default for Action { + fn default() -> Self { + Action::Continue + } +} + +/// Represents any possible change in order to turn one tree into another. +#[derive(Debug, Clone, Copy)] +pub struct Change<'a, 'repo, 'other_repo> { + /// The location of the file or directory described by `event`, if tracking was enabled. + /// + /// 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: Event<'repo, 'other_repo>, +} + +/// An event emitted when finding differences between two trees. +#[derive(Debug, Clone, Copy)] +pub enum Event<'repo, 'other_repo> { + /// An entry was added, like the addition of a file or directory. + Addition { + /// The mode of the added entry. + entry_mode: git_object::tree::EntryMode, + /// The object id of the added entry. + id: Id<'other_repo>, + }, + /// An entry was deleted, like the deletion of a file or directory. + Deletion { + /// The mode of the deleted entry. + entry_mode: git_object::tree::EntryMode, + /// The object id of the deleted entry. + id: Id<'repo>, + }, + /// 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: git_object::tree::EntryMode, + /// The object id of the entry before the modification. + previous_id: Id<'repo>, + + /// The mode of the entry after the modification. + entry_mode: git_object::tree::EntryMode, + /// The object id after the modification. + id: Id<'other_repo>, + }, +} + +/// Diffing +impl<'repo> Tree<'repo> { + /// Return a platform to see the changes needed to create other trees, for instance. + pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> { + Platform { + state: Default::default(), + lhs: self, + tracking: None, + } + } +} + +/// The diffing platform returned by [`Tree::changes()`]. +#[derive(Clone)] +pub struct Platform<'a, 'repo> { + state: git_diff::tree::State, + lhs: &'a Tree<'repo>, + tracking: Option, +} + +#[derive(Clone, Copy)] +enum Tracking { + FileName, +} + +/// Configuration +impl<'a, 'repo> Platform<'a, 'repo> { + /// 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(Tracking::FileName); + self + } +} + +/// Add the item to compare to. +impl<'a, 'repo> Platform<'a, 'repo> { + /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`. + pub fn for_each_to_obtain_tree<'other_repo, E>( + &mut self, + other: &Tree<'other_repo>, + for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result, + ) -> Result<(), Error> + where + E: std::error::Error + Sync + Send + 'static, + { + let repo = self.lhs.repo; + let mut delegate = Delegate { + repo: self.lhs.repo, + other_repo: other.repo, + tracking: self.tracking, + location: BString::default(), + visit: for_each, + err: None, + }; + git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain( + TreeRefIter::from_bytes(&other.data), + &mut self.state, + |oid, buf| repo.objects.find_tree_iter(oid, buf), + &mut delegate, + )?; + match delegate.err { + Some(err) => Err(Error::ForEach(Box::new(err))), + None => Ok(()), + } + } +} + +struct Delegate<'repo, 'other_repo, VisitFn, E> { + repo: &'repo Repository, + other_repo: &'other_repo Repository, + tracking: Option, + location: BString, + visit: VisitFn, + err: Option, +} + +impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E> +where + VisitFn: for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result, + E: std::error::Error + Sync + Send + 'static, +{ + fn pop_front_tracked_path_and_set_current(&mut self) {} + + fn push_back_tracked_path_component(&mut self, _component: &BStr) { + {} + } + + fn push_path_component(&mut self, component: &BStr) { + match self.tracking { + Some(Tracking::FileName) => { + self.location.clear(); + self.location.push_str(component); + } + None => {} + } + } + + fn pop_path_component(&mut self) {} + + fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { + use git_diff::tree::visit::Change::*; + let event = match change { + Addition { entry_mode, oid } => Event::Addition { + entry_mode, + id: oid.attach(self.other_repo), + }, + Deletion { entry_mode, oid } => Event::Deletion { + entry_mode, + id: oid.attach(self.repo), + }, + Modification { + previous_entry_mode, + previous_oid, + entry_mode, + oid, + } => Event::Modification { + previous_entry_mode, + entry_mode, + previous_id: previous_oid.attach(self.repo), + id: oid.attach(self.other_repo), + }, + }; + match (self.visit)(Change { + event, + location: self.location.as_ref(), + }) { + Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel, + Ok(Action::Continue) => git_diff::tree::visit::Action::Continue, + Err(err) => { + self.err = Some(err); + git_diff::tree::visit::Action::Cancel + } + } + } +} diff --git a/git-repository/src/object/tree/mod.rs b/git-repository/src/object/tree/mod.rs index 963bca537f..d8a2bad728 100644 --- a/git-repository/src/object/tree/mod.rs +++ b/git-repository/src/object/tree/mod.rs @@ -62,219 +62,8 @@ impl<'repo> Tree<'repo> { } } -#[allow(missing_docs)] /// -pub mod diff { - use crate::bstr::{BStr, BString, ByteVec}; - use crate::ext::ObjectIdExt; - use crate::{Id, Repository, Tree}; - use git_object::TreeRefIter; - use git_odb::FindExt; - - /// The error return by methods on the [diff platform][Platform]. - #[derive(Debug, thiserror::Error)] - #[allow(missing_docs)] - pub enum Error { - #[error(transparent)] - Diff(#[from] git_diff::tree::changes::Error), - #[error("The user-provided callback failed")] - ForEach(#[source] Box), - } - - /// Returned by the `for_each` function to control flow. - #[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] - pub enum Action { - /// Continue the traversal of changes. - Continue, - /// Stop the traversal of changes and stop calling this function. - Cancel, - } - - impl Default for Action { - fn default() -> Self { - Action::Continue - } - } - - /// Represents any possible change in order to turn one tree into another. - #[derive(Debug, Clone, Copy)] - pub struct Change<'a, 'repo, 'other_repo> { - /// The location of the file or directory described by `event`, if tracking was enabled. - /// - /// 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: Event<'repo, 'other_repo>, - } - - /// An event emitted when finding differences between two trees. - #[derive(Debug, Clone, Copy)] - pub enum Event<'repo, 'other_repo> { - /// An entry was added, like the addition of a file or directory. - Addition { - /// The mode of the added entry. - entry_mode: git_object::tree::EntryMode, - /// The object id of the added entry. - id: Id<'other_repo>, - }, - /// An entry was deleted, like the deletion of a file or directory. - Deletion { - /// The mode of the deleted entry. - entry_mode: git_object::tree::EntryMode, - /// The object id of the deleted entry. - id: Id<'repo>, - }, - /// 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: git_object::tree::EntryMode, - /// The object id of the entry before the modification. - previous_id: Id<'repo>, - - /// The mode of the entry after the modification. - entry_mode: git_object::tree::EntryMode, - /// The object id after the modification. - id: Id<'other_repo>, - }, - } - - /// Diffing - impl<'repo> Tree<'repo> { - /// Return a platform to see the changes needed to create other trees, for instance. - pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> { - Platform { - state: Default::default(), - lhs: self, - tracking: None, - } - } - } - - /// The diffing platform returned by [`Tree::changes()`]. - #[derive(Clone)] - pub struct Platform<'a, 'repo> { - state: git_diff::tree::State, - lhs: &'a Tree<'repo>, - tracking: Option, - } - - #[derive(Clone, Copy)] - enum Tracking { - FileName, - } - - /// Configuration - impl<'a, 'repo> Platform<'a, 'repo> { - /// 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(Tracking::FileName); - self - } - } - - /// Add the item to compare to. - impl<'a, 'repo> Platform<'a, 'repo> { - /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`. - pub fn for_each_to_obtain_tree<'other_repo, E>( - &mut self, - other: &Tree<'other_repo>, - for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result, - ) -> Result<(), Error> - where - E: std::error::Error + Sync + Send + 'static, - { - let repo = self.lhs.repo; - let mut delegate = Delegate { - repo: self.lhs.repo, - other_repo: other.repo, - tracking: self.tracking, - location: BString::default(), - visit: for_each, - err: None, - }; - git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain( - TreeRefIter::from_bytes(&other.data), - &mut self.state, - |oid, buf| repo.objects.find_tree_iter(oid, buf), - &mut delegate, - )?; - match delegate.err { - Some(err) => Err(Error::ForEach(Box::new(err))), - None => Ok(()), - } - } - } - - struct Delegate<'repo, 'other_repo, VisitFn, E> { - repo: &'repo Repository, - other_repo: &'other_repo Repository, - tracking: Option, - location: BString, - visit: VisitFn, - err: Option, - } - - impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E> - where - VisitFn: for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result, - E: std::error::Error + Sync + Send + 'static, - { - fn pop_front_tracked_path_and_set_current(&mut self) {} - - fn push_back_tracked_path_component(&mut self, _component: &BStr) { - {} - } - - fn push_path_component(&mut self, component: &BStr) { - match self.tracking { - Some(Tracking::FileName) => { - self.location.clear(); - self.location.push_str(component); - } - None => {} - } - } - - fn pop_path_component(&mut self) {} - - fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { - use git_diff::tree::visit::Change::*; - let event = match change { - Addition { entry_mode, oid } => Event::Addition { - entry_mode, - id: oid.attach(self.other_repo), - }, - Deletion { entry_mode, oid } => Event::Deletion { - entry_mode, - id: oid.attach(self.repo), - }, - Modification { - previous_entry_mode, - previous_oid, - entry_mode, - oid, - } => Event::Modification { - previous_entry_mode, - entry_mode, - previous_id: previous_oid.attach(self.repo), - id: oid.attach(self.other_repo), - }, - }; - match (self.visit)(Change { - event, - location: self.location.as_ref(), - }) { - Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel, - Ok(Action::Continue) => git_diff::tree::visit::Action::Continue, - Err(err) => { - self.err = Some(err); - git_diff::tree::visit::Action::Cancel - } - } - } - } -} +pub mod diff; /// pub mod traverse;