Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add index() to diff two indices
Browse files Browse the repository at this point in the history
It comes with pathspec support to allow for easier integration into
the `status()` machinery.
Byron committed Dec 30, 2024
1 parent 4989cda commit a867322
Showing 11 changed files with 2,067 additions and 4 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 8 additions & 3 deletions gix-diff/Cargo.toml
Original file line number Diff line number Diff line change
@@ -13,18 +13,23 @@ rust-version = "1.65"
autotests = false

[features]
default = ["blob"]
## Enable diffing of blobs using imara-diff, which also allows for a generic rewrite tracking implementation.
default = ["blob", "index"]
## Enable diffing of blobs using imara-diff.
blob = ["dep:imara-diff", "dep:gix-filter", "dep:gix-worktree", "dep:gix-path", "dep:gix-fs", "dep:gix-command", "dep:gix-tempfile", "dep:gix-trace", "dep:gix-traverse"]
## Enable diffing of two indices, which also allows for a generic rewrite tracking implementation.
index = ["dep:gix-index", "dep:gix-pathspec", "dep:gix-attributes"]
## Data structures implement `serde::Serialize` and `serde::Deserialize`.
serde = ["dep:serde", "gix-hash/serde", "gix-object/serde"]
serde = ["dep:serde", "gix-hash/serde", "gix-object/serde", "gix-index?/serde"]
## Make it possible to compile to the `wasm32-unknown-unknown` target.
wasm = ["dep:getrandom"]

[lib]
doctest = false

[dependencies]
gix-index = { version = "^0.37.0", path = "../gix-index", optional = true }
gix-pathspec = { version = "^0.8.1", path = "../gix-pathspec", optional = true }
gix-attributes = { version = "^0.23.1", path = "../gix-attributes", optional = true }
gix-hash = { version = "^0.15.1", path = "../gix-hash" }
gix-object = { version = "^0.46.1", path = "../gix-object" }
gix-filter = { version = "^0.16.0", path = "../gix-filter", optional = true }
197 changes: 197 additions & 0 deletions gix-diff/src/index/change.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
use crate::index::{Change, ChangeRef};
use crate::rewrites;
use crate::rewrites::tracker::ChangeKind;
use crate::tree::visit::Relation;
use bstr::BStr;
use gix_object::tree;
use std::borrow::Cow;

impl ChangeRef<'_, '_> {
/// Copy everything into an owned version of this instance.
pub fn into_owned(self) -> Change {
match self {
ChangeRef::Addition {
location,
index,
entry_mode,
id,
} => ChangeRef::Addition {
location: Cow::Owned(location.into_owned()),
index,
entry_mode,
id: Cow::Owned(id.into_owned()),
},
ChangeRef::Deletion {
location,
index,
entry_mode,
id,
} => ChangeRef::Deletion {
location: Cow::Owned(location.into_owned()),
index,
entry_mode,
id: Cow::Owned(id.into_owned()),
},
ChangeRef::Modification {
location,
previous_index,
previous_entry_mode,
previous_id,
index,
entry_mode,
id,
} => ChangeRef::Modification {
location: Cow::Owned(location.into_owned()),
previous_index,
previous_entry_mode,
previous_id: Cow::Owned(previous_id.into_owned()),
index,
entry_mode,
id: Cow::Owned(id.into_owned()),
},
ChangeRef::Rewrite {
source_location,
source_index,
source_entry_mode,
source_id,
location,
index,
entry_mode,
id,
copy,
} => ChangeRef::Rewrite {
source_location: Cow::Owned(source_location.into_owned()),
source_index,
source_entry_mode,
source_id: Cow::Owned(source_id.into_owned()),
location: Cow::Owned(location.into_owned()),
index,
entry_mode,
id: Cow::Owned(id.into_owned()),
copy,
},
ChangeRef::Unmerged {
location,
stage,
index,
entry_mode,
id,
} => ChangeRef::Unmerged {
location: Cow::Owned(location.into_owned()),
stage,
index,
entry_mode,
id: Cow::Owned(id.into_owned()),
},
}
}
}

impl ChangeRef<'_, '_> {
/// Return all shared fields among all variants: `(location, index, entry_mode, id)`
///
/// In case of rewrites, the fields return to the current change.
pub fn fields(&self) -> (&BStr, usize, gix_index::entry::Mode, &gix_hash::oid) {
match self {
ChangeRef::Addition {
location,
index,
entry_mode,
id,
..
}
| ChangeRef::Deletion {
location,
index,
entry_mode,
id,
..
}
| ChangeRef::Modification {
location,
index,
entry_mode,
id,
..
}
| ChangeRef::Rewrite {
location,
index,
entry_mode,
id,
..
}
| ChangeRef::Unmerged {
location,
index,
entry_mode,
id,
..
} => (location.as_ref(), *index, *entry_mode, id),
}
}
}

impl rewrites::tracker::Change for ChangeRef<'_, '_> {
fn id(&self) -> &gix_hash::oid {
match self {
ChangeRef::Addition { id, .. } | ChangeRef::Deletion { id, .. } | ChangeRef::Modification { id, .. } => {
id.as_ref()
}
ChangeRef::Rewrite { .. } | ChangeRef::Unmerged { .. } => {
unreachable!("BUG")
}
}
}

fn relation(&self) -> Option<Relation> {
None
}

fn kind(&self) -> ChangeKind {
match self {
ChangeRef::Addition { .. } => ChangeKind::Addition,
ChangeRef::Deletion { .. } => ChangeKind::Deletion,
ChangeRef::Modification { .. } => ChangeKind::Modification,
ChangeRef::Rewrite { .. } => {
unreachable!("BUG: rewrites can't be determined ahead of time")
}
ChangeRef::Unmerged { .. } => {
unreachable!("BUG: unmerged don't participate in rename tracking")
}
}
}

fn entry_mode(&self) -> tree::EntryMode {
match self {
ChangeRef::Addition { entry_mode, .. }
| ChangeRef::Deletion { entry_mode, .. }
| ChangeRef::Modification { entry_mode, .. }
| ChangeRef::Rewrite { entry_mode, .. }
| ChangeRef::Unmerged { entry_mode, .. } => {
entry_mode
.to_tree_entry_mode()
// Default is for the impossible case - just don't let it participate in rename tracking.
.unwrap_or(tree::EntryKind::Tree.into())
}
}
}

fn id_and_entry_mode(&self) -> (&gix_hash::oid, tree::EntryMode) {
match self {
ChangeRef::Addition { id, entry_mode, .. }
| ChangeRef::Deletion { id, entry_mode, .. }
| ChangeRef::Modification { id, entry_mode, .. }
| ChangeRef::Rewrite { id, entry_mode, .. }
| ChangeRef::Unmerged { id, entry_mode, .. } => {
(
id,
entry_mode
.to_tree_entry_mode()
// Default is for the impossible case - just don't let it participate in rename tracking.
.unwrap_or(tree::EntryKind::Tree.into()),
)
}
}
}
}
324 changes: 324 additions & 0 deletions gix-diff/src/index/function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
use super::{Action, ChangeRef, Error, RewriteOptions};
use crate::rewrites;
use bstr::{BStr, BString, ByteSlice};
use gix_filter::attributes::glob::pattern::Case;
use std::borrow::Cow;
use std::cell::RefCell;
use std::cmp::Ordering;

/// Produce an entry-by-entry diff between `lhs` and `rhs`, sending changes to `cb(change) -> Action` for consumption,
/// which would turn `lhs` into `rhs` if applied.
/// Use `pathspec` to reduce the set of entries to look at, and `pathspec_attributes` may be used by pathspecs that perform
/// attribute lookups.
///
/// If `cb` indicated that the operation should be cancelled, no error is triggered as this isn't supposed to
/// occur through user-interaction - this diff is typically too fast.
///
/// Note that rewrites will be emitted at the end, so no ordering can be assumed. They will only be tracked if
/// `rewrite_options` is `Some`. Note that the set of entries participating in rename tracking is affected by `pathspec`.
///
/// Return the outcome of the rewrite tracker if it was enabled.
///
/// Note that only `rhs` may contain unmerged entries, as `rhs` is expected to be the index read from `.git/index`.
/// Unmerged entries are always provided as changes, one stage at a time, up to three stages for *base*, *ours* and *theirs*.
/// Conceptually, `rhs` is *ours*, and `lhs` is *theirs*.
/// The entries in `lhs` and `rhs` are both expected to be sorted like index entries are typically sorted.
///
/// Note that sparse indices aren't supported, they must be "unsparsed" before.
pub fn diff<'rhs, 'lhs: 'rhs, E, Find>(
lhs: &'lhs gix_index::State,
rhs: &'rhs gix_index::State,
mut cb: impl FnMut(ChangeRef<'lhs, 'rhs>) -> Result<Action, E>,
rewrite_options: Option<RewriteOptions<'_, Find>>,
pathspec: &mut gix_pathspec::Search,
pathspec_attributes: &mut dyn FnMut(&BStr, Case, bool, &mut gix_attributes::search::Outcome) -> bool,
) -> Result<Option<rewrites::Outcome>, Error>
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
Find: gix_object::FindObjectOrHeader,
{
if lhs.is_sparse() || rhs.is_sparse() {
return Err(Error::IsSparse);
}
if lhs
.entries()
.iter()
.any(|e| e.stage() != gix_index::entry::Stage::Unconflicted)
{
return Err(Error::LhsHasUnmerged);
}

let lhs_range = lhs
.prefixed_entries_range(pathspec.common_prefix())
.unwrap_or_else(|| 0..lhs.entries().len());
let rhs_range = rhs
.prefixed_entries_range(pathspec.common_prefix())
.unwrap_or_else(|| 0..rhs.entries().len());

let pattern_matches = RefCell::new(|relative_path, entry: &gix_index::Entry| {
pathspec
.pattern_matching_relative_path(relative_path, Some(entry.mode.is_submodule()), pathspec_attributes)
.map_or(false, |m| !m.is_excluded())
});

let (mut lhs_iter, mut rhs_iter) = (
lhs.entries()[lhs_range.clone()]
.iter()
.enumerate()
.map(|(idx, e)| (idx + lhs_range.start, e.path(lhs), e))
.filter(|(_, path, e)| pattern_matches.borrow_mut()(path, e)),
rhs.entries()[rhs_range.clone()]
.iter()
.enumerate()
.map(|(idx, e)| (idx + rhs_range.start, e.path(rhs), e))
.filter(|(_, path, e)| pattern_matches.borrow_mut()(path, e)),
);

let mut conflicting_paths = Vec::<BString>::new();
let mut cb = move |change: ChangeRef<'lhs, 'rhs>| {
let (location, ..) = change.fields();
if let ChangeRef::Unmerged { .. } = &change {
if let Err(insert_idx) = conflicting_paths.binary_search_by(|p| p.as_bstr().cmp(location)) {
conflicting_paths.insert(insert_idx, location.to_owned());
}
cb(change)
} else if conflicting_paths
.binary_search_by(|p| p.as_bstr().cmp(location))
.is_err()
{
cb(change)
} else {
Ok(Action::Continue)
}
};
let mut resource_cache_storage = None;
let mut tracker = rewrite_options.map(
|RewriteOptions {
resource_cache,
rewrites,
find,
}| {
resource_cache_storage = Some((resource_cache, find));
rewrites::Tracker::<ChangeRef<'lhs, 'rhs>>::new(rewrites)
},
);

let (mut lhs_storage, mut rhs_storage) = (lhs_iter.next(), rhs_iter.next());
loop {
match (lhs_storage, rhs_storage) {
(Some(lhs), Some(rhs)) => {
match emit_unmerged_ignore_intent_to_add(rhs, &mut cb)? {
None => {}
Some(Action::Cancel) => return Ok(None),
Some(Action::Continue) => {
rhs_storage = rhs_iter.next();
continue;
}
};

let (lhs_idx, lhs_path, lhs_entry) = lhs;
let (rhs_idx, rhs_path, rhs_entry) = rhs;
match lhs_path.cmp(rhs_path) {
Ordering::Less => match emit_deletion(lhs, &mut cb, tracker.as_mut())? {
Action::Continue => {
lhs_storage = lhs_iter.next();
}
Action::Cancel => return Ok(None),
},
Ordering::Equal => {
if lhs_entry.id != rhs_entry.id || lhs_entry.mode != rhs_entry.mode {
let change = ChangeRef::Modification {
location: Cow::Borrowed(rhs_path),
previous_index: lhs_idx,
previous_entry_mode: lhs_entry.mode,
previous_id: Cow::Borrowed(lhs_entry.id.as_ref()),
index: rhs_idx,
entry_mode: rhs_entry.mode,
id: Cow::Borrowed(rhs_entry.id.as_ref()),
};

let change = match tracker.as_mut() {
None => Some(change),
Some(tracker) => tracker.try_push_change(change, rhs_path),
};
if let Some(change) = change {
match cb(change).map_err(|err| Error::Callback(err.into()))? {
Action::Continue => {}
Action::Cancel => return Ok(None),
}
}
}
lhs_storage = lhs_iter.next();
rhs_storage = rhs_iter.next();
}
Ordering::Greater => match emit_addition(rhs, &mut cb, tracker.as_mut())? {
Action::Continue => {
rhs_storage = rhs_iter.next();
}
Action::Cancel => return Ok(None),
},
}
}
(Some(lhs), None) => match emit_deletion(lhs, &mut cb, tracker.as_mut())? {
Action::Cancel => return Ok(None),
Action::Continue => {
lhs_storage = lhs_iter.next();
}
},
(None, Some(rhs)) => match emit_addition(rhs, &mut cb, tracker.as_mut())? {
Action::Cancel => return Ok(None),
Action::Continue => {
rhs_storage = rhs_iter.next();
}
},
(None, None) => break,
}
}

if let Some((mut tracker, (resource_cache, find))) = tracker.zip(resource_cache_storage) {
let mut cb_err = None;
let out = tracker.emit(
|dst, src| {
let change = if let Some(src) = src {
let (lhs_path, lhs_index, lhs_mode, lhs_id) = src.change.fields();
let (rhs_path, rhs_index, rhs_mode, rhs_id) = dst.change.fields();
ChangeRef::Rewrite {
source_location: Cow::Owned(lhs_path.into()),
source_index: lhs_index,
source_entry_mode: lhs_mode,
source_id: Cow::Owned(lhs_id.into()),
location: Cow::Owned(rhs_path.into()),
index: rhs_index,
entry_mode: rhs_mode,
id: Cow::Owned(rhs_id.into()),
copy: match src.kind {
rewrites::tracker::visit::SourceKind::Rename => false,
rewrites::tracker::visit::SourceKind::Copy => true,
},
}
} else {
dst.change
};
match cb(change) {
Ok(Action::Continue) => crate::tree::visit::Action::Continue,
Ok(Action::Cancel) => crate::tree::visit::Action::Cancel,
Err(err) => {
cb_err = Some(Error::Callback(err.into()));
crate::tree::visit::Action::Cancel
}
}
},
resource_cache,
find,
|push| {
for (index, entry) in lhs.entries().iter().enumerate() {
let path = entry.path(rhs);
push(
ChangeRef::Modification {
location: Cow::Borrowed(path),
previous_index: 0, /* does not matter */
previous_entry_mode: entry.mode,
previous_id: Cow::Owned(entry.id.kind().null()),
index,
entry_mode: entry.mode,
id: Cow::Borrowed(entry.id.as_ref()),
},
path,
);
}
Ok::<_, std::convert::Infallible>(())
},
)?;

if let Some(err) = cb_err {
Err(err)
} else {
Ok(Some(out))
}
} else {
Ok(None)
}
}

fn emit_deletion<'rhs, 'lhs: 'rhs, E>(
(idx, path, entry): (usize, &'lhs BStr, &'lhs gix_index::Entry),
mut cb: impl FnMut(ChangeRef<'lhs, 'rhs>) -> Result<Action, E>,
tracker: Option<&mut rewrites::Tracker<ChangeRef<'lhs, 'rhs>>>,
) -> Result<Action, Error>
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
let change = ChangeRef::Deletion {
location: Cow::Borrowed(path),
index: idx,
entry_mode: entry.mode,
id: Cow::Borrowed(entry.id.as_ref()),
};

let change = match tracker {
None => change,
Some(tracker) => match tracker.try_push_change(change, path) {
Some(change) => change,
None => return Ok(Action::Continue),
},
};

cb(change).map_err(|err| Error::Callback(err.into()))
}

fn emit_addition<'rhs, 'lhs: 'rhs, E>(
(idx, path, entry): (usize, &'rhs BStr, &'rhs gix_index::Entry),
mut cb: impl FnMut(ChangeRef<'lhs, 'rhs>) -> Result<Action, E>,
tracker: Option<&mut rewrites::Tracker<ChangeRef<'lhs, 'rhs>>>,
) -> Result<Action, Error>
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
if let Some(action) = emit_unmerged_ignore_intent_to_add((idx, path, entry), &mut cb)? {
return Ok(action);
}

let change = ChangeRef::Addition {
location: Cow::Borrowed(path),
index: idx,
entry_mode: entry.mode,
id: Cow::Borrowed(entry.id.as_ref()),
};

let change = match tracker {
None => change,
Some(tracker) => match tracker.try_push_change(change, path) {
Some(change) => change,
None => return Ok(Action::Continue),
},
};

cb(change).map_err(|err| Error::Callback(err.into()))
}

fn emit_unmerged_ignore_intent_to_add<'rhs, 'lhs: 'rhs, E>(
(idx, path, entry): (usize, &'rhs BStr, &'rhs gix_index::Entry),
cb: &mut impl FnMut(ChangeRef<'lhs, 'rhs>) -> Result<Action, E>,
) -> Result<Option<Action>, Error>
where
E: Into<Box<dyn std::error::Error + Send + Sync>>,
{
if entry.flags.contains(gix_index::entry::Flags::INTENT_TO_ADD) {
return Ok(Some(Action::Continue));
}
let stage = entry.stage();
if stage == gix_index::entry::Stage::Unconflicted {
return Ok(None);
}

Ok(Some(
cb(ChangeRef::Unmerged {
location: Cow::Borrowed(path),
stage,
index: idx,
entry_mode: entry.mode,
id: Cow::Borrowed(entry.id.as_ref()),
})
.map_err(|err| Error::Callback(err.into()))?,
))
}
141 changes: 141 additions & 0 deletions gix-diff/src/index/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
use bstr::BStr;
use std::borrow::Cow;

/// The error returned by [`index()`](crate::index()).
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Cannot diff indices that contain sparse entries")]
IsSparse,
#[error("Unmerged entries aren't allowed in the left-hand index, only in the right-hand index")]
LhsHasUnmerged,
#[error("The callback indicated failure")]
Callback(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("Failure during rename tracking")]
RenameTracking(#[from] crate::rewrites::tracker::emit::Error),
}

/// What to do after a [ChangeRef] was passed ot the callback of [`index()`](crate::index()).
#[derive(Default, Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)]
pub enum Action {
/// Continue the operation.
#[default]
Continue,
/// Stop the operation immediately.
///
/// This is useful if one just wants to determine if something changed or not.
Cancel,
}

/// Options to configure how rewrites are tracked as part of the [`index()`](crate::index()) call.
pub struct RewriteOptions<'a, Find>
where
Find: gix_object::FindObjectOrHeader,
{
/// The cache to be used when rename-tracking by similarity is enabled, typically the default.
/// Note that it's recommended to call [`clear_resource_cache()`](`crate::blob::Platform::clear_resource_cache()`)
/// between the calls to avoid runaway memory usage, as the cache isn't limited.
pub resource_cache: &'a mut crate::blob::Platform,
/// A way to lookup objects from the object database, for use in similarity checks.
pub find: &'a Find,
/// Configure how rewrites are tracked.
pub rewrites: crate::Rewrites,
}

/// Identify a change that would have to be applied to `lhs` to obtain `rhs`, as provided in [`index()`](crate::index()).
///
/// Note that all variants are unconflicted entries, unless it's the [`Self::Unmerged`] one.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ChangeRef<'lhs, 'rhs> {
/// An entry was added to `rhs`.
Addition {
/// The location of the newly added entry in `rhs`.
location: Cow<'rhs, BStr>,
/// The index into the entries array of `rhs` for full access.
index: usize,
/// The mode of the entry in `rhs`.
entry_mode: gix_index::entry::Mode,
/// The object id of the entry in `rhs`.
id: Cow<'rhs, gix_hash::oid>,
},
/// An entry was removed from `rhs`.
Deletion {
/// The location the entry that doesn't exist in `rhs`.
location: Cow<'lhs, BStr>,
/// The index into the entries array of `lhs` for full access.
index: usize,
/// The mode of the entry in `lhs`.
entry_mode: gix_index::entry::Mode,
/// The object id of the entry in `lhs`.
id: Cow<'rhs, gix_hash::oid>,
},
/// An entry was modified, i.e. has changed its content or its mode.
Modification {
/// The location of the modified entry both in `lhs` and `rhs`.
location: Cow<'rhs, BStr>,
/// The index into the entries array of `lhs` for full access.
previous_index: usize,
/// The previous mode of the entry, in `lhs`.
previous_entry_mode: gix_index::entry::Mode,
/// The previous object id of the entry, in `lhs`.
previous_id: Cow<'lhs, gix_hash::oid>,
/// The index into the entries array of `rhs` for full access.
index: usize,
/// The mode of the entry in `rhs`.
entry_mode: gix_index::entry::Mode,
/// The object id of the entry in `rhs`.
id: Cow<'rhs, gix_hash::oid>,
},
/// An entry was renamed or copied from `lhs` to `rhs`.
///
/// A rename is effectively fusing together the `Deletion` of the source and the `Addition` of the destination.
Rewrite {
/// The location of the source of the rename or copy operation, in `lhs`.
source_location: Cow<'lhs, BStr>,
/// The index of the entry before the rename, into the entries array of `rhs` for full access.
source_index: usize,
/// The mode of the entry before the rewrite, in `lhs`.
source_entry_mode: gix_index::entry::Mode,
/// The object id of the entry before the rewrite.
///
/// Note that this is the same as `id` if we require the [similarity to be 100%](super::Rewrites::percentage), but may
/// be different otherwise.
source_id: Cow<'lhs, gix_hash::oid>,

/// The current location of the entry in `rhs`.
location: Cow<'rhs, BStr>,
/// The index of the entry after the rename, into the entries array of `rhs` for full access.
index: usize,
/// The mode of the entry after the rename in `rhs`.
entry_mode: gix_index::entry::Mode,
/// The object id of the entry after the rename in `rhs`.
id: Cow<'rhs, gix_hash::oid>,

/// If true, this rewrite is created by copy, and `source_id` is pointing to its source. Otherwise, it's a rename,
/// and `source_id` points to a deleted object, as renames are tracked as deletions and additions of the same
/// or similar content.
copy: bool,
},
/// One of up to three unmerged entries that are provided in order, one for each stage, ordered
/// by `location` and `stage`.
///
/// Unmerged entries also don't participate in rename tracking, and they are never present in `lhs`.
Unmerged {
/// The current location of the entry in `rhs`.
location: Cow<'rhs, BStr>,
/// The stage of the entry, either *base*, *ours*, or *theirs*.
stage: gix_index::entry::Stage,
/// The index into the entries array of `rhs` for full access.
index: usize,
/// The mode of the entry in `rhs`.
entry_mode: gix_index::entry::Mode,
/// The object id of the entry in `rhs`.
id: Cow<'rhs, gix_hash::oid>,
},
}

/// The fully-owned version of [`ChangeRef`].
pub type Change = ChangeRef<'static, 'static>;

mod change;
pub(super) mod function;
6 changes: 6 additions & 0 deletions gix-diff/src/lib.rs
Original file line number Diff line number Diff line change
@@ -58,6 +58,12 @@ pub mod tree_with_rewrites;
#[cfg(feature = "blob")]
pub use tree_with_rewrites::function::diff as tree_with_rewrites;

///
#[cfg(feature = "index")]
pub mod index;
#[cfg(feature = "index")]
pub use index::function::diff as index;

///
#[cfg(feature = "blob")]
pub mod blob;
5 changes: 4 additions & 1 deletion gix-diff/tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -17,8 +17,9 @@ name = "diff"
path = "diff/main.rs"

[dev-dependencies]
insta = "1.40.0"
gix-diff = { path = ".." }
gix-index = { version = "^0.37.0", path = "../../gix-index" }
gix-pathspec = { version = "^0.8.1", path = "../../gix-pathspec" }
gix-hash = { path = "../../gix-hash" }
gix-fs = { path = "../../gix-fs" }
gix-worktree = { path = "../../gix-worktree" }
@@ -27,5 +28,7 @@ gix-odb = { path = "../../gix-odb" }
gix-filter = { path = "../../gix-filter" }
gix-traverse = { path = "../../gix-traverse" }
gix-testtools = { path = "../../tests/tools" }

insta = "1.40.0"
shell-words = "1"
pretty_assertions = "1.4.0"
1,367 changes: 1,367 additions & 0 deletions gix-diff/tests/diff/index.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions gix-diff/tests/diff/main.rs
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ fn hex_to_id(hex: &str) -> gix_hash::ObjectId {
}

mod blob;
mod index;
mod rewrites;
mod tree;
mod tree_with_rewrites;
Binary file not shown.
14 changes: 14 additions & 0 deletions gix-diff/tests/fixtures/make_diff_for_rewrites_repo.sh
Original file line number Diff line number Diff line change
@@ -806,4 +806,18 @@ git mv src/plumbing src/plumbing-renamed
git commit -m "r4-dir-rename-non-identity"
store_tree "r4-dir-rename-non-identity"

git checkout -b conflicting @~1
git rm src/plumbing/main.rs
git commit -m "remove main.rs"

git checkout main
git merge conflicting || :

echo not-empty >will-add
git add --intent-to-add will-add

# a file with skip-worktree flag, which has no bearing on tree/index diffs.
git update-index --skip-worktree src/shared.rs
rm src/shared.rs

mv ../*.tree .

0 comments on commit a867322

Please sign in to comment.