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).
3pub use imara_diff::*;
git-diff/src/lib.rs+1−1
@@ -6,4 +6,4 @@ · 5 unmodified lines
6pub mod tree;6pub mod tree;
7 7
8///8///
9pub mod text;9pub mod blob;
git-diff/src/text.rs+0−26
@@ -1,26 +0,0 @@
1use git_object::bstr::BStr;
2/// The crate powering file diffs.
3pub use imara_diff as imara;
4pub 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].
10pub 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)
17where
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]
2fn 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
2 2
3pub use git_testtools::hex_to_id;3pub use git_testtools::hex_to_id;
4 4
5mod visit;5mod blob;
6mod 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 @@
1use filetime::FileTime;1use filetime::FileTime;
2use git_index::{entry, extension, verify::extensions::no_find, write, write::Options, State, Version};2use git_index::{entry, extension, verify::extensions::no_find, write, write::Options, State, Version};
3use git_repository as git;
4 3
5use crate::{fixture_index_path, index::Fixture::*, loose_file_path};4use crate::{fixture_index_path, index::Fixture::*, loose_file_path};
6 5
git-repository/src/config/cache/access.rs+6−6
@@ -10,7 +10,7 @@ use crate::{ · 9 unmodified lines
10 10
11/// Access11/// Access
12impl Cache {12impl 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> {
14 use crate::config::diff::algorithm::Error;14 use crate::config::diff::algorithm::Error;
15 self.diff_algorithm15 self.diff_algorithm
16 .get_or_try_init(|| {16 .get_or_try_init(|| {
@@ -20,14 +20,14 @@ impl Cache { · 3 unmodified lines
20 .string("diff", None, "algorithm")20 .string("diff", None, "algorithm")
21 .unwrap_or_else(|| Cow::Borrowed("myers".into()));21 .unwrap_or_else(|| Cow::Borrowed("myers".into()));
22 if name.eq_ignore_ascii_case(b"myers") || name.eq_ignore_ascii_case(b"default") {22 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)
24 } else if name.eq_ignore_ascii_case(b"minimal") {24 } else if name.eq_ignore_ascii_case(b"minimal") {
25 Ok(git_diff::text::Algorithm::MyersMinimal)25 Ok(git_diff::blob::Algorithm::MyersMinimal)
26 } else if name.eq_ignore_ascii_case(b"histogram") {26 } else if name.eq_ignore_ascii_case(b"histogram") {
27 Ok(git_diff::text::Algorithm::Histogram)27 Ok(git_diff::blob::Algorithm::Histogram)
28 } else if name.eq_ignore_ascii_case(b"patience") {28 } else if name.eq_ignore_ascii_case(b"patience") {
29 if self.lenient_config {29 if self.lenient_config {
30 Ok(git_diff::text::Algorithm::Histogram)30 Ok(git_diff::blob::Algorithm::Histogram)
31 } else {31 } else {
32 Err(Error::Unimplemented {32 Err(Error::Unimplemented {
33 name: name.into_owned(),33 name: name.into_owned(),
@@ -39,7 +39,7 @@ impl Cache { · 5 unmodified lines
39 })39 })
40 }40 }
41 };41 };
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)
43 })43 })
44 .copied()44 .copied()
45 }45 }
git-repository/src/config/mod.rs+1−1
@@ -132,7 +132,7 @@ pub(crate) struct Cache { · 131 unmodified lines
132 #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]132 #[cfg(any(feature = "blocking-network-client", feature = "async-network-client"))]
133 pub(crate) url_scheme: OnceCell<remote::url::SchemePermission>,133 pub(crate) url_scheme: OnceCell<remote::url::SchemePermission>,
134 /// The algorithm to use when diffing blobs134 /// 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>,
136 /// The config section filter from the options used to initialize this instance. Keep these in sync!136 /// The config section filter from the options used to initialize this instance. Keep these in sync!
137 filter_config_section: fn(&git_config::file::Metadata) -> bool,137 filter_config_section: fn(&git_config::file::Metadata) -> bool,
138 /// The object kind to pick if a prefix is ambiguous.138 /// The object kind to pick if a prefix is ambiguous.
git-repository/src/object/blob.rs+148−0
@@ -0,0 +1,148 @@
1///
2pub 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
12}12}
13pub use errors::{conversion, find, write};13pub use errors::{conversion, find, write};
14///14///
15pub mod blob;
16///
15pub mod commit;17pub mod commit;
16mod impls;18mod impls;
17pub mod peel;19pub mod peel;
git-repository/src/object/tree/diff/change.rs+5−68
@@ -1,6 +1,6 @@
1use git_object::tree::EntryMode;1use git_object::tree::EntryMode;
2 2
3use crate::{bstr::ByteSlice, Id, Repository};3use crate::Id;
4 4
5/// An event emitted when finding differences between two trees.5/// An event emitted when finding differences between two trees.
6#[derive(Debug, Clone, Copy)]6#[derive(Debug, Clone, Copy)]
@@ -34,83 +34,20 @@ pub enum Event<'old, 'new> { · 27 unmodified lines
34 },34 },
35}35}
36 36
37/// A platform to keep temporary information to perform line diffs.
38pub struct DiffPlatform<'old, 'new> {
39 old: crate::Object<'old>,
40 new: crate::Object<'new>,
41 algo: git_diff::text::Algorithm,
42}
43
44impl<'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///
55pub 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
70impl<'old, 'new> Event<'old, 'new> {37impl<'old, 'new> Event<'old, 'new> {
71 /// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification]38 /// Produce a platform for performing a line-diff, or `None` if this is not a [`Modification`][Event::Modification]
72 /// or one of the entries to compare is not a blob.39 /// 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>> {
74 match self {43 match self {
75 Event::Modification {44 Event::Modification {
76 previous_entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,45 previous_entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
77 previous_id,46 previous_id,
78 entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,47 entry_mode: EntryMode::BlobExecutable | EntryMode::Blob,
79 id,48 id,
80 } => match previous_id.object().and_then(|old| id.object().map(|new| (old, new))) {49 } => Some(crate::object::blob::diff::Platform::from_ids(previous_id, id)),
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 },
90 _ => None,50 _ => None,
91 }51 }
92 }52 }
93}53}
94
95impl<'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
2mod diff {}
git-repository/tests/object/mod.rs+1−0
@@ -1,3 +1,4 @@
1mod blob;
1mod commit;2mod commit;
2mod tree;3mod tree;
3 4
git-repository/tests/object/tree.rs+12−6
@@ -3,6 +3,7 @@ mod diff { · 2 unmodified lines
3 3
4 use git_object::{bstr::ByteSlice, tree::EntryMode};4 use git_object::{bstr::ByteSlice, tree::EntryMode};
5 use git_repository as git;5 use git_repository as git;
6 use git_repository::object::blob::diff::line::Change;
6 use git_repository::object::tree::diff::change::Event;7 use git_repository::object::tree::diff::change::Event;
7 8
8 use crate::named_repo;9 use crate::named_repo;
@@ -30,14 +31,19 @@ mod diff { · 21 unmodified lines
30 Event::Deletion { .. } | Event::Addition { .. } => unreachable!("only modification is expected"),31 Event::Deletion { .. } | Event::Addition { .. } => unreachable!("only modification is expected"),
31 };32 };
32 33
33 let count = change34 let diff = change.event.diff().expect("changed file").expect("objects available");
34 .event35 let count = diff.line_counts();
35 .diff()
36 .expect("changed file")
37 .expect("objects available")
38 .lines(|_| git_diff::text::imara::sink::Counter::default());
39 assert_eq!(count.insertions, 1);36 assert_eq!(count.insertions, 1);
40 assert_eq!(count.removals, 0);37 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");
41 Ok(Default::default())47 Ok(Default::default())
42 })48 })
43 .unwrap();49 .unwrap();
gitoxide-core/src/hours/mod.rs+1−3
@@ -229,9 +229,7 @@ where · 228 unmodified lines
229 is_text_file.then(|| change.event.diff()).flatten()229 is_text_file.then(|| change.event.diff()).flatten()
230 {230 {
231 let mut nl = 0;231 let mut nl = 0;
232 let counts = diff.lines(|_| {232 let counts = diff.line_counts();
233 git::diff::text::imara::sink::Counter::default()
234 });
235 nl += counts.insertions as usize233 nl += counts.insertions as usize
236 + counts.removals as usize;234 + counts.removals as usize;
237 lines.added += counts.insertions as usize;235 lines.added += counts.insertions as usize;