History

refactor (#450)

Sebastian Thiel committed 4 years ago

Commit 71c6a201 parent 71f15fc4 files · +334 −336Browse files

Unified · Split

4 files
  1. git-repository/src/object/tree/diff.rs +0 −336
  2. git-repository/src/object/tree/diff/change.rs +99 −0
  3. git-repository/src/object/tree/diff/for_each.rs +155 −0
  4. git-repository/src/object/tree/diff/mod.rs +80 −0
git-repository/src/object/tree/diff.rs+0−336
@@ -1,336 +0,0 @@
1use crate::{bstr::BStr, Repository, Tree};
2
3/// Returned by the `for_each` function to control flow.
4#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
5pub enum Action {
6 /// Continue the traversal of changes.
7 Continue,
8 /// Stop the traversal of changes and stop calling this function.
9 Cancel,
10}
11
12impl Default for Action {
13 fn default() -> Self {
14 Action::Continue
15 }
16}
17
18/// Represents any possible change in order to turn one tree into another.
19#[derive(Debug, Clone, Copy)]
20pub struct Change<'a, 'old, 'new> {
21 /// The location of the file or directory described by `event`, if tracking was enabled.
22 ///
23 /// Otherwise this value is always an empty path.
24 pub location: &'a BStr,
25 /// The diff event itself to provide information about what would need to change.
26 pub event: change::Event<'old, 'new>,
27}
28
29///
30pub mod change {
31 use git_object::tree::EntryMode;
32
33 use crate::{bstr::ByteSlice, Id, Repository};
34
35 /// An event emitted when finding differences between two trees.
36 #[derive(Debug, Clone, Copy)]
37 pub enum Event<'old, 'new> {
38 /// An entry was added, like the addition of a file or directory.
39 Addition {
40 /// The mode of the added entry.
41 entry_mode: git_object::tree::EntryMode,
42 /// The object id of the added entry.
43 id: Id<'new>,
44 },
45 /// An entry was deleted, like the deletion of a file or directory.
46 Deletion {
47 /// The mode of the deleted entry.
48 entry_mode: git_object::tree::EntryMode,
49 /// The object id of the deleted entry.
50 id: Id<'old>,
51 },
52 /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning
53 /// a file into a symbolic link adjusts its mode.
54 Modification {
55 /// The mode of the entry before the modification.
56 previous_entry_mode: git_object::tree::EntryMode,
57 /// The object id of the entry before the modification.
58 previous_id: Id<'old>,
59
60 /// The mode of the entry after the modification.
61 entry_mode: git_object::tree::EntryMode,
62 /// The object id after the modification.
63 id: Id<'new>,
64 },
65 }
66
67 /// A platform to keep temporary information to perform line diffs.
68 pub struct DiffPlatform<'old, 'new> {
69 old: crate::Object<'old>,
70 new: crate::Object<'new>,
71 algo: git_diff::text::Algorithm,
72 }
73
74 impl<'old, 'new> Event<'old, 'new> {
75 fn repo(&self) -> &Repository {
76 match self {
77 Event::Addition { id, .. } => id.repo,
78 Event::Deletion { id, .. } => id.repo,
79 Event::Modification { id, .. } => id.repo,
80 }
81 }
82 }
83
84 impl<'old, 'new> Event<'old, 'new> {
85 /// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification]
86 /// or one of the entries to compare is not a blob.
87 pub fn diff(&self) -> Option<Result<DiffPlatform<'old, 'new>, crate::object::find::existing::Error>> {
88 // let algo = self.repo().config.diff_algorithm()?;
89 match self {
90 Event::Modification {
91 previous_entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
92 previous_id,
93 entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
94 id,
95 } => match previous_id.object().and_then(|old| id.object().map(|new| (old, new))) {
96 Ok((old, new)) => Some(Ok(DiffPlatform {
97 old,
98 new,
99 algo: git_diff::text::Algorithm::Myers,
100 })),
101 Err(err) => Some(Err(err)),
102 },
103 _ => None,
104 }
105 }
106 }
107
108 impl<'old, 'new> DiffPlatform<'old, 'new> {
109 /// Perform a diff on lines between the old and the new version of a blob.
110 /// The algorithm is determined by the `diff.algorithm` configuration.
111 /// Note that the [`Sink`][git_diff::text::imara::Sink] implementation is
112 /// what makes the diff usable and relies heavily on what the caller requires, as created by `make_sink`.
113 pub fn lines<FnS, S>(&self, new_sink: FnS) -> S::Out
114 where
115 FnS: for<'a> FnOnce(&git_diff::text::imara::intern::InternedInput<&'a [u8]>) -> S,
116 S: git_diff::text::imara::Sink,
117 {
118 git_diff::text::with(
119 self.old.data.as_bstr(),
120 self.new.data.as_bstr(),
121 git_diff::text::Algorithm::Myers, // TODO: use diff.algorithm
122 // TODO: make use of `core.eol` and/or filters to do line-counting correctly. It's probably
123 // OK to just know how these objects are saved to know what constitutes a line.
124 git_diff::text::imara::intern::InternedInput::new,
125 new_sink,
126 )
127 .1
128 }
129 }
130}
131
132/// Diffing
133impl<'repo> Tree<'repo> {
134 /// Return a platform to see the changes needed to create other trees, for instance.
135 ///
136 /// # Performance
137 ///
138 /// It's highly recommended to set an object cache to avoid extracting the same object multiple times.
139 pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> {
140 Platform {
141 state: Default::default(),
142 lhs: self,
143 tracking: None,
144 }
145 }
146}
147
148/// The diffing platform returned by [`Tree::changes()`].
149#[derive(Clone)]
150pub struct Platform<'a, 'repo> {
151 state: git_diff::tree::State,
152 lhs: &'a Tree<'repo>,
153 tracking: Option<Tracking>,
154}
155
156#[derive(Clone, Copy)]
157enum Tracking {
158 FileName,
159 Path,
160}
161
162/// Configuration
163impl<'a, 'repo> Platform<'a, 'repo> {
164 /// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item.
165 pub fn track_filename(&mut self) -> &mut Self {
166 self.tracking = Some(Tracking::FileName);
167 self
168 }
169
170 /// Keep track of the entire path of a change, relative to the repository.
171 ///
172 /// This makes the [`location`][Change::location] field usable.
173 pub fn track_path(&mut self) -> &mut Self {
174 self.tracking = Some(Tracking::Path);
175 self
176 }
177}
178
179///
180pub mod for_each {
181 use super::{change, Action, Change, Platform, Tracking};
182 use crate::bstr::{BStr, BString, ByteSlice, ByteVec};
183 use crate::ext::ObjectIdExt;
184 use crate::{Repository, Tree};
185 use git_object::TreeRefIter;
186 use git_odb::FindExt;
187 use std::collections::VecDeque;
188
189 /// The error return by methods on the [diff platform][Platform].
190 #[derive(Debug, thiserror::Error)]
191 #[allow(missing_docs)]
192 pub enum Error {
193 #[error(transparent)]
194 Diff(#[from] git_diff::tree::changes::Error),
195 #[error("The user-provided callback failed")]
196 ForEach(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
197 }
198
199 /// Add the item to compare to.
200 impl<'a, 'old> Platform<'a, 'old> {
201 /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`.
202 pub fn for_each_to_obtain_tree<'new, E>(
203 &mut self,
204 other: &Tree<'new>,
205 for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>,
206 ) -> Result<(), Error>
207 where
208 E: std::error::Error + Sync + Send + 'static,
209 {
210 let repo = self.lhs.repo;
211 let mut delegate = Delegate {
212 repo: self.lhs.repo,
213 other_repo: other.repo,
214 tracking: self.tracking,
215 location: BString::default(),
216 path_deque: Default::default(),
217 visit: for_each,
218 err: None,
219 };
220 git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain(
221 TreeRefIter::from_bytes(&other.data),
222 &mut self.state,
223 |oid, buf| repo.objects.find_tree_iter(oid, buf),
224 &mut delegate,
225 )?;
226 match delegate.err {
227 Some(err) => Err(Error::ForEach(Box::new(err))),
228 None => Ok(()),
229 }
230 }
231 }
232
233 struct Delegate<'old, 'new, VisitFn, E> {
234 repo: &'old Repository,
235 other_repo: &'new Repository,
236 tracking: Option<Tracking>,
237 location: BString,
238 path_deque: VecDeque<BString>,
239 visit: VisitFn,
240 err: Option<E>,
241 }
242
243 impl<A, B> Delegate<'_, '_, A, B> {
244 fn pop_element(&mut self) {
245 if let Some(pos) = self.location.rfind_byte(b'/') {
246 self.location.resize(pos, 0);
247 } else {
248 self.location.clear();
249 }
250 }
251
252 fn push_element(&mut self, name: &BStr) {
253 if !self.location.is_empty() {
254 self.location.push(b'/');
255 }
256 self.location.push_str(name);
257 }
258 }
259
260 impl<'old, 'new, VisitFn, E> git_diff::tree::Visit for Delegate<'old, 'new, VisitFn, E>
261 where
262 VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result<Action, E>,
263 E: std::error::Error + Sync + Send + 'static,
264 {
265 fn pop_front_tracked_path_and_set_current(&mut self) {
266 if let Some(Tracking::Path) = self.tracking {
267 self.location = self
268 .path_deque
269 .pop_front()
270 .expect("every call is matched with push_tracked_path_component");
271 }
272 }
273
274 fn push_back_tracked_path_component(&mut self, component: &BStr) {
275 if let Some(Tracking::Path) = self.tracking {
276 self.push_element(component);
277 self.path_deque.push_back(self.location.clone());
278 }
279 }
280
281 fn push_path_component(&mut self, component: &BStr) {
282 match self.tracking {
283 Some(Tracking::FileName) => {
284 self.location.clear();
285 self.location.push_str(component);
286 }
287 Some(Tracking::Path) => {
288 self.push_element(component);
289 }
290 None => {}
291 }
292 }
293
294 fn pop_path_component(&mut self) {
295 if let Some(Tracking::Path) = self.tracking {
296 self.pop_element();
297 }
298 }
299
300 fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action {
301 use git_diff::tree::visit::Change::*;
302 let event = match change {
303 Addition { entry_mode, oid } => change::Event::Addition {
304 entry_mode,
305 id: oid.attach(self.other_repo),
306 },
307 Deletion { entry_mode, oid } => change::Event::Deletion {
308 entry_mode,
309 id: oid.attach(self.repo),
310 },
311 Modification {
312 previous_entry_mode,
313 previous_oid,
314 entry_mode,
315 oid,
316 } => change::Event::Modification {
317 previous_entry_mode,
318 entry_mode,
319 previous_id: previous_oid.attach(self.repo),
320 id: oid.attach(self.other_repo),
321 },
322 };
323 match (self.visit)(Change {
324 event,
325 location: self.location.as_ref(),
326 }) {
327 Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel,
328 Ok(Action::Continue) => git_diff::tree::visit::Action::Continue,
329 Err(err) => {
330 self.err = Some(err);
331 git_diff::tree::visit::Action::Cancel
332 }
333 }
334 }
335 }
336}
git-repository/src/object/tree/diff/change.rs+99−0
@@ -0,0 +1,99 @@
1use git_object::tree::EntryMode;
2
3use crate::{bstr::ByteSlice, Id, Repository};
4
5/// An event emitted when finding differences between two trees.
6#[derive(Debug, Clone, Copy)]
7pub enum Event<'old, 'new> {
8 /// An entry was added, like the addition of a file or directory.
9 Addition {
10 /// The mode of the added entry.
11 entry_mode: git_object::tree::EntryMode,
12 /// The object id of the added entry.
13 id: Id<'new>,
14 },
15 /// An entry was deleted, like the deletion of a file or directory.
16 Deletion {
17 /// The mode of the deleted entry.
18 entry_mode: git_object::tree::EntryMode,
19 /// The object id of the deleted entry.
20 id: Id<'old>,
21 },
22 /// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning
23 /// a file into a symbolic link adjusts its mode.
24 Modification {
25 /// The mode of the entry before the modification.
26 previous_entry_mode: git_object::tree::EntryMode,
27 /// The object id of the entry before the modification.
28 previous_id: Id<'old>,
29
30 /// The mode of the entry after the modification.
31 entry_mode: git_object::tree::EntryMode,
32 /// The object id after the modification.
33 id: Id<'new>,
34 },
35}
36
37/// A platform to keep temporary information to perform line diffs.
38pub struct DiffPlatform<'old, 'new> {
39 old: crate::Object<'old>,
40 new: crate::Object<'new>,
41 algo: git_diff::text::Algorithm,
42}
43
44impl<'old, 'new> Event<'old, 'new> {
45 fn repo(&self) -> &Repository {
46 match self {
47 Event::Addition { id, .. } => id.repo,
48 Event::Deletion { id, .. } => id.repo,
49 Event::Modification { id, .. } => id.repo,
50 }
51 }
52}
53
54impl<'old, 'new> Event<'old, 'new> {
55 /// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification]
56 /// or one of the entries to compare is not a blob.
57 pub fn diff(&self) -> Option<Result<DiffPlatform<'old, 'new>, crate::object::find::existing::Error>> {
58 // let algo = self.repo().config.diff_algorithm()?;
59 match self {
60 Event::Modification {
61 previous_entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
62 previous_id,
63 entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
64 id,
65 } => match previous_id.object().and_then(|old| id.object().map(|new| (old, new))) {
66 Ok((old, new)) => Some(Ok(DiffPlatform {
67 old,
68 new,
69 algo: git_diff::text::Algorithm::Myers,
70 })),
71 Err(err) => Some(Err(err)),
72 },
73 _ => None,
74 }
75 }
76}
77
78impl<'old, 'new> DiffPlatform<'old, 'new> {
79 /// Perform a diff on lines between the old and the new version of a blob.
80 /// The algorithm is determined by the `diff.algorithm` configuration.
81 /// Note that the [`Sink`][git_diff::text::imara::Sink] implementation is
82 /// what makes the diff usable and relies heavily on what the caller requires, as created by `make_sink`.
83 pub fn lines<FnS, S>(&self, new_sink: FnS) -> S::Out
84 where
85 FnS: for<'a> FnOnce(&git_diff::text::imara::intern::InternedInput<&'a [u8]>) -> S,
86 S: git_diff::text::imara::Sink,
87 {
88 git_diff::text::with(
89 self.old.data.as_bstr(),
90 self.new.data.as_bstr(),
91 git_diff::text::Algorithm::Myers, // TODO: use diff.algorithm
92 // TODO: make use of `core.eol` and/or filters to do line-counting correctly. It's probably
93 // OK to just know how these objects are saved to know what constitutes a line.
94 git_diff::text::imara::intern::InternedInput::new,
95 new_sink,
96 )
97 .1
98 }
99}
git-repository/src/object/tree/diff/for_each.rs+155−0
@@ -0,0 +1,155 @@
1use super::{change, Action, Change, Platform, Tracking};
2use crate::bstr::{BStr, BString, ByteSlice, ByteVec};
3use crate::ext::ObjectIdExt;
4use crate::{Repository, Tree};
5use git_object::TreeRefIter;
6use git_odb::FindExt;
7use std::collections::VecDeque;
8
9/// The error return by methods on the [diff platform][Platform].
10#[derive(Debug, thiserror::Error)]
11#[allow(missing_docs)]
12pub enum Error {
13 #[error(transparent)]
14 Diff(#[from] git_diff::tree::changes::Error),
15 #[error("The user-provided callback failed")]
16 ForEach(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
17}
18
19/// Add the item to compare to.
20impl<'a, 'old> Platform<'a, 'old> {
21 /// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`.
22 pub fn for_each_to_obtain_tree<'new, E>(
23 &mut self,
24 other: &Tree<'new>,
25 for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>,
26 ) -> Result<(), Error>
27 where
28 E: std::error::Error + Sync + Send + 'static,
29 {
30 let repo = self.lhs.repo;
31 let mut delegate = Delegate {
32 repo: self.lhs.repo,
33 other_repo: other.repo,
34 tracking: self.tracking,
35 location: BString::default(),
36 path_deque: Default::default(),
37 visit: for_each,
38 err: None,
39 };
40 git_diff::tree::Changes::from(TreeRefIter::from_bytes(&self.lhs.data)).needed_to_obtain(
41 TreeRefIter::from_bytes(&other.data),
42 &mut self.state,
43 |oid, buf| repo.objects.find_tree_iter(oid, buf),
44 &mut delegate,
45 )?;
46 match delegate.err {
47 Some(err) => Err(Error::ForEach(Box::new(err))),
48 None => Ok(()),
49 }
50 }
51}
52
53struct Delegate<'old, 'new, VisitFn, E> {
54 repo: &'old Repository,
55 other_repo: &'new Repository,
56 tracking: Option<Tracking>,
57 location: BString,
58 path_deque: VecDeque<BString>,
59 visit: VisitFn,
60 err: Option<E>,
61}
62
63impl<A, B> Delegate<'_, '_, A, B> {
64 fn pop_element(&mut self) {
65 if let Some(pos) = self.location.rfind_byte(b'/') {
66 self.location.resize(pos, 0);
67 } else {
68 self.location.clear();
69 }
70 }
71
72 fn push_element(&mut self, name: &BStr) {
73 if !self.location.is_empty() {
74 self.location.push(b'/');
75 }
76 self.location.push_str(name);
77 }
78}
79
80impl<'old, 'new, VisitFn, E> git_diff::tree::Visit for Delegate<'old, 'new, VisitFn, E>
81where
82 VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result<Action, E>,
83 E: std::error::Error + Sync + Send + 'static,
84{
85 fn pop_front_tracked_path_and_set_current(&mut self) {
86 if let Some(Tracking::Path) = self.tracking {
87 self.location = self
88 .path_deque
89 .pop_front()
90 .expect("every call is matched with push_tracked_path_component");
91 }
92 }
93
94 fn push_back_tracked_path_component(&mut self, component: &BStr) {
95 if let Some(Tracking::Path) = self.tracking {
96 self.push_element(component);
97 self.path_deque.push_back(self.location.clone());
98 }
99 }
100
101 fn push_path_component(&mut self, component: &BStr) {
102 match self.tracking {
103 Some(Tracking::FileName) => {
104 self.location.clear();
105 self.location.push_str(component);
106 }
107 Some(Tracking::Path) => {
108 self.push_element(component);
109 }
110 None => {}
111 }
112 }
113
114 fn pop_path_component(&mut self) {
115 if let Some(Tracking::Path) = self.tracking {
116 self.pop_element();
117 }
118 }
119
120 fn visit(&mut self, change: git_diff::tree::visit::Change) -> git_diff::tree::visit::Action {
121 use git_diff::tree::visit::Change::*;
122 let event = match change {
123 Addition { entry_mode, oid } => change::Event::Addition {
124 entry_mode,
125 id: oid.attach(self.other_repo),
126 },
127 Deletion { entry_mode, oid } => change::Event::Deletion {
128 entry_mode,
129 id: oid.attach(self.repo),
130 },
131 Modification {
132 previous_entry_mode,
133 previous_oid,
134 entry_mode,
135 oid,
136 } => change::Event::Modification {
137 previous_entry_mode,
138 entry_mode,
139 previous_id: previous_oid.attach(self.repo),
140 id: oid.attach(self.other_repo),
141 },
142 };
143 match (self.visit)(Change {
144 event,
145 location: self.location.as_ref(),
146 }) {
147 Ok(Action::Cancel) => git_diff::tree::visit::Action::Cancel,
148 Ok(Action::Continue) => git_diff::tree::visit::Action::Continue,
149 Err(err) => {
150 self.err = Some(err);
151 git_diff::tree::visit::Action::Cancel
152 }
153 }
154 }
155}
git-repository/src/object/tree/diff/mod.rs+80−0
@@ -0,0 +1,80 @@
1use crate::{bstr::BStr, Tree};
2
3/// Returned by the `for_each` function to control flow.
4#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
5pub enum Action {
6 /// Continue the traversal of changes.
7 Continue,
8 /// Stop the traversal of changes and stop calling this function.
9 Cancel,
10}
11
12impl Default for Action {
13 fn default() -> Self {
14 Action::Continue
15 }
16}
17
18/// Represents any possible change in order to turn one tree into another.
19#[derive(Debug, Clone, Copy)]
20pub struct Change<'a, 'old, 'new> {
21 /// The location of the file or directory described by `event`, if tracking was enabled.
22 ///
23 /// Otherwise this value is always an empty path.
24 pub location: &'a BStr,
25 /// The diff event itself to provide information about what would need to change.
26 pub event: change::Event<'old, 'new>,
27}
28
29///
30pub mod change;
31
32/// Diffing
33impl<'repo> Tree<'repo> {
34 /// Return a platform to see the changes needed to create other trees, for instance.
35 ///
36 /// # Performance
37 ///
38 /// It's highly recommended to set an object cache to avoid extracting the same object multiple times.
39 pub fn changes<'a>(&'a self) -> Platform<'a, 'repo> {
40 Platform {
41 state: Default::default(),
42 lhs: self,
43 tracking: None,
44 }
45 }
46}
47
48/// The diffing platform returned by [`Tree::changes()`].
49#[derive(Clone)]
50pub struct Platform<'a, 'repo> {
51 state: git_diff::tree::State,
52 lhs: &'a Tree<'repo>,
53 tracking: Option<Tracking>,
54}
55
56#[derive(Clone, Copy)]
57enum Tracking {
58 FileName,
59 Path,
60}
61
62/// Configuration
63impl<'a, 'repo> Platform<'a, 'repo> {
64 /// Keep track of file-names, which makes the [`location`][Change::location] field usable with the filename of the changed item.
65 pub fn track_filename(&mut self) -> &mut Self {
66 self.tracking = Some(Tracking::FileName);
67 self
68 }
69
70 /// Keep track of the entire path of a change, relative to the repository.
71 ///
72 /// This makes the [`location`][Change::location] field usable.
73 pub fn track_path(&mut self) -> &mut Self {
74 self.tracking = Some(Tracking::Path);
75 self
76 }
77}
78
79///
80pub mod for_each;

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t