gix_diff/tree/
recorder.rs

1use gix_hash::ObjectId;
2use gix_object::{
3    bstr::{BStr, BString, ByteSlice, ByteVec},
4    tree,
5};
6
7use crate::tree::{visit, visit::Relation, Recorder, Visit};
8
9/// Describe how to track the location of a change.
10#[derive(Debug, Clone, Copy, Eq, PartialEq)]
11pub enum Location {
12    /// Track the entire path, relative to the repository.
13    Path,
14    /// Keep only the file-name as location, which may be enough for some calculations.
15    ///
16    /// This is less expensive than tracking the entire `Path`.
17    FileName,
18}
19
20/// A Change as observed by a call to [`visit(…)`](Visit::visit()), enhanced with the path affected by the change.
21/// Its similar to [`visit::Change`] but includes the path that changed.
22#[derive(Clone, Debug, PartialEq, Eq)]
23#[allow(missing_docs)]
24pub enum Change {
25    Addition {
26        entry_mode: tree::EntryMode,
27        oid: ObjectId,
28        path: BString,
29        relation: Option<Relation>,
30    },
31    Deletion {
32        entry_mode: tree::EntryMode,
33        oid: ObjectId,
34        path: BString,
35        relation: Option<Relation>,
36    },
37    Modification {
38        previous_entry_mode: tree::EntryMode,
39        previous_oid: ObjectId,
40
41        entry_mode: tree::EntryMode,
42        oid: ObjectId,
43
44        path: BString,
45    },
46}
47
48impl Default for Recorder {
49    fn default() -> Self {
50        Recorder {
51            path_deque: Default::default(),
52            path: Default::default(),
53            location: Some(Location::Path),
54            records: vec![],
55        }
56    }
57}
58
59/// Builder
60impl Recorder {
61    /// Obtain a copy of the currently tracked, full path of the entry.
62    pub fn track_location(mut self, location: Option<Location>) -> Self {
63        self.location = location;
64        self
65    }
66}
67
68/// Access
69impl Recorder {
70    /// Obtain a copy of the currently tracked, full path of the entry.
71    pub fn path_clone(&self) -> BString {
72        self.path.clone()
73    }
74
75    /// Return the currently set path.
76    pub fn path(&self) -> &BStr {
77        self.path.as_ref()
78    }
79}
80
81impl Recorder {
82    fn pop_element(&mut self) {
83        if let Some(pos) = self.path.rfind_byte(b'/') {
84            self.path.resize(pos, 0);
85        } else {
86            self.path.clear();
87        }
88    }
89
90    fn push_element(&mut self, name: &BStr) {
91        if name.is_empty() {
92            return;
93        }
94        if !self.path.is_empty() {
95            self.path.push(b'/');
96        }
97        self.path.push_str(name);
98    }
99}
100
101impl Visit for Recorder {
102    fn pop_front_tracked_path_and_set_current(&mut self) {
103        if let Some(Location::Path) = self.location {
104            self.path = self.path_deque.pop_front().expect("every parent is set only once");
105        }
106    }
107
108    fn push_back_tracked_path_component(&mut self, component: &BStr) {
109        match self.location {
110            None => {}
111            Some(Location::Path) => {
112                self.push_element(component);
113                self.path_deque.push_back(self.path.clone());
114            }
115            Some(Location::FileName) => {
116                self.path.clear();
117                self.path.extend_from_slice(component);
118            }
119        }
120    }
121
122    fn push_path_component(&mut self, component: &BStr) {
123        match self.location {
124            None => {}
125            Some(Location::Path) => {
126                self.push_element(component);
127            }
128            Some(Location::FileName) => {
129                self.path.clear();
130                self.path.extend_from_slice(component);
131            }
132        }
133    }
134
135    fn pop_path_component(&mut self) {
136        if let Some(Location::Path) = self.location {
137            self.pop_element();
138        }
139    }
140
141    fn visit(&mut self, change: visit::Change) -> visit::Action {
142        use visit::Change::*;
143        self.records.push(match change {
144            Deletion {
145                entry_mode,
146                oid,
147                relation,
148            } => Change::Deletion {
149                entry_mode,
150                oid,
151                path: self.path_clone(),
152                relation,
153            },
154            Addition {
155                entry_mode,
156                oid,
157                relation,
158            } => Change::Addition {
159                entry_mode,
160                oid,
161                path: self.path_clone(),
162                relation,
163            },
164            Modification {
165                previous_entry_mode,
166                previous_oid,
167                entry_mode,
168                oid,
169            } => Change::Modification {
170                previous_entry_mode,
171                previous_oid,
172                entry_mode,
173                oid,
174                path: self.path_clone(),
175            },
176        });
177        visit::Action::Continue
178    }
179}