diff --git a/services/pull/merge_prepare.go b/services/pull/merge_prepare.go index fb09515dbd..9beb28e31b 100644 --- a/services/pull/merge_prepare.go +++ b/services/pull/merge_prepare.go @@ -234,8 +234,64 @@ func getDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string, o } // rebaseTrackingOnToBase checks out the tracking branch as staging and rebases it on to the base branch -// if there is a conflict it will return a models.ErrRebaseConflicts func rebaseTrackingOnToBase(ctx *mergeContext, mergeStyle repo_model.MergeStyle) error { + // Check git version for availability of git-replay. If it is available, we use + // it for performance and to preserve unknown commit headers like the + // "change-id" header used by Jujutsu and GitButler to track changes across + // rebase, amend etc. + if err := git.CheckGitVersionAtLeast("2.44"); err == nil { + // Create staging branch + if err := git.NewCommand(ctx, "branch").AddDynamicArguments(stagingBranch, trackingBranch). + Run(ctx.RunOpts()); err != nil { + return fmt.Errorf( + "unable to git branch tracking as staging in temp repo for %v: %w\n%s\n%s", + ctx.pr, err, + ctx.outbuf.String(), + ctx.errbuf.String(), + ) + } + ctx.outbuf.Reset() + ctx.errbuf.Reset() + + // Use git-replay for performance and to preserve unknown headers, + // like the "change-id" header used by Jujutsu and GitButler. + if err := git.NewCommand(ctx, "replay", "--onto").AddDynamicArguments(baseBranch). + AddDynamicArguments(fmt.Sprintf("%s..%s", baseBranch, stagingBranch)). + Run(ctx.RunOpts()); err != nil { + return fmt.Errorf("Failed to replay commits on base branch") + } + // git-replay worked, stdout contains the instructions for update-ref + updateRefInstructions := ctx.outbuf.String() + opts := ctx.RunOpts() + opts.Stdin = strings.NewReader(updateRefInstructions) + if err := git.NewCommand(ctx, "update-ref", "--stdin").Run(opts); err != nil { + return fmt.Errorf( + "Failed to update ref for %v: %w\n%s\n%s", + ctx.pr, + err, + ctx.outbuf.String(), + ctx.errbuf.String(), + ) + } + // Checkout staging branch + if err := git.NewCommand(ctx, "checkout").AddDynamicArguments(stagingBranch). + Run(ctx.RunOpts()); err != nil { + return fmt.Errorf( + "unable to git checkout staging in temp repo for %v: %w\n%s\n%s", + ctx.pr, + err, + ctx.outbuf.String(), + ctx.errbuf.String(), + ) + } + ctx.outbuf.Reset() + ctx.errbuf.Reset() + return nil + } + + // The available git version is too old to support git-replay. + // Fall back to regular rebase. + // Checkout head branch if err := git.NewCommand(ctx, "checkout", "-b").AddDynamicArguments(stagingBranch, trackingBranch). Run(ctx.RunOpts()); err != nil {