diff --git a/app/api/controller/pullreq/merge.go b/app/api/controller/pullreq/merge.go index aef8c49ab..f2dac6911 100644 --- a/app/api/controller/pullreq/merge.go +++ b/app/api/controller/pullreq/merge.go @@ -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. } } diff --git a/app/services/protection/rule_branch_test.go b/app/services/protection/rule_branch_test.go index c228bdaea..1cd305846 100644 --- a/app/services/protection/rule_branch_test.go +++ b/app/services/protection/rule_branch_test.go @@ -228,6 +228,7 @@ func TestBranch_MergeVerify(t *testing.T) { }, expOut: MergeVerifyOutput{ AllowedMethods: []enum.MergeMethod{ + enum.MergeMethodFastForward, enum.MergeMethodMerge, enum.MergeMethodRebase, enum.MergeMethodSquash, diff --git a/git/enum/merge.go b/git/enum/merge.go index d4abaf925..c182539aa 100644 --- a/git/enum/merge.go +++ b/git/enum/merge.go @@ -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 diff --git a/git/merge.go b/git/merge.go index 63cc21896..d6daa1115 100644 --- a/git/merge.go +++ b/git/merge.go @@ -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) diff --git a/git/merge/merge.go b/git/merge/merge.go index 7afb9cd37..0749afd3d 100644 --- a/git/merge/merge.go +++ b/git/merge/merge.go @@ -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 +} diff --git a/types/enum/pullreq.go b/types/enum/pullreq.go index da32950f7..b1b1d51ae 100644 --- a/types/enum/pullreq.go +++ b/types/enum/pullreq.go @@ -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) }