From 25a0f31df60e9b517ce00ba858740eb3957e0cb6 Mon Sep 17 00:00:00 2001 From: Enver Bisevac Date: Sat, 13 May 2023 10:09:52 +0200 Subject: [PATCH] rebase and merge implementation --- gitrpc/internal/gitea/merge.go | 82 ++++++++++++++++++- gitrpc/internal/service/interface.go | 2 +- gitrpc/internal/service/merge.go | 17 +++- gitrpc/internal/types/errors.go | 13 +-- .../PullRequestActionsBox.tsx | 2 +- 5 files changed, 102 insertions(+), 14 deletions(-) diff --git a/gitrpc/internal/gitea/merge.go b/gitrpc/internal/gitea/merge.go index 203b8fdf1..e37aa5b94 100644 --- a/gitrpc/internal/gitea/merge.go +++ b/gitrpc/internal/gitea/merge.go @@ -18,6 +18,7 @@ import ( "github.com/harness/gitness/gitrpc/enum" "github.com/harness/gitness/gitrpc/internal/tempdir" "github.com/harness/gitness/gitrpc/internal/types" + "github.com/rs/zerolog/log" "code.gitea.io/gitea/modules/git" ) @@ -175,7 +176,7 @@ func (g Adapter) CreateTemporaryRepoForPR( func runMergeCommand( ctx context.Context, pr *types.PullRequest, - mergeMethod string, + mergeMethod enum.MergeMethod, cmd *git.Command, tmpBasePath string, env []string, @@ -254,6 +255,7 @@ func (g Adapter) Merge( ctx context.Context, pr *types.PullRequest, mergeMethod enum.MergeMethod, + baseBranch string, trackingBranch string, tmpBasePath string, mergeMsg string, @@ -268,13 +270,14 @@ func (g Adapter) Merge( mergeMsg = "Merge commit" } + stagingBranch := "staging" // TODO: sign merge commit signArg := "--no-gpg-sign" switch mergeMethod { case enum.MergeMethodMerge: cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit", trackingBranch) - if err := runMergeCommand(ctx, pr, string(mergeMethod), cmd, tmpBasePath, env); err != nil { + if err := runMergeCommand(ctx, pr, mergeMethod, cmd, tmpBasePath, env); err != nil { return fmt.Errorf("unable to merge tracking into base: %w", err) } @@ -284,7 +287,7 @@ func (g Adapter) Merge( case enum.MergeMethodSquash: // Merge with squash cmd := git.NewCommand(ctx, "merge", "--squash", trackingBranch) - if err := runMergeCommand(ctx, pr, string(mergeMethod), cmd, tmpBasePath, env); err != nil { + if err := runMergeCommand(ctx, pr, mergeMethod, cmd, tmpBasePath, env); err != nil { return fmt.Errorf("unable to merge --squash tracking into base: %v", err) } @@ -311,6 +314,79 @@ func (g Adapter) Merge( pr.HeadBranch, pr.BaseBranch, outbuf.String(), errbuf.String()) } } + case enum.MergeMethodRebase: + // Checkout head branch + if err := git.NewCommand(ctx, "checkout", "-b", stagingBranch, trackingBranch). + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, + }); err != nil { + return fmt.Errorf("git checkout base prior to merge post staging rebase [%s -> %s]: %v\n%s\n%s", pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String()) + } + outbuf.Reset() + errbuf.Reset() + + // Rebase before merging + if err := git.NewCommand(ctx, "rebase", baseBranch). + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, + }); err != nil { + // Rebase will leave a REBASE_HEAD file in .git if there is a conflict + if _, statErr := os.Stat(filepath.Join(tmpBasePath, ".git", "REBASE_HEAD")); statErr == nil { + var commitSha string + // TBD git version we will support + // failingCommitPath := filepath.Join(tmpBasePath, ".git", "rebase-apply", "original-commit") // Git < 2.26 + // if _, statErr := os.Stat(failingCommitPath); statErr != nil { + // return fmt.Errorf("git rebase staging on to base [%s -> %s]: %v\n%s\n%s", pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String()) + // } + + failingCommitPath := filepath.Join(tmpBasePath, ".git", "rebase-merge", "stopped-sha") // Git >= 2.26 + if _, statErr := os.Stat(failingCommitPath); statErr != nil { + return fmt.Errorf("git rebase staging on to base [%s -> %s]: %v\n%s\n%s", pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String()) + } + + commitShaBytes, readErr := os.ReadFile(failingCommitPath) + if readErr != nil { + // Abandon this attempt to handle the error + return fmt.Errorf("git rebase staging on to base [%s -> %s]: %v\n%s\n%s", pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String()) + } + commitSha = strings.TrimSpace(string(commitShaBytes)) + + log.Debug().Msgf("RebaseConflict at %s [%s -> %s]: %v\n%s\n%s", commitSha, pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String()) + return &types.MergeConflictsError{ + Method: mergeMethod, + CommitSHA: commitSha, + StdOut: outbuf.String(), + StdErr: errbuf.String(), + Err: err, + } + } + return fmt.Errorf("git rebase staging on to base [%s -> %s]: %v\n%s\n%s", pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String()) + } + outbuf.Reset() + errbuf.Reset() + + // Checkout base branch again + if err := git.NewCommand(ctx, "checkout", baseBranch). + Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: &outbuf, + Stderr: &errbuf, + }); err != nil { + return fmt.Errorf("git checkout base prior to merge post staging rebase [%s -> %s]: %v\n%s\n%s", pr.HeadBranch, pr.BaseBranch, err, outbuf.String(), errbuf.String()) + } + outbuf.Reset() + errbuf.Reset() + + cmd := git.NewCommand(ctx, "merge", "--ff-only", stagingBranch) + + // Prepare merge with commit + if err := runMergeCommand(ctx, pr, mergeMethod, cmd, tmpBasePath, env); err != nil { + return err + } default: return fmt.Errorf("wrong merge method provided: %s", mergeMethod) } diff --git a/gitrpc/internal/service/interface.go b/gitrpc/internal/service/interface.go index e68993715..99d85f724 100644 --- a/gitrpc/internal/service/interface.go +++ b/gitrpc/internal/service/interface.go @@ -46,7 +46,7 @@ type GitAdapter interface { GetRefPath(refName string, refType enum.RefType) (string, error) UpdateRef(ctx context.Context, repoPath, refName string, refType enum.RefType, newValue, oldValue string) error CreateTemporaryRepoForPR(ctx context.Context, reposTempPath string, pr *types.PullRequest) (string, error) - Merge(ctx context.Context, pr *types.PullRequest, mergeMethod enum.MergeMethod, trackingBranch string, + Merge(ctx context.Context, pr *types.PullRequest, mergeMethod enum.MergeMethod, baseBranch, trackingBranch string, tmpBasePath string, mergeMsg string, env []string, identity *types.Identity) error GetMergeBase(ctx context.Context, repoPath, remote, base, head string) (string, string, error) GetDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) (string, error) diff --git a/gitrpc/internal/service/merge.go b/gitrpc/internal/service/merge.go index d66389ff2..12c1d1e21 100644 --- a/gitrpc/internal/service/merge.go +++ b/gitrpc/internal/service/merge.go @@ -169,10 +169,19 @@ func (s MergeService) Merge( mergeMsg += "\n\n" + strings.TrimSpace(request.Message) } - if err = s.adapter.Merge(ctx, pr, enum.MergeMethodFromRPC(request.Method), trackingBranch, tmpBasePath, mergeMsg, env, &types.Identity{ - Name: author.Name, - Email: author.Email, - }); err != nil { + if err = s.adapter.Merge( + ctx, + pr, + enum.MergeMethodFromRPC(request.Method), + baseBranch, + trackingBranch, + tmpBasePath, + mergeMsg, + env, + &types.Identity{ + Name: author.Name, + Email: author.Email, + }); err != nil { return nil, processGitErrorf(err, "merge failed") } diff --git a/gitrpc/internal/types/errors.go b/gitrpc/internal/types/errors.go index 93d4f10af..54d46017a 100644 --- a/gitrpc/internal/types/errors.go +++ b/gitrpc/internal/types/errors.go @@ -7,6 +7,8 @@ package types import ( "errors" "fmt" + + "github.com/harness/gitness/gitrpc/enum" ) var ( @@ -29,10 +31,11 @@ var ( // MergeConflictsError represents an error if merging fails with a conflict. type MergeConflictsError struct { - Method string - StdOut string - StdErr string - Err error + Method enum.MergeMethod + CommitSHA string + StdOut string + StdErr string + Err error } func IsMergeConflictsError(err error) bool { @@ -55,7 +58,7 @@ func (e *MergeConflictsError) Is(target error) bool { // MergeUnrelatedHistoriesError represents an error if merging fails due to unrelated histories. type MergeUnrelatedHistoriesError struct { - Method string + Method enum.MergeMethod StdOut string StdErr string Err error diff --git a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx index f47b1e419..66a71a506 100644 --- a/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx +++ b/web/src/pages/PullRequest/Conversation/PullRequestActionsBox/PullRequestActionsBox.tsx @@ -88,7 +88,7 @@ export const PullRequestActionsBox: React.FC = ({ method: 'rebase', title: getString('pr.mergeOptions.rebaseAndMerge'), desc: getString('pr.mergeOptions.rebaseAndMergeDesc'), - disabled: true + disabled: false }, { method: 'close',