History

refactor

Sebastian Thiel committed 5 years ago

Commit c0318cf1 parent aaba9dc7 files · +51 −51Browse files

Showing the changes to git-diff/src/tree/changes.rs (1 of 7 files). Show all

Unified · Split

git-diff/src/tree/changes.rs+364−0
@@ -0,0 +1,364 @@
1+use crate::{
2+ tree,
3+ tree::{changes::Error::Cancelled, visit::Change, TreeInfo, TreeInfoPair},
4+};
5+use git_hash::{oid, ObjectId};
6+use git_object::immutable;
7+use quick_error::quick_error;
8+use std::{borrow::BorrowMut, collections::VecDeque};
9+
10+quick_error! {
11+ #[derive(Debug)]
12+ pub enum Error {
13+ NotFound(oid: ObjectId) {
14+ display("The object {} referenced by the tree was not found in the database", oid)
15+ }
16+ Cancelled {
17+ display("The delegate cancelled the operation")
18+ }
19+ EntriesDecode(err: immutable::object::decode::Error) {
20+ display("tree entries could not be decoded.")
21+ from()
22+ source(err)
23+ }
24+ }
25+}
26+
27+impl<'a> tree::Changes<'a> {
28+ /// that need to be applied to `self` to get `other`.
29+ /// TODO: Talk about choices in error handling and why Option<T> is enough, as opposed to the usual Result<Option<T>>
30+ /// Talk about progress (done via delegate if needed)
31+ ///
32+ /// # Notes
33+ ///
34+ /// * Tree entries are expected to be ordered using [`tree-entry-comparison`][git_cmp_c] (the same [in Rust][git_cmp_rs])
35+ ///
36+ /// [git_cmp_c]: https://github.com/git/git/blob/311531c9de557d25ac087c1637818bd2aad6eb3a/tree-diff.c#L49:L65
37+ /// [git_cmp_rs]: https://github.com/Byron/gitoxide/blob/a4d5f99c8dc99bf814790928a3bf9649cd99486b/git-object/src/mutable/tree.rs#L52-L55
38+ ///
39+ /// * it does a breadth first iteration as buffer space only fits two trees, the current one on the one we compare with.
40+ /// * does not do rename tracking but attempts to reduce allocations to zero (so performance is mostly determined
41+ /// by the delegate implementation which should be as specific as possible.
42+ /// * cycle checking is not performed, but can be performed in the delegate
43+ /// * [std::mem::ManuallyDrop] is used because `Peekable` is needed. When using it as wrapper around our no-drop iterators, all of the sudden
44+ /// borrowcheck complains as Drop is present (even though it's not)
45+ pub fn needed_to_obtain<LocateFn, R, StateMut>(
46+ mut self,
47+ other: immutable::TreeIter<'a>,
48+ mut state: StateMut,
49+ mut locate: LocateFn,
50+ delegate: &mut R,
51+ ) -> Result<(), Error>
52+ where
53+ LocateFn: for<'b> FnMut(&oid, &'b mut Vec<u8>) -> Option<immutable::tree::TreeIter<'b>>,
54+ R: tree::Visit,
55+ StateMut: BorrowMut<tree::State<R::PathId>>,
56+ {
57+ let state = state.borrow_mut();
58+ state.clear();
59+ let mut lhs_entries = peekable(self.0.take().unwrap_or_default());
60+ let mut rhs_entries = peekable(other);
61+ let mut avoid_popping_path: Option<()> = None;
62+
63+ loop {
64+ if avoid_popping_path.take().is_none() {
65+ delegate.pop_path_component();
66+ }
67+ match (lhs_entries.next(), rhs_entries.next()) {
68+ (None, None) => {
69+ match state.trees.pop_front() {
70+ Some((None, Some(rhs))) => {
71+ delegate.set_current_path(rhs.parent_path_id.clone());
72+ rhs_entries =
73+ peekable(locate(&rhs.tree_id, &mut state.buf2).ok_or(Error::NotFound(rhs.tree_id))?);
74+ }
75+ Some((Some(lhs), Some(rhs))) => {
76+ delegate.set_current_path(lhs.parent_path_id.clone());
77+ lhs_entries =
78+ peekable(locate(&lhs.tree_id, &mut state.buf1).ok_or(Error::NotFound(lhs.tree_id))?);
79+ rhs_entries =
80+ peekable(locate(&rhs.tree_id, &mut state.buf2).ok_or(Error::NotFound(rhs.tree_id))?);
81+ }
82+ Some((Some(lhs), None)) => {
83+ delegate.set_current_path(lhs.parent_path_id.clone());
84+ lhs_entries =
85+ peekable(locate(&lhs.tree_id, &mut state.buf1).ok_or(Error::NotFound(lhs.tree_id))?);
86+ }
87+ Some((None, None)) => unreachable!("BUG: it makes no sense to fill the stack with empties"),
88+ None => return Ok(()),
89+ };
90+ avoid_popping_path = Some(());
91+ }
92+ (Some(lhs), Some(rhs)) => {
93+ use std::cmp::Ordering::*;
94+ let (lhs, rhs) = (lhs?, rhs?);
95+ match lhs.filename.cmp(rhs.filename) {
96+ Equal => handle_lhs_and_rhs_with_equal_filenames(lhs, rhs, &mut state.trees, delegate)?,
97+ Less => catchup_lhs_with_rhs(&mut lhs_entries, lhs, rhs, &mut state.trees, delegate)?,
98+ Greater => catchup_rhs_with_lhs(&mut rhs_entries, lhs, rhs, &mut state.trees, delegate)?,
99+ }
100+ }
101+ (Some(lhs), None) => {
102+ let lhs = lhs?;
103+ delete_entry_schedule_recursion(lhs, &mut state.trees, delegate)?;
104+ }
105+ (None, Some(rhs)) => {
106+ let rhs = rhs?;
107+ add_entry_schedule_recursion(rhs, &mut state.trees, delegate)?;
108+ }
109+ }
110+ }
111+ }
112+}
113+
114+fn delete_entry_schedule_recursion<R: tree::Visit>(
115+ entry: immutable::tree::Entry<'_>,
116+ queue: &mut VecDeque<TreeInfoPair<R::PathId>>,
117+ delegate: &mut R,
118+) -> Result<(), Error> {
119+ delegate.push_path_component(entry.filename);
120+ if delegate
121+ .visit(Change::Deletion {
122+ entry_mode: entry.mode,
123+ oid: entry.oid.to_owned(),
124+ })
125+ .cancelled()
126+ {
127+ return Err(Error::Cancelled);
128+ }
129+ if entry.mode.is_tree() {
130+ delegate.pop_path_component();
131+ let path_id = delegate.push_tracked_path_component(entry.filename);
132+ queue.push_back((
133+ Some(TreeInfo {
134+ tree_id: entry.oid.to_owned(),
135+ parent_path_id: path_id,
136+ }),
137+ None,
138+ ));
139+ }
140+ Ok(())
141+}
142+
143+fn add_entry_schedule_recursion<R: tree::Visit>(
144+ entry: immutable::tree::Entry<'_>,
145+ queue: &mut VecDeque<TreeInfoPair<R::PathId>>,
146+ delegate: &mut R,
147+) -> Result<(), Error> {
148+ delegate.push_path_component(entry.filename);
149+ if delegate
150+ .visit(Change::Addition {
151+ entry_mode: entry.mode,
152+ oid: entry.oid.to_owned(),
153+ })
154+ .cancelled()
155+ {
156+ return Err(Error::Cancelled);
157+ }
158+ if entry.mode.is_tree() {
159+ delegate.pop_path_component();
160+ let path_id = delegate.push_tracked_path_component(entry.filename);
161+ queue.push_back((
162+ None,
163+ Some(TreeInfo {
164+ tree_id: entry.oid.to_owned(),
165+ parent_path_id: path_id,
166+ }),
167+ ))
168+ }
169+ Ok(())
170+}
171+fn catchup_rhs_with_lhs<R: tree::Visit>(
172+ rhs_entries: &mut IteratorType<immutable::TreeIter<'_>>,
173+ lhs: immutable::tree::Entry<'_>,
174+ rhs: immutable::tree::Entry<'_>,
175+ queue: &mut VecDeque<TreeInfoPair<R::PathId>>,
176+ delegate: &mut R,
177+) -> Result<(), Error> {
178+ use std::cmp::Ordering::*;
179+ add_entry_schedule_recursion(rhs, queue, delegate)?;
180+ loop {
181+ match rhs_entries.peek() {
182+ Some(Ok(rhs)) => match lhs.filename.cmp(rhs.filename) {
183+ Equal => {
184+ let rhs = rhs_entries.next().transpose()?.expect("the peeked item tobe present");
185+ delegate.pop_path_component();
186+ handle_lhs_and_rhs_with_equal_filenames(lhs, rhs, queue, delegate)?;
187+ break;
188+ }
189+ Greater => {
190+ let rhs = rhs_entries.next().transpose()?.expect("the peeked item tobe present");
191+ delegate.pop_path_component();
192+ add_entry_schedule_recursion(rhs, queue, delegate)?;
193+ }
194+ Less => {
195+ delegate.pop_path_component();
196+ delete_entry_schedule_recursion(lhs, queue, delegate)?;
197+ break;
198+ }
199+ },
200+ Some(Err(err)) => return Err(Error::EntriesDecode(err.to_owned())),
201+ None => {
202+ delegate.pop_path_component();
203+ delete_entry_schedule_recursion(lhs, queue, delegate)?;
204+ break;
205+ }
206+ }
207+ }
208+ Ok(())
209+}
210+
211+fn catchup_lhs_with_rhs<R: tree::Visit>(
212+ lhs_entries: &mut IteratorType<immutable::TreeIter<'_>>,
213+ lhs: immutable::tree::Entry<'_>,
214+ rhs: immutable::tree::Entry<'_>,
215+ queue: &mut VecDeque<TreeInfoPair<R::PathId>>,
216+ delegate: &mut R,
217+) -> Result<(), Error> {
218+ use std::cmp::Ordering::*;
219+ delete_entry_schedule_recursion(lhs, queue, delegate)?;
220+ loop {
221+ match lhs_entries.peek() {
222+ Some(Ok(lhs)) => match lhs.filename.cmp(rhs.filename) {
223+ Equal => {
224+ let lhs = lhs_entries.next().expect("the peeked item to be present")?;
225+ delegate.pop_path_component();
226+ handle_lhs_and_rhs_with_equal_filenames(lhs, rhs, queue, delegate)?;
227+ break;
228+ }
229+ Less => {
230+ let lhs = lhs_entries.next().expect("the peeked item to be present")?;
231+ delegate.pop_path_component();
232+ delete_entry_schedule_recursion(lhs, queue, delegate)?;
233+ }
234+ Greater => {
235+ delegate.pop_path_component();
236+ add_entry_schedule_recursion(rhs, queue, delegate)?;
237+ break;
238+ }
239+ },
240+ Some(Err(err)) => return Err(Error::EntriesDecode(err.to_owned())),
241+ None => {
242+ delegate.pop_path_component();
243+ add_entry_schedule_recursion(rhs, queue, delegate)?;
244+ break;
245+ }
246+ }
247+ }
248+ Ok(())
249+}
250+
251+fn handle_lhs_and_rhs_with_equal_filenames<R: tree::Visit>(
252+ lhs: immutable::tree::Entry<'_>,
253+ rhs: immutable::tree::Entry<'_>,
254+ queue: &mut VecDeque<TreeInfoPair<R::PathId>>,
255+ delegate: &mut R,
256+) -> Result<(), Error> {
257+ use git_object::tree::EntryMode::*;
258+ match (lhs.mode, rhs.mode) {
259+ (Tree, Tree) => {
260+ let path_id = delegate.push_tracked_path_component(lhs.filename);
261+ if lhs.oid != rhs.oid
262+ && delegate
263+ .visit(Change::Modification {
264+ previous_entry_mode: lhs.mode,
265+ previous_oid: lhs.oid.to_owned(),
266+ entry_mode: rhs.mode,
267+ oid: rhs.oid.to_owned(),
268+ })
269+ .cancelled()
270+ {
271+ return Err(Cancelled);
272+ }
273+ queue.push_back((
274+ Some(TreeInfo {
275+ tree_id: lhs.oid.to_owned(),
276+ parent_path_id: path_id.clone(),
277+ }),
278+ Some(TreeInfo {
279+ tree_id: rhs.oid.to_owned(),
280+ parent_path_id: path_id,
281+ }),
282+ ));
283+ }
284+ (lhs_mode, Tree) if lhs_mode.is_no_tree() => {
285+ let path_id = delegate.push_tracked_path_component(lhs.filename);
286+ if delegate
287+ .visit(Change::Deletion {
288+ entry_mode: lhs.mode,
289+ oid: lhs.oid.to_owned(),
290+ })
291+ .cancelled()
292+ {
293+ return Err(Cancelled);
294+ };
295+ if delegate
296+ .visit(Change::Addition {
297+ entry_mode: rhs.mode,
298+ oid: rhs.oid.to_owned(),
299+ })
300+ .cancelled()
301+ {
302+ return Err(Cancelled);
303+ };
304+ queue.push_back((
305+ None,
306+ Some(TreeInfo {
307+ tree_id: rhs.oid.to_owned(),
308+ parent_path_id: path_id,
309+ }),
310+ ));
311+ }
312+ (Tree, rhs_mode) if rhs_mode.is_no_tree() => {
313+ let path_id = delegate.push_tracked_path_component(lhs.filename);
314+ if delegate
315+ .visit(Change::Deletion {
316+ entry_mode: lhs.mode,
317+ oid: lhs.oid.to_owned(),
318+ })
319+ .cancelled()
320+ {
321+ return Err(Error::Cancelled);
322+ }
323+ if delegate
324+ .visit(Change::Addition {
325+ entry_mode: rhs.mode,
326+ oid: rhs.oid.to_owned(),
327+ })
328+ .cancelled()
329+ {
330+ return Err(Cancelled);
331+ };
332+ queue.push_back((
333+ Some(TreeInfo {
334+ tree_id: lhs.oid.to_owned(),
335+ parent_path_id: path_id,
336+ }),
337+ None,
338+ ));
339+ }
340+ (lhs_non_tree, rhs_non_tree) => {
341+ delegate.push_path_component(lhs.filename);
342+ debug_assert!(lhs_non_tree.is_no_tree() && rhs_non_tree.is_no_tree());
343+ if lhs.oid != rhs.oid
344+ && delegate
345+ .visit(Change::Modification {
346+ previous_entry_mode: lhs.mode,
347+ previous_oid: lhs.oid.to_owned(),
348+ entry_mode: rhs.mode,
349+ oid: rhs.oid.to_owned(),
350+ })
351+ .cancelled()
352+ {
353+ return Err(Error::Cancelled);
354+ }
355+ }
356+ };
357+ Ok(())
358+}
359+
360+type IteratorType<I> = std::mem::ManuallyDrop<std::iter::Peekable<I>>;
361+
362+fn peekable<I: Iterator>(iter: I) -> IteratorType<I> {
363+ std::mem::ManuallyDrop::new(iter.peekable())
364+}

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