From d803ff5e32bb616b36409be25fdb93f07387babf Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 19 Nov 2024 11:42:32 +0100 Subject: [PATCH] feat: provide a way to record and apply index changes. These changes will then be applicable to an index that is created from the written tree editor. --- Cargo.lock | 1 + crate-status.md | 5 +- gix-merge/Cargo.toml | 1 + gix-merge/src/tree/function.rs | 212 +++++++++++++++--- gix-merge/src/tree/mod.rs | 188 +++++++++++++++- gix-merge/src/tree/utils.rs | 47 +++- .../generated-archives/tree-baseline.tar | Bin 2789376 -> 2840576 bytes gix-merge/tests/fixtures/tree-baseline.sh | 200 ++++++++++++++++- gix-merge/tests/merge/tree/baseline.rs | 68 +++++- gix-merge/tests/merge/tree/mod.rs | 53 ++++- 10 files changed, 725 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dcfc0729ce5..21a56b4c908 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2114,6 +2114,7 @@ dependencies = [ "gix-filter", "gix-fs 0.12.0", "gix-hash 0.15.0", + "gix-index 0.36.0", "gix-object 0.45.0", "gix-odb", "gix-path 0.10.12", diff --git a/crate-status.md b/crate-status.md index 9dcf98f1248..0774895b67a 100644 --- a/crate-status.md +++ b/crate-status.md @@ -318,6 +318,8 @@ Check out the [performance discussion][gix-diff-performance] as well. * [x] find blobs by similarity check * [ ] heuristics to find best candidate * [ ] find by basename to support similarity check + - Not having it can lead to issues when files with the same or similar content are part of a move + as files can be lost that way. * [x] directory tracking - [x] by identity - [ ] by similarity @@ -349,8 +351,7 @@ Check out the [performance discussion][gix-diff-performance] as well. - [ ] various newlines-related options during the merge (see https://git-scm.com/docs/git-merge#Documentation/git-merge.txt-ignore-space-change). - [ ] a way to control inter-hunk merging based on proximity (maybe via `gix-diff` feature which could use the same) * [x] **tree**-diff-heuristics match Git for its test-cases - - [ ] a way to generate an index with stages - - *currently the data it provides won't generate index entries, and possibly can't be used for it yet* + - [x] a way to generate an index with stages, mostly conforming with Git. - [ ] submodule merges (*right now they count as conflicts if they differ*) * [x] **commits** - with handling of multiple merge bases by recursive merge-base merge * [x] API documentation diff --git a/gix-merge/Cargo.toml b/gix-merge/Cargo.toml index b8439ba982b..5f3f0740cf9 100644 --- a/gix-merge/Cargo.toml +++ b/gix-merge/Cargo.toml @@ -32,6 +32,7 @@ gix-quote = { version = "^0.4.13", path = "../gix-quote" } gix-revision = { version = "^0.30.0", path = "../gix-revision", default-features = false, features = ["merge_base"] } gix-revwalk = { version = "^0.16.0", path = "../gix-revwalk" } gix-diff = { version = "^0.47.0", path = "../gix-diff", default-features = false, features = ["blob"] } +gix-index = { version = "^0.36.0", path = "../gix-index" } thiserror = "2.0.0" imara-diff = { version = "0.1.7" } diff --git a/gix-merge/src/tree/function.rs b/gix-merge/src/tree/function.rs index eaa0ea933ba..ca131db27a7 100644 --- a/gix-merge/src/tree/function.rs +++ b/gix-merge/src/tree/function.rs @@ -3,7 +3,10 @@ use crate::tree::utils::{ to_components, track, unique_path_in_tree, ChangeList, ChangeListRef, PossibleConflict, TrackedChange, TreeNodes, }; use crate::tree::ConflictMapping::{Original, Swapped}; -use crate::tree::{Conflict, ConflictMapping, ContentMerge, Error, Options, Outcome, Resolution, ResolutionFailure}; +use crate::tree::{ + Conflict, ConflictIndexEntry, ConflictIndexEntryPathHint, ConflictMapping, ContentMerge, Error, Options, Outcome, + Resolution, ResolutionFailure, +}; use bstr::{BString, ByteSlice}; use gix_diff::tree::recorder::Location; use gix_diff::tree_with_rewrites::Change; @@ -190,11 +193,14 @@ where }) { None => { if let Some((rewritten_location, ours_idx)) = rewritten_location { + // `no_entry` to the index because that's not a conflict at all, + // but somewhat advanced rename tracking. if should_fail_on_conflict(Conflict::with_resolution( Resolution::SourceLocationAffectedByRename { final_location: rewritten_location.to_owned(), }, (&our_changes[*ours_idx].inner, theirs, Original, outer_side), + [None, None, None], )) { break 'outer; }; @@ -254,10 +260,12 @@ where if let Some(ours) = ours { gix_trace::debug!("Turning a case we could probably handle into a conflict for now. theirs: {theirs:#?} ours: {ours:#?} kind: {match_kind:?}"); if allow_resolution_failure - && should_fail_on_conflict(Conflict::without_resolution( - ResolutionFailure::Unknown, - (&ours.inner, theirs, Original, outer_side), - )) + && should_fail_on_conflict(Conflict::unknown(( + &ours.inner, + theirs, + Original, + outer_side, + ))) { break 'outer; }; @@ -328,7 +336,7 @@ where pick_our_changes(side, our_changes, their_changes), ); let renamed_without_change = their_source_id == their_id; - let (our_id, resolution) = if renamed_without_change { + let (merged_blob_id, resolution) = if renamed_without_change { (*our_id, None) } else { let (our_location, our_id, our_mode, their_location, their_id, their_mode) = @@ -373,18 +381,23 @@ where location: their_rewritten_location.unwrap_or_else(|| their_location.to_owned()), relation: None, entry_mode: merged_mode, - id: our_id, + id: merged_blob_id, }; if should_fail_on_conflict(Conflict::with_resolution( Resolution::OursModifiedTheirsRenamedAndChangedThenRename { merged_mode: (merged_mode != *their_mode).then_some(merged_mode), merged_blob: resolution.map(|resolution| ContentMerge { resolution, - merged_blob_id: our_id, + merged_blob_id, }), final_location, }, (ours, theirs, side, outer_side), + [ + index_entry(previous_entry_mode, previous_id), + index_entry(our_mode, our_id), + index_entry(their_mode, their_id), + ], )) { break 'outer; } @@ -401,6 +414,19 @@ where if should_fail_on_conflict(Conflict::without_resolution( ResolutionFailure::OursModifiedTheirsRenamedTypeMismatch, (ours, theirs, side, outer_side), + [ + index_entry_at_path( + previous_entry_mode, + previous_id, + ConflictIndexEntryPathHint::RenamedOrTheirs, + ), + None, + index_entry_at_path( + their_mode, + their_id, + ConflictIndexEntryPathHint::RenamedOrTheirs, + ), + ], )) { break 'outer; } @@ -421,7 +447,7 @@ where .. }, ) if !involves_submodule(our_mode, their_mode) - && our_mode.kind() == their_mode.kind() + && merge_modes(*our_mode, *their_mode).is_some() && our_id != their_id => { let (merged_blob_id, resolution) = perform_blob_merge( @@ -436,7 +462,11 @@ where (0, outer_side), &options, )?; - editor.upsert(toc(location), our_mode.kind(), merged_blob_id)?; + + let merged_mode = merge_modes_prev(*our_mode, *their_mode, *previous_entry_mode) + .expect("BUG: merge_modes() reports a valid mode, this one should do too"); + + editor.upsert(toc(location), merged_mode.kind(), merged_blob_id)?; if should_fail_on_conflict(Conflict::with_resolution( Resolution::OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob: ContentMerge { @@ -445,6 +475,11 @@ where }, }, (ours, theirs, Original, outer_side), + [ + index_entry(previous_entry_mode, previous_id), + index_entry(our_mode, our_id), + index_entry(their_mode, their_id), + ], )) { break 'outer; }; @@ -489,27 +524,28 @@ where }, }, (ours, theirs, Original, outer_side), + [None, index_entry(our_mode, our_id), index_entry(their_mode, their_id)], )) } else if allow_resolution_failure { // Actually this has a preference, as symlinks are always left in place with the other side renamed. let ( logical_side, label_of_side_to_be_moved, - (our_mode, our_id), - (their_mode, their_id), + (our_mode, our_id, our_path_hint), + (their_mode, their_id, their_path_hint), ) = if matches!(our_mode.kind(), EntryKind::Link | EntryKind::Tree) { ( Original, labels.other.unwrap_or_default(), - (*our_mode, *our_id), - (*their_mode, *their_id), + (*our_mode, *our_id, ConflictIndexEntryPathHint::Current), + (*their_mode, *their_id, ConflictIndexEntryPathHint::RenamedOrTheirs), ) } else { ( Swapped, labels.current.unwrap_or_default(), - (*their_mode, *their_id), - (*our_mode, *our_id), + (*their_mode, *their_id, ConflictIndexEntryPathHint::RenamedOrTheirs), + (*our_mode, *our_id, ConflictIndexEntryPathHint::Current), ) }; let tree_with_rename = pick_our_tree(logical_side, their_tree, our_tree); @@ -525,6 +561,11 @@ where their_unique_location: renamed_location.clone(), }, (ours, theirs, logical_side, outer_side), + [ + None, + index_entry_at_path(&our_mode, &our_id, our_path_hint), + index_entry_at_path(&their_mode, &their_id, their_path_hint), + ], ); let new_change = Change::Addition { @@ -554,7 +595,8 @@ where location, entry_mode, id, - .. + previous_entry_mode, + previous_id, }, Change::Deletion { .. }, ) @@ -564,7 +606,8 @@ where location, entry_mode, id, - .. + previous_entry_mode, + previous_id, }, ) if allow_resolution_failure => { let (label_of_side_to_be_moved, side) = if matches!(ours, Change::Modification { .. }) { @@ -606,6 +649,11 @@ where renamed_unique_path_to_modified_blob: renamed_path, }, (ours, theirs, side, outer_side), + [ + index_entry(previous_entry_mode, previous_id), + index_entry(entry_mode, id), + None, + ], )); // Since we move *our* side, our tree needs to be modified. @@ -621,6 +669,11 @@ where let should_break = should_fail_on_conflict(Conflict::without_resolution( ResolutionFailure::OursModifiedTheirsDeleted, (ours, theirs, side, outer_side), + [ + index_entry(previous_entry_mode, previous_id), + index_entry(entry_mode, id), + None, + ], )); editor.upsert(toc(location), entry_mode.kind(), *id)?; if should_break { @@ -702,10 +755,9 @@ where // Pretend this is the end of the loop and keep this as conflict. // If this happens in the wild, we'd want to reproduce it. if allow_resolution_failure - && should_fail_on_conflict(Conflict::without_resolution( - ResolutionFailure::Unknown, - (ours, theirs, Original, outer_side), - )) + && should_fail_on_conflict(Conflict::unknown(( + ours, theirs, Original, outer_side, + ))) { break 'outer; }; @@ -738,6 +790,23 @@ where }), }, (ours, theirs, Original, outer_side), + [ + index_entry_at_path( + source_entry_mode, + source_id, + ConflictIndexEntryPathHint::Source, + ), + index_entry_at_path( + our_mode, + &merged_blob_id, + ConflictIndexEntryPathHint::Current, + ), + index_entry_at_path( + their_mode, + &merged_blob_id, + ConflictIndexEntryPathHint::RenamedOrTheirs, + ), + ], )) { break 'outer; }; @@ -767,6 +836,23 @@ where }, }, (ours, theirs, Original, outer_side), + [ + index_entry_at_path( + source_entry_mode, + source_id, + ConflictIndexEntryPathHint::Source, + ), + index_entry_at_path( + our_mode, + &merged_blob_id, + ConflictIndexEntryPathHint::Current, + ), + index_entry_at_path( + their_mode, + &merged_blob_id, + ConflictIndexEntryPathHint::RenamedOrTheirs, + ), + ], )) { break 'outer; }; @@ -824,6 +910,15 @@ where if should_fail_on_conflict(Conflict::without_resolution( ResolutionFailure::OursDeletedTheirsRenamed, (ours, theirs, side, outer_side), + [ + None, + None, + index_entry_at_path( + rewritten_mode, + rewritten_id, + ConflictIndexEntryPathHint::RenamedOrTheirs, + ), + ], )) { break 'outer; }; @@ -901,6 +996,7 @@ where }, }, (ours, theirs, Original, outer_side), + [None, index_entry(our_mode, our_id), index_entry(their_mode, their_id)], )) { break 'outer; }; @@ -916,21 +1012,21 @@ where let ( logical_side, label_of_side_to_be_moved, - (our_mode, our_id), - (their_mode, their_id), + (our_mode, our_id, our_path_hint), + (their_mode, their_id, their_path_hint), ) = if matches!(our_mode.kind(), EntryKind::Link | EntryKind::Tree) { ( Original, labels.other.unwrap_or_default(), - (*our_mode, *our_id), - (*their_mode, *their_id), + (*our_mode, *our_id, ConflictIndexEntryPathHint::Current), + (*their_mode, *their_id, ConflictIndexEntryPathHint::RenamedOrTheirs), ) } else { ( Swapped, labels.current.unwrap_or_default(), - (*their_mode, *their_id), - (*our_mode, *our_id), + (*their_mode, *their_id, ConflictIndexEntryPathHint::RenamedOrTheirs), + (*our_mode, *our_id, ConflictIndexEntryPathHint::Current), ) }; let tree_with_rename = pick_our_tree(logical_side, their_tree, our_tree); @@ -946,6 +1042,11 @@ where their_unique_location: renamed_location.clone(), }, (ours, theirs, side, outer_side), + [ + None, + index_entry_at_path(&our_mode, &our_id, our_path_hint), + index_entry_at_path(&their_mode, &their_id, their_path_hint), + ], ); let new_change_with_rename = Change::Addition { @@ -970,10 +1071,7 @@ where } _unknown => { if allow_resolution_failure - && should_fail_on_conflict(Conflict::without_resolution( - ResolutionFailure::Unknown, - (ours, theirs, Original, outer_side), - )) + && should_fail_on_conflict(Conflict::unknown((ours, theirs, Original, outer_side))) { break 'outer; }; @@ -1004,12 +1102,40 @@ fn involves_submodule(a: &EntryMode, b: &EntryMode) -> bool { a.is_commit() || b.is_commit() } -/// Allows equal modes or preferes executables bits in case of blobs +/// Allows equal modes or prefers executables bits in case of blobs +/// +/// Note that this is often not correct as the previous mode of each side should be taken into account so that: +/// +/// on | on = on +/// off | off = off +/// on | off || off | on = conflict fn merge_modes(a: EntryMode, b: EntryMode) -> Option { match (a.kind(), b.kind()) { + (_, _) if a == b => Some(a), (EntryKind::BlobExecutable, EntryKind::BlobExecutable | EntryKind::Blob) | (EntryKind::Blob, EntryKind::BlobExecutable) => Some(EntryKind::BlobExecutable.into()), + _ => None, + } +} + +/// Use this version if there is a single common `prev` value for both `a` and `b` to detect +/// if the mode was turned on or off. +fn merge_modes_prev(a: EntryMode, b: EntryMode, prev: EntryMode) -> Option { + match (a.kind(), b.kind()) { (_, _) if a == b => Some(a), + (a @ EntryKind::BlobExecutable, b @ (EntryKind::BlobExecutable | EntryKind::Blob)) + | (a @ EntryKind::Blob, b @ EntryKind::BlobExecutable) => { + let prev = prev.kind(); + let changed = if a == prev { b } else { a }; + Some( + match (prev, changed) { + (EntryKind::Blob, EntryKind::BlobExecutable) => EntryKind::BlobExecutable, + (EntryKind::BlobExecutable, EntryKind::Blob) => EntryKind::Blob, + _ => unreachable!("upper match already assured we only deal with blobs"), + } + .into(), + ) + } _ => None, } } @@ -1066,3 +1192,23 @@ fn pick_our_changes_mut<'a>( Swapped => theirs, } } + +fn index_entry(mode: &gix_object::tree::EntryMode, id: &gix_hash::ObjectId) -> Option { + Some(ConflictIndexEntry { + mode: *mode, + id: *id, + path_hint: None, + }) +} + +fn index_entry_at_path( + mode: &gix_object::tree::EntryMode, + id: &gix_hash::ObjectId, + hint: ConflictIndexEntryPathHint, +) -> Option { + Some(ConflictIndexEntry { + mode: *mode, + id: *id, + path_hint: Some(hint), + }) +} diff --git a/gix-merge/src/tree/mod.rs b/gix-merge/src/tree/mod.rs index 37a6453ea82..cd66d1ffa9a 100644 --- a/gix-merge/src/tree/mod.rs +++ b/gix-merge/src/tree/mod.rs @@ -81,6 +81,17 @@ impl Outcome<'_> { pub fn has_unresolved_conflicts(&self, how: TreatAsUnresolved) -> bool { self.conflicts.iter().any(|c| c.is_unresolved(how)) } + + /// Returns `true` if `index` changed as we applied conflicting stages to it, using `how` to determine if a + /// conflict should be considered unresolved. + /// It's important that `index` is at the state of [`Self::tree`]. + /// + /// Note that in practice, whenever there is a single [conflict](Conflict), this function will return `true`. + /// Also, the unconflicted stage of such entries will be removed merely by setting a flag, so the + /// in-memory entry is still present. + pub fn index_changed_after_applying_conflicts(&self, index: &mut gix_index::State, how: TreatAsUnresolved) -> bool { + apply_index_entries(&self.conflicts, how, index) + } } /// A description of a conflict (i.e. merge issue without an auto-resolution) as seen during a [tree-merge](crate::tree()). @@ -99,11 +110,45 @@ pub struct Conflict { pub ours: Change, /// The change representing *their* side. pub theirs: Change, + /// An array to store an entry for each stage of the conflict. + /// + /// * `entries[0]` => Base + /// * `entries[1]` => Ours + /// * `entries[2]` => Theirs + /// + /// Note that ours and theirs might be swapped, so one should access it through [`Self::entries()`] to compensate for that. + pub entries: [Option; 3], /// Determine how to interpret the `ours` and `theirs` fields. This is used to implement [`Self::changes_in_resolution()`] /// and [`Self::into_parts_by_resolution()`]. map: ConflictMapping, } +/// A conflicting entry for insertion into the index. +/// It will always be either on stage 1 (ancestor/base), 2 (ours) or 3 (theirs) +#[derive(Debug, Clone, Copy)] +pub struct ConflictIndexEntry { + /// The kind of object at this stage. + /// Note that it's possible that this is a directory, for instance if a directory was replaced with a file. + pub mode: gix_object::tree::EntryMode, + /// The id defining the state of the object. + pub id: gix_hash::ObjectId, + /// Hidden, maybe one day we can do without? + path_hint: Option, +} + +/// A hint for [`apply_index_entries()`] to know which paths to use for an entry. +/// This is only used when necessary. +#[derive(Debug, Clone, Copy)] +enum ConflictIndexEntryPathHint { + /// Use the previous path, i.e. rename source. + Source, + /// Use the current path as it is in the tree. + Current, + /// Use the path of the final destination, or *their* name. + /// It's definitely finicky, as we don't store the actual path and instead refer to it. + RenamedOrTheirs, +} + /// A utility to help define which side is what in the [`Conflict`] type. #[derive(Debug, Clone, Copy)] enum ConflictMapping { @@ -147,7 +192,11 @@ impl Conflict { TreatAsUnresolved::Renames | TreatAsUnresolved::RenamesAndAutoResolvedContent => match &self.resolution { Ok(success) => match success { Resolution::SourceLocationAffectedByRename { .. } => false, - Resolution::OursModifiedTheirsRenamedAndChangedThenRename { .. } => true, + Resolution::OursModifiedTheirsRenamedAndChangedThenRename { + merged_blob, + final_location, + .. + } => final_location.is_some() || merged_blob.as_ref().map_or(false, content_merge_matches), Resolution::OursModifiedTheirsModifiedThenBlobContentMerge { merged_blob } => { content_merge_matches(merged_blob) } @@ -178,6 +227,14 @@ impl Conflict { } } + /// Return the index entries for insertion into the index, to match with what's returned by [`Self::changes_in_resolution()`]. + pub fn entries(&self) -> [Option; 3] { + match self.map { + ConflictMapping::Original => self.entries, + ConflictMapping::Swapped => [self.entries[0], self.entries[2], self.entries[1]], + } + } + /// Return information about the content merge if it was performed. pub fn content_merge(&self) -> Option { match &self.resolution { @@ -308,3 +365,132 @@ pub struct Options { pub(super) mod function; mod utils; +pub mod apply_index_entries { + + pub(super) mod function { + use crate::tree::{Conflict, ConflictIndexEntryPathHint, Resolution, ResolutionFailure, TreatAsUnresolved}; + use bstr::{BStr, ByteSlice}; + use std::collections::{hash_map, HashMap}; + + /// Returns `true` if `index` changed as we applied conflicting stages to it, using `how` to determine if a + /// conflict should be considered unresolved. + /// Once a stage of a path conflicts, the unconflicting stage is removed even though it might be the one + /// that is currently checked out. + /// This removal, however, is only done by flagging it with [gix_index::entry::Flags::REMOVE], which means + /// these entries won't be written back to disk but will still be present in the index. + /// It's important that `index` matches the tree that was produced as part of the merge that also + /// brought about `conflicts`, or else this function will fail if it cannot find the path matching + /// the conflicting entries. + /// + /// Note that in practice, whenever there is a single [conflict](Conflict), this function will return `true`. + /// Errors can only occour if `index` isn't the one created from the merged tree that produced the `conflicts`. + pub fn apply_index_entries( + conflicts: &[Conflict], + how: TreatAsUnresolved, + index: &mut gix_index::State, + ) -> bool { + let len = index.entries().len(); + let mut idx_by_path_stage = HashMap::<(gix_index::entry::Stage, &BStr), usize>::default(); + for conflict in conflicts.iter().filter(|c| c.is_unresolved(how)) { + let (renamed_path, current_path): (Option<&BStr>, &BStr) = match &conflict.resolution { + Ok(success) => match success { + Resolution::SourceLocationAffectedByRename { final_location } => { + (Some(final_location.as_bstr()), final_location.as_bstr()) + } + Resolution::OursModifiedTheirsRenamedAndChangedThenRename { final_location, .. } => ( + final_location.as_ref().map(|p| p.as_bstr()), + conflict.changes_in_resolution().1.location(), + ), + Resolution::OursModifiedTheirsModifiedThenBlobContentMerge { .. } => { + (None, conflict.ours.location()) + } + }, + Err(failure) => match failure { + ResolutionFailure::OursRenamedTheirsRenamedDifferently { .. } => { + (Some(conflict.theirs.location()), conflict.ours.location()) + } + ResolutionFailure::OursModifiedTheirsRenamedTypeMismatch + | ResolutionFailure::OursDeletedTheirsRenamed + | ResolutionFailure::OursModifiedTheirsDeleted + | ResolutionFailure::Unknown => (None, conflict.ours.location()), + ResolutionFailure::OursModifiedTheirsDirectoryThenOursRenamed { + renamed_unique_path_to_modified_blob, + } => ( + Some(renamed_unique_path_to_modified_blob.as_bstr()), + conflict.ours.location(), + ), + ResolutionFailure::OursAddedTheirsAddedTypeMismatch { their_unique_location } => { + (Some(their_unique_location.as_bstr()), conflict.ours.location()) + } + }, + }; + let source_path = conflict.ours.source_location(); + + let entries_with_stage = conflict.entries().into_iter().enumerate().filter_map(|(idx, entry)| { + entry.filter(|e| e.mode.is_no_tree()).map(|e| { + ( + match idx { + 0 => gix_index::entry::Stage::Base, + 1 => gix_index::entry::Stage::Ours, + 2 => gix_index::entry::Stage::Theirs, + _ => unreachable!("fixed size array with three items"), + }, + match e.path_hint { + None => renamed_path.unwrap_or(current_path), + Some(ConflictIndexEntryPathHint::Source) => source_path, + Some(ConflictIndexEntryPathHint::Current) => current_path, + Some(ConflictIndexEntryPathHint::RenamedOrTheirs) => { + renamed_path.unwrap_or_else(|| conflict.changes_in_resolution().1.location()) + } + }, + e, + ) + }) + }); + + if !entries_with_stage.clone().any(|(_, path, _)| { + index + .entry_index_by_path_and_stage_bounded(path, gix_index::entry::Stage::Unconflicted, len) + .is_some() + }) { + continue; + } + + for (stage, path, entry) in entries_with_stage { + if let Some(pos) = + index.entry_index_by_path_and_stage_bounded(path, gix_index::entry::Stage::Unconflicted, len) + { + index.entries_mut()[pos].flags.insert(gix_index::entry::Flags::REMOVE); + }; + match idx_by_path_stage.entry((stage, path)) { + hash_map::Entry::Occupied(map_entry) => { + // This can happen due to the way the algorithm works. + // The same happens in Git, but it stores the index-related data as part of its deduplicating tree. + // We store each conflict we encounter, which also may duplicate their index entries, sometimes, but + // with different values. The most recent value wins. + // Instead of trying to deduplicate the index entries when the merge runs, we put the cost + // to the tree-assembly - there is no way around it. + let index_entry = &mut index.entries_mut()[*map_entry.get()]; + index_entry.mode = entry.mode.into(); + index_entry.id = entry.id; + } + hash_map::Entry::Vacant(map_entry) => { + map_entry.insert(index.entries().len()); + index.dangerously_push_entry( + Default::default(), + entry.id, + stage.into(), + entry.mode.into(), + path, + ); + } + }; + } + } + + index.sort_entries(); + index.entries().len() != len + } + } +} +pub use apply_index_entries::function::apply_index_entries; diff --git a/gix-merge/src/tree/utils.rs b/gix-merge/src/tree/utils.rs index cd37f809e1e..318916ff981 100644 --- a/gix-merge/src/tree/utils.rs +++ b/gix-merge/src/tree/utils.rs @@ -7,7 +7,10 @@ //! contribute to finding a fix faster. use crate::blob::builtin_driver::binary::Pick; use crate::blob::ResourceKind; -use crate::tree::{Conflict, ConflictMapping, Error, Options, Resolution, ResolutionFailure}; +use crate::tree::{ + Conflict, ConflictIndexEntry, ConflictIndexEntryPathHint, ConflictMapping, Error, Options, Resolution, + ResolutionFailure, +}; use bstr::ByteSlice; use bstr::{BStr, BString, ByteVec}; use gix_diff::tree_with_rewrites::{Change, ChangeRef}; @@ -98,6 +101,14 @@ pub fn perform_blob_merge( where E: Into>, { + if our_id == their_id { + // This can happen if the merge modes are different. + debug_assert_ne!( + our_mode, their_mode, + "BUG: we must think anything has to be merged if the modes and the ids are the same" + ); + return Ok((their_id, crate::blob::Resolution::Complete)); + } if matches!(our_mode.kind(), EntryKind::Link) && matches!(their_mode.kind(), EntryKind::Link) { let (pick, resolution) = crate::blob::builtin_driver::binary(options.symlink_conflicts); let (our_id, their_id) = match outer_side { @@ -544,29 +555,57 @@ impl Conflict { pub(super) fn without_resolution( resolution: ResolutionFailure, changes: (&Change, &Change, ConflictMapping, ConflictMapping), + entries: [Option; 3], ) -> Self { - Conflict::maybe_resolved(Err(resolution), changes) + Conflict::maybe_resolved(Err(resolution), changes, entries) } pub(super) fn with_resolution( resolution: Resolution, changes: (&Change, &Change, ConflictMapping, ConflictMapping), + entries: [Option; 3], ) -> Self { - Conflict::maybe_resolved(Ok(resolution), changes) + Conflict::maybe_resolved(Ok(resolution), changes, entries) } - pub(super) fn maybe_resolved( + fn maybe_resolved( resolution: Result, (ours, theirs, map, outer_map): (&Change, &Change, ConflictMapping, ConflictMapping), + entries: [Option; 3], ) -> Self { Conflict { resolution, ours: ours.clone(), theirs: theirs.clone(), + entries, map: match outer_map { ConflictMapping::Original => map, ConflictMapping::Swapped => map.swapped(), }, } } + + pub(super) fn unknown(changes: (&Change, &Change, ConflictMapping, ConflictMapping)) -> Self { + let (source_mode, source_id) = changes.0.source_entry_mode_and_id(); + let (our_mode, our_id) = changes.0.entry_mode_and_id(); + let (their_mode, their_id) = changes.1.entry_mode_and_id(); + let entries = [ + Some(ConflictIndexEntry { + mode: source_mode, + id: source_id.into(), + path_hint: Some(ConflictIndexEntryPathHint::Source), + }), + Some(ConflictIndexEntry { + mode: our_mode, + id: our_id.into(), + path_hint: Some(ConflictIndexEntryPathHint::Current), + }), + Some(ConflictIndexEntry { + mode: their_mode, + id: their_id.into(), + path_hint: Some(ConflictIndexEntryPathHint::RenamedOrTheirs), + }), + ]; + Conflict::maybe_resolved(Err(ResolutionFailure::Unknown), changes, entries) + } } diff --git a/gix-merge/tests/fixtures/generated-archives/tree-baseline.tar b/gix-merge/tests/fixtures/generated-archives/tree-baseline.tar index bd50e785648cce991953eb04acf30de922a6d063..325533a59a83b71145d73e245f284b601995841b 100644 GIT binary patch delta 50125 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 delta 101573 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

6eVpgZ)5vatHDhI7Z-`QtGE4nuI`EYjt16YM zQZ=Y5m8wz|%qmb-s#ekMdGH}srBYQYRi(PyDwQ|hhR|vYdJSYmww;Z$* z6RV70K}?K56+xo41c{JxIoPf$4!CrB{0bst3itvW6UyZkZ|N4?fsD}Wf&9_Im7;VC_IRXcMqreZt$-3Gi1Ems!CVY^27^XDo)SkqQjswS!!VnNXaq7z z2YaMDX^Tg#7nVypXr*!9i3c|my#p24Z%8P9eRIOIC{ACMa0A?>H~}t*%-N84PaO?U zSgH@e)zBcrsZcP;4nqQyy>?p>S;U`yVtrx)YTP>p(J`c+4jxOkn?E+gAJBT$Y=Xz5Stna>n7`1<38whJ zBU#;{ES*M{Yt5sH8Y72(XXSU)R~Jgg`>OM;Ay~S%n9;Nu7SSDy0xm#rRrR&&ZX*H zs?MeAT&m8c>RfkK=jxi~Ns*jeLx5*nG(b%(!Js8UtL(p$wU6_ybt1!1#JI+IHd3~S z5LRN0%i>wa85eIU`TKPrEGjPr1e7wzIUiUjy6=$lfzNGya2ApuZ0z-R&l;ng(BSC_ z^tONeVk<DsyJScH#>%ajXnZ&^?P2!qo zF%IqWn3O;yeuxtU&tgHg-SmLaB%Jn`(Dg<1!1FkyVl`#|F3(;eU_0c|vGmpe<{WGj zJ=^X98%o>j0mT`pVWC*bwEi^ad5kH!>?{T;Y$j`ICR^Af%UPflKSZ4!VaT0Pm7tZn zx#Vfl%Q3oatxh%{&Jqxm6r2627Iz>foo @@ -317,6 +345,34 @@ git init super-2 git add foo git mv foo olddir/bar git commit -m "Modify foo & rename foo -> olddir/bar" + + rm .git/index + git update-index --index-info <a/sub/y.f git mv a a-different git commit -am "changed all content, renamed a -> a-different" + +# Git only sees the files with content changes as conflicting, and somehow misses to add the +# bases of the files without content changes. After all, these also have been renamed into +# different places which must be a conflict just as much. + rm .git/index + git update-index --index-info <a/sub/y.f git mv a/sub a/sub-different git commit -am "changed all content, renamed a/sub -> a/sub-different" + +# Here it's the same as above, i.e. Git doesn't list files as conflicting if +# they didn't change, even though they have a conflicting rename. + rm .git/index + git update-index --index-info <a/x.f git mv a a-renamed git commit -am "Git, when branches are reversed, doesn't keep the +x flag on a/w so we specify our own expectation" + # Git sets +x and adds it as conflict, even though the merge is perfect, i.e. one side adds +x on top, perfectly additive. + make_conflict_index same-rename-different-mode-A-B + make_conflict_index same-rename-different-mode-A-B-reversed +) + +git init remove-executable-mode +(cd remove-executable-mode + touch w + chmod +x w + git add --chmod=+x w + git add . && git commit -m "original" + + git branch A + git branch B + + git checkout A + chmod -x w + git update-index --chmod=-x w + git commit -am "remove executable bit from w" + + git checkout B + write_lines 1 2 3 4 5 >w + git commit -am "unrelated change to w" ) git init renamed-symlink-with-conflict @@ -614,6 +787,23 @@ git init submodule-both-modify git add sub tick git commit -m b + + # We cannot handle submodules yet and thus mark them as conflicted, always if they mismatch at least. + rm .git/index + git update-index --index-info <a/x.f git add . && git commit -m "original" @@ -678,8 +868,8 @@ git init big-file-merge git branch B git checkout A - seq 30 >a/x.f - git commit -am "turn normal file into big one (81 bytes)" + seq 37 >a/x.f + git commit -am "turn normal file into big one (102 bytes)" git branch expected git checkout B @@ -803,6 +993,9 @@ git init type-change-to-symlink +# TODO: Git does not detect the conflict (one turns exe off, the other turns it on), and we do exactly the same. +baseline rename-add-exe-bit-conflict A-B A B +baseline remove-executable-mode A-B A B baseline simple side-1-3-without-conflict side1 side3 baseline simple fast-forward side1 main baseline simple no-change main main @@ -838,7 +1031,6 @@ baseline conflicting-rename-2 A-B A B baseline conflicting-rename-complex A-B A B "Git has different rename tracking which is why a-renamed/w disappears - it's still close enough" baseline same-rename-different-mode A-B A B "Git works for the A/B case, but for B/A it forgets to set the executable bit" -baseline same-rename-different-mode A-B-diff3 A B "Git works for the A/B case, but for B/A it forgets to set the executable bit" baseline renamed-symlink-with-conflict A-B A B baseline added-file-changed-content-and-mode A-B A B "We improve on executable bit handling, but loose on diff quality as we are definitely missing some tweaks" diff --git a/gix-merge/tests/merge/tree/baseline.rs b/gix-merge/tests/merge/tree/baseline.rs index 18ba8aa0f06..1eddf0211a7 100644 --- a/gix-merge/tests/merge/tree/baseline.rs +++ b/gix-merge/tests/merge/tree/baseline.rs @@ -6,7 +6,7 @@ use gix_object::FindExt; use std::path::{Path, PathBuf}; /// An entry in the conflict -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub struct Entry { /// The relative path in the repository pub location: String, @@ -17,7 +17,7 @@ pub struct Entry { } /// Keep track of all the sides of a conflict. Some might not be set to indicate removal, including the ancestor. -#[derive(Default, Debug)] +#[derive(Default, Debug, Eq, PartialEq)] pub struct Conflict { pub ancestor: Option, pub ours: Option, @@ -129,7 +129,7 @@ impl Iterator for Expectations<'_> { let mut tokens = line.split(' '); let ( Some(subdir), - Some(conflict_style), + Some(conflict_style_name), Some(our_commit_id), Some(our_side_name), Some(their_commit_id), @@ -160,7 +160,7 @@ impl Iterator for Expectations<'_> { }); let subdir_path = self.root.join(subdir); - let conflict_style = match conflict_style { + let conflict_style = match conflict_style_name { "merge" => ConflictStyle::Merge, "diff3" => ConflictStyle::Diff3, unknown => unreachable!("Unknown conflict style: '{unknown}'"), @@ -221,6 +221,10 @@ fn parse_merge_info(content: String) -> MergeInfo { *field = Some(entry); } + if conflict.any_location().is_some() && conflicts.last() != Some(&conflict) { + conflicts.push(conflict); + } + while lines.peek().is_some() { out.information .push(parse_info(&mut lines).expect("if there are lines, it should be valid info")); @@ -285,6 +289,30 @@ fn parse_info<'a>(mut lines: impl Iterator) -> Option { + path: &'a BStr, + id: gix_hash::ObjectId, + mode: gix_index::entry::Mode, + stage: gix_index::entry::Stage, +} + +pub fn clear_entries(state: &gix_index::State) -> Vec> { + state + .entries() + .iter() + .map(|entry| { + let path = entry.path(state); + DebugIndexEntry { + path, + id: entry.id, + mode: entry.mode, + stage: entry.stage(), + } + }) + .collect() +} + pub fn visualize_tree( id: &gix_hash::oid, odb: &impl gix_object::Find, @@ -342,3 +370,35 @@ pub fn show_diff_and_fail( expected.information ); } + +pub(crate) fn apply_git_index_entries(conflicts: &[Conflict], state: &mut gix_index::State) { + let len = state.entries().len(); + for Conflict { ours, theirs, ancestor } in conflicts { + for (entry, stage) in [ + ancestor.as_ref().map(|e| (e, gix_index::entry::Stage::Base)), + ours.as_ref().map(|e| (e, gix_index::entry::Stage::Ours)), + theirs.as_ref().map(|e| (e, gix_index::entry::Stage::Theirs)), + ] + .into_iter() + .flatten() + { + if let Some(pos) = state.entry_index_by_path_and_stage_bounded( + entry.location.as_str().into(), + gix_index::entry::Stage::Unconflicted, + len, + ) { + state.entries_mut()[pos].flags.insert(gix_index::entry::Flags::REMOVE); + } + + state.dangerously_push_entry( + Default::default(), + entry.id, + stage.into(), + entry.mode.into(), + entry.location.as_str().into(), + ); + } + } + state.sort_entries(); + state.remove_entries(|_, _, e| e.flags.contains(gix_index::entry::Flags::REMOVE)); +} diff --git a/gix-merge/tests/merge/tree/mod.rs b/gix-merge/tests/merge/tree/mod.rs index 833166bc61a..662bcc67998 100644 --- a/gix-merge/tests/merge/tree/mod.rs +++ b/gix-merge/tests/merge/tree/mod.rs @@ -1,6 +1,7 @@ use crate::tree::baseline::Deviation; use gix_diff::Rewrites; use gix_merge::commit::Options; +use gix_merge::tree::TreatAsUnresolved; use gix_object::Write; use gix_worktree::stack::state::attributes; use std::path::Path; @@ -20,7 +21,7 @@ fn run_baseline() -> crate::Result { let root = gix_testtools::scripted_fixture_read_only("tree-baseline.sh")?; let cases = std::fs::read_to_string(root.join("baseline.cases"))?; let mut actual_cases = 0; - // let new_test = Some("simple-fast-forward"); + // let new_test = Some("rename-add-symlink-A-B"); let new_test = None; for baseline::Expectation { root, @@ -98,10 +99,58 @@ fn run_baseline() -> crate::Result { ); } } + + let mut actual_index = gix_index::State::from_tree(&actual_id, &odb, Default::default())?; + let expected_index = { + let derivative_index_path = root.join(".git").join(format!("{case_name}.index")); + if derivative_index_path.exists() { + gix_index::File::at( + derivative_index_path, + odb.store().object_hash(), + true, + Default::default(), + )? + .into() + } else { + let mut index = actual_index.clone(); + if let Some(conflicts) = &merge_info.conflicts { + baseline::apply_git_index_entries(conflicts, &mut index); + } + index + } + }; + let conflicts_like_in_git = TreatAsUnresolved::Renames; + let did_change = actual.index_changed_after_applying_conflicts(&mut actual_index, conflicts_like_in_git); + actual_index.remove_entries(|_, _, e| e.flags.contains(gix_index::entry::Flags::REMOVE)); + + pretty_assertions::assert_eq!( + baseline::clear_entries(&actual_index), + baseline::clear_entries(&expected_index), + "{case_name}: index mismatch\n{:#?}\n{:#?}", + actual.conflicts, + merge_info.conflicts + ); + // if case_name.starts_with("submodule-both-modify-A-B") { + if false { + assert!( + !did_change, + "{case_name}: We can't handle submodules, so there is no index change" + ); + assert!( + actual.has_unresolved_conflicts(conflicts_like_in_git), + "{case_name}: submodules currently result in an unresolved (unknown) conflict" + ); + } else { + assert_eq!( + did_change, + actual.has_unresolved_conflicts(conflicts_like_in_git), + "{case_name}: If there is any kind of conflict, the index should have been changed" + ); + } } assert_eq!( - actual_cases, 105, + actual_cases, 107, "BUG: update this number, and don't forget to remove a filter in the end" );