Merge branch 'eb/code-225' of _OKE5H2PQKOUfzFFDuD4FA/default/CODE/gitness (#70)

This commit is contained in:
Enver Bisevac 2023-05-15 16:21:08 +00:00 committed by Harness
commit 1b04c8f98e
5 changed files with 102 additions and 14 deletions

View File

@ -18,6 +18,7 @@ import (
"github.com/harness/gitness/gitrpc/enum" "github.com/harness/gitness/gitrpc/enum"
"github.com/harness/gitness/gitrpc/internal/tempdir" "github.com/harness/gitness/gitrpc/internal/tempdir"
"github.com/harness/gitness/gitrpc/internal/types" "github.com/harness/gitness/gitrpc/internal/types"
"github.com/rs/zerolog/log"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
) )
@ -175,7 +176,7 @@ func (g Adapter) CreateTemporaryRepoForPR(
func runMergeCommand( func runMergeCommand(
ctx context.Context, ctx context.Context,
pr *types.PullRequest, pr *types.PullRequest,
mergeMethod string, mergeMethod enum.MergeMethod,
cmd *git.Command, cmd *git.Command,
tmpBasePath string, tmpBasePath string,
env []string, env []string,
@ -254,6 +255,7 @@ func (g Adapter) Merge(
ctx context.Context, ctx context.Context,
pr *types.PullRequest, pr *types.PullRequest,
mergeMethod enum.MergeMethod, mergeMethod enum.MergeMethod,
baseBranch string,
trackingBranch string, trackingBranch string,
tmpBasePath string, tmpBasePath string,
mergeMsg string, mergeMsg string,
@ -268,13 +270,14 @@ func (g Adapter) Merge(
mergeMsg = "Merge commit" mergeMsg = "Merge commit"
} }
stagingBranch := "staging"
// TODO: sign merge commit // TODO: sign merge commit
signArg := "--no-gpg-sign" signArg := "--no-gpg-sign"
switch mergeMethod { switch mergeMethod {
case enum.MergeMethodMerge: case enum.MergeMethodMerge:
cmd := git.NewCommand(ctx, "merge", "--no-ff", "--no-commit", trackingBranch) 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) return fmt.Errorf("unable to merge tracking into base: %w", err)
} }
@ -284,7 +287,7 @@ func (g Adapter) Merge(
case enum.MergeMethodSquash: case enum.MergeMethodSquash:
// Merge with squash // Merge with squash
cmd := git.NewCommand(ctx, "merge", "--squash", trackingBranch) 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) 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()) 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: default:
return fmt.Errorf("wrong merge method provided: %s", mergeMethod) return fmt.Errorf("wrong merge method provided: %s", mergeMethod)
} }

View File

@ -46,7 +46,7 @@ type GitAdapter interface {
GetRefPath(refName string, refType enum.RefType) (string, error) GetRefPath(refName string, refType enum.RefType) (string, error)
UpdateRef(ctx context.Context, repoPath, refName string, refType enum.RefType, newValue, oldValue 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) 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 tmpBasePath string, mergeMsg string, env []string, identity *types.Identity) error
GetMergeBase(ctx context.Context, repoPath, remote, base, head string) (string, string, error) GetMergeBase(ctx context.Context, repoPath, remote, base, head string) (string, string, error)
GetDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) (string, error) GetDiffTree(ctx context.Context, repoPath, baseBranch, headBranch string) (string, error)

View File

@ -169,10 +169,19 @@ func (s MergeService) Merge(
mergeMsg += "\n\n" + strings.TrimSpace(request.Message) mergeMsg += "\n\n" + strings.TrimSpace(request.Message)
} }
if err = s.adapter.Merge(ctx, pr, enum.MergeMethodFromRPC(request.Method), trackingBranch, tmpBasePath, mergeMsg, env, &types.Identity{ if err = s.adapter.Merge(
Name: author.Name, ctx,
Email: author.Email, pr,
}); err != nil { 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") return nil, processGitErrorf(err, "merge failed")
} }

View File

@ -7,6 +7,8 @@ package types
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/harness/gitness/gitrpc/enum"
) )
var ( var (
@ -29,10 +31,11 @@ var (
// MergeConflictsError represents an error if merging fails with a conflict. // MergeConflictsError represents an error if merging fails with a conflict.
type MergeConflictsError struct { type MergeConflictsError struct {
Method string Method enum.MergeMethod
StdOut string CommitSHA string
StdErr string StdOut string
Err error StdErr string
Err error
} }
func IsMergeConflictsError(err error) bool { 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. // MergeUnrelatedHistoriesError represents an error if merging fails due to unrelated histories.
type MergeUnrelatedHistoriesError struct { type MergeUnrelatedHistoriesError struct {
Method string Method enum.MergeMethod
StdOut string StdOut string
StdErr string StdErr string
Err error Err error

View File

@ -88,7 +88,7 @@ export const PullRequestActionsBox: React.FC<PullRequestActionsBoxProps> = ({
method: 'rebase', method: 'rebase',
title: getString('pr.mergeOptions.rebaseAndMerge'), title: getString('pr.mergeOptions.rebaseAndMerge'),
desc: getString('pr.mergeOptions.rebaseAndMergeDesc'), desc: getString('pr.mergeOptions.rebaseAndMergeDesc'),
disabled: true disabled: false
}, },
{ {
method: 'close', method: 'close',