Skip to content

Commit 8c2e5c6

Browse files
committed
feat: Once a change is obtained, it's easy to obtain changes line by line. (#470)
1 parent e164856 commit 8c2e5c6

File tree

2 files changed

+69
-18
lines changed

2 files changed

+69
-18
lines changed

git-repository/src/object/tree/diff.rs

+56-16
Original file line numberDiff line numberDiff line change
@@ -32,50 +32,90 @@ impl Default for Action {
3232

3333
/// Represents any possible change in order to turn one tree into another.
3434
#[derive(Debug, Clone, Copy)]
35-
pub struct Change<'a, 'repo, 'other_repo> {
35+
pub struct Change<'a, 'old, 'new> {
3636
/// The location of the file or directory described by `event`, if tracking was enabled.
3737
///
3838
/// Otherwise this value is always an empty path.
3939
pub location: &'a BStr,
4040
/// The diff event itself to provide information about what would need to change.
41-
pub event: change::Event<'repo, 'other_repo>,
41+
pub event: change::Event<'old, 'new>,
4242
}
4343

4444
///
4545
pub mod change {
46+
use crate::bstr::ByteSlice;
4647
use crate::Id;
48+
use git_object::tree::EntryMode;
4749

4850
/// An event emitted when finding differences between two trees.
4951
#[derive(Debug, Clone, Copy)]
50-
pub enum Event<'repo, 'other_repo> {
52+
pub enum Event<'old, 'new> {
5153
/// An entry was added, like the addition of a file or directory.
5254
Addition {
5355
/// The mode of the added entry.
5456
entry_mode: git_object::tree::EntryMode,
5557
/// The object id of the added entry.
56-
id: Id<'other_repo>,
58+
id: Id<'new>,
5759
},
5860
/// An entry was deleted, like the deletion of a file or directory.
5961
Deletion {
6062
/// The mode of the deleted entry.
6163
entry_mode: git_object::tree::EntryMode,
6264
/// The object id of the deleted entry.
63-
id: Id<'repo>,
65+
id: Id<'old>,
6466
},
6567
/// An entry was modified, e.g. changing the contents of a file adjusts its object id and turning
6668
/// a file into a symbolic link adjusts its mode.
6769
Modification {
6870
/// The mode of the entry before the modification.
6971
previous_entry_mode: git_object::tree::EntryMode,
7072
/// The object id of the entry before the modification.
71-
previous_id: Id<'repo>,
73+
previous_id: Id<'old>,
7274

7375
/// The mode of the entry after the modification.
7476
entry_mode: git_object::tree::EntryMode,
7577
/// The object id after the modification.
76-
id: Id<'other_repo>,
78+
id: Id<'new>,
7779
},
7880
}
81+
82+
/// A platform to keep temporary information to perform line diffs.
83+
pub struct DiffPlatform<'old, 'new> {
84+
old: crate::Object<'old>,
85+
new: crate::Object<'new>,
86+
}
87+
88+
impl<'old, 'new> Event<'old, 'new> {
89+
/// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification]
90+
/// or one of the entries to compare is not a blob.
91+
pub fn diff(&self) -> Option<Result<DiffPlatform<'old, 'new>, crate::object::find::existing::Error>> {
92+
match self {
93+
Event::Modification {
94+
previous_entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
95+
previous_id,
96+
entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
97+
id,
98+
} => match previous_id.object().and_then(|old| id.object().map(|new| (old, new))) {
99+
Ok((old, new)) => Some(Ok(DiffPlatform { old, new })),
100+
Err(err) => Some(Err(err)),
101+
},
102+
_ => None,
103+
}
104+
}
105+
}
106+
107+
impl<'old, 'new> DiffPlatform<'old, 'new> {
108+
/// Create a platform for performing various tasks to diff text.
109+
///
110+
/// It can be used to traverse [all line changes](git_diff::lines::similar::TextDiff::iter_all_changes()) for example.
111+
// TODO: How should this integrate with configurable algorithms? Maybe users should get it themselves and pass it here?
112+
pub fn text<'bufs>(
113+
&self,
114+
algorithm: git_diff::lines::Algorithm,
115+
) -> git_diff::lines::similar::TextDiff<'_, '_, 'bufs, [u8]> {
116+
git_diff::lines::with(self.old.data.as_bstr(), self.new.data.as_bstr(), algorithm)
117+
}
118+
}
79119
}
80120

81121
/// Diffing
@@ -126,12 +166,12 @@ impl<'a, 'repo> Platform<'a, 'repo> {
126166
}
127167

128168
/// Add the item to compare to.
129-
impl<'a, 'repo> Platform<'a, 'repo> {
169+
impl<'a, 'old> Platform<'a, 'old> {
130170
/// Call `for_each` repeatedly with all changes that are needed to convert the source of the diff to the tree to `other`.
131-
pub fn for_each_to_obtain_tree<'other_repo, E>(
171+
pub fn for_each_to_obtain_tree<'new, E>(
132172
&mut self,
133-
other: &Tree<'other_repo>,
134-
for_each: impl FnMut(Change<'_, 'repo, 'other_repo>) -> Result<Action, E>,
173+
other: &Tree<'new>,
174+
for_each: impl FnMut(Change<'_, 'old, 'new>) -> Result<Action, E>,
135175
) -> Result<(), Error>
136176
where
137177
E: std::error::Error + Sync + Send + 'static,
@@ -159,9 +199,9 @@ impl<'a, 'repo> Platform<'a, 'repo> {
159199
}
160200
}
161201

162-
struct Delegate<'repo, 'other_repo, VisitFn, E> {
163-
repo: &'repo Repository,
164-
other_repo: &'other_repo Repository,
202+
struct Delegate<'old, 'new, VisitFn, E> {
203+
repo: &'old Repository,
204+
other_repo: &'new Repository,
165205
tracking: Option<Tracking>,
166206
location: BString,
167207
path_deque: VecDeque<BString>,
@@ -186,9 +226,9 @@ impl<A, B> Delegate<'_, '_, A, B> {
186226
}
187227
}
188228

189-
impl<'repo, 'other_repo, VisitFn, E> git_diff::tree::Visit for Delegate<'repo, 'other_repo, VisitFn, E>
229+
impl<'old, 'new, VisitFn, E> git_diff::tree::Visit for Delegate<'old, 'new, VisitFn, E>
190230
where
191-
VisitFn: for<'delegate> FnMut(Change<'delegate, 'repo, 'other_repo>) -> Result<Action, E>,
231+
VisitFn: for<'delegate> FnMut(Change<'delegate, 'old, 'new>) -> Result<Action, E>,
192232
E: std::error::Error + Sync + Send + 'static,
193233
{
194234
fn pop_front_tracked_path_and_set_current(&mut self) {

git-repository/tests/object/tree.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,24 @@ mod diff {
2525
assert_eq!(entry_mode, EntryMode::Blob);
2626
assert_eq!(previous_id.object().unwrap().data.as_bstr(), "a\n");
2727
assert_eq!(id.object().unwrap().data.as_bstr(), "a\na1\n");
28-
Ok(Default::default())
2928
}
3029
Event::Deletion { .. } | Event::Addition { .. } => unreachable!("only modification is expected"),
31-
}
30+
};
31+
32+
let count = change
33+
.event
34+
.diff()
35+
.expect("changed file")
36+
.expect("objects available")
37+
.text(git::diff::lines::Algorithm::Myers)
38+
.iter_all_changes()
39+
.count();
40+
assert_eq!(count, 2);
41+
Ok(Default::default())
3242
})
3343
.unwrap();
3444
}
45+
3546
#[test]
3647
fn changes_against_tree_with_filename_tracking() {
3748
let repo = named_repo("make_diff_repo.sh").unwrap();

0 commit comments

Comments
 (0)