Unified · Split
| @@ -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::*; | ||
| @@ -6,4 +6,4 @@ · 5 unmodified lines | |||
| 6 | pub mod tree; | 6 | pub mod tree; |
| 7 | 7 | ||
| 8 | /// | 8 | /// |
| 9 | pub mod text; | 9 | pub mod blob; |
| @@ -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 | } | ||
| @@ -0,0 +1,2 @@ | |||
| 1 | #[test] | ||
| 2 | fn currently_there_is_no_api_surface_to_test_as_it_is_reexporting_imara_diff() {} | ||
| @@ -2,4 +2,5 @@ pub type Result<T = ()> = std::result::Result<T, Box<dyn std::error::Error>>; · 1 unmodified line | |||
| 2 | 2 | ||
| 3 | pub use git_testtools::hex_to_id; | 3 | pub use git_testtools::hex_to_id; |
| 4 | 4 | ||
| 5 | mod visit; | 5 | mod blob; |
| 6 | mod tree; | ||
File renamed without changes.
| @@ -1,6 +1,5 @@ | |||
| 1 | use filetime::FileTime; | 1 | use filetime::FileTime; |
| 2 | use git_index::{entry, extension, verify::extensions::no_find, write, write::Options, State, Version}; | 2 | use git_index::{entry, extension, verify::extensions::no_find, write, write::Options, State, Version}; |
| 3 | use git_repository as git; | ||
| 4 | 3 | ||
| 5 | use crate::{fixture_index_path, index::Fixture::*, loose_file_path}; | 4 | use crate::{fixture_index_path, index::Fixture::*, loose_file_path}; |
| 6 | 5 | ||
| @@ -10,7 +10,7 @@ use crate::{ · 9 unmodified lines | |||
| 10 | 10 | ||
| 11 | /// Access | 11 | /// Access |
| 12 | impl Cache { | 12 | 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> { |
| 14 | use crate::config::diff::algorithm::Error; | 14 | use crate::config::diff::algorithm::Error; |
| 15 | self.diff_algorithm | 15 | 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 | } |
| @@ -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 blobs | 134 | /// 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. |
| @@ -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 | } | ||
| @@ -12,6 +12,8 @@ pub(crate) mod cache { · 11 unmodified lines | |||
| 12 | } | 12 | } |
| 13 | pub use errors::{conversion, find, write}; | 13 | pub use errors::{conversion, find, write}; |
| 14 | /// | 14 | /// |
| 15 | pub mod blob; | ||
| 16 | /// | ||
| 15 | pub mod commit; | 17 | pub mod commit; |
| 16 | mod impls; | 18 | mod impls; |
| 17 | pub mod peel; | 19 | pub mod peel; |
| @@ -1,6 +1,6 @@ | |||
| 1 | use git_object::tree::EntryMode; | 1 | use git_object::tree::EntryMode; |
| 2 | 2 | ||
| 3 | use crate::{bstr::ByteSlice, Id, Repository}; | 3 | use 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. | ||
| 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 | |||
| 70 | impl<'old, 'new> Event<'old, 'new> { | 37 | impl<'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 | |||
| 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 | } | ||
| @@ -0,0 +1,2 @@ | |||
| 1 | // TODO: needs repos with specific known objects for proper testing | ||
| 2 | mod diff {} | ||
| @@ -1,3 +1,4 @@ | |||
| 1 | mod blob; | ||
| 1 | mod commit; | 2 | mod commit; |
| 2 | mod tree; | 3 | mod tree; |
| 3 | 4 | ||
| @@ -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 = change | 34 | let diff = change.event.diff().expect("changed file").expect("objects available"); |
| 34 | .event | 35 | 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(); |
| @@ -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 usize | 233 | 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; |