mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-31 00:12:06 +00:00
git: use merged parent tree for git index
Instead of setting the index to match the tree of HEAD, we now set the index to the merged parent tree of the working copy commit. This means that if you edit a merge commit, it will make the Git index look like it would in the middle of a `git merge` operation (with all of the successfully-merged files staged in the index). If there are any 2-sided conflicts in the merged parent tree, then they will be added to the index as conflicts. Since Git doesn't support conflicts with more than 2 sides, many-sided conflicts are staged as the first side of the conflict. The following commit will improve this.
This commit is contained in:
parent
9cc8b35251
commit
42b390bbc4
4 changed files with 224 additions and 99 deletions
|
@ -26,6 +26,11 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
* The deprecated `--siblings` options for `jj split` has been removed.
|
||||
`jj split --parallel` can be used instead.
|
||||
|
||||
* In colocated repos, the Git index now contains the changes from all parents
|
||||
of the working copy instead of just the first parent (`HEAD`). 2-sided
|
||||
conflicts from the merged parents are now added to the Git index as conflicts
|
||||
as well.
|
||||
|
||||
### Deprecations
|
||||
|
||||
### New features
|
||||
|
|
|
@ -899,12 +899,15 @@ fn test_git_colocated_update_index_merge_conflict() {
|
|||
◆ 0000000000000000000000000000000000000000
|
||||
"#);
|
||||
|
||||
// The index should contain the tree of the Git HEAD. The stat for base.txt
|
||||
// should not change.
|
||||
// Conflict should be added in index with correct blob IDs. The stat for
|
||||
// base.txt should not change.
|
||||
insta::assert_snapshot!(get_index_state(&repo_path), @r#"
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 base.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Base Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Ours Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Theirs Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 left.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 right.txt
|
||||
"#);
|
||||
|
||||
test_env.jj_cmd_ok(&repo_path, &["new"]);
|
||||
|
@ -920,21 +923,14 @@ fn test_git_colocated_update_index_merge_conflict() {
|
|||
◆ 0000000000000000000000000000000000000000
|
||||
"#);
|
||||
|
||||
// The Git HEAD now contains ".jjconflict" files instead of the real contents.
|
||||
// Index should be the same after `jj new`.
|
||||
insta::assert_snapshot!(get_index_state(&repo_path), @r#"
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/base.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/conflict.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/left.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/right.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/base.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/conflict.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/left.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/right.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/base.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/conflict.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/left.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/right.txt
|
||||
Unconflicted Mode(FILE) 5dc38902e68e ctime=0:0 mtime=0:0 size=0 README
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 base.txt
|
||||
Base Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Ours Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Theirs Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 left.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 right.txt
|
||||
"#);
|
||||
}
|
||||
|
||||
|
@ -992,8 +988,8 @@ fn test_git_colocated_update_index_rebase_conflict() {
|
|||
◆ 0000000000000000000000000000000000000000
|
||||
"#);
|
||||
|
||||
// The index should contain the tree of the Git HEAD. The stat for base.txt
|
||||
// should not change.
|
||||
// Index should contain files from parent commit, so there should be no conflict
|
||||
// in conflict.txt yet. The stat for base.txt should not change.
|
||||
insta::assert_snapshot!(get_index_state(&repo_path), @r#"
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 base.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
|
@ -1010,21 +1006,15 @@ fn test_git_colocated_update_index_rebase_conflict() {
|
|||
◆ 0000000000000000000000000000000000000000
|
||||
"#);
|
||||
|
||||
// The Git HEAD now contains ".jjconflict" files instead of the real contents.
|
||||
// Now the working copy commit's parent is conflicted, so the index should have
|
||||
// a conflict with correct blob IDs.
|
||||
insta::assert_snapshot!(get_index_state(&repo_path), @r#"
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/base.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/conflict.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/left.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/right.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/base.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/conflict.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/left.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/right.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/base.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/conflict.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/left.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/right.txt
|
||||
Unconflicted Mode(FILE) 5dc38902e68e ctime=0:0 mtime=0:0 size=0 README
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 base.txt
|
||||
Base Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Ours Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Theirs Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Unconflicted Mode(FILE) 45cf141ba67d ctime=0:0 mtime=0:0 size=0 left.txt
|
||||
Unconflicted Mode(FILE) c376d892e8b1 ctime=0:0 mtime=0:0 size=0 right.txt
|
||||
"#);
|
||||
}
|
||||
|
||||
|
@ -1082,12 +1072,14 @@ fn test_git_colocated_update_index_3_sided_conflict() {
|
|||
◆ 0000000000000000000000000000000000000000
|
||||
"#);
|
||||
|
||||
// The index should contain the tree of the Git HEAD. The stat for base.txt
|
||||
// should not change.
|
||||
// We can't add conflicts with more than 2 sides to the index, so they should
|
||||
// show as unconflicted. The stat for base.txt should not change.
|
||||
insta::assert_snapshot!(get_index_state(&repo_path), @r#"
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 base.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 side-1.txt
|
||||
Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 side-2.txt
|
||||
Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 side-3.txt
|
||||
"#);
|
||||
|
||||
test_env.jj_cmd_ok(&repo_path, &["new"]);
|
||||
|
@ -1105,34 +1097,13 @@ fn test_git_colocated_update_index_3_sided_conflict() {
|
|||
◆ 0000000000000000000000000000000000000000
|
||||
"#);
|
||||
|
||||
// The Git HEAD now contains ".jjconflict" files instead of the real contents.
|
||||
// Index should be the same after `jj new`.
|
||||
insta::assert_snapshot!(get_index_state(&repo_path), @r#"
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/base.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/conflict.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/side-1.txt
|
||||
Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/side-2.txt
|
||||
Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-0/side-3.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-1/base.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-1/conflict.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-1/side-1.txt
|
||||
Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-1/side-2.txt
|
||||
Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 .jjconflict-base-1/side-3.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/base.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/conflict.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/side-1.txt
|
||||
Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/side-2.txt
|
||||
Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-0/side-3.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/base.txt
|
||||
Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/conflict.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/side-1.txt
|
||||
Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/side-2.txt
|
||||
Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-1/side-3.txt
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-2/base.txt
|
||||
Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-2/conflict.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-2/side-1.txt
|
||||
Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-2/side-2.txt
|
||||
Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 .jjconflict-side-2/side-3.txt
|
||||
Unconflicted Mode(FILE) 5dc38902e68e ctime=0:0 mtime=0:0 size=0 README
|
||||
Unconflicted Mode(FILE) df967b96a579 ctime=[nonzero] mtime=[nonzero] size=5 base.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 conflict.txt
|
||||
Unconflicted Mode(FILE) dd8f930010b3 ctime=0:0 mtime=0:0 size=0 side-1.txt
|
||||
Unconflicted Mode(FILE) 7b44e11df720 ctime=0:0 mtime=0:0 size=0 side-2.txt
|
||||
Unconflicted Mode(FILE) 42f37a71bf20 ctime=0:0 mtime=0:0 size=0 side-3.txt
|
||||
"#);
|
||||
}
|
||||
|
||||
|
|
138
lib/src/git.rs
138
lib/src/git.rs
|
@ -25,16 +25,18 @@ use std::num::NonZeroU32;
|
|||
use std::path::PathBuf;
|
||||
use std::str;
|
||||
|
||||
use bstr::BStr;
|
||||
use itertools::Itertools;
|
||||
use tempfile::NamedTempFile;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::backend::BackendError;
|
||||
use crate::backend::CommitId;
|
||||
use crate::backend::TreeId;
|
||||
use crate::backend::TreeValue;
|
||||
use crate::commit::Commit;
|
||||
use crate::git_backend::GitBackend;
|
||||
use crate::index::Index;
|
||||
use crate::merged_tree::MergedTree;
|
||||
use crate::object_id::ObjectId;
|
||||
use crate::op_store::RefTarget;
|
||||
use crate::op_store::RefTargetOptionExt;
|
||||
|
@ -44,6 +46,7 @@ use crate::refs;
|
|||
use crate::refs::BookmarkPushUpdate;
|
||||
use crate::repo::MutableRepo;
|
||||
use crate::repo::Repo;
|
||||
use crate::repo_path::RepoPath;
|
||||
use crate::revset::RevsetExpression;
|
||||
use crate::settings::GitSettings;
|
||||
use crate::store::Store;
|
||||
|
@ -1024,39 +1027,29 @@ pub fn reset_head(mut_repo: &mut MutableRepo, wc_commit: &Commit) -> Result<(),
|
|||
.map_err(GitExportError::from_git)?;
|
||||
}
|
||||
|
||||
// This is a way to find the tree ID associated with the raw Git commit, meaning
|
||||
// it contains the ".jjconflict" trees as well. This is temporary; we just want
|
||||
// to maintain the same behavior from git2.
|
||||
let parent_tree_id = if first_parent_id == mut_repo.store().root_commit_id() {
|
||||
mut_repo.store().empty_tree_id().clone()
|
||||
} else {
|
||||
TreeId::new(
|
||||
git_repo
|
||||
.find_commit(gix::ObjectId::from_bytes_or_panic(
|
||||
first_parent_id.as_bytes(),
|
||||
))
|
||||
.map_err(GitExportError::from_git)?
|
||||
.tree_id()
|
||||
.map_err(GitExportError::from_git)?
|
||||
.as_bytes()
|
||||
.to_owned(),
|
||||
)
|
||||
};
|
||||
let parent_tree = wc_commit.parent_tree(mut_repo)?;
|
||||
|
||||
let mut index = if &parent_tree_id == mut_repo.store().empty_tree_id() {
|
||||
// If the tree is empty, gix can fail to load the object (since Git doesn't
|
||||
// require the empty tree to actually be present in the object database), so we
|
||||
// just use an empty index directly.
|
||||
gix::index::File::from_state(
|
||||
gix::index::State::new(git_repo.object_hash()),
|
||||
git_repo.index_path(),
|
||||
)
|
||||
// Use the merged parent tree as the Git index, allowing `git diff` to show the
|
||||
// same changes as `jj diff`. If the merged parent tree has 2-sided conflicts,
|
||||
// then the Git index will also be conflicted.
|
||||
let mut index = if let Some(tree) = parent_tree.as_merge().as_resolved() {
|
||||
if tree.id() == mut_repo.store().empty_tree_id() {
|
||||
// If the tree is empty, gix can fail to load the object (since Git doesn't
|
||||
// require the empty tree to actually be present in the object database), so we
|
||||
// just use an empty index directly.
|
||||
gix::index::File::from_state(
|
||||
gix::index::State::new(git_repo.object_hash()),
|
||||
git_repo.index_path(),
|
||||
)
|
||||
} else {
|
||||
// If the parent tree is resolved, we can use gix's `index_from_tree` method.
|
||||
// This is more efficient than iterating over the tree and adding each entry.
|
||||
git_repo
|
||||
.index_from_tree(&gix::ObjectId::from_bytes_or_panic(tree.id().as_bytes()))
|
||||
.map_err(GitExportError::from_git)?
|
||||
}
|
||||
} else {
|
||||
git_repo
|
||||
.index_from_tree(&gix::ObjectId::from_bytes_or_panic(
|
||||
parent_tree_id.as_bytes(),
|
||||
))
|
||||
.map_err(GitExportError::from_git)?
|
||||
build_index_from_merged_tree(&git_repo, parent_tree)?
|
||||
};
|
||||
|
||||
// Match entries in the new index with entries in the old index, and copy stat
|
||||
|
@ -1083,6 +1076,87 @@ pub fn reset_head(mut_repo: &mut MutableRepo, wc_commit: &Commit) -> Result<(),
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn build_index_from_merged_tree(
|
||||
git_repo: &gix::Repository,
|
||||
merged_tree: MergedTree,
|
||||
) -> Result<gix::index::File, GitExportError> {
|
||||
let mut index = gix::index::File::from_state(
|
||||
gix::index::State::new(git_repo.object_hash()),
|
||||
git_repo.index_path(),
|
||||
);
|
||||
|
||||
let mut push_index_entry =
|
||||
|path: &RepoPath, maybe_entry: &Option<TreeValue>, stage: gix::index::entry::Stage| {
|
||||
let Some(entry) = maybe_entry else {
|
||||
return;
|
||||
};
|
||||
|
||||
let (id, mode) = match entry {
|
||||
TreeValue::File { id, executable } => {
|
||||
if *executable {
|
||||
(id.as_bytes(), gix::index::entry::Mode::FILE_EXECUTABLE)
|
||||
} else {
|
||||
(id.as_bytes(), gix::index::entry::Mode::FILE)
|
||||
}
|
||||
}
|
||||
TreeValue::Symlink(id) => (id.as_bytes(), gix::index::entry::Mode::SYMLINK),
|
||||
TreeValue::Tree(_) => {
|
||||
// This case is only possible if there is a file-directory conflict, since
|
||||
// `MergedTree::entries` handles the recursion otherwise. We only materialize a
|
||||
// file in the working copy for file-directory conflicts, so we don't add the
|
||||
// tree to the index here either.
|
||||
return;
|
||||
}
|
||||
TreeValue::GitSubmodule(id) => (id.as_bytes(), gix::index::entry::Mode::COMMIT),
|
||||
TreeValue::Conflict(_) => panic!("unexpected merged tree entry: {entry:?}"),
|
||||
};
|
||||
|
||||
let path = BStr::new(path.as_internal_file_string());
|
||||
|
||||
// It is safe to push the entry because we ensure that we only add each path to
|
||||
// a stage once, and we sort the entries after we finish adding them.
|
||||
index.dangerously_push_entry(
|
||||
gix::index::entry::Stat::default(),
|
||||
gix::ObjectId::from_bytes_or_panic(id),
|
||||
gix::index::entry::Flags::from_stage(stage),
|
||||
mode,
|
||||
path,
|
||||
);
|
||||
};
|
||||
|
||||
for (path, entry) in merged_tree.entries() {
|
||||
let entry = entry?;
|
||||
if let Some(resolved) = entry.as_resolved() {
|
||||
push_index_entry(&path, resolved, gix::index::entry::Stage::Unconflicted);
|
||||
continue;
|
||||
}
|
||||
|
||||
let conflict = entry.simplify();
|
||||
if let [left, base, right] = conflict.as_slice() {
|
||||
// 2-sided conflicts can be represented in the Git index
|
||||
push_index_entry(&path, left, gix::index::entry::Stage::Ours);
|
||||
push_index_entry(&path, base, gix::index::entry::Stage::Base);
|
||||
push_index_entry(&path, right, gix::index::entry::Stage::Theirs);
|
||||
} else {
|
||||
// We can't represent many-sided conflicts in the Git index, so just add the
|
||||
// first side as staged. This is preferable to adding the first 2 sides as a
|
||||
// conflict, since some tools rely on being able to resolve conflicts using the
|
||||
// index, which could lead to an incorrect conflict resolution if the index
|
||||
// didn't contain all of the conflict sides.
|
||||
push_index_entry(
|
||||
&path,
|
||||
conflict.first(),
|
||||
gix::index::entry::Stage::Unconflicted,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Required after `dangerously_push_entry` for correctness
|
||||
index.sort_entries();
|
||||
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GitRemoteManagementError {
|
||||
#[error("No git remote named '{0}'")]
|
||||
|
|
|
@ -2408,16 +2408,91 @@ fn test_reset_head_with_index_merge_conflict() {
|
|||
// Reset head to working copy commit with merge conflict
|
||||
git::reset_head(mut_repo, &wc_commit).unwrap();
|
||||
|
||||
// Files from left commit (HEAD) should be added to index as "Unconflicted".
|
||||
// Index should contain conflicted files from merge of parent commits.
|
||||
// `Mode(DIR | SYMLINK)` actually means `MODE(COMMIT)`, as in a git submodule.
|
||||
insta::assert_snapshot!(get_index_state(&workspace_root), @r#"
|
||||
Unconflicted some/dir/commit Mode(DIR | SYMLINK)
|
||||
Unconflicted some/dir/executable-file Mode(FILE | FILE_EXECUTABLE)
|
||||
Unconflicted some/dir/normal-file Mode(FILE)
|
||||
Unconflicted some/dir/symlink Mode(SYMLINK)
|
||||
Base some/dir/commit Mode(DIR | SYMLINK)
|
||||
Ours some/dir/commit Mode(DIR | SYMLINK)
|
||||
Theirs some/dir/commit Mode(DIR | SYMLINK)
|
||||
Base some/dir/executable-file Mode(FILE | FILE_EXECUTABLE)
|
||||
Ours some/dir/executable-file Mode(FILE | FILE_EXECUTABLE)
|
||||
Theirs some/dir/executable-file Mode(FILE | FILE_EXECUTABLE)
|
||||
Base some/dir/normal-file Mode(FILE)
|
||||
Ours some/dir/normal-file Mode(FILE)
|
||||
Theirs some/dir/normal-file Mode(FILE)
|
||||
Base some/dir/symlink Mode(SYMLINK)
|
||||
Ours some/dir/symlink Mode(SYMLINK)
|
||||
Theirs some/dir/symlink Mode(SYMLINK)
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reset_head_with_index_file_directory_conflict() {
|
||||
// Create colocated workspace
|
||||
let settings = testutils::user_settings();
|
||||
let temp_dir = testutils::new_temp_dir();
|
||||
let workspace_root = temp_dir.path().join("repo");
|
||||
gix::init(&workspace_root).unwrap();
|
||||
let (_workspace, repo) =
|
||||
Workspace::init_external_git(&settings, &workspace_root, &workspace_root.join(".git"))
|
||||
.unwrap();
|
||||
|
||||
let mut tx = repo.start_transaction();
|
||||
let mut_repo = tx.repo_mut();
|
||||
|
||||
// Build conflict trees containing file-directory conflict
|
||||
let left_tree_id = {
|
||||
let mut tree_builder =
|
||||
TreeBuilder::new(repo.store().clone(), repo.store().empty_tree_id().clone());
|
||||
testutils::write_normal_file(
|
||||
&mut tree_builder,
|
||||
RepoPath::from_internal_string("test/dir/file"),
|
||||
"dir\n",
|
||||
);
|
||||
MergedTreeId::resolved(tree_builder.write_tree().unwrap())
|
||||
};
|
||||
|
||||
let right_tree_id = {
|
||||
let mut tree_builder =
|
||||
TreeBuilder::new(repo.store().clone(), repo.store().empty_tree_id().clone());
|
||||
testutils::write_normal_file(
|
||||
&mut tree_builder,
|
||||
RepoPath::from_internal_string("test"),
|
||||
"file\n",
|
||||
);
|
||||
MergedTreeId::resolved(tree_builder.write_tree().unwrap())
|
||||
};
|
||||
|
||||
let left_commit = mut_repo
|
||||
.new_commit(
|
||||
vec![repo.store().root_commit_id().clone()],
|
||||
left_tree_id.clone(),
|
||||
)
|
||||
.write()
|
||||
.unwrap();
|
||||
let right_commit = mut_repo
|
||||
.new_commit(
|
||||
vec![repo.store().root_commit_id().clone()],
|
||||
right_tree_id.clone(),
|
||||
)
|
||||
.write()
|
||||
.unwrap();
|
||||
|
||||
let wc_commit = mut_repo
|
||||
.new_commit(
|
||||
vec![left_commit.id().clone(), right_commit.id().clone()],
|
||||
repo.store().empty_merged_tree_id().clone(),
|
||||
)
|
||||
.write()
|
||||
.unwrap();
|
||||
|
||||
// Reset head to working copy commit with file-directory conflict
|
||||
git::reset_head(mut_repo, &wc_commit).unwrap();
|
||||
|
||||
// Only the file should be added to the index (the tree should be skipped).
|
||||
insta::assert_snapshot!(get_index_state(&workspace_root), @"Theirs test Mode(FILE)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_init() {
|
||||
let settings = testutils::user_settings();
|
||||
|
|
Loading…
Reference in a new issue