feat: [CODE-2385]: add fast-forward merge (#2718)

* add fast-forward merge
This commit is contained in:
Marko Gaćeša 2024-09-23 11:34:19 +00:00 committed by Harness
parent 8bcd4aa0a6
commit 7c83c5520e
6 changed files with 50 additions and 10 deletions

View File

@ -71,8 +71,10 @@ func (in *MergeInput) sanitize() error {
in.Title = strings.TrimSpace(in.Title) in.Title = strings.TrimSpace(in.Title)
in.Message = strings.TrimSpace(in.Message) in.Message = strings.TrimSpace(in.Message)
if in.Method == enum.MergeMethodRebase && (in.Title != "" || in.Message != "") { if (in.Method == enum.MergeMethodRebase || in.Method == enum.MergeMethodFastForward) &&
return usererror.BadRequest("rebase doesn't support customizing commit title and message") (in.Title != "" || in.Message != "") {
return usererror.BadRequestf(
"merge method %q doesn't support customizing commit title and message", in.Method)
} }
return nil return nil
@ -338,8 +340,8 @@ func (c *Controller) Merge(
author = identityFromPrincipalInfo(*session.Principal.ToPrincipalInfo()) author = identityFromPrincipalInfo(*session.Principal.ToPrincipalInfo())
case enum.MergeMethodSquash: case enum.MergeMethodSquash:
author = identityFromPrincipalInfo(pr.Author) author = identityFromPrincipalInfo(pr.Author)
case enum.MergeMethodRebase: case enum.MergeMethodRebase, enum.MergeMethodFastForward:
author = nil // Not important for the rebase merge: the author info in the commits will be preserved. author = nil // Not important for these merge methods: the author info in the commits will be preserved.
} }
var committer *git.Identity var committer *git.Identity
@ -349,6 +351,8 @@ func (c *Controller) Merge(
committer = identityFromPrincipalInfo(*bootstrap.NewSystemServiceSession().Principal.ToPrincipalInfo()) committer = identityFromPrincipalInfo(*bootstrap.NewSystemServiceSession().Principal.ToPrincipalInfo())
case enum.MergeMethodRebase: case enum.MergeMethodRebase:
committer = identityFromPrincipalInfo(*session.Principal.ToPrincipalInfo()) committer = identityFromPrincipalInfo(*session.Principal.ToPrincipalInfo())
case enum.MergeMethodFastForward:
committer = nil // Not important for fast-forward merge
} }
// backfill commit title if none provided // backfill commit title if none provided
@ -358,7 +362,7 @@ func (c *Controller) Merge(
in.Title = fmt.Sprintf("Merge branch '%s' of %s (#%d)", pr.SourceBranch, sourceRepo.Path, pr.Number) in.Title = fmt.Sprintf("Merge branch '%s' of %s (#%d)", pr.SourceBranch, sourceRepo.Path, pr.Number)
case enum.MergeMethodSquash: case enum.MergeMethodSquash:
in.Title = fmt.Sprintf("%s (#%d)", pr.Title, pr.Number) in.Title = fmt.Sprintf("%s (#%d)", pr.Title, pr.Number)
case enum.MergeMethodRebase: case enum.MergeMethodRebase, enum.MergeMethodFastForward:
// Not used. // Not used.
} }
} }

View File

@ -228,6 +228,7 @@ func TestBranch_MergeVerify(t *testing.T) {
}, },
expOut: MergeVerifyOutput{ expOut: MergeVerifyOutput{
AllowedMethods: []enum.MergeMethod{ AllowedMethods: []enum.MergeMethod{
enum.MergeMethodFastForward,
enum.MergeMethodMerge, enum.MergeMethodMerge,
enum.MergeMethodRebase, enum.MergeMethodRebase,
enum.MergeMethodSquash, enum.MergeMethodSquash,

View File

@ -24,17 +24,20 @@ const (
MergeMethodSquash MergeMethod = "squash" MergeMethodSquash MergeMethod = "squash"
// MergeMethodRebase rebase before merging. // MergeMethodRebase rebase before merging.
MergeMethodRebase MergeMethod = "rebase" MergeMethodRebase MergeMethod = "rebase"
// MergeMethodFastForward fast-forward merging.
MergeMethodFastForward MergeMethod = "fast-forward"
) )
var MergeMethods = []MergeMethod{ var MergeMethods = []MergeMethod{
MergeMethodMerge, MergeMethodMerge,
MergeMethodSquash, MergeMethodSquash,
MergeMethodRebase, MergeMethodRebase,
MergeMethodFastForward,
} }
func (m MergeMethod) Sanitize() (MergeMethod, bool) { func (m MergeMethod) Sanitize() (MergeMethod, bool) {
switch m { switch m {
case MergeMethodMerge, MergeMethodSquash, MergeMethodRebase: case MergeMethodMerge, MergeMethodSquash, MergeMethodRebase, MergeMethodFastForward:
return m, true return m, true
default: default:
return MergeMethodMerge, false return MergeMethodMerge, false

View File

@ -144,6 +144,8 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
mergeFunc = merge.Squash mergeFunc = merge.Squash
case enum.MergeMethodRebase: case enum.MergeMethodRebase:
mergeFunc = merge.Rebase mergeFunc = merge.Rebase
case enum.MergeMethodFastForward:
mergeFunc = merge.FastForward
default: default:
// should not happen, the call to Sanitize above should handle this case. // should not happen, the call to Sanitize above should handle this case.
panic("unsupported merge method") panic("unsupported merge method")
@ -295,6 +297,10 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
&author, &committer, &author, &committer,
mergeMsg, mergeMsg,
mergeBaseCommitSHA, baseCommitSHA, headCommitSHA) mergeBaseCommitSHA, baseCommitSHA, headCommitSHA)
if errors.IsConflict(err) {
return MergeOutput{}, fmt.Errorf("failed to merge %q to %q in %q using the %q merge method: %w",
params.HeadBranch, params.BaseBranch, params.RepoUID, mergeMethod, err)
}
if err != nil { if err != nil {
return MergeOutput{}, errors.Internal(err, "failed to merge %q to %q in %q using the %q merge method", return MergeOutput{}, errors.Internal(err, "failed to merge %q to %q in %q using the %q merge method",
params.HeadBranch, params.BaseBranch, params.RepoUID, mergeMethod) params.HeadBranch, params.BaseBranch, params.RepoUID, mergeMethod)

View File

@ -16,9 +16,9 @@ package merge
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/git/api" "github.com/harness/gitness/git/api"
"github.com/harness/gitness/git/hook" "github.com/harness/gitness/git/hook"
"github.com/harness/gitness/git/sha" "github.com/harness/gitness/git/sha"
@ -215,3 +215,27 @@ func Rebase(
return mergeSHA, conflicts, nil return mergeSHA, conflicts, nil
} }
// FastForward points the is internal implementation of merge used for Merge and Squash methods.
func FastForward(
ctx context.Context,
refUpdater *hook.RefUpdater,
repoPath, tmpDir string,
_, _ *api.Signature, // commit author and committer aren't used here
_ string, // commit message isn't used here
mergeBaseSHA, targetSHA, sourceSHA sha.SHA,
) (mergeSHA sha.SHA, conflicts []string, err error) {
if targetSHA != mergeBaseSHA {
return sha.None, nil,
errors.Conflict("Target branch has diverged from the source branch. Fast-forward not possible.")
}
err = sharedrepo.Run(ctx, refUpdater, tmpDir, repoPath, func(*sharedrepo.SharedRepo) error {
return refUpdater.InitNew(ctx, sourceSHA)
})
if err != nil {
return sha.None, nil, fmt.Errorf("merge method=fast-forward: %w", err)
}
return sourceSHA, nil, nil
}

View File

@ -219,15 +219,17 @@ type MergeMethod gitenum.MergeMethod
// MergeMethod enumeration. // MergeMethod enumeration.
const ( const (
MergeMethodMerge = MergeMethod(gitenum.MergeMethodMerge) MergeMethodMerge = MergeMethod(gitenum.MergeMethodMerge)
MergeMethodSquash = MergeMethod(gitenum.MergeMethodSquash) MergeMethodSquash = MergeMethod(gitenum.MergeMethodSquash)
MergeMethodRebase = MergeMethod(gitenum.MergeMethodRebase) MergeMethodRebase = MergeMethod(gitenum.MergeMethodRebase)
MergeMethodFastForward = MergeMethod(gitenum.MergeMethodFastForward)
) )
var MergeMethods = sortEnum([]MergeMethod{ var MergeMethods = sortEnum([]MergeMethod{
MergeMethodMerge, MergeMethodMerge,
MergeMethodSquash, MergeMethodSquash,
MergeMethodRebase, MergeMethodRebase,
MergeMethodFastForward,
}) })
func (MergeMethod) Enum() []interface{} { return toInterfaceSlice(MergeMethods) } func (MergeMethod) Enum() []interface{} { return toInterfaceSlice(MergeMethods) }