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

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

View File

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

View File

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

View File

@ -144,6 +144,8 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
mergeFunc = merge.Squash
case enum.MergeMethodRebase:
mergeFunc = merge.Rebase
case enum.MergeMethodFastForward:
mergeFunc = merge.FastForward
default:
// should not happen, the call to Sanitize above should handle this case.
panic("unsupported merge method")
@ -295,6 +297,10 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
&author, &committer,
mergeMsg,
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 {
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)

View File

@ -16,9 +16,9 @@ package merge
import (
"context"
"errors"
"fmt"
"github.com/harness/gitness/errors"
"github.com/harness/gitness/git/api"
"github.com/harness/gitness/git/hook"
"github.com/harness/gitness/git/sha"
@ -215,3 +215,27 @@ func Rebase(
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.
const (
MergeMethodMerge = MergeMethod(gitenum.MergeMethodMerge)
MergeMethodSquash = MergeMethod(gitenum.MergeMethodSquash)
MergeMethodRebase = MergeMethod(gitenum.MergeMethodRebase)
MergeMethodMerge = MergeMethod(gitenum.MergeMethodMerge)
MergeMethodSquash = MergeMethod(gitenum.MergeMethodSquash)
MergeMethodRebase = MergeMethod(gitenum.MergeMethodRebase)
MergeMethodFastForward = MergeMethod(gitenum.MergeMethodFastForward)
)
var MergeMethods = sortEnum([]MergeMethod{
MergeMethodMerge,
MergeMethodSquash,
MergeMethodRebase,
MergeMethodFastForward,
})
func (MergeMethod) Enum() []interface{} { return toInterfaceSlice(MergeMethods) }