Skip to content

Commit 45b7155

Browse files
committedOct 8, 2024
refactor!: Use the new tree_with_rewrites plumbing implementation.
This merges `object::tree::diff::change::Event` into `object::tree::diff::Change` as well.
1 parent f2e813d commit 45b7155

File tree

9 files changed

+433
-1764
lines changed

9 files changed

+433
-1764
lines changed
 

‎gitoxide-core/src/hours/core.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -128,19 +128,19 @@ pub fn spawn_tree_delta_threads<'scope>(
128128
.track_filename()
129129
.track_rewrites(None)
130130
.for_each_to_obtain_tree(&to, |change| {
131-
use gix::object::tree::diff::change::Event::*;
131+
use gix::object::tree::diff::Change::*;
132132
changes.fetch_add(1, Ordering::Relaxed);
133-
match change.event {
133+
match change {
134134
Rewrite { .. } => {
135135
unreachable!("we turned that off")
136136
}
137-
Addition { entry_mode, id } => {
137+
Addition { entry_mode, id, .. } => {
138138
if entry_mode.is_no_tree() {
139139
files.added += 1;
140140
add_lines(line_stats, &lines_count, &mut lines, id);
141141
}
142142
}
143-
Deletion { entry_mode, id } => {
143+
Deletion { entry_mode, id, .. } => {
144144
if entry_mode.is_no_tree() {
145145
files.removed += 1;
146146
remove_lines(line_stats, &lines_count, &mut lines, id);
@@ -151,6 +151,7 @@ pub fn spawn_tree_delta_threads<'scope>(
151151
previous_entry_mode,
152152
id,
153153
previous_id,
154+
..
154155
} => match (previous_entry_mode.is_blob(), entry_mode.is_blob()) {
155156
(false, false) => {}
156157
(false, true) => {

‎gitoxide-core/src/query/engine/update.rs

+22-15
Original file line numberDiff line numberDiff line change
@@ -210,31 +210,32 @@ pub fn update(
210210
.track_path()
211211
.track_rewrites(Some(rewrites))
212212
.for_each_to_obtain_tree_with_cache(&to, &mut rewrite_cache, |change| {
213-
use gix::object::tree::diff::change::Event::*;
213+
use gix::object::tree::diff::Change::*;
214214
change_counter.fetch_add(1, Ordering::SeqCst);
215-
match change.event {
216-
Addition { entry_mode, id } => {
215+
match change {
216+
Addition {
217+
entry_mode,
218+
id,
219+
location,
220+
..
221+
} => {
217222
if entry_mode.is_blob_or_symlink() {
218-
add_lines(&mut out, change.location, &lines_counter, id);
223+
add_lines(&mut out, location, &lines_counter, id);
219224
}
220225
}
221226
Modification {
222227
entry_mode,
223228
previous_entry_mode,
224229
id,
225230
previous_id,
231+
location,
226232
} => match (previous_entry_mode.is_blob(), entry_mode.is_blob()) {
227233
(false, false) => {}
228234
(false, true) => {
229-
add_lines(&mut out, change.location, &lines_counter, id);
235+
add_lines(&mut out, location, &lines_counter, id);
230236
}
231237
(true, false) => {
232-
add_lines(
233-
&mut out,
234-
change.location,
235-
&lines_counter,
236-
previous_id,
237-
);
238+
add_lines(&mut out, location, &lines_counter, previous_id);
238239
}
239240
(true, true) => {
240241
if let Ok(cache) =
@@ -266,7 +267,7 @@ pub fn update(
266267
lines_counter
267268
.fetch_add(nl, Ordering::SeqCst);
268269
out.push(FileChange {
269-
relpath: change.location.to_owned(),
270+
relpath: location.to_owned(),
270271
mode: FileMode::Modified,
271272
source_relpath: None,
272273
lines: Some(lines),
@@ -281,19 +282,25 @@ pub fn update(
281282
}
282283
}
283284
},
284-
Deletion { entry_mode, id } => {
285+
Deletion {
286+
entry_mode,
287+
id,
288+
location,
289+
..
290+
} => {
285291
if entry_mode.is_blob_or_symlink() {
286-
remove_lines(&mut out, change.location, &lines_counter, id);
292+
remove_lines(&mut out, location, &lines_counter, id);
287293
}
288294
}
289295
Rewrite {
290296
source_location,
291297
diff,
292298
copy,
299+
location,
293300
..
294301
} => {
295302
out.push(FileChange {
296-
relpath: change.location.to_owned(),
303+
relpath: location.to_owned(),
297304
source_relpath: Some(source_location.to_owned()),
298305
mode: if copy { FileMode::Copy } else { FileMode::Rename },
299306
lines: diff.map(|d| LineStats {

‎gix/src/ext/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ pub use object_id::ObjectIdExt;
22
pub use reference::ReferenceExt;
33
#[cfg(feature = "revision")]
44
pub use rev_spec::RevSpecExt;
5+
#[cfg(feature = "blob-diff")]
6+
pub use tree::TreeDiffChangeExt;
57
pub use tree::{TreeEntryExt, TreeEntryRefExt, TreeIterExt};
68

79
mod object_id;

‎gix/src/ext/tree.rs

+16-4
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ impl TreeIterExt for TreeRefIter<'_> {
4242
}
4343
}
4444

45-
/// Extensions for [EntryRef][gix_object::tree::EntryRef].
45+
/// Extensions for [EntryRef](gix_object::tree::EntryRef).
4646
pub trait TreeEntryRefExt<'a>: 'a {
47-
/// Attach [`Repository`][crate::Repository] to the given tree entry. It can be detached later with `detach()`.
47+
/// Attach [`repo`](crate::Repository) to the given tree entry. It can be detached later with `detach()`.
4848
fn attach<'repo>(self, repo: &'repo crate::Repository) -> crate::object::tree::EntryRef<'repo, 'a>;
4949
}
5050

@@ -54,9 +54,9 @@ impl<'a> TreeEntryRefExt<'a> for gix_object::tree::EntryRef<'a> {
5454
}
5555
}
5656

57-
/// Extensions for [Entry][gix_object::tree::Entry].
57+
/// Extensions for [Entry](gix_object::tree::Entry).
5858
pub trait TreeEntryExt {
59-
/// Attach [`Repository`][crate::Repository] to the given tree entry. It can be detached later with `detach()`.
59+
/// Attach [`repo`](crate::Repository) to the given tree entry. It can be detached later with `detach()`.
6060
fn attach(self, repo: &crate::Repository) -> crate::object::tree::Entry<'_>;
6161
}
6262

@@ -65,3 +65,15 @@ impl TreeEntryExt for gix_object::tree::Entry {
6565
crate::object::tree::Entry { inner: self, repo }
6666
}
6767
}
68+
69+
/// Extensions for [Change](gix_diff::tree_with_rewrites::Change).
70+
#[cfg(feature = "blob-diff")]
71+
pub trait TreeDiffChangeExt {
72+
/// Attach [`old_repo`](crate::Repository) and `new_repo` to current instance. It can be detached later with `detach()`.
73+
/// Note that both repositories are usually the same.
74+
fn attach<'old, 'new>(
75+
&self,
76+
old_repo: &'old crate::Repository,
77+
new_repo: &'new crate::Repository,
78+
) -> crate::object::tree::diff::Change<'_, 'old, 'new>;
79+
}

‎gix/src/object/blob.rs

+6-98
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ use crate::{Blob, ObjectDetached};
55
pub mod diff {
66
use std::ops::Range;
77

8-
use gix_diff::blob::{platform::prepare_diff::Operation, ResourceKind};
8+
use gix_diff::blob::platform::prepare_diff::Operation;
99

10-
use crate::{
11-
bstr::ByteSlice,
12-
object::{blob::diff::lines::Change, tree::diff::change::Event},
13-
};
10+
use crate::bstr::ByteSlice;
1411

1512
/// A platform to keep temporary information to perform line diffs on modified blobs.
1613
///
@@ -21,99 +18,10 @@ pub mod diff {
2118

2219
///
2320
pub mod init {
24-
/// The error returned by [`Platform::from_tree_change()`][super::Platform::from_tree_change()].
21+
/// The error returned by [`object::tree::diff::Change::diff`](crate::object::tree::diff::Change::diff()).
2522
pub type Error = gix_diff::blob::platform::set_resource::Error;
2623
}
2724

28-
impl<'a> Platform<'a> {
29-
/// Produce a platform for performing various diffs after obtaining the data from a single `tree_change`.
30-
pub fn from_tree_change(
31-
tree_change: &crate::object::tree::diff::Change<'_, '_, '_>,
32-
resource_cache: &'a mut gix_diff::blob::Platform,
33-
) -> Result<Platform<'a>, init::Error> {
34-
match tree_change.event {
35-
Event::Addition { entry_mode, id } => {
36-
resource_cache.set_resource(
37-
id.repo.object_hash().null(),
38-
entry_mode.kind(),
39-
tree_change.location,
40-
ResourceKind::OldOrSource,
41-
&id.repo.objects,
42-
)?;
43-
resource_cache.set_resource(
44-
id.inner,
45-
entry_mode.kind(),
46-
tree_change.location,
47-
ResourceKind::NewOrDestination,
48-
&id.repo.objects,
49-
)?;
50-
}
51-
Event::Deletion { entry_mode, id } => {
52-
resource_cache.set_resource(
53-
id.inner,
54-
entry_mode.kind(),
55-
tree_change.location,
56-
ResourceKind::OldOrSource,
57-
&id.repo.objects,
58-
)?;
59-
resource_cache.set_resource(
60-
id.repo.object_hash().null(),
61-
entry_mode.kind(),
62-
tree_change.location,
63-
ResourceKind::NewOrDestination,
64-
&id.repo.objects,
65-
)?;
66-
}
67-
Event::Modification {
68-
previous_entry_mode,
69-
previous_id,
70-
entry_mode,
71-
id,
72-
} => {
73-
resource_cache.set_resource(
74-
previous_id.inner,
75-
previous_entry_mode.kind(),
76-
tree_change.location,
77-
ResourceKind::OldOrSource,
78-
&previous_id.repo.objects,
79-
)?;
80-
resource_cache.set_resource(
81-
id.inner,
82-
entry_mode.kind(),
83-
tree_change.location,
84-
ResourceKind::NewOrDestination,
85-
&id.repo.objects,
86-
)?;
87-
}
88-
Event::Rewrite {
89-
source_location,
90-
source_entry_mode,
91-
source_id,
92-
entry_mode,
93-
id,
94-
diff: _,
95-
copy: _,
96-
} => {
97-
resource_cache.set_resource(
98-
source_id.inner,
99-
source_entry_mode.kind(),
100-
source_location,
101-
ResourceKind::OldOrSource,
102-
&source_id.repo.objects,
103-
)?;
104-
resource_cache.set_resource(
105-
id.inner,
106-
entry_mode.kind(),
107-
tree_change.location,
108-
ResourceKind::NewOrDestination,
109-
&id.repo.objects,
110-
)?;
111-
}
112-
}
113-
Ok(Self { resource_cache })
114-
}
115-
}
116-
11725
///
11826
pub mod lines {
11927
use crate::bstr::BStr;
@@ -195,11 +103,11 @@ pub mod diff {
195103
let hunk_before = &lines[..end_of_before];
196104
let hunk_after = &lines[end_of_before..];
197105
if hunk_after.is_empty() {
198-
err = process_hunk(Change::Deletion { lines: hunk_before }).err();
106+
err = process_hunk(lines::Change::Deletion { lines: hunk_before }).err();
199107
} else if hunk_before.is_empty() {
200-
err = process_hunk(Change::Addition { lines: hunk_after }).err();
108+
err = process_hunk(lines::Change::Addition { lines: hunk_after }).err();
201109
} else {
202-
err = process_hunk(Change::Modification {
110+
err = process_hunk(lines::Change::Modification {
203111
lines_before: hunk_before,
204112
lines_after: hunk_after,
205113
})

‎gix/src/object/tree/diff/change.rs

+232-189
Large diffs are not rendered by default.

‎gix/src/object/tree/diff/for_each.rs

+24-289
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
11
use gix_object::TreeRefIter;
22

3-
use super::{change, Action, Change, Platform};
4-
use crate::{
5-
bstr::BStr,
6-
diff::{rewrites, rewrites::tracker},
7-
ext::ObjectIdExt,
8-
object::tree::diff,
9-
Repository, Tree,
10-
};
3+
use super::{Action, Change, Platform};
4+
use crate::{diff::rewrites::tracker, Tree};
115

126
/// The error return by methods on the [diff platform][Platform].
137
#[derive(Debug, thiserror::Error)]
148
#[allow(missing_docs)]
159
pub enum Error {
1610
#[error(transparent)]
17-
Diff(#[from] gix_diff::tree::Error),
11+
Diff(#[from] gix_diff::tree_with_rewrites::Error),
1812
#[error("The user-provided callback failed")]
1913
ForEach(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
2014
#[error(transparent)]
@@ -23,13 +17,6 @@ pub enum Error {
2317
RenameTracking(#[from] tracker::emit::Error),
2418
}
2519

26-
///
27-
#[derive(Clone, Debug, Copy, PartialEq)]
28-
pub struct Outcome {
29-
/// Available only if [rewrite-tracking was enabled][Platform::track_rewrites()].
30-
pub rewrites: Option<rewrites::Outcome>,
31-
}
32-
3320
/// Add the item to compare to.
3421
impl<'old> Platform<'_, 'old> {
3522
/// 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> {
4027
&mut self,
4128
other: &Tree<'new>,
4229
for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>,
43-
) -> Result<Outcome, Error>
30+
) -> Result<Option<gix_diff::rewrites::Outcome>, Error>
4431
where
4532
E: Into<Box<dyn std::error::Error + Sync + Send + 'static>>,
4633
{
@@ -62,7 +49,7 @@ impl<'old> Platform<'_, 'old> {
6249
other: &Tree<'new>,
6350
resource_cache: &mut gix_diff::blob::Platform,
6451
for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>,
65-
) -> Result<Outcome, Error>
52+
) -> Result<Option<gix_diff::rewrites::Outcome>, Error>
6653
where
6754
E: Into<Box<dyn std::error::Error + Sync + Send + 'static>>,
6855
{
@@ -72,289 +59,37 @@ impl<'old> Platform<'_, 'old> {
7259
fn for_each_to_obtain_tree_inner<'new, E>(
7360
&mut self,
7461
other: &Tree<'new>,
75-
for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>,
62+
mut for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>,
7663
resource_cache: Option<&mut gix_diff::blob::Platform>,
77-
) -> Result<Outcome, Error>
64+
) -> Result<Option<gix_diff::rewrites::Outcome>, Error>
7865
where
7966
E: Into<Box<dyn std::error::Error + Sync + Send + 'static>>,
8067
{
8168
let repo = self.lhs.repo;
82-
let mut delegate = Delegate {
83-
src_tree: self.lhs,
84-
other_repo: other.repo,
85-
recorder: gix_diff::tree::Recorder::default().track_location(self.tracking),
86-
visit: for_each,
87-
location: self.tracking,
88-
tracked: self.rewrites.map(rewrites::Tracker::new),
89-
err: None,
90-
};
91-
match gix_diff::tree(
92-
TreeRefIter::from_bytes(&self.lhs.data),
93-
TreeRefIter::from_bytes(&other.data),
94-
&mut self.state,
95-
&repo.objects,
96-
&mut delegate,
97-
) {
98-
Ok(()) => {
99-
let outcome = Outcome {
100-
rewrites: delegate.process_tracked_changes(resource_cache)?,
101-
};
102-
match delegate.err {
103-
Some(err) => Err(Error::ForEach(err.into())),
104-
None => Ok(outcome),
105-
}
106-
}
107-
Err(gix_diff::tree::Error::Cancelled) => delegate
108-
.err
109-
.map_or(Err(Error::Diff(gix_diff::tree::Error::Cancelled)), |err| {
110-
Err(Error::ForEach(err.into()))
111-
}),
112-
Err(err) => Err(err.into()),
113-
}
114-
}
115-
}
116-
117-
struct Delegate<'a, 'old, 'new, VisitFn, E> {
118-
src_tree: &'a Tree<'old>,
119-
other_repo: &'new Repository,
120-
recorder: gix_diff::tree::Recorder,
121-
visit: VisitFn,
122-
tracked: Option<rewrites::Tracker<gix_diff::tree::visit::Change>>,
123-
location: Option<gix_diff::tree::recorder::Location>,
124-
err: Option<E>,
125-
}
126-
127-
impl<'old, 'new, VisitFn, E> Delegate<'_, 'old, 'new, VisitFn, E>
128-
where
129-
VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result<Action, E>,
130-
E: Into<Box<dyn std::error::Error + Sync + Send + 'static>>,
131-
{
132-
/// Call `visit` on an attached version of `change`.
133-
fn emit_change(
134-
change: gix_diff::tree::visit::Change,
135-
location: &BStr,
136-
visit: &mut VisitFn,
137-
repo: &'old Repository,
138-
other_repo: &'new Repository,
139-
stored_err: &mut Option<E>,
140-
) -> gix_diff::tree::visit::Action {
141-
use gix_diff::tree::visit::Change::*;
142-
let event = match change {
143-
Addition {
144-
entry_mode,
145-
oid,
146-
relation: _,
147-
} => change::Event::Addition {
148-
entry_mode,
149-
id: oid.attach(other_repo),
150-
},
151-
Deletion {
152-
entry_mode,
153-
oid,
154-
relation: _,
155-
} => change::Event::Deletion {
156-
entry_mode,
157-
id: oid.attach(repo),
158-
},
159-
Modification {
160-
previous_entry_mode,
161-
previous_oid,
162-
entry_mode,
163-
oid,
164-
} => change::Event::Modification {
165-
previous_entry_mode,
166-
entry_mode,
167-
previous_id: previous_oid.attach(repo),
168-
id: oid.attach(other_repo),
169-
},
170-
};
171-
match visit(Change { event, location }) {
172-
Ok(Action::Cancel) => gix_diff::tree::visit::Action::Cancel,
173-
Ok(Action::Continue) => gix_diff::tree::visit::Action::Continue,
174-
Err(err) => {
175-
*stored_err = Some(err);
176-
gix_diff::tree::visit::Action::Cancel
177-
}
178-
}
179-
}
180-
181-
fn process_tracked_changes(
182-
&mut self,
183-
diff_cache: Option<&mut gix_diff::blob::Platform>,
184-
) -> Result<Option<rewrites::Outcome>, Error> {
185-
let tracked = match self.tracked.as_mut() {
186-
Some(t) => t,
187-
None => return Ok(None),
188-
};
189-
190-
let repo = self.src_tree.repo;
19169
let mut storage;
192-
let diff_cache = match diff_cache {
193-
Some(cache) => cache,
70+
let cache = match resource_cache {
19471
None => {
19572
storage = repo.diff_resource_cache(gix_diff::blob::pipeline::Mode::ToGit, Default::default())?;
19673
&mut storage
19774
}
75+
Some(cache) => cache,
19876
};
199-
200-
let outcome = tracked.emit(
201-
|dest, source| match source {
202-
Some(source) => {
203-
let (oid, mode) = dest.change.oid_and_entry_mode();
204-
let change = diff::Change {
205-
location: dest.location,
206-
event: diff::change::Event::Rewrite {
207-
source_location: source.location,
208-
source_entry_mode: source.entry_mode,
209-
source_id: source.id.attach(self.src_tree.repo),
210-
entry_mode: mode,
211-
id: oid.to_owned().attach(self.other_repo),
212-
diff: source.diff,
213-
copy: match source.kind {
214-
tracker::visit::SourceKind::Rename => false,
215-
tracker::visit::SourceKind::Copy => true,
216-
},
217-
},
218-
};
219-
match (self.visit)(change) {
220-
Ok(Action::Cancel) => gix_diff::tree::visit::Action::Cancel,
221-
Ok(Action::Continue) => gix_diff::tree::visit::Action::Continue,
222-
Err(err) => {
223-
self.err = Some(err);
224-
gix_diff::tree::visit::Action::Cancel
225-
}
226-
}
227-
}
228-
None => Self::emit_change(
229-
dest.change,
230-
dest.location,
231-
&mut self.visit,
232-
self.src_tree.repo,
233-
self.other_repo,
234-
&mut self.err,
235-
),
77+
Ok(gix_diff::tree_with_rewrites(
78+
TreeRefIter::from_bytes(&self.lhs.data),
79+
TreeRefIter::from_bytes(&other.data),
80+
cache,
81+
&mut self.state,
82+
&repo.objects,
83+
|change| {
84+
for_each(Change::from_change_ref(change, repo, other.repo)).map(|action| match action {
85+
Action::Continue => gix_diff::tree_with_rewrites::Action::Continue,
86+
Action::Cancel => gix_diff::tree_with_rewrites::Action::Cancel,
87+
})
23688
},
237-
diff_cache,
238-
&self.src_tree.repo.objects,
239-
|push| {
240-
self.src_tree
241-
.traverse()
242-
.breadthfirst(&mut tree_to_changes::Delegate::new(push, self.location))
89+
gix_diff::tree_with_rewrites::Options {
90+
location: self.location,
91+
rewrites: self.rewrites,
24392
},
244-
)?;
245-
Ok(Some(outcome))
246-
}
247-
}
248-
249-
impl<'old, 'new, VisitFn, E> gix_diff::tree::Visit for Delegate<'_, 'old, 'new, VisitFn, E>
250-
where
251-
VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result<Action, E>,
252-
E: Into<Box<dyn std::error::Error + Sync + Send + 'static>>,
253-
{
254-
fn pop_front_tracked_path_and_set_current(&mut self) {
255-
self.recorder.pop_front_tracked_path_and_set_current();
256-
}
257-
258-
fn push_back_tracked_path_component(&mut self, component: &BStr) {
259-
self.recorder.push_back_tracked_path_component(component);
260-
}
261-
262-
fn push_path_component(&mut self, component: &BStr) {
263-
self.recorder.push_path_component(component);
264-
}
265-
266-
fn pop_path_component(&mut self) {
267-
self.recorder.pop_path_component();
268-
}
269-
270-
fn visit(&mut self, change: gix_diff::tree::visit::Change) -> gix_diff::tree::visit::Action {
271-
match self.tracked.as_mut() {
272-
Some(tracked) => tracked.try_push_change(change, self.recorder.path()).map_or(
273-
gix_diff::tree::visit::Action::Continue,
274-
|change| {
275-
Self::emit_change(
276-
change,
277-
self.recorder.path(),
278-
&mut self.visit,
279-
self.src_tree.repo,
280-
self.other_repo,
281-
&mut self.err,
282-
)
283-
},
284-
),
285-
None => Self::emit_change(
286-
change,
287-
self.recorder.path(),
288-
&mut self.visit,
289-
self.src_tree.repo,
290-
self.other_repo,
291-
&mut self.err,
292-
),
293-
}
294-
}
295-
}
296-
297-
mod tree_to_changes {
298-
use gix_diff::tree::visit::Change;
299-
use gix_object::tree::EntryRef;
300-
301-
use crate::bstr::BStr;
302-
303-
pub struct Delegate<'a> {
304-
push: &'a mut dyn FnMut(Change, &BStr),
305-
recorder: gix_traverse::tree::Recorder,
306-
}
307-
308-
impl<'a> Delegate<'a> {
309-
pub fn new(
310-
push: &'a mut dyn FnMut(Change, &BStr),
311-
location: Option<gix_diff::tree::recorder::Location>,
312-
) -> Self {
313-
let location = location.map(|t| match t {
314-
gix_diff::tree::recorder::Location::FileName => gix_traverse::tree::recorder::Location::FileName,
315-
gix_diff::tree::recorder::Location::Path => gix_traverse::tree::recorder::Location::Path,
316-
});
317-
Self {
318-
push,
319-
recorder: gix_traverse::tree::Recorder::default().track_location(location),
320-
}
321-
}
322-
}
323-
324-
impl gix_traverse::tree::Visit for Delegate<'_> {
325-
fn pop_front_tracked_path_and_set_current(&mut self) {
326-
self.recorder.pop_front_tracked_path_and_set_current();
327-
}
328-
329-
fn push_back_tracked_path_component(&mut self, component: &BStr) {
330-
self.recorder.push_back_tracked_path_component(component);
331-
}
332-
333-
fn push_path_component(&mut self, component: &BStr) {
334-
self.recorder.push_path_component(component);
335-
}
336-
337-
fn pop_path_component(&mut self) {
338-
self.recorder.pop_path_component();
339-
}
340-
341-
fn visit_tree(&mut self, _entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action {
342-
gix_traverse::tree::visit::Action::Continue
343-
}
344-
345-
fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action {
346-
if entry.mode.is_blob() {
347-
(self.push)(
348-
Change::Modification {
349-
previous_entry_mode: entry.mode,
350-
previous_oid: gix_hash::ObjectId::null(entry.oid.kind()),
351-
entry_mode: entry.mode,
352-
oid: entry.oid.to_owned(),
353-
},
354-
self.recorder.path(),
355-
);
356-
}
357-
gix_traverse::tree::visit::Action::Continue
358-
}
93+
)?)
35994
}
36095
}

‎gix/src/object/tree/diff/mod.rs

+98-23
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
use gix_diff::tree;
12
use gix_diff::tree::recorder::Location;
23

3-
use crate::bstr::BString;
4-
use crate::{bstr::BStr, diff::Rewrites, Tree};
4+
use crate::{bstr::BStr, diff::Rewrites, Id, Tree};
55

66
/// Returned by the `for_each` function to control flow.
77
#[derive(Default, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
@@ -15,25 +15,100 @@ pub enum Action {
1515

1616
/// Represents any possible change in order to turn one tree into another.
1717
#[derive(Debug, Clone, Copy)]
18-
pub struct Change<'a, 'old, 'new> {
19-
/// The location of the file or directory described by `event`, if tracking was enabled.
18+
pub enum Change<'a, 'old, 'new> {
19+
/// An entry was added, like the addition of a file or directory.
20+
Addition {
21+
/// The location of the file or directory, if [tracking](Platform::track_path) was enabled.
22+
///
23+
/// It may be empty if neither [file names](Platform::track_filename()) nor [file paths](Platform::track_path())
24+
/// are tracked.
25+
location: &'a BStr,
26+
/// The mode of the added entry.
27+
entry_mode: gix_object::tree::EntryMode,
28+
/// Identifies a relationship between this instance and another one,
29+
/// making it easy to reconstruct the top-level of directory changes.
30+
relation: Option<tree::visit::Relation>,
31+
/// The object id of the added entry.
32+
id: Id<'new>,
33+
},
34+
/// An entry was deleted, like the deletion of a file or directory.
35+
Deletion {
36+
/// The location of the file or directory, if [tracking](Platform::track_path) was enabled.
37+
///
38+
/// Otherwise, this value is always an empty path.
39+
location: &'a BStr,
40+
/// The mode of the deleted entry.
41+
entry_mode: gix_object::tree::EntryMode,
42+
/// Identifies a relationship between this instance and another one,
43+
/// making it easy to reconstruct the top-level of directory changes.
44+
relation: Option<tree::visit::Relation>,
45+
/// The object id of the deleted entry.
46+
id: Id<'old>,
47+
},
48+
/// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning
49+
/// a file into a symbolic link adjusts its mode.
50+
Modification {
51+
/// The location of the file or directory, if [tracking](Platform::track_path) was enabled.
52+
///
53+
/// It may be empty if neither [file names](Platform::track_filename()) nor [file paths](Platform::track_path())
54+
/// are tracked.
55+
location: &'a BStr,
56+
/// The mode of the entry before the modification.
57+
previous_entry_mode: gix_object::tree::EntryMode,
58+
/// The object id of the entry before the modification.
59+
previous_id: Id<'old>,
60+
61+
/// The mode of the entry after the modification.
62+
entry_mode: gix_object::tree::EntryMode,
63+
/// The object id after the modification.
64+
id: Id<'new>,
65+
},
66+
/// Entries are considered rewritten if they are not trees and they, according to some understanding of identity, were renamed
67+
/// or copied.
68+
/// In case of renames, this means they originally appeared as [`Deletion`](Change::Deletion) signalling their source as well as an
69+
/// [`Addition`](Change::Addition) acting as destination.
2070
///
21-
/// Otherwise, this value is always an empty path.
22-
pub location: &'a BStr,
23-
/// The diff event itself to provide information about what would need to change.
24-
pub event: change::Event<'a, 'old, 'new>,
25-
}
26-
27-
/// Represents any possible change in order to turn one tree into another, the fully owned version
28-
/// of [`Change`].
29-
#[derive(Debug, Clone)]
30-
pub struct ChangeDetached {
31-
/// The location of the file or directory described by `event`, if tracking was enabled.
71+
/// In case of copies, the `copy` flag is true and typically represents a perfect copy of a source was made.
72+
///
73+
/// This variant can only be encountered if [rewrite tracking](Platform::track_rewrites()) is enabled.
3274
///
33-
/// Otherwise, this value is always an empty path.
34-
pub location: BString,
35-
/// The diff event itself to provide information about what would need to change.
36-
pub event: change::EventDetached,
75+
/// Note that mode changes may have occurred as well, i.e. changes from executable to non-executable or vice-versa.
76+
Rewrite {
77+
/// The location of the source of the rename operation.
78+
///
79+
/// It may be empty if neither [file names](Platform::track_filename()) nor [file paths](Platform::track_path())
80+
/// are tracked.
81+
source_location: &'a BStr,
82+
/// Identifies a relationship between the source and another source,
83+
/// making it easy to reconstruct the top-level of directory changes.
84+
source_relation: Option<tree::visit::Relation>,
85+
/// The mode of the entry before the rename.
86+
source_entry_mode: gix_object::tree::EntryMode,
87+
/// The object id of the entry before the rename.
88+
///
89+
/// Note that this is the same as `id` if we require the [similarity to be 100%](Rewrites::percentage), but may
90+
/// be different otherwise.
91+
source_id: Id<'old>,
92+
/// Information about the diff we performed to detect similarity and match the `source_id` with the current state at `id`.
93+
/// It's `None` if `source_id` is equal to `id`, as identity made an actual diff computation unnecessary.
94+
diff: Option<gix_diff::blob::DiffLineStats>,
95+
/// The mode of the entry after the rename.
96+
/// It could differ but still be considered a rename as we are concerned only about content.
97+
entry_mode: gix_object::tree::EntryMode,
98+
/// The location of the destination file or directory, if [tracking](Platform::track_path) was enabled.
99+
///
100+
/// It may be empty if neither [file names](Platform::track_filename()) nor [file paths](Platform::track_path())
101+
/// are tracked.
102+
location: &'a BStr,
103+
/// The object id after the rename.
104+
id: Id<'new>,
105+
/// Identifies a relationship between this destination and another destination,
106+
/// making it easy to reconstruct the top-level of directory changes.
107+
relation: Option<tree::visit::Relation>,
108+
/// If true, this rewrite is created by copy, and `source_id` is pointing to its source. Otherwise, it's a rename, and `source_id`
109+
/// points to a deleted object, as renames are tracked as deletions and additions of the same or similar content.
110+
copy: bool,
111+
},
37112
}
38113

39114
///
@@ -58,7 +133,7 @@ impl<'repo> Tree<'repo> {
58133
Ok(Platform {
59134
state: Default::default(),
60135
lhs: self,
61-
tracking: None,
136+
location: None,
62137
rewrites: self.repo.config.diff_renames()?.unwrap_or_default().into(),
63138
})
64139
}
@@ -69,23 +144,23 @@ impl<'repo> Tree<'repo> {
69144
pub struct Platform<'a, 'repo> {
70145
state: gix_diff::tree::State,
71146
lhs: &'a Tree<'repo>,
72-
tracking: Option<Location>,
147+
location: Option<Location>,
73148
rewrites: Option<Rewrites>,
74149
}
75150

76151
/// Configuration
77152
impl Platform<'_, '_> {
78153
/// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item.
79154
pub fn track_filename(&mut self) -> &mut Self {
80-
self.tracking = Some(Location::FileName);
155+
self.location = Some(Location::FileName);
81156
self
82157
}
83158

84159
/// Keep track of the entire path of a change, relative to the repository.
85160
///
86161
/// This makes the [`location`][Change::location] field usable.
87162
pub fn track_path(&mut self) -> &mut Self {
88-
self.tracking = Some(Location::Path);
163+
self.location = Some(Location::Path);
89164
self
90165
}
91166

‎gix/tests/object/tree/diff.rs

+28-1,142
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.