Skip to content

Commit 90b9c90

Browse files
committed
refactor (#470)
1 parent 9b7aaa0 commit 90b9c90

File tree

2 files changed

+210
-212
lines changed

2 files changed

+210
-212
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use crate::bstr::{BStr, BString, ByteVec};
2+
use crate::ext::ObjectIdExt;
3+
use crate::{Id, Repository, Tree};
4+
use git_object::TreeRefIter;
5+
use git_odb::FindExt;
6+
7+
/// The error return by methods on the [diff platform][Platform].
8+
#[derive(Debug, thiserror::Error)]
9+
#[allow(missing_docs)]
10+
pub enum Error {
11+
#[error(transparent)]
12+
Diff(#[from] git_diff::tree::changes::Error),
13+
#[error("The user-provided callback failed")]
14+
ForEach(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
15+
}
16+
17+
/// Returned by the `for_each` function to control flow.
18+
#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
19+
pub enum Action {
20+
/// Continue the traversal of changes.
21+
Continue,
22+
/// Stop the traversal of changes and stop calling this function.
23+
Cancel,
24+
}
25+
26+
impl Default for Action {
27+
fn default() -> Self {
28+
Action::Continue
29+
}
30+
}
31+
32+
/// Represents any possible change in order to turn one tree into another.
33+
#[derive(Debug, Clone, Copy)]
34+
pub struct Change<'a, 'repo, 'other_repo> {
35+
/// The location of the file or directory described by `event`, if tracking was enabled.
36+
///
37+
/// Otherwise this value is always an empty path.
38+
pub location: &'a BStr,
39+
/// The diff event itself to provide information about what would need to change.
40+
pub event: Event<'repo, 'other_repo>,
41+
}
42+
43+
/// An event emitted when finding differences between two trees.
44+
#[derive(Debug, Clone, Copy)]
45+
pub enum Event<'repo, 'other_repo> {
46+
/// An entry was added, like the addition of a file or directory.
47+
Addition {
48+
/// The mode of the added entry.
49+
entry_mode: git_object::tree::EntryMode,
50+
/// The object id of the added entry.
51+
id: Id<'other_repo>,
52+
},
53+
/// An entry was deleted, like the deletion of a file or directory.
54+
Deletion {
55+
/// The mode of the deleted entry.
56+
entry_mode: git_object::tree::EntryMode,
57+
/// The object id of the deleted entry.
58+
id: Id<'repo>,
59+
},
60+
/// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning
61+
/// a file into a symbolic link adjusts its mode.
62+
Modification {
63+
/// The mode of the entry before the modification.
64+
previous_entry_mode: git_object::tree::EntryMode,
65+
/// The object id of the entry before the modification.
66+
previous_id: Id<'repo>,
67+
68+
/// The mode of the entry after the modification.
69+
entry_mode: git_object::tree::EntryMode,
70+
/// The object id after the modification.
71+
id: Id<'other_repo>,
72+
},
73+
}
74+
75+
/// Diffing
76+
impl<'repo> Tree<'repo> {
77+
/// Return a platform to see the changes needed to create other trees, for instance.
78+
pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> {
79+
Platform {
80+
state: Default::default(),
81+
lhs: self,
82+
tracking: None,
83+
}
84+
}
85+
}
86+
87+
/// The diffing platform returned by [`Tree::changes()`].
88+
#[derive(Clone)]
89+
pub struct Platform<'a, 'repo> {
90+
state: git_diff::tree::State,
91+
lhs: &'a Tree<'repo>,
92+
tracking: Option<Tracking>,
93+
}
94+
95+
#[derive(Clone, Copy)]
96+
enum Tracking {
97+
FileName,
98+
}
99+
100+
/// Configuration
101+
impl<'a, 'repo> Platform<'a, 'repo> {
102+
/// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item.
103+
pub fn track_filename(&mut self) -> &mut Self {
104+
self.tracking = Some(Tracking::FileName);
105+
self
106+
}
107+
}
108+
109+
/// Add the item to compare to.
110+
impl<'a, 'repo> Platform<'a, 'repo> {
111+
/// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`.
112+
pub fn for_each_to_obtain_tree<'other_repo, E>(
113+
&mut self,
114+
other: &Tree<'other_repo>,
115+
for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result<Action, E>,
116+
) -> Result<(), Error>
117+
where
118+
E: std::error::Error + Sync + Send + 'static,
119+
{
120+
let repo = self.lhs.repo;
121+
let mut delegate = Delegate {
122+
repo: self.lhs.repo,
123+
other_repo: other.repo,
124+
tracking: self.tracking,
125+
location: BString::default(),
126+
visit: for_each,
127+
err: None,
128+
};
129+
git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain(
130+
TreeRefIter::from_bytes(&other.data),
131+
&mut self.state,
132+
|oid, buf| repo.objects.find_tree_iter(oid, buf),
133+
&mut delegate,
134+
)?;
135+
match delegate.err {
136+
Some(err) => Err(Error::ForEach(Box::new(err))),
137+
None => Ok(()),
138+
}
139+
}
140+
}
141+
142+
struct Delegate<'repo, 'other_repo, VisitFn, E> {
143+
repo: &'repo Repository,
144+
other_repo: &'other_repo Repository,
145+
tracking: Option<Tracking>,
146+
location: BString,
147+
visit: VisitFn,
148+
err: Option<E>,
149+
}
150+
151+
impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E>
152+
where
153+
VisitFn: for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result<Action, E>,
154+
E: std::error::Error + Sync + Send + 'static,
155+
{
156+
fn pop_front_tracked_path_and_set_current(&mut self) {}
157+
158+
fn push_back_tracked_path_component(&mut self, _component: &BStr) {
159+
{}
160+
}
161+
162+
fn push_path_component(&mut self, component: &BStr) {
163+
match self.tracking {
164+
Some(Tracking::FileName) => {
165+
self.location.clear();
166+
self.location.push_str(component);
167+
}
168+
None => {}
169+
}
170+
}
171+
172+
fn pop_path_component(&mut self) {}
173+
174+
fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action {
175+
use git_diff::tree::visit::Change::*;
176+
let event = match change {
177+
Addition { entry_mode, oid } => Event::Addition {
178+
entry_mode,
179+
id: oid.attach(self.other_repo),
180+
},
181+
Deletion { entry_mode, oid } => Event::Deletion {
182+
entry_mode,
183+
id: oid.attach(self.repo),
184+
},
185+
Modification {
186+
previous_entry_mode,
187+
previous_oid,
188+
entry_mode,
189+
oid,
190+
} => Event::Modification {
191+
previous_entry_mode,
192+
entry_mode,
193+
previous_id: previous_oid.attach(self.repo),
194+
id: oid.attach(self.other_repo),
195+
},
196+
};
197+
match (self.visit)(Change {
198+
event,
199+
location: self.location.as_ref(),
200+
}) {
201+
Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel,
202+
Ok(Action::Continue) => git_diff::tree::visit::Action::Continue,
203+
Err(err) => {
204+
self.err = Some(err);
205+
git_diff::tree::visit::Action::Cancel
206+
}
207+
}
208+
}
209+
}

0 commit comments

Comments
 (0)