From aff8f261e2d3a176a15fb31fe76f1a79ec451b6c Mon Sep 17 00:00:00 2001 From: Scott Chacon Date: Wed, 20 Sep 2023 15:25:12 +0200 Subject: [PATCH] add tests, remove other branches before merging, checkout conflict state --- packages/tauri/src/virtual_branches/tests.rs | 105 ++++++++++++++++++ .../tauri/src/virtual_branches/virtual.rs | 53 ++++++--- 2 files changed, 144 insertions(+), 14 deletions(-) diff --git a/packages/tauri/src/virtual_branches/tests.rs b/packages/tauri/src/virtual_branches/tests.rs index 2661d36ff..65330c7eb 100644 --- a/packages/tauri/src/virtual_branches/tests.rs +++ b/packages/tauri/src/virtual_branches/tests.rs @@ -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 { diff --git a/packages/tauri/src/virtual_branches/virtual.rs b/packages/tauri/src/virtual_branches/virtual.rs index dec50760a..21ba025cd 100644 --- a/packages/tauri/src/virtual_branches/virtual.rs +++ b/packages/tauri/src/virtual_branches/virtual.rs @@ -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(¤t_session_reader) - .context("failed to get target")? - .context("no target found")?; + // if any other branches are applied, unapply them + let applied_branches = Iterator::new(¤t_session_reader) + .context("failed to create branch iterator")? + .collect::, reader::Error>>() + .context("failed to read virtual branches")? + .into_iter() + .filter(|b| b.applied) + .filter(|b| b.id != branch_id) + .collect::>(); + + // 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(()) }