blob: 8925685b0187f9d51e84daa86fbdb96471c6274d [file] [log] [blame]
use crate::{Blob, ObjectDetached};
///
#[cfg(feature = "blob-diff")]
pub mod diff {
use std::ops::Range;
use gix_diff::blob::{platform::prepare_diff::Operation, ResourceKind};
use crate::{
bstr::ByteSlice,
object::{blob::diff::lines::Change, tree::diff::change::Event},
};
/// A platform to keep temporary information to perform line diffs on modified blobs.
///
pub struct Platform<'a> {
/// The cache holding diffable data related to our blobs.
pub resource_cache: &'a mut gix_diff::blob::Platform,
}
///
#[allow(clippy::empty_docs)]
pub mod init {
/// The error returned by [`Platform::from_tree_change()`][super::Platform::from_tree_change()].
pub type Error = gix_diff::blob::platform::set_resource::Error;
}
impl<'a> Platform<'a> {
/// Produce a platform for performing various diffs after obtaining the data from a single `tree_change`.
pub fn from_tree_change(
tree_change: &crate::object::tree::diff::Change<'_, '_, '_>,
resource_cache: &'a mut gix_diff::blob::Platform,
) -> Result<Platform<'a>, init::Error> {
match tree_change.event {
Event::Addition { entry_mode, id } => {
resource_cache.set_resource(
id.repo.object_hash().null(),
entry_mode.kind(),
tree_change.location,
ResourceKind::OldOrSource,
&id.repo.objects,
)?;
resource_cache.set_resource(
id.inner,
entry_mode.kind(),
tree_change.location,
ResourceKind::NewOrDestination,
&id.repo.objects,
)?;
}
Event::Deletion { entry_mode, id } => {
resource_cache.set_resource(
id.inner,
entry_mode.kind(),
tree_change.location,
ResourceKind::OldOrSource,
&id.repo.objects,
)?;
resource_cache.set_resource(
id.repo.object_hash().null(),
entry_mode.kind(),
tree_change.location,
ResourceKind::NewOrDestination,
&id.repo.objects,
)?;
}
Event::Modification {
previous_entry_mode,
previous_id,
entry_mode,
id,
} => {
resource_cache.set_resource(
previous_id.inner,
previous_entry_mode.kind(),
tree_change.location,
ResourceKind::OldOrSource,
&previous_id.repo.objects,
)?;
resource_cache.set_resource(
id.inner,
entry_mode.kind(),
tree_change.location,
ResourceKind::NewOrDestination,
&id.repo.objects,
)?;
}
Event::Rewrite {
source_location,
source_entry_mode,
source_id,
entry_mode,
id,
diff: _,
copy: _,
} => {
resource_cache.set_resource(
source_id.inner,
source_entry_mode.kind(),
source_location,
ResourceKind::OldOrSource,
&source_id.repo.objects,
)?;
resource_cache.set_resource(
id.inner,
entry_mode.kind(),
tree_change.location,
ResourceKind::NewOrDestination,
&id.repo.objects,
)?;
}
}
Ok(Self { resource_cache })
}
}
///
#[allow(clippy::empty_docs)]
pub mod lines {
use crate::bstr::BStr;
/// The error returned by [Platform::lines()](super::Platform::lines()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error<E>
where
E: std::error::Error + Send + Sync + 'static,
{
#[error(transparent)]
ProcessHunk(E),
#[error(transparent)]
PrepareDiff(#[from] gix_diff::blob::platform::prepare_diff::Error),
}
/// 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<'a> Platform<'a> {
/// 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, or individual diff drivers.
/// Note that `process_hunk` is not called if one of the involved resources are binary, but that can be determined
/// by introspecting the outcome.
// TODO: more tests (only tested insertion right now)
pub fn lines<FnH, E>(
&mut self,
mut process_hunk: FnH,
) -> Result<gix_diff::blob::platform::prepare_diff::Outcome<'_>, lines::Error<E>>
where
FnH: FnMut(lines::Change<'_, '_>) -> Result<(), E>,
E: std::error::Error + Send + Sync + 'static,
{
self.resource_cache.options.skip_internal_diff_if_external_is_configured = false;
let prep = self.resource_cache.prepare_diff()?;
match prep.operation {
Operation::InternalDiff { algorithm } => {
let input = prep.interned_input();
let mut err = None;
let mut lines = Vec::new();
gix_diff::blob::diff(algorithm, &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();
}
});
if let Some(err) = err {
return Err(lines::Error::ProcessHunk(err));
}
}
Operation::ExternalCommand { .. } => {
unreachable!("we disabled that")
}
Operation::SourceOrDestinationIsBinary => {}
};
Ok(prep)
}
/// Count the amount of removed and inserted lines efficiently.
/// Note that nothing will happen if one of the inputs is binary, and `None` will be returned.
pub fn line_counts(
&mut self,
) -> Result<Option<gix_diff::blob::sink::Counter<()>>, gix_diff::blob::platform::prepare_diff::Error> {
self.resource_cache.options.skip_internal_diff_if_external_is_configured = false;
let prep = self.resource_cache.prepare_diff()?;
match prep.operation {
Operation::InternalDiff { algorithm } => {
let tokens = prep.interned_input();
let counter = gix_diff::blob::diff(algorithm, &tokens, gix_diff::blob::sink::Counter::default());
Ok(Some(counter))
}
Operation::ExternalCommand { .. } => {
unreachable!("we disabled that")
}
Operation::SourceOrDestinationIsBinary => Ok(None),
}
}
}
}
/// Remove Lifetime
impl Blob<'_> {
/// Create an owned instance of this object, copying our data in the process.
pub fn detached(&self) -> ObjectDetached {
ObjectDetached {
id: self.id,
kind: gix_object::Kind::Blob,
data: self.data.clone(),
}
}
/// Sever the connection to the `Repository` and turn this instance into a standalone object.
pub fn detach(self) -> ObjectDetached {
self.into()
}
/// Retrieve this instance's data, leaving its own data empty.
///
/// This method works around the immovability of members of this type.
pub fn take_data(&mut self) -> Vec<u8> {
std::mem::take(&mut self.data)
}
}