Skip to main content

gix_diff/tree_with_rewrites/
function.rs

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