Skip to content

Commit 74565bc

Browse files
committed
Cancel tree diffing early when matching path is found
1 parent 0ab4f64 commit 74565bc

File tree

1 file changed

+119
-8
lines changed

1 file changed

+119
-8
lines changed

gix-blame/src/file/function.rs

Lines changed: 119 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
use super::{process_changes, Change, UnblamedHunk};
22
use crate::{BlameEntry, Error, Outcome, Statistics};
33
use gix_diff::blob::intern::TokenSource;
4+
use gix_diff::tree::Visit;
45
use gix_hash::ObjectId;
5-
use gix_object::{bstr::BStr, FindExt};
6+
use gix_object::{
7+
bstr::{BStr, BString, ByteSlice, ByteVec},
8+
FindExt,
9+
};
10+
use std::collections::VecDeque;
611
use std::num::NonZeroU32;
712
use std::ops::Range;
813

@@ -322,15 +327,121 @@ fn tree_diff_at_file_path(
322327
let tree_iter = odb.find_tree_iter(&tree_id, rhs_tree_buf)?;
323328
stats.trees_decoded += 1;
324329

325-
let mut recorder = gix_diff::tree::Recorder::default();
326-
gix_diff::tree(parent_tree_iter, tree_iter, state, &odb, &mut recorder)?;
330+
let mut recorder = Recorder::new(file_path.into());
331+
// TODO
332+
// `recorder` cancels the traversal by returning `Cancel` when a change to `file_path` is
333+
// found. `gix_diff::tree` converts `Cancel` into `Err(...)` which is why we ignore its return
334+
// value here. I don’t know whether this has the potential to hide bugs.
335+
let _ = gix_diff::tree(parent_tree_iter, tree_iter, state, &odb, &mut recorder);
327336
stats.trees_diffed += 1;
328337

329-
Ok(recorder.records.into_iter().find(|change| match change {
330-
gix_diff::tree::recorder::Change::Modification { path, .. } => path == file_path,
331-
gix_diff::tree::recorder::Change::Addition { path, .. } => path == file_path,
332-
gix_diff::tree::recorder::Change::Deletion { path, .. } => path == file_path,
333-
}))
338+
Ok(recorder.change)
339+
}
340+
341+
// TODO
342+
// The name is preliminary and can potentially include more context. Also, this should probably be
343+
// moved to its own location.
344+
struct Recorder {
345+
interesting_path: BString,
346+
path_deque: VecDeque<BString>,
347+
path: BString,
348+
change: Option<gix_diff::tree::recorder::Change>,
349+
}
350+
351+
impl Recorder {
352+
fn new(interesting_path: BString) -> Self {
353+
Recorder {
354+
interesting_path,
355+
path_deque: Default::default(),
356+
path: Default::default(),
357+
change: None,
358+
}
359+
}
360+
361+
fn pop_element(&mut self) {
362+
if let Some(pos) = self.path.rfind_byte(b'/') {
363+
self.path.resize(pos, 0);
364+
} else {
365+
self.path.clear();
366+
}
367+
}
368+
369+
fn push_element(&mut self, name: &BStr) {
370+
if name.is_empty() {
371+
return;
372+
}
373+
if !self.path.is_empty() {
374+
self.path.push(b'/');
375+
}
376+
self.path.push_str(name);
377+
}
378+
}
379+
380+
impl Visit for Recorder {
381+
fn pop_front_tracked_path_and_set_current(&mut self) {
382+
self.path = self.path_deque.pop_front().expect("every parent is set only once");
383+
}
384+
385+
fn push_back_tracked_path_component(&mut self, component: &BStr) {
386+
self.push_element(component);
387+
self.path_deque.push_back(self.path.clone());
388+
}
389+
390+
fn push_path_component(&mut self, component: &BStr) {
391+
self.push_element(component);
392+
}
393+
394+
fn pop_path_component(&mut self) {
395+
self.pop_element();
396+
}
397+
398+
fn visit(&mut self, change: gix_diff::tree::visit::Change) -> gix_diff::tree::visit::Action {
399+
use gix_diff::tree::visit::Action::*;
400+
use gix_diff::tree::visit::Change::*;
401+
402+
if self.path == self.interesting_path {
403+
self.change = Some(match change {
404+
Deletion {
405+
entry_mode,
406+
oid,
407+
relation,
408+
} => gix_diff::tree::recorder::Change::Deletion {
409+
entry_mode,
410+
oid,
411+
path: self.path.clone(),
412+
relation,
413+
},
414+
Addition {
415+
entry_mode,
416+
oid,
417+
relation,
418+
} => gix_diff::tree::recorder::Change::Addition {
419+
entry_mode,
420+
oid,
421+
path: self.path.clone(),
422+
relation,
423+
},
424+
Modification {
425+
previous_entry_mode,
426+
previous_oid,
427+
entry_mode,
428+
oid,
429+
} => gix_diff::tree::recorder::Change::Modification {
430+
previous_entry_mode,
431+
previous_oid,
432+
entry_mode,
433+
oid,
434+
path: self.path.clone(),
435+
},
436+
});
437+
438+
// When we return `Cancel`, `gix_diff::tree` will convert this `Cancel` into an
439+
// `Err(...)`. Keep this in mind when using `Recorder`.
440+
Cancel
441+
} else {
442+
Continue
443+
}
444+
}
334445
}
335446

336447
fn blob_changes(

0 commit comments

Comments
 (0)