add tests, remove other branches before merging, checkout conflict state

This commit is contained in:
Scott Chacon 2023-09-20 15:25:12 +02:00
parent b2833e2549
commit aff8f261e2
No known key found for this signature in database
2 changed files with 144 additions and 14 deletions

View File

@ -1427,6 +1427,111 @@ fn test_update_base_branch_no_commits_no_conflict() -> Result<()> {
Ok(())
}
#[test]
fn test_merge_vbranch_upstream() -> Result<()> {
let TestDeps {
repository,
project,
gb_repo_path,
user_store,
project_store,
..
} = new_test_deps()?;
let gb_repo =
gb_repository::Repository::open(gb_repo_path, &project.id, project_store, user_store)?;
let project_repository = project_repository::Repository::open(&project)?;
// create a commit and set the target
let file_path = std::path::Path::new("test.txt");
std::fs::write(
std::path::Path::new(&project.path).join(file_path),
"line1\nline2\nline3\nline4\n",
)?;
test_utils::commit_all(&repository);
let target_oid = repository.head().unwrap().target().unwrap();
std::fs::write(
std::path::Path::new(&project.path).join(file_path),
"line1\nline2\nline3\nline4\nupstream\n",
)?;
// add a commit to the target branch it's pointing to so there is something "upstream"
test_utils::commit_all(&repository);
let last_push = repository.head().unwrap().target().unwrap();
// coworker adds some work
std::fs::write(
std::path::Path::new(&project.path).join(file_path),
"line1\nline2\nline3\nline4\nupstream\ncoworker work\n",
)?;
test_utils::commit_all(&repository);
let coworker_work = repository.head().unwrap().target().unwrap();
//update repo ref refs/remotes/origin/master to up_target oid
repository.reference(
"refs/remotes/origin/master",
coworker_work,
true,
"update target",
)?;
// revert to our file
std::fs::write(
std::path::Path::new(&project.path).join(file_path),
"line1\nline2\nline3\nline4\nupstream\n",
)?;
set_test_target(&gb_repo, &project_repository, &repository)?;
target::Writer::new(&gb_repo).write_default(&target::Target {
branch: "refs/remotes/origin/master".parse().unwrap(),
remote_url: "origin".to_string(),
sha: target_oid,
})?;
// add some uncommitted work
let file_path2 = std::path::Path::new("test2.txt");
std::fs::write(
std::path::Path::new(&project.path).join(file_path2),
"file2\n",
)?;
let remote_branch: git::RemoteBranchName = "refs/remotes/origin/master".parse().unwrap();
let branch_writer = branch::Writer::new(&gb_repo);
let mut branch = create_virtual_branch(&gb_repo, &BranchCreateRequest::default())
.expect("failed to create virtual branch");
branch.upstream = Some(remote_branch.clone());
branch.head = last_push;
branch_writer
.write(&branch)
.context("failed to write target branch after push")?;
// create the branch
let branches = list_virtual_branches(&gb_repo, &project_repository)?;
let branch1 = &branches[0];
assert_eq!(branch1.files.len(), 1);
assert_eq!(branch1.commits.len(), 1);
assert_eq!(branch1.upstream_commits.len(), 1);
merge_virtual_branch_upstream(&gb_repo, &project_repository, &branch1.id)?;
let branches = list_virtual_branches(&gb_repo, &project_repository)?;
let branch1 = &branches[0];
let contents = std::fs::read(std::path::Path::new(&project.path).join(file_path))?;
assert_eq!(
"line1\nline2\nline3\nline4\nupstream\ncoworker work\n",
String::from_utf8(contents)?
);
let contents = std::fs::read(std::path::Path::new(&project.path).join(file_path2))?;
assert_eq!("file2\n", String::from_utf8(contents)?);
assert_eq!(branch1.files.len(), 0);
assert_eq!(branch1.commits.len(), 3);
assert_eq!(branch1.upstream_commits.len(), 0);
Ok(())
}
#[test]
fn test_update_target_with_conflicts_in_vbranches() -> Result<()> {
let TestDeps {

View File

@ -858,10 +858,21 @@ pub fn merge_virtual_branch_upstream(
return Ok(());
}
// look up the target to figure out a merge base
let target = get_default_target(&current_session_reader)
.context("failed to get target")?
.context("no target found")?;
// if any other branches are applied, unapply them
let applied_branches = Iterator::new(&current_session_reader)
.context("failed to create branch iterator")?
.collect::<Result<Vec<branch::Branch>, reader::Error>>()
.context("failed to read virtual branches")?
.into_iter()
.filter(|b| b.applied)
.filter(|b| b.id != branch_id)
.collect::<Vec<_>>();
// unapply all other branches
for other_branch in applied_branches {
unapply_branch(gb_repository, project_repository, &other_branch.id)
.context("failed to unapply branch")?;
}
// get merge base from remote branch commit and target commit
let merge_base = repo
@ -877,14 +888,29 @@ pub fn merge_virtual_branch_upstream(
.merge_trees(&merge_tree, &wd_tree, &remote_tree)
.context("failed to merge trees")?;
// three scenarios:
// - clean merge, clean wd, upstream is a fast forward, just fast forward it
// - clean merge, upstream is not a fast forward, merge it
// - upstream is not a fast forward, and cannot be merged cleanly
// - unapply all other branches, create the merge conflicts in the wd
if merge_index.has_conflicts() {
bail!("cannot merge upstream, conflicts found");
// checkout the conflicts
let mut checkout_options = git2::build::CheckoutBuilder::new();
checkout_options
.allow_conflicts(true)
.conflict_style_merge(true)
.force();
repo.checkout_index(Some(&mut merge_index), Some(&mut checkout_options))?;
// mark conflicts
let conflicts = merge_index.conflicts()?;
let mut merge_conflicts = Vec::new();
for path in conflicts.flatten() {
if let Some(ours) = path.our {
let path = std::str::from_utf8(&ours.path)?.to_string();
merge_conflicts.push(path);
}
}
conflicts::mark(
project_repository,
&merge_conflicts,
Some(upstream_commit.id()),
)?;
} else {
// get the merge tree oid from writing the index out
let merge_tree_oid = merge_index
@ -912,15 +938,14 @@ pub fn merge_virtual_branch_upstream(
repo.checkout_tree(&merge_tree, Some(&mut checkout_options))?;
// write the branch data
// TODO: update ownership?
let branch_writer = branch::Writer::new(gb_repository);
branch.head = new_branch_head;
branch.tree = merge_tree_oid;
branch_writer.write(&branch)?;
super::integration::update_gitbutler_integration(gb_repository, project_repository)?;
}
super::integration::update_gitbutler_integration(gb_repository, project_repository)?;
Ok(())
}