diff --git a/git-repository/src/object/tree/diff.rs b/git-repository/src/object/tree/diff.rs index 2171ec6521..6059b07dc7 100644 --- a/git-repository/src/object/tree/diff.rs +++ b/git-repository/src/object/tree/diff.rs @@ -1,8 +1,9 @@ -use crate::bstr::{BStr, BString, ByteVec}; +use crate::bstr::{BStr, BString, ByteSlice, ByteVec}; use crate::ext::ObjectIdExt; use crate::{Repository, Tree}; use git_object::TreeRefIter; use git_odb::FindExt; +use std::collections::VecDeque; /// The error return by methods on the [diff platform][Platform]. #[derive(Debug, thiserror::Error)] @@ -100,6 +101,7 @@ pub struct Platform<'a, 'repo> { #[derive(Clone, Copy)] enum Tracking { FileName, + Path, } /// Configuration @@ -109,6 +111,14 @@ impl<'a, 'repo> Platform<'a, 'repo> { self.tracking = Some(Tracking::FileName); self } + + /// Keep track of the entire path of a change, relative to the repository. + /// + /// This makes the [`location`][Change::location] field usable. + pub fn track_path(&mut self) -> &mut Self { + self.tracking = Some(Tracking::Path); + self + } } /// Add the item to compare to. @@ -128,6 +138,7 @@ impl<'a, 'repo> Platform<'a, 'repo> { other_repo: other.repo, tracking: self.tracking, location: BString::default(), + path_deque: Default::default(), visit: for_each, err: None, }; @@ -149,19 +160,47 @@ struct Delegate<'repo, 'other_repo, VisitFn, E> { other_repo: &'other_repo Repository, tracking: Option, location: BString, + path_deque: VecDeque, visit: VisitFn, err: Option, } +impl Delegate<'_, '_, A, B> { + fn pop_element(&mut self) { + if let Some(pos) = self.location.rfind_byte(b'/') { + self.location.resize(pos, 0); + } else { + self.location.clear(); + } + } + + fn push_element(&mut self, name: &BStr) { + if !self.location.is_empty() { + self.location.push(b'/'); + } + self.location.push_str(name); + } +} + 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 pop_front_tracked_path_and_set_current(&mut self) { + if let Some(Tracking::Path) = self.tracking { + self.location = self + .path_deque + .pop_front() + .expect("every call is matched with push_tracked_path_component"); + } + } - fn push_back_tracked_path_component(&mut self, _component: &BStr) { - {} + fn push_back_tracked_path_component(&mut self, component: &BStr) { + if let Some(Tracking::Path) = self.tracking { + self.push_element(component); + self.path_deque.push_back(self.location.clone()); + } } fn push_path_component(&mut self, component: &BStr) { @@ -170,11 +209,18 @@ where self.location.clear(); self.location.push_str(component); } + Some(Tracking::Path) => { + self.push_element(component); + } None => {} } } - fn pop_path_component(&mut self) {} + fn pop_path_component(&mut self) { + if let Some(Tracking::Path) = self.tracking { + self.pop_element(); + } + } fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action { use git_diff::tree::visit::Change::*; diff --git a/git-repository/tests/object/tree.rs b/git-repository/tests/object/tree.rs index d120350666..094753ed03 100644 --- a/git-repository/tests/object/tree.rs +++ b/git-repository/tests/object/tree.rs @@ -49,7 +49,17 @@ mod diff { Ok(Default::default()) }) .unwrap(); - assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen") + assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen"); + + let mut expected = vec!["a", "b", "dir/c"]; + from.changes() + .track_path() + .for_each_to_obtain_tree(&to, |change| -> Result<_, Infallible> { + expected.retain(|name| name != change.location); + Ok(Default::default()) + }) + .unwrap(); + assert_eq!(expected, Vec::<&str>::new(), "all paths should have been seen"); } fn tree_named<'repo>(repo: &'repo git::Repository, rev_spec: &str) -> git::Tree<'repo> {