gix_diff/tree_with_rewrites/
function.rs

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