diff --git a/gitbutler-app/src/virtual_branches/tests.rs b/gitbutler-app/src/virtual_branches/tests.rs index ec9cc583e..f74880fb7 100644 --- a/gitbutler-app/src/virtual_branches/tests.rs +++ b/gitbutler-app/src/virtual_branches/tests.rs @@ -947,7 +947,7 @@ fn test_add_new_hunk_to_the_end() -> Result<()> { } #[test] -fn test_merge_vbranch_upstream_clean() -> Result<()> { +fn test_merge_vbranch_upstream_clean_rebase() -> Result<()> { let suite = Suite::default(); let Case { project_repository, @@ -1064,15 +1064,10 @@ fn test_merge_vbranch_upstream_clean() -> Result<()> { ); 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.files.len(), 1); + assert_eq!(branch1.commits.len(), 2); // assert_eq!(branch1.upstream.as_ref().unwrap().commits.len(), 0); - // make sure the last commit was signed - let last_id = &branch1.commits[0].id; - let last_commit = project_repository.git_repository.find_commit(*last_id)?; - assert!(last_commit.raw_header().unwrap().contains("SSH SIGNATURE")); - Ok(()) } diff --git a/gitbutler-app/src/virtual_branches/virtual.rs b/gitbutler-app/src/virtual_branches/virtual.rs index a798b6df9..b5cbcd869 100644 --- a/gitbutler-app/src/virtual_branches/virtual.rs +++ b/gitbutler-app/src/virtual_branches/virtual.rs @@ -5,7 +5,7 @@ use std::os::unix::prelude::*; use anyhow::{bail, Context, Result}; use diffy::{apply_bytes, Patch}; -use git2_hooks::HookResult; +use git2_hooks::{HookResult, PrepareCommitMsgSource}; use regex::Regex; use serde::Serialize; @@ -1430,20 +1430,88 @@ pub fn merge_virtual_branch_upstream( Some(upstream_commit.id()), )?; } else { - // get the merge tree oid from writing the index out let merge_tree_oid = merge_index .write_tree_to(repo) .context("failed to write tree")?; + let merge_tree = repo + .find_tree(merge_tree_oid) + .context("failed to find merge tree")?; + let branch_writer = + branch::Writer::new(gb_repository).context("failed to create writer")?; + + if *project_repository.project().ok_with_force_push { + // attempt a rebase + let (_, committer) = project_repository.git_signatures(user)?; + let mut rebase_options = git2::RebaseOptions::new(); + rebase_options.quiet(true); + rebase_options.inmemory(true); + let mut rebase = repo + .rebase( + Some(branch.head), + Some(upstream_commit.id()), + None, + Some(&mut rebase_options), + ) + .context("failed to rebase")?; + + let mut rebase_success = true; + // check to see if these commits have already been pushed + let mut last_rebase_head = upstream_commit.id(); + while rebase.next().is_some() { + let index = rebase + .inmemory_index() + .context("failed to get inmemory index")?; + if index.has_conflicts() { + rebase_success = false; + break; + } + + if let Ok(commit_id) = rebase.commit(None, &committer.clone().into(), None) { + last_rebase_head = commit_id.into(); + } else { + rebase_success = false; + break; + } + } + + if rebase_success { + // rebase worked out, rewrite the branch head + rebase.finish(None).context("failed to finish rebase")?; + + project_repository + .git_repository + .checkout_tree(&merge_tree) + .force() + .checkout() + .context("failed to checkout tree")?; + + branch.head = last_rebase_head; + branch.tree = merge_tree_oid; + branch_writer.write(&mut branch)?; + super::integration::update_gitbutler_integration( + gb_repository, + project_repository, + )?; + + return Ok(()); + } + + rebase.abort().context("failed to abort rebase")?; + } let head_commit = repo .find_commit(branch.head) .context("failed to find head commit")?; - let merge_tree = repo - .find_tree(merge_tree_oid) - .context("failed to find merge tree")?; + let new_branch_head = project_repository.commit( user, - "merged from upstream", + format!( + "Merged {}/{} into {}", + upstream_branch.remote(), + upstream_branch.branch(), + branch.name + ) + .as_str(), &merge_tree, &[&head_commit, &upstream_commit], signing_key, @@ -1456,8 +1524,6 @@ pub fn merge_virtual_branch_upstream( .context("failed to checkout tree")?; // write the branch data - let branch_writer = - branch::Writer::new(gb_repository).context("failed to create writer")?; branch.head = new_branch_head; branch.tree = merge_tree_oid; branch_writer.write(&mut branch)?;