History

Merge branch 'fixes-for-crates-index-diff'

Sebastian Thiel committed 4 years ago

Commit 255be4d2 parents cf04218 efe0a5116 files · +186 −113Browse files

Unified · Split

16 files
  1. git-diff/src/blob.rs +3 −0
  2. git-diff/src/lib.rs +1 −1
  3. git-diff/src/text.rs +0 −26
  4. git-diff/tests/blob/mod.rs +2 −0
  5. git-diff/tests/diff.rs +2 −1
  6. git-diff/tests/tree/mod.rs +0 −0
  7. git-index/tests/index/file/write.rs +0 −1
  8. git-repository/src/config/cache/access.rs +6 −6
  9. git-repository/src/config/mod.rs +1 −1
  10. git-repository/src/object/blob.rs +148 −0
  11. git-repository/src/object/mod.rs +2 −0
  12. git-repository/src/object/tree/diff/change.rs +5 −68
  13. git-repository/tests/object/blob.rs +2 −0
  14. git-repository/tests/object/mod.rs +1 −0
  15. git-repository/tests/object/tree.rs +12 −6
  16. gitoxide-core/src/hours/mod.rs +1 −3
git-diff/src/blob.rs+3−0
@@ -0,0 +1,3 @@
1+//! For using text diffs, please have a look at the [`imara-diff` documentation](https://docs.rs/imara-diff),
2+//! maintained by [Pascal Kuthe](https://github.com/pascalkuthe).
3+pub use imara_diff::*;
git-diff/src/lib.rs+1−1
@@ -6,4 +6,4 @@ · 5 unmodified lines
66 pub mod tree;
77
88 ///
9−pub mod text;
9+pub mod blob;
git-diff/src/text.rs+0−26
@@ -1,26 +0,0 @@
1−use git_object::bstr::BStr;
2−/// The crate powering file diffs.
3−pub use imara_diff as imara;
4−pub use imara_diff::Algorithm;
5−
6−/// Create a diff yielding the changes to turn `old` into `new` with `algorithm`. `new_input` obtains the `old` and `new`
7−/// byte buffers and produces an interner, which is then passed to `new_sink` for creating a processor over the changes.
8−///
9−/// See [the `imara-diff` crate documentation][imara] for information on how to implement a [`Sink`][imara::Sink].
10−pub fn with<'a, FnI, FnS, S>(
11− old: &'a BStr,
12− new: &'a BStr,
13− algorithm: Algorithm,
14− new_input: FnI,
15− new_sink: FnS,
16−) -> (imara::intern::InternedInput<&'a [u8]>, S::Out)
17−where
18− FnI: FnOnce(&'a [u8], &'a [u8]) -> imara::intern::InternedInput<&'a [u8]>,
19− FnS: FnOnce(&imara::intern::InternedInput<&'a [u8]>) -> S,
20− S: imara_diff::Sink,
21−{
22− let input = new_input(old.as_ref(), new.as_ref());
23− let sink = new_sink(&input);
24− let out = imara::diff(algorithm, &input, sink);
25− (input, out)
26−}
git-diff/tests/blob/mod.rs+2−0
@@ -0,0 +1,2 @@
1+#[test]
2+fn currently_there_is_no_api_surface_to_test_as_it_is_reexporting_imara_diff() {}
git-diff/tests/diff.rs+2−1
@@ -2,4 +2,5 @@ pub type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>; · 1 unmodified line
22
33 pub use git_testtools::hex_to_id;
44
5−mod visit;
5+mod blob;
6+mod tree;
git-diff/tests/tree/mod.rsrenamed from git-diff/tests/visit/mod.rs+0−0

File renamed without changes.

git-index/tests/index/file/write.rs+0−1
@@ -1,6 +1,5 @@
11 use filetime::FileTime;
22 use git_index::{entry, extension, verify::extensions::no_find, write, write::Options, State, Version};
3−use git_repository as git;
43
54 use crate::{fixture_index_path, index::Fixture::*, loose_file_path};
65
git-repository/src/config/cache/access.rs+6−6
@@ -10,7 +10,7 @@ use crate::{ · 9 unmodified lines
1010
1111 /// Access
1212 impl Cache {
13− pub(crate) fn diff_algorithm(&self) -> Result<git_diff::text::Algorithm, crate::config::diff::algorithm::Error> {
13+ pub(crate) fn diff_algorithm(&self) -> Result<git_diff::blob::Algorithm, crate::config::diff::algorithm::Error> {
1414 use crate::config::diff::algorithm::Error;
1515 self.diff_algorithm
1616 .get_or_try_init(|| {
@@ -20,14 +20,14 @@ impl Cache { · 3 unmodified lines
2020 .string("diff", None, "algorithm")
2121 .unwrap_or_else(|| Cow::Borrowed("myers".into()));
2222 if name.eq_ignore_ascii_case(b"myers") || name.eq_ignore_ascii_case(b"default") {
23− Ok(git_diff::text::Algorithm::Myers)
23+ Ok(git_diff::blob::Algorithm::Myers)
2424 } else if name.eq_ignore_ascii_case(b"minimal") {
25− Ok(git_diff::text::Algorithm::MyersMinimal)
25+ Ok(git_diff::blob::Algorithm::MyersMinimal)
2626 } else if name.eq_ignore_ascii_case(b"histogram") {
27− Ok(git_diff::text::Algorithm::Histogram)
27+ Ok(git_diff::blob::Algorithm::Histogram)
2828 } else if name.eq_ignore_ascii_case(b"patience") {
2929 if self.lenient_config {
30− Ok(git_diff::text::Algorithm::Histogram)
30+ Ok(git_diff::blob::Algorithm::Histogram)
3131 } else {
3232 Err(Error::Unimplemented {
3333 name: name.into_owned(),
@@ -39,7 +39,7 @@ impl Cache { · 5 unmodified lines
3939 })
4040 }
4141 };
42− check_lenient_default(res, self.lenient_config, || git_diff::text::Algorithm::Myers)
42+ check_lenient_default(res, self.lenient_config, || git_diff::blob::Algorithm::Myers)
4343 })
4444 .copied()
4545 }
git-repository/src/config/mod.rs+1−1
@@ -132,7 +132,7 @@ pub(crate) struct Cache { · 131 unmodified lines
132132 #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
133133 pub(crate) url_scheme: OnceCell<remote::url::SchemePermission>,
134134 /// The algorithm to use when diffing blobs
135− pub(crate) diff_algorithm: OnceCell<git_diff::text::Algorithm>,
135+ pub(crate) diff_algorithm: OnceCell<git_diff::blob::Algorithm>,
136136 /// The config section filter from the options used to initialize this instance. Keep these in sync!
137137 filter_config_section: fn(&git_config::file::Metadata) -> bool,
138138 /// The object kind to pick if a prefix is ambiguous.
git-repository/src/object/blob.rs+148−0
@@ -0,0 +1,148 @@
1+///
2+pub mod diff {
3+ use crate::bstr::ByteSlice;
4+ use crate::object::blob::diff::line::Change;
5+ use std::ops::Range;
6+
7+ /// A platform to keep temporary information to perform line diffs on modified blobs.
8+ ///
9+ pub struct Platform<'old, 'new> {
10+ /// The previous version of the blob.
11+ pub old: crate::Object<'old>,
12+ /// The new version of the blob.
13+ pub new: crate::Object<'new>,
14+ /// The algorithm to use when calling [imara_diff::diff()][git_diff::blob::diff()].
15+ /// This value is determined by the `diff.algorithm` configuration.
16+ pub algo: git_diff::blob::Algorithm,
17+ }
18+
19+ ///
20+ pub mod init {
21+ /// The error returned by [`Platform::from_ids()`][super::Platform::from_ids()].
22+ #[derive(Debug, thiserror::Error)]
23+ #[allow(missing_docs)]
24+ pub enum Error {
25+ #[error("Could not find the previous blob or the new blob to diff against")]
26+ FindExisting(#[from] crate::object::find::existing::Error),
27+ #[error("Could not obtain diff algorithm from configuration")]
28+ DiffAlgorithm(#[from] crate::config::diff::algorithm::Error),
29+ }
30+ }
31+
32+ impl<'old, 'new> Platform<'old, 'new> {
33+ /// Produce a platform for performing various diffs after obtaining the object data of `previous_id` and `new_id`.
34+ ///
35+ /// Note that these objects are treated as raw data and are assumed to be blobs.
36+ pub fn from_ids(
37+ previous_id: &crate::Id<'old>,
38+ new_id: &crate::Id<'new>,
39+ ) -> Result<Platform<'old, 'new>, init::Error> {
40+ match previous_id
41+ .object()
42+ .and_then(|old| new_id.object().map(|new| (old, new)))
43+ {
44+ Ok((old, new)) => {
45+ let algo = match new_id.repo.config.diff_algorithm() {
46+ Ok(algo) => algo,
47+ Err(err) => return Err(err.into()),
48+ };
49+ Ok(Platform { old, new, algo })
50+ }
51+ Err(err) => Err(err.into()),
52+ }
53+ }
54+ }
55+
56+ ///
57+ pub mod line {
58+ use crate::bstr::BStr;
59+
60+ /// A change to a hunk of lines.
61+ pub enum Change<'a, 'data> {
62+ /// Lines were added.
63+ Addition {
64+ /// The lines themselves without terminator.
65+ lines: &'a [&'data BStr],
66+ },
67+ /// Lines were removed.
68+ Deletion {
69+ /// The lines themselves without terminator.
70+ lines: &'a [&'data BStr],
71+ },
72+ /// Lines have been replaced.
73+ Modification {
74+ /// The replaced lines without terminator.
75+ lines_before: &'a [&'data BStr],
76+ /// The new lines without terminator.
77+ lines_after: &'a [&'data BStr],
78+ },
79+ }
80+ }
81+
82+ impl<'old, 'new> Platform<'old, 'new> {
83+ /// Perform a diff on lines between the old and the new version of a blob, passing each hunk of lines to `process_hunk`.
84+ /// The diffing algorithm is determined by the `diff.algorithm` configuration.
85+ ///
86+ /// Note that you can invoke the diff more flexibly as well.
87+ // TODO: more tests (only tested insertion right now)
88+ pub fn lines<FnH, E>(&self, mut process_hunk: FnH) -> Result<(), E>
89+ where
90+ FnH: FnMut(line::Change<'_, '_>) -> Result<(), E>,
91+ E: std::error::Error,
92+ {
93+ let input = git_diff::blob::intern::InternedInput::new(self.old.data.as_bytes(), self.new.data.as_bytes());
94+ let mut err = None;
95+ let mut lines = Vec::new();
96+ git_diff::blob::diff(self.algo, &input, |before: Range<u32>, after: Range<u32>| {
97+ if err.is_some() {
98+ return;
99+ }
100+ lines.clear();
101+ lines.extend(
102+ input.before[before.start as usize..before.end as usize]
103+ .iter()
104+ .map(|&line| input.interner[line].as_bstr()),
105+ );
106+ let end_of_before = lines.len();
107+ lines.extend(
108+ input.after[after.start as usize..after.end as usize]
109+ .iter()
110+ .map(|&line| input.interner[line].as_bstr()),
111+ );
112+ let hunk_before = &lines[..end_of_before];
113+ let hunk_after = &lines[end_of_before..];
114+ if hunk_after.is_empty() {
115+ err = process_hunk(Change::Deletion { lines: hunk_before }).err();
116+ } else if hunk_before.is_empty() {
117+ err = process_hunk(Change::Addition { lines: hunk_after }).err();
118+ } else {
119+ err = process_hunk(Change::Modification {
120+ lines_before: hunk_before,
121+ lines_after: hunk_after,
122+ })
123+ .err();
124+ }
125+ });
126+
127+ match err {
128+ Some(err) => Err(err),
129+ None => Ok(()),
130+ }
131+ }
132+
133+ /// Count the amount of removed and inserted lines efficiently.
134+ pub fn line_counts(&self) -> git_diff::blob::sink::Counter<()> {
135+ let tokens = self.line_tokens();
136+ git_diff::blob::diff(self.algo, &tokens, git_diff::blob::sink::Counter::default())
137+ }
138+
139+ /// Return a tokenizer which treats lines as smallest unit for use in a [diff operation][git_diff::blob::diff()].
140+ ///
141+ /// The line separator is determined according to normal git rules and filters.
142+ pub fn line_tokens(&self) -> git_diff::blob::intern::InternedInput<&[u8]> {
143+ // TODO: make use of `core.eol` and/or filters to do line-counting correctly. It's probably
144+ // OK to just know how these objects are saved to know what constitutes a line.
145+ git_diff::blob::intern::InternedInput::new(self.old.data.as_bytes(), self.new.data.as_bytes())
146+ }
147+ }
148+}
git-repository/src/object/mod.rs+2−0
@@ -12,6 +12,8 @@ pub(crate) mod cache { · 11 unmodified lines
1212 }
1313 pub use errors::{conversion, find, write};
1414 ///
15+pub mod blob;
16+///
1517 pub mod commit;
1618 mod impls;
1719 pub mod peel;
git-repository/src/object/tree/diff/change.rs+5−68
@@ -1,6 +1,6 @@
11 use git_object::tree::EntryMode;
22
3−use crate::{bstr::ByteSlice, Id, Repository};
3+use crate::Id;
44
55 /// An event emitted when finding differences between two trees.
66 #[derive(Debug, Clone, Copy)]
@@ -34,83 +34,20 @@ pub enum Event<'old, 'new> { · 27 unmodified lines
3434 },
3535 }
3636
37−/// A platform to keep temporary information to perform line diffs.
38−pub struct DiffPlatform<'old, 'new> {
39− old: crate::Object<'old>,
40− new: crate::Object<'new>,
41− algo: git_diff::text::Algorithm,
42−}
43−
44−impl<'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−
54−///
55−pub mod event {
56− ///
57− pub mod diff {
58− /// The error returned by [`Event::diff()`][super::super::Event::diff()].
59− #[derive(Debug, thiserror::Error)]
60− #[allow(missing_docs)]
61− pub enum Error {
62− #[error("Could not find the previous object to diff against")]
63− FindPrevious(#[from] crate::object::find::existing::Error),
64− #[error("Could not obtain diff algorithm from configuration")]
65− DiffAlgorithm(#[from] crate::config::diff::algorithm::Error),
66− }
67− }
68−}
69−
7037 impl<'old, 'new> Event<'old, 'new> {
7138 /// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification]
7239 /// or one of the entries to compare is not a blob.
73− pub fn diff(&self) -> Option<Result<DiffPlatform<'old, 'new>, event::diff::Error>> {
40+ pub fn diff(
41+ &self,
42+ ) -> Option<Result<crate::object::blob::diff::Platform<'old, 'new>, crate::object::blob::diff::init::Error>> {
7443 match self {
7544 Event::Modification {
7645 previous_entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
7746 previous_id,
7847 entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
7948 id,
80− } => match previous_id.object().and_then(|old| id.object().map(|new| (old, new))) {
81− Ok((old, new)) => {
82− let algo = match self.repo().config.diff_algorithm() {
83− Ok(algo) => algo,
84− Err(err) => return Some(Err(err.into())),
85− };
86− Some(Ok(DiffPlatform { old, new, algo }))
87− }
88− Err(err) => Some(Err(err.into())),
89− },
49+ } => Some(crate::object::blob::diff::Platform::from_ids(previous_id, id)),
9050 _ => None,
9151 }
9252 }
9353 }
94−
95−impl<'old, 'new> DiffPlatform<'old, 'new> {
96− /// Perform a diff on lines between the old and the new version of a blob.
97− /// The algorithm is determined by the `diff.algorithm` configuration.
98− /// Note that the [`Sink`][git_diff::text::imara::Sink] implementation is
99− /// what makes the diff usable and relies heavily on what the caller requires, as created by `make_sink`.
100− pub fn lines<FnS, S>(&self, new_sink: FnS) -> S::Out
101− where
102− FnS: for<'a> FnOnce(&git_diff::text::imara::intern::InternedInput<&'a [u8]>) -> S,
103− S: git_diff::text::imara::Sink,
104− {
105− git_diff::text::with(
106− self.old.data.as_bstr(),
107− self.new.data.as_bstr(),
108− self.algo,
109− // TODO: make use of `core.eol` and/or filters to do line-counting correctly. It's probably
110− // OK to just know how these objects are saved to know what constitutes a line.
111− git_diff::text::imara::intern::InternedInput::new,
112− new_sink,
113− )
114− .1
115− }
116−}
git-repository/tests/object/blob.rs+2−0
@@ -0,0 +1,2 @@
1+// TODO: needs repos with specific known objects for proper testing
2+mod diff {}
git-repository/tests/object/mod.rs+1−0
@@ -1,3 +1,4 @@
1+mod blob;
12 mod commit;
23 mod tree;
34
git-repository/tests/object/tree.rs+12−6
@@ -3,6 +3,7 @@ mod diff { · 2 unmodified lines
33
44 use git_object::{bstr::ByteSlice, tree::EntryMode};
55 use git_repository as git;
6+ use git_repository::object::blob::diff::line::Change;
67 use git_repository::object::tree::diff::change::Event;
78
89 use crate::named_repo;
@@ -30,14 +31,19 @@ mod diff { · 21 unmodified lines
3031 Event::Deletion { .. } | Event::Addition { .. } => unreachable!("only modification is expected"),
3132 };
3233
33− let count = change
34− .event
35− .diff()
36− .expect("changed file")
37− .expect("objects available")
38− .lines(|_| git_diff::text::imara::sink::Counter::default());
34+ let diff = change.event.diff().expect("changed file").expect("objects available");
35+ let count = diff.line_counts();
3936 assert_eq!(count.insertions, 1);
4037 assert_eq!(count.removals, 0);
38+ diff.lines(|hunk| {
39+ match hunk {
40+ Change::Deletion { .. } => unreachable!("there was no deletion"),
41+ Change::Addition { lines } => assert_eq!(lines, vec!["a1".as_bytes().as_bstr()]),
42+ Change::Modification { .. } => unreachable!("there was no modification"),
43+ };
44+ Ok::<_, Infallible>(())
45+ })
46+ .expect("infallible");
4147 Ok(Default::default())
4248 })
4349 .unwrap();
gitoxide-core/src/hours/mod.rs+1−3
@@ -229,9 +229,7 @@ where · 228 unmodified lines
229229 is_text_file.then(|| change.event.diff()).flatten()
230230 {
231231 let mut nl = 0;
232− let counts = diff.lines(|_| {
233− git::diff::text::imara::sink::Counter::default()
234− });
232+ let counts = diff.line_counts();
235233 nl += counts.insertions as usize
236234 + counts.removals as usize;
237235 lines.added += counts.insertions as usize;