///pub mod diff { use std::ops::Range; use crate::{bstr::ByteSlice, object::blob::diff::line::Change}; /// A platform to keep temporary information to perform line diffs on modified blobs. /// pub struct Platform<'old, 'new> { /// The previous version of the blob. pub old: crate::Object<'old>, /// The new version of the blob. pub new: crate::Object<'new>, /// The algorithm to use when calling [imara_diff::diff()][git_diff::blob::diff()]. /// This value is determined by the `diff.algorithm` configuration. pub algo: git_diff::blob::Algorithm, } /// pub mod init { /// The error returned by [`Platform::from_ids()`][super::Platform::from_ids()]. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error("Could not find the previous blob or the new blob to diff against")] FindExisting(#[from] crate::object::find::existing::Error), #[error("Could not obtain diff algorithm from configuration")] DiffAlgorithm(#[from] crate::config::diff::algorithm::Error), } } impl<'old, 'new> Platform<'old, 'new> { /// Produce a platform for performing various diffs after obtaining the object data of `previous_id` and `new_id`. /// /// Note that these objects are treated as raw data and are assumed to be blobs. pub fn from_ids( previous_id: &crate::Id<'old>, new_id: &crate::Id<'new>, ) -> Result<Platform<'old, 'new>, init::Error> { match previous_id .object() .and_then(|old| new_id.object().map(|new| (old, new))) { Ok((old, new)) => { let algo = match new_id.repo.config.diff_algorithm() { Ok(algo) => algo, Err(err) => return Err(err.into()), }; Ok(Platform { old, new, algo }) } Err(err) => Err(err.into()), } } } /// pub mod line { use crate::bstr::BStr; /// A change to a hunk of lines. pub enum Change<'a, 'data> { /// Lines were added. Addition { /// The lines themselves without terminator. lines: &'a [&'data BStr], }, /// Lines were removed. Deletion { /// The lines themselves without terminator. lines: &'a [&'data BStr], }, /// Lines have been replaced. Modification { /// The replaced lines without terminator. lines_before: &'a [&'data BStr], /// The new lines without terminator. lines_after: &'a [&'data BStr], }, } } impl<'old, 'new> Platform<'old, 'new> { /// Perform a diff on lines between the old and the new version of a blob, passing each hunk of lines to `process_hunk`. /// The diffing algorithm is determined by the `diff.algorithm` configuration. /// /// Note that you can invoke the diff more flexibly as well. // TODO: more tests (only tested insertion right now) pub fn lines<FnH, E>(&self, mut process_hunk: FnH) -> Result<(), E> where FnH: FnMut(line::Change<'_, '_>) -> Result<(), E>, E: std::error::Error, { let input = self.line_tokens(); let mut err = None; let mut lines = Vec::new(); git_diff::blob::diff(self.algo, &input, |before: Range<u32>, after: Range<u32>| { if err.is_some() { return; } lines.clear(); lines.extend( input.before[before.start as usize..before.end as usize] .iter() .map(|&line| input.interner[line].as_bstr()), ); let end_of_before = lines.len(); lines.extend( input.after[after.start as usize..after.end as usize] .iter() .map(|&line| input.interner[line].as_bstr()), ); let hunk_before = &lines[..end_of_before]; let hunk_after = &lines[end_of_before..]; if hunk_after.is_empty() { err = process_hunk(Change::Deletion { lines: hunk_before }).err(); } else if hunk_before.is_empty() { err = process_hunk(Change::Addition { lines: hunk_after }).err(); } else { err = process_hunk(Change::Modification { lines_before: hunk_before, lines_after: hunk_after, }) .err(); } }); match err { Some(err) => Err(err), None => Ok(()), } } /// Count the amount of removed and inserted lines efficiently. pub fn line_counts(&self) -> git_diff::blob::sink::Counter<()> { let tokens = self.line_tokens(); git_diff::blob::diff(self.algo, &tokens, git_diff::blob::sink::Counter::default()) } /// Return a tokenizer which treats lines as smallest unit for use in a [diff operation][git_diff::blob::diff()]. /// /// The line separator is determined according to normal git rules and filters. pub fn line_tokens(&self) -> git_diff::blob::intern::InternedInput<&[u8]> { // TODO: make use of `core.eol` and/or filters to do line-counting correctly. It's probably // OK to just know how these objects are saved to know what constitutes a line. git_diff::blob::intern::InternedInput::new(self.old.data.as_bytes(), self.new.data.as_bytes()) } }}
///
pub mod diff {
use std::ops::Range;
use crate::{bstr::ByteSlice, object::blob::diff::line::Change};
/// A platform to keep temporary information to perform line diffs on modified blobs.
///
pub struct Platform<'old, 'new> {
/// The previous version of the blob.
pub old: crate::Object<'old>,
/// The new version of the blob.
pub new: crate::Object<'new>,
/// The algorithm to use when calling [imara_diff::diff()][git_diff::blob::diff()].
/// This value is determined by the `diff.algorithm` configuration.
pub algo: git_diff::blob::Algorithm,
}
///
pub mod init {
/// The error returned by [`Platform::from_ids()`][super::Platform::from_ids()].
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Could not find the previous blob or the new blob to diff against")]
FindExisting(#[from] crate::object::find::existing::Error),
#[error("Could not obtain diff algorithm from configuration")]
DiffAlgorithm(#[from] crate::config::diff::algorithm::Error),
}
}
impl<'old, 'new> Platform<'old, 'new> {
/// Produce a platform for performing various diffs after obtaining the object data of `previous_id` and `new_id`.
///
/// Note that these objects are treated as raw data and are assumed to be blobs.
pub fn from_ids(
previous_id: &crate::Id<'old>,
new_id: &crate::Id<'new>,
) -> Result<Platform<'old, 'new>, init::Error> {
match previous_id
.object()
.and_then(|old| new_id.object().map(|new| (old, new)))
{
Ok((old, new)) => {
let algo = match new_id.repo.config.diff_algorithm() {
Ok(algo) => algo,
Err(err) => return Err(err.into()),
};
Ok(Platform { old, new, algo })
}
Err(err) => Err(err.into()),
}
}
}
///
pub mod line {
use crate::bstr::BStr;
/// A change to a hunk of lines.
pub enum Change<'a, 'data> {
/// Lines were added.
Addition {
/// The lines themselves without terminator.
lines: &'a [&'data BStr],
},
/// Lines were removed.
Deletion {
/// The lines themselves without terminator.
lines: &'a [&'data BStr],
},
/// Lines have been replaced.
Modification {
/// The replaced lines without terminator.
lines_before: &'a [&'data BStr],
/// The new lines without terminator.
lines_after: &'a [&'data BStr],
},
}
}
impl<'old, 'new> Platform<'old, 'new> {
/// Perform a diff on lines between the old and the new version of a blob, passing each hunk of lines to `process_hunk`.
/// The diffing algorithm is determined by the `diff.algorithm` configuration.
///
/// Note that you can invoke the diff more flexibly as well.
// TODO: more tests (only tested insertion right now)
pub fn lines<FnH, E>(&self, mut process_hunk: FnH) -> Result<(), E>
where
FnH: FnMut(line::Change<'_, '_>) -> Result<(), E>,
E: std::error::Error,
{
let input = self.line_tokens();
let mut err = None;
let mut lines = Vec::new();
git_diff::blob::diff(self.algo, &input, |before: Range<u32>, after: Range<u32>| {
if err.is_some() {
return;
}
lines.clear();
lines.extend(
input.before[before.start as usize..before.end as usize]
.iter()
.map(|&line| input.interner[line].as_bstr()),
);
let end_of_before = lines.len();
lines.extend(
input.after[after.start as usize..after.end as usize]
.iter()
.map(|&line| input.interner[line].as_bstr()),
);
let hunk_before = &lines[..end_of_before];
let hunk_after = &lines[end_of_before..];
if hunk_after.is_empty() {
err = process_hunk(Change::Deletion { lines: hunk_before }).err();
} else if hunk_before.is_empty() {
err = process_hunk(Change::Addition { lines: hunk_after }).err();
} else {
err = process_hunk(Change::Modification {
lines_before: hunk_before,
lines_after: hunk_after,
})
.err();
}
});
match err {
Some(err) => Err(err),
None => Ok(()),
}
}
/// Count the amount of removed and inserted lines efficiently.
pub fn line_counts(&self) -> git_diff::blob::sink::Counter<()> {
let tokens = self.line_tokens();
git_diff::blob::diff(self.algo, &tokens, git_diff::blob::sink::Counter::default())
}
/// Return a tokenizer which treats lines as smallest unit for use in a [diff operation][git_diff::blob::diff()].
///
/// The line separator is determined according to normal git rules and filters.
pub fn line_tokens(&self) -> git_diff::blob::intern::InternedInput<&[u8]> {
// TODO: make use of `core.eol` and/or filters to do line-counting correctly. It's probably
// OK to just know how these objects are saved to know what constitutes a line.
git_diff::blob::intern::InternedInput::new(self.old.data.as_bytes(), self.new.data.as_bytes())
}
}
}