gix_diff/tree_with_rewrites/
function.rs

1use bstr::BStr;
2use gix_object::TreeRefIter;
3
4use super::{Action, ChangeRef, Error, Options};
5use crate::rewrites;
6use crate::rewrites::tracker;
7
8/// Call `for_each` repeatedly with all changes that are needed to convert `lhs` to `rhs`.
9/// Provide a `resource_cache` to speed up obtaining blobs for similarity checks.
10/// `tree_diff_state` can be used to re-use tree-diff memory between calls.
11/// `objects` are used to lookup trees while performing the diff.
12/// Use `options` to further configure how the rename tracking is performed.
13///
14/// Reusing `resource_cache` between multiple invocations saves a lot of IOps as it avoids the creation
15/// of a temporary `resource_cache` that triggers reading or checking for multiple gitattribute files.
16/// Note that it's recommended to call [`clear_resource_cache()`](`crate::blob::Platform::clear_resource_cache()`)
17/// between the calls to avoid runaway memory usage, as the cache isn't limited.
18///
19/// Note that to do rename tracking like `git` does, one has to configure the `resource_cache` with
20/// a conversion pipeline that uses [`crate::blob::pipeline::Mode::ToGit`].
21///
22/// `rhs` or `lhs` can be empty to indicate deletion or addition of an entire tree.
23///
24/// Note that the rewrite outcome is only available if [rewrite-tracking was enabled](Options::rewrites).
25pub fn diff<E>(
26    lhs: TreeRefIter<'_>,
27    rhs: TreeRefIter<'_>,
28    resource_cache: &mut crate::blob::Platform,
29    tree_diff_state: &mut crate::tree::State,
30    objects: &impl gix_object::FindObjectOrHeader,
31    for_each: impl FnMut(ChangeRef<'_>) -> Result<Action, E>,
32    options: Options,
33) -> Result<Option<rewrites::Outcome>, Error>
34where
35    E: Into<Box<dyn std::error::Error + Sync + Send + 'static>>,
36{
37    let mut delegate = Delegate {
38        src_tree: lhs,
39        recorder: crate::tree::Recorder::default().track_location(options.location),
40        visit: for_each,
41        location: options.location,
42        objects,
43        tracked: options.rewrites.map(rewrites::Tracker::new),
44        err: None,
45    };
46    match crate::tree(lhs, rhs, tree_diff_state, objects, &mut delegate) {
47        Ok(()) => {
48            let outcome = delegate.process_tracked_changes(resource_cache)?;
49            match delegate.err {
50                Some(err) => Err(Error::ForEach(err.into())),
51                None => Ok(outcome),
52            }
53        }
54        Err(crate::tree::Error::Cancelled) => delegate
55            .err
56            .map_or(Err(Error::Diff(crate::tree::Error::Cancelled)), |err| {
57                Err(Error::ForEach(err.into()))
58            }),
59        Err(err) => Err(err.into()),
60    }
61}
62
63struct Delegate<'a, 'old, VisitFn, E, Objects> {
64    src_tree: TreeRefIter<'old>,
65    recorder: crate::tree::Recorder,
66    objects: &'a Objects,
67    visit: VisitFn,
68    tracked: Option<rewrites::Tracker<crate::tree::visit::Change>>,
69    location: Option<crate::tree::recorder::Location>,
70    err: Option<E>,
71}
72
73impl<VisitFn, E, Objects> Delegate<'_, '_, VisitFn, E, Objects>
74where
75    Objects: gix_object::FindObjectOrHeader,
76    VisitFn: for<'delegate> FnMut(ChangeRef<'_>) -> Result<Action, E>,
77    E: Into<Box<dyn std::error::Error + Sync + Send + 'static>>,
78{
79    /// Call `visit` on an attached version of `change`.
80    fn emit_change(
81        change: crate::tree::visit::Change,
82        location: &BStr,
83        visit: &mut VisitFn,
84        stored_err: &mut Option<E>,
85    ) -> crate::tree::visit::Action {
86        use crate::tree::visit::Change::*;
87        let change = match change {
88            Addition {
89                entry_mode,
90                oid,
91                relation,
92            } => ChangeRef::Addition {
93                location,
94                relation,
95                entry_mode,
96                id: oid,
97            },
98            Deletion {
99                entry_mode,
100                oid,
101                relation,
102            } => ChangeRef::Deletion {
103                entry_mode,
104                location,
105                relation,
106                id: oid,
107            },
108            Modification {
109                previous_entry_mode,
110                previous_oid,
111                entry_mode,
112                oid,
113            } => ChangeRef::Modification {
114                location,
115                previous_entry_mode,
116                entry_mode,
117                previous_id: previous_oid,
118                id: oid,
119            },
120        };
121        match visit(change) {
122            Ok(Action::Cancel) => crate::tree::visit::Action::Cancel,
123            Ok(Action::Continue) => crate::tree::visit::Action::Continue,
124            Err(err) => {
125                *stored_err = Some(err);
126                crate::tree::visit::Action::Cancel
127            }
128        }
129    }
130
131    fn process_tracked_changes(
132        &mut self,
133        diff_cache: &mut crate::blob::Platform,
134    ) -> Result<Option<rewrites::Outcome>, Error> {
135        use crate::rewrites::tracker::Change as _;
136        let tracked = match self.tracked.as_mut() {
137            Some(t) => t,
138            None => return Ok(None),
139        };
140
141        let outcome = tracked.emit(
142            |dest, source| match source {
143                Some(source) => {
144                    let (oid, mode) = dest.change.oid_and_entry_mode();
145                    let change = ChangeRef::Rewrite {
146                        source_location: source.location,
147                        source_entry_mode: source.entry_mode,
148                        source_id: source.id,
149                        source_relation: source.change.relation(),
150                        entry_mode: mode,
151                        id: oid.to_owned(),
152                        relation: dest.change.relation(),
153                        diff: source.diff,
154                        location: dest.location,
155                        copy: match source.kind {
156                            tracker::visit::SourceKind::Rename => false,
157                            tracker::visit::SourceKind::Copy => true,
158                        },
159                    };
160                    match (self.visit)(change) {
161                        Ok(Action::Cancel) => crate::tree::visit::Action::Cancel,
162                        Ok(Action::Continue) => crate::tree::visit::Action::Continue,
163                        Err(err) => {
164                            self.err = Some(err);
165                            crate::tree::visit::Action::Cancel
166                        }
167                    }
168                }
169                None => Self::emit_change(dest.change, dest.location, &mut self.visit, &mut self.err),
170            },
171            diff_cache,
172            self.objects,
173            |push| {
174                let mut delegate = tree_to_changes::Delegate::new(push, self.location);
175                let state = gix_traverse::tree::breadthfirst::State::default();
176                gix_traverse::tree::breadthfirst(self.src_tree, state, self.objects, &mut delegate)
177            },
178        )?;
179        Ok(Some(outcome))
180    }
181}
182
183impl<VisitFn, E, Objects> crate::tree::Visit for Delegate<'_, '_, VisitFn, E, Objects>
184where
185    Objects: gix_object::FindObjectOrHeader,
186    VisitFn: for<'delegate> FnMut(ChangeRef<'_>) -> Result<Action, E>,
187    E: Into<Box<dyn std::error::Error + Sync + Send + 'static>>,
188{
189    fn pop_front_tracked_path_and_set_current(&mut self) {
190        self.recorder.pop_front_tracked_path_and_set_current();
191    }
192
193    fn push_back_tracked_path_component(&mut self, component: &BStr) {
194        self.recorder.push_back_tracked_path_component(component);
195    }
196
197    fn push_path_component(&mut self, component: &BStr) {
198        self.recorder.push_path_component(component);
199    }
200
201    fn pop_path_component(&mut self) {
202        self.recorder.pop_path_component();
203    }
204
205    fn visit(&mut self, change: crate::tree::visit::Change) -> crate::tree::visit::Action {
206        match self.tracked.as_mut() {
207            Some(tracked) => tracked
208                .try_push_change(change, self.recorder.path())
209                .map_or(crate::tree::visit::Action::Continue, |change| {
210                    Self::emit_change(change, self.recorder.path(), &mut self.visit, &mut self.err)
211                }),
212            None => Self::emit_change(change, self.recorder.path(), &mut self.visit, &mut self.err),
213        }
214    }
215}
216
217mod tree_to_changes {
218    use crate::tree::visit::Change;
219    use gix_object::tree::EntryRef;
220
221    use bstr::BStr;
222
223    pub struct Delegate<'a> {
224        push: &'a mut dyn FnMut(Change, &BStr),
225        recorder: gix_traverse::tree::Recorder,
226    }
227
228    impl<'a> Delegate<'a> {
229        pub fn new(push: &'a mut dyn FnMut(Change, &BStr), location: Option<crate::tree::recorder::Location>) -> Self {
230            let location = location.map(|t| match t {
231                crate::tree::recorder::Location::FileName => gix_traverse::tree::recorder::Location::FileName,
232                crate::tree::recorder::Location::Path => gix_traverse::tree::recorder::Location::Path,
233            });
234            Self {
235                push,
236                recorder: gix_traverse::tree::Recorder::default().track_location(location),
237            }
238        }
239    }
240
241    impl gix_traverse::tree::Visit for Delegate<'_> {
242        fn pop_back_tracked_path_and_set_current(&mut self) {
243            self.recorder.pop_back_tracked_path_and_set_current();
244        }
245
246        fn pop_front_tracked_path_and_set_current(&mut self) {
247            self.recorder.pop_front_tracked_path_and_set_current();
248        }
249
250        fn push_back_tracked_path_component(&mut self, component: &BStr) {
251            self.recorder.push_back_tracked_path_component(component);
252        }
253
254        fn push_path_component(&mut self, component: &BStr) {
255            self.recorder.push_path_component(component);
256        }
257
258        fn pop_path_component(&mut self) {
259            self.recorder.pop_path_component();
260        }
261
262        fn visit_tree(&mut self, _entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action {
263            gix_traverse::tree::visit::Action::Continue
264        }
265
266        fn visit_nontree(&mut self, entry: &EntryRef<'_>) -> gix_traverse::tree::visit::Action {
267            if entry.mode.is_blob() {
268                (self.push)(
269                    Change::Modification {
270                        previous_entry_mode: entry.mode,
271                        previous_oid: gix_hash::ObjectId::null(entry.oid.kind()),
272                        entry_mode: entry.mode,
273                        oid: entry.oid.to_owned(),
274                    },
275                    self.recorder.path(),
276                );
277            }
278            gix_traverse::tree::visit::Action::Continue
279        }
280    }
281}