Skip to content

Commit cbacca0

Browse files
committed
delegate-based tree diff traversal for maximum flexibility and performance
1 parent 230ef04 commit cbacca0

File tree

5 files changed

+104
-72
lines changed

5 files changed

+104
-72
lines changed

Cargo.lock

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,7 @@ members = [
105105

106106
"tests/tools",
107107
]
108+
109+
[patch.crates-io]
110+
# Until https://github.com/tailhook/quick-error/pull/56 is merged
111+
quick-error = {path = "/Users/byron/dev/github.com/tailhook/quick-error"}

git-diff/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ git-hash = { version = "^0.2.0", path = "../git-hash" }
1818
git-object = { version = "0.7", path = "../git-object" }
1919
git-odb = { version = "0.12", path = "../git-odb" }
2020
petgraph = { version = "0.5.1", default-features = false }
21+
quick-error = "2.0.0"
2122

2223
[dev-dependencies]
2324
test-tools = { path = "../tests/tools" }

git-diff/src/lib.rs

Lines changed: 94 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,129 @@
11
// TODO: add deny(missing_docs)
22
#![forbid(unsafe_code, rust_2018_idioms)]
3-
#![allow(dead_code, unused_variables)]
43

54
///
65
pub mod tree {
76
use git_hash::{oid, ObjectId};
8-
use git_object::{
9-
bstr::{BStr, BString},
10-
immutable, tree,
11-
};
7+
use git_object::immutable;
8+
use quick_error::quick_error;
129

1310
const EMPTY_TREE: immutable::Tree<'static> = immutable::Tree::empty();
1411

15-
type PathNodeId = usize;
16-
17-
/// A tree of paths to make storage and allocation of paths more efficient.
18-
#[derive(Default)]
19-
pub struct PathTree {
20-
graph: petgraph::graph::DiGraph<BString, ()>,
21-
}
22-
23-
impl PathTree {
24-
/// Find the path with the given `id` and place all of its elements in `out_elements`.
25-
/// Returns the amount of elements the path contains, like `&["a", "b", "c"]` would be `Some(3)`.
26-
///
27-
/// The output vector will be cleared beforehand.
28-
pub fn elements(&self, id: PathNodeId, out_elements: &mut Vec<&BStr>) -> Option<usize> {
29-
todo!("fetch elements")
12+
quick_error! {
13+
#[derive(Debug)]
14+
pub enum Error {
15+
NotFound(oid: ObjectId) {
16+
display("The object {} referenced by the tree was not found in the database", oid)
17+
}
18+
Cancelled {
19+
display("The delegate cancelled the operation")
20+
}
3021
}
3122
}
3223

33-
pub enum Change {
34-
Addition {
35-
mode: tree::Mode,
36-
oid: ObjectId,
37-
path: PathNodeId,
38-
},
39-
Copy,
40-
Deletion,
41-
Modification,
42-
Renaming,
43-
Type,
24+
#[derive(Default, Clone)]
25+
pub struct State {
26+
buf1: Vec<u8>,
27+
buf2: Vec<u8>,
4428
}
4529

46-
pub struct Changes<'a>(Option<&'a immutable::Tree<'a>>);
30+
pub struct VisitChanges<'a>(Option<&'a immutable::Tree<'a>>);
4731

48-
impl<'a, T> From<T> for Changes<'a>
32+
impl<'a, T> From<T> for VisitChanges<'a>
4933
where
5034
T: Into<Option<&'a immutable::Tree<'a>>>,
5135
{
5236
fn from(v: T) -> Self {
53-
Changes(v.into())
37+
VisitChanges(v.into())
5438
}
5539
}
5640

57-
// Possible things to detect (from git diff --help)
58-
// o A: addition of a file
59-
// o C: copy of a file into a new one
60-
// o D: deletion of a file
61-
// o M: modification of the contents or mode of a file
62-
// o R: renaming of a file
63-
// o T: change in the type of the file
64-
// o U: file is unmerged (you must complete the merge before it can be committed)
65-
// o X: "unknown" change type (most probably a bug, please report it)
66-
impl<'a> Changes<'a> {
41+
impl<'a> VisitChanges<'a> {
6742
/// Returns the changes that need to be applied to `self` to get `other`.
6843
pub fn to_obtain<LocateFn>(
6944
&self,
7045
_other: &git_object::immutable::Tree<'_>,
71-
buf: (&mut Vec<u8>, &mut Vec<u8>),
72-
locate: LocateFn,
73-
out_paths: &mut PathTree,
74-
out_changes: &mut Vec<Change>,
75-
) where
76-
LocateFn: for<'b> FnMut(&oid, &'b mut Vec<u8>) -> Option<immutable::Tree<'b>>,
46+
_state: &mut State,
47+
_locate: LocateFn,
48+
_delegate: &mut impl Delegate,
49+
) -> Result<(), Error>
50+
where
51+
LocateFn: for<'b> FnMut(&oid, &'b mut Vec<u8>) -> Option<immutable::Object<'b>>,
7752
{
78-
out_paths.graph.clear();
79-
out_changes.clear();
80-
8153
let _this = *self.0.as_ref().unwrap_or(&&EMPTY_TREE);
8254
todo!("changes tree to tree")
8355
}
8456
}
8557

86-
#[cfg(test)]
87-
mod tests {
88-
use super::*;
89-
90-
#[test]
91-
fn size_of_change() {
92-
assert_eq!(
93-
std::mem::size_of::<Change>(),
94-
32,
95-
"this type shouldn't grow without us knowing"
96-
)
58+
pub mod delegate {
59+
use git_hash::ObjectId;
60+
use git_object::{bstr::BStr, tree};
61+
62+
pub enum Change {
63+
Addition { mode: tree::Mode, oid: ObjectId },
64+
Copy,
65+
Deletion,
66+
Modification,
67+
Renaming,
68+
Type,
69+
}
70+
71+
#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
72+
pub enum PathComponentMode {
73+
Replace,
74+
Push,
75+
}
76+
77+
#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
78+
pub struct PathComponent<'a> {
79+
pub component: &'a BStr,
80+
/// An ID referring uniquely to the path built thus far. Used to keep track of source paths
81+
/// in case of [renames][Change::Rename] and [copies][Change::Copy].
82+
pub id: usize,
83+
}
84+
85+
#[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
86+
pub enum Action {
87+
Continue,
88+
Cancel,
89+
}
90+
91+
pub trait Delegate {
92+
fn update_path_component(&mut self, component: PathComponent<'_>, mode: PathComponentMode);
93+
fn pop_path_component(&mut self);
94+
fn record(change: Change) -> Action;
95+
}
96+
97+
#[derive(Clone, Default)]
98+
pub struct Recorder;
99+
100+
impl Delegate for Recorder {
101+
fn update_path_component(&mut self, _component: PathComponent<'_>, _mode: PathComponentMode) {
102+
todo!()
103+
}
104+
105+
fn pop_path_component(&mut self) {
106+
todo!()
107+
}
108+
109+
fn record(_change: Change) -> Action {
110+
todo!()
111+
}
112+
}
113+
114+
#[cfg(test)]
115+
mod tests {
116+
use super::*;
117+
118+
#[test]
119+
fn size_of_change() {
120+
assert_eq!(
121+
std::mem::size_of::<Change>(),
122+
22,
123+
"this type shouldn't grow without us knowing"
124+
)
125+
}
97126
}
98127
}
128+
pub use delegate::Delegate;
99129
}

git-diff/tests/tree/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,12 @@ mod with_tree {
4141
.and_then(|tree| tree.into_tree())
4242
};
4343

44-
let (mut buf3, mut buf4) = (Vec::new(), Vec::new());
45-
git_diff::tree::Changes::from(previous_tree.as_ref()).to_obtain(
44+
git_diff::tree::VisitChanges::from(previous_tree.as_ref()).to_obtain(
4645
&main_tree,
47-
(&mut buf3, &mut buf4),
46+
&mut git_diff::tree::State::default(),
4847
|_oid, _buf| todo!("Actual lookup in db"),
49-
&mut git_diff::tree::PathTree::default(),
50-
&mut Vec::new(),
51-
);
48+
&mut git_diff::tree::delegate::Recorder::default(),
49+
)?;
5250
Ok(())
5351
}
5452
#[test]

0 commit comments

Comments
 (0)