mirror of https://github.com/harness/drone.git
feat: [CODE-1474]: add check PR rebaseability (#2763)
* update migration number * rename UpdateConflict->UpdateMergeOutcome * small fixes * handle mergeability for all merge methods * alway separate rebaseability check * add check PR rebaseabilitypull/3571/head
parent
4e2c9fae28
commit
2892378036
|
@ -58,15 +58,17 @@ func (in *MergeInput) sanitize() error {
|
|||
return usererror.BadRequest("source SHA must be provided")
|
||||
}
|
||||
|
||||
if in.Method != "" {
|
||||
method, ok := in.Method.Sanitize()
|
||||
if !ok {
|
||||
return usererror.BadRequestf("unsupported merge method: %s", in.Method)
|
||||
}
|
||||
|
||||
in.Method = method
|
||||
if in.Method == "" {
|
||||
in.Method = enum.MergeMethodMerge
|
||||
}
|
||||
|
||||
method, ok := in.Method.Sanitize()
|
||||
if !ok {
|
||||
return usererror.BadRequestf("unsupported merge method: %s", in.Method)
|
||||
}
|
||||
|
||||
in.Method = method
|
||||
|
||||
// cleanup title / message (NOTE: git doesn't support white space only)
|
||||
in.Title = strings.TrimSpace(in.Title)
|
||||
in.Message = strings.TrimSpace(in.Message)
|
||||
|
@ -233,17 +235,44 @@ func (c *Controller) Merge(
|
|||
// has advanced. It's possible that the merge base commit is different too.
|
||||
// So, the next time the API gets called for the same PR the mergeability status will not be unchecked.
|
||||
// Without dry-run the execution would proceed below and would either merge the PR or set the conflict status.
|
||||
if pr.MergeCheckStatus == enum.MergeCheckStatusUnchecked {
|
||||
mergeOutput, err := c.git.Merge(ctx, &git.MergeParams{
|
||||
|
||||
var mergeOutput git.MergeOutput
|
||||
|
||||
// We distinguish two types when checking mergeability: Rebase and Non-Rebase.
|
||||
// * Merge methods Merge and Squash will always have the same results.
|
||||
// * Merge method Rebase is special because it must always check all commits, one at a time.
|
||||
// * Merge method Fast-Forward can never have conflicts,
|
||||
// but for it the merge base SHA must be equal to target branch SHA.
|
||||
// The result of the tests will be stored (think cached) in the database for these two types
|
||||
// in the fields merge_check_status and rebase_check_status.
|
||||
|
||||
checkMergeability := func(method enum.MergeMethod) bool {
|
||||
switch method {
|
||||
case enum.MergeMethodMerge, enum.MergeMethodSquash:
|
||||
return pr.MergeCheckStatus == enum.MergeCheckStatusUnchecked
|
||||
case enum.MergeMethodRebase:
|
||||
return pr.RebaseCheckStatus == enum.MergeCheckStatusUnchecked
|
||||
case enum.MergeMethodFastForward:
|
||||
// Always check for ff merge. There can never be conflicts,
|
||||
// but we are interested in if it returns the conflict error and merge-output data.
|
||||
return true
|
||||
default:
|
||||
return true // should not happen
|
||||
}
|
||||
}(in.Method)
|
||||
|
||||
if checkMergeability {
|
||||
mergeOutput, err = c.git.Merge(ctx, &git.MergeParams{
|
||||
WriteParams: targetWriteParams,
|
||||
BaseBranch: pr.TargetBranch,
|
||||
HeadRepoUID: sourceRepo.GitUID,
|
||||
HeadBranch: pr.SourceBranch,
|
||||
RefType: gitenum.RefTypeUndefined, // update no refs -> no commit will be created
|
||||
HeadExpectedSHA: sha.Must(in.SourceSHA),
|
||||
Method: gitenum.MergeMethod(in.Method),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("merge check execution failed: %w", err)
|
||||
return nil, nil, fmt.Errorf("failed merge check with method=%s: %w", in.Method, err)
|
||||
}
|
||||
|
||||
pr, err = c.pullreqStore.UpdateOptLock(ctx, pr, func(pr *types.PullReq) error {
|
||||
|
@ -255,25 +284,19 @@ func (c *Controller) Merge(
|
|||
return usererror.BadRequest("Pull request must be open")
|
||||
}
|
||||
|
||||
if len(mergeOutput.ConflictFiles) > 0 {
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||
pr.MergeSHA = nil
|
||||
pr.MergeConflicts = mergeOutput.ConflictFiles
|
||||
} else {
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||
pr.MergeSHA = nil // dry-run doesn't create a merge commit so output is empty.
|
||||
pr.MergeConflicts = nil
|
||||
}
|
||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||
pr.MergeSHA = nil // dry-run doesn't create a merge commit so output is empty.
|
||||
|
||||
pr.UpdateMergeOutcome(in.Method, mergeOutput.ConflictFiles)
|
||||
|
||||
pr.Stats.DiffStats = types.NewDiffStats(
|
||||
mergeOutput.CommitCount,
|
||||
mergeOutput.ChangedFileCount,
|
||||
mergeOutput.Additions,
|
||||
mergeOutput.Deletions,
|
||||
)
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -286,6 +309,13 @@ func (c *Controller) Merge(
|
|||
}
|
||||
}
|
||||
|
||||
var conflicts []string
|
||||
if in.Method == enum.MergeMethodRebase {
|
||||
conflicts = pr.RebaseConflicts
|
||||
} else {
|
||||
conflicts = pr.MergeConflicts
|
||||
}
|
||||
|
||||
// With in.DryRun=true this function never returns types.MergeViolations
|
||||
out := &types.MergeResponse{
|
||||
BranchDeleted: ruleOut.DeleteSourceBranch,
|
||||
|
@ -293,7 +323,8 @@ func (c *Controller) Merge(
|
|||
|
||||
// values only returned by dry run
|
||||
DryRun: true,
|
||||
ConflictFiles: pr.MergeConflicts,
|
||||
Mergeable: len(conflicts) == 0,
|
||||
ConflictFiles: conflicts,
|
||||
AllowedMethods: ruleOut.AllowedMethods,
|
||||
RequiresCodeOwnersApproval: ruleOut.RequiresCodeOwnersApproval,
|
||||
RequiresCodeOwnersApprovalLatest: ruleOut.RequiresCodeOwnersApprovalLatest,
|
||||
|
@ -399,11 +430,10 @@ func (c *Controller) Merge(
|
|||
}
|
||||
|
||||
// update all Merge specific information
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||
pr.MergeSHA = nil
|
||||
pr.MergeConflicts = mergeOutput.ConflictFiles
|
||||
pr.UpdateMergeOutcome(in.Method, mergeOutput.ConflictFiles)
|
||||
pr.Stats.DiffStats = types.NewDiffStats(
|
||||
mergeOutput.CommitCount,
|
||||
mergeOutput.ChangedFileCount,
|
||||
|
@ -447,12 +477,11 @@ func (c *Controller) Merge(
|
|||
|
||||
// update all Merge specific information (might be empty if previous merge check failed)
|
||||
// since this is the final operation on the PR, we update any sha that might've changed by now.
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.SourceSHA = mergeOutput.HeadSHA.String()
|
||||
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||
pr.MergeSHA = ptr.String(mergeOutput.MergeSHA.String())
|
||||
pr.MergeConflicts = nil
|
||||
pr.MarkAsMerged()
|
||||
pr.Stats.DiffStats = types.NewDiffStats(
|
||||
mergeOutput.CommitCount,
|
||||
mergeOutput.ChangedFileCount,
|
||||
|
|
|
@ -183,29 +183,30 @@ func newPullReq(
|
|||
) *types.PullReq {
|
||||
now := time.Now().UnixMilli()
|
||||
return &types.PullReq{
|
||||
ID: 0, // the ID will be populated in the data layer
|
||||
Version: 0,
|
||||
Number: number,
|
||||
CreatedBy: session.Principal.ID,
|
||||
Created: now,
|
||||
Updated: now,
|
||||
Edited: now,
|
||||
State: enum.PullReqStateOpen,
|
||||
IsDraft: in.IsDraft,
|
||||
Title: in.Title,
|
||||
Description: in.Description,
|
||||
SourceRepoID: sourceRepo.ID,
|
||||
SourceBranch: in.SourceBranch,
|
||||
SourceSHA: sourceSHA.String(),
|
||||
TargetRepoID: targetRepo.ID,
|
||||
TargetBranch: in.TargetBranch,
|
||||
ActivitySeq: 0,
|
||||
MergedBy: nil,
|
||||
Merged: nil,
|
||||
MergeCheckStatus: enum.MergeCheckStatusUnchecked,
|
||||
MergeMethod: nil,
|
||||
MergeBaseSHA: mergeBaseSHA.String(),
|
||||
Author: *session.Principal.ToPrincipalInfo(),
|
||||
Merger: nil,
|
||||
ID: 0, // the ID will be populated in the data layer
|
||||
Version: 0,
|
||||
Number: number,
|
||||
CreatedBy: session.Principal.ID,
|
||||
Created: now,
|
||||
Updated: now,
|
||||
Edited: now,
|
||||
State: enum.PullReqStateOpen,
|
||||
IsDraft: in.IsDraft,
|
||||
Title: in.Title,
|
||||
Description: in.Description,
|
||||
SourceRepoID: sourceRepo.ID,
|
||||
SourceBranch: in.SourceBranch,
|
||||
SourceSHA: sourceSHA.String(),
|
||||
TargetRepoID: targetRepo.ID,
|
||||
TargetBranch: in.TargetBranch,
|
||||
ActivitySeq: 0,
|
||||
MergedBy: nil,
|
||||
Merged: nil,
|
||||
MergeMethod: nil,
|
||||
MergeBaseSHA: mergeBaseSHA.String(),
|
||||
MergeCheckStatus: enum.MergeCheckStatusUnchecked,
|
||||
RebaseCheckStatus: enum.MergeCheckStatusUnchecked,
|
||||
Author: *session.Principal.ToPrincipalInfo(),
|
||||
Merger: nil,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,11 +146,11 @@ func (c *Controller) State(ctx context.Context,
|
|||
nowMilli := time.Now().UnixMilli()
|
||||
|
||||
// clear all merge (check) related fields
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
|
||||
pr.MergeSHA = nil
|
||||
pr.MergeConflicts = nil
|
||||
pr.MergeTargetSHA = nil
|
||||
pr.Closed = &nowMilli
|
||||
pr.MarkAsMergeUnchecked()
|
||||
|
||||
case changeReopen:
|
||||
pr.SourceSHA = sourceSHA.String()
|
||||
pr.MergeBaseSHA = mergeBaseSHA.String()
|
||||
|
|
|
@ -277,12 +277,11 @@ func (r *repoImportState) convertPullReq(
|
|||
mergeMethod := enum.MergeMethodMerge // Don't know
|
||||
pr.MergeMethod = &mergeMethod
|
||||
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.SourceSHA = extPullReq.Head.SHA
|
||||
pr.MergeTargetSHA = &extPullReq.Base.SHA
|
||||
pr.MergeBaseSHA = extPullReq.Base.SHA
|
||||
pr.MergeSHA = nil // Don't have this.
|
||||
pr.MergeConflicts = nil
|
||||
pr.MarkAsMerged()
|
||||
|
||||
case enum.PullReqStateClosed:
|
||||
// For closed PR's it's not important to verify existence of branches and commits.
|
||||
|
@ -290,10 +289,11 @@ func (r *repoImportState) convertPullReq(
|
|||
pr.SourceSHA = extPullReq.Head.SHA
|
||||
pr.MergeTargetSHA = &extPullReq.Base.SHA
|
||||
pr.MergeBaseSHA = extPullReq.Base.SHA
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
|
||||
pr.MergeSHA = nil
|
||||
pr.MergeConflicts = nil
|
||||
pr.MergeTargetSHA = nil
|
||||
pr.MarkAsMergeUnchecked()
|
||||
|
||||
pr.Closed = &pr.Updated
|
||||
|
||||
case enum.PullReqStateOpen:
|
||||
|
@ -330,7 +330,7 @@ func (r *repoImportState) convertPullReq(
|
|||
pr.SourceSHA = sourceSHA
|
||||
pr.MergeTargetSHA = &targetSHA
|
||||
pr.MergeBaseSHA = mergeBase.MergeBaseSHA.String()
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
|
||||
pr.MarkAsMergeUnchecked()
|
||||
}
|
||||
|
||||
log.Debug().Str("pullreq.state", string(pr.State)).Msg("importing pull request")
|
||||
|
|
|
@ -122,11 +122,11 @@ func (s *Service) triggerPREventOnBranchUpdate(ctx context.Context,
|
|||
pr.MergeBaseSHA = newMergeBase.String()
|
||||
|
||||
// reset merge-check fields for new run
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
|
||||
|
||||
pr.MergeSHA = nil
|
||||
pr.MergeConflicts = nil
|
||||
pr.Stats.DiffStats.Commits = nil
|
||||
pr.Stats.DiffStats.FilesChanged = nil
|
||||
pr.MarkAsMergeUnchecked()
|
||||
|
||||
return nil
|
||||
})
|
||||
|
@ -198,9 +198,8 @@ func (s *Service) closePullReqOnBranchDelete(ctx context.Context,
|
|||
activitySeqPRClosed = pr.ActivitySeq
|
||||
|
||||
pr.State = enum.PullReqStateClosed
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
|
||||
pr.MergeSHA = nil
|
||||
pr.MergeConflicts = nil
|
||||
pr.MarkAsMergeUnchecked()
|
||||
|
||||
return nil
|
||||
})
|
||||
|
|
|
@ -221,19 +221,14 @@ func (s *Service) updateMergeData(
|
|||
return events.NewDiscardEventErrorf("PR SHA %s is newer than %s", pr.SourceSHA, newSHA)
|
||||
}
|
||||
|
||||
if len(mergeOutput.ConflictFiles) > 0 {
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||
if mergeOutput.MergeSHA.IsEmpty() {
|
||||
pr.MergeSHA = nil
|
||||
pr.MergeConflicts = mergeOutput.ConflictFiles
|
||||
} else {
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
|
||||
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
|
||||
pr.MergeSHA = ptr.String(mergeOutput.MergeSHA.String())
|
||||
pr.MergeConflicts = nil
|
||||
}
|
||||
pr.UpdateMergeOutcome(enum.MergeMethodMerge, mergeOutput.ConflictFiles)
|
||||
pr.Stats.DiffStats = types.NewDiffStats(
|
||||
mergeOutput.CommitCount,
|
||||
mergeOutput.ChangedFileCount,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE pullreqs
|
||||
DROP COLUMN pullreq_rebase_conflicts,
|
||||
DROP COLUMN pullreq_rebase_check_status;
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE pullreqs
|
||||
ADD COLUMN pullreq_rebase_check_status TEXT NOT NULL DEFAULT 'unchecked',
|
||||
ADD COLUMN pullreq_rebase_conflicts TEXT;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE pullreqs DROP COLUMN pullreq_rebase_conflicts;
|
||||
ALTER TABLE pullreqs DROP COLUMN pullreq_rebase_check_status;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE pullreqs ADD COLUMN pullreq_rebase_check_status TEXT NOT NULL DEFAULT 'unchecked';
|
||||
ALTER TABLE pullreqs ADD COLUMN pullreq_rebase_conflicts TEXT;
|
|
@ -85,11 +85,14 @@ type pullReq struct {
|
|||
Merged null.Int `db:"pullreq_merged"`
|
||||
MergeMethod null.String `db:"pullreq_merge_method"`
|
||||
|
||||
MergeCheckStatus enum.MergeCheckStatus `db:"pullreq_merge_check_status"`
|
||||
MergeTargetSHA null.String `db:"pullreq_merge_target_sha"`
|
||||
MergeBaseSHA string `db:"pullreq_merge_base_sha"`
|
||||
MergeSHA null.String `db:"pullreq_merge_sha"`
|
||||
MergeConflicts null.String `db:"pullreq_merge_conflicts"`
|
||||
MergeTargetSHA null.String `db:"pullreq_merge_target_sha"`
|
||||
MergeBaseSHA string `db:"pullreq_merge_base_sha"`
|
||||
MergeSHA null.String `db:"pullreq_merge_sha"`
|
||||
|
||||
MergeCheckStatus enum.MergeCheckStatus `db:"pullreq_merge_check_status"`
|
||||
MergeConflicts null.String `db:"pullreq_merge_conflicts"`
|
||||
RebaseCheckStatus enum.MergeCheckStatus `db:"pullreq_rebase_check_status"`
|
||||
RebaseConflicts null.String `db:"pullreq_rebase_conflicts,omitempty"`
|
||||
|
||||
CommitCount null.Int `db:"pullreq_commit_count"`
|
||||
FileCount null.Int `db:"pullreq_file_count"`
|
||||
|
@ -121,11 +124,13 @@ const (
|
|||
,pullreq_merged_by
|
||||
,pullreq_merged
|
||||
,pullreq_merge_method
|
||||
,pullreq_merge_check_status
|
||||
,pullreq_merge_target_sha
|
||||
,pullreq_merge_base_sha
|
||||
,pullreq_merge_sha
|
||||
,pullreq_merge_check_status
|
||||
,pullreq_merge_conflicts
|
||||
,pullreq_rebase_check_status
|
||||
,pullreq_rebase_conflicts
|
||||
,pullreq_commit_count
|
||||
,pullreq_file_count
|
||||
,pullreq_additions
|
||||
|
@ -218,11 +223,13 @@ func (s *PullReqStore) Create(ctx context.Context, pr *types.PullReq) error {
|
|||
,pullreq_merged_by
|
||||
,pullreq_merged
|
||||
,pullreq_merge_method
|
||||
,pullreq_merge_check_status
|
||||
,pullreq_merge_target_sha
|
||||
,pullreq_merge_base_sha
|
||||
,pullreq_merge_sha
|
||||
,pullreq_merge_check_status
|
||||
,pullreq_merge_conflicts
|
||||
,pullreq_rebase_check_status
|
||||
,pullreq_rebase_conflicts
|
||||
,pullreq_commit_count
|
||||
,pullreq_file_count
|
||||
,pullreq_additions
|
||||
|
@ -250,11 +257,13 @@ func (s *PullReqStore) Create(ctx context.Context, pr *types.PullReq) error {
|
|||
,:pullreq_merged_by
|
||||
,:pullreq_merged
|
||||
,:pullreq_merge_method
|
||||
,:pullreq_merge_check_status
|
||||
,:pullreq_merge_target_sha
|
||||
,:pullreq_merge_base_sha
|
||||
,:pullreq_merge_sha
|
||||
,:pullreq_merge_check_status
|
||||
,:pullreq_merge_conflicts
|
||||
,:pullreq_rebase_check_status
|
||||
,:pullreq_rebase_conflicts
|
||||
,:pullreq_commit_count
|
||||
,:pullreq_file_count
|
||||
,:pullreq_additions
|
||||
|
@ -295,11 +304,13 @@ func (s *PullReqStore) Update(ctx context.Context, pr *types.PullReq) error {
|
|||
,pullreq_merged_by = :pullreq_merged_by
|
||||
,pullreq_merged = :pullreq_merged
|
||||
,pullreq_merge_method = :pullreq_merge_method
|
||||
,pullreq_merge_check_status = :pullreq_merge_check_status
|
||||
,pullreq_merge_target_sha = :pullreq_merge_target_sha
|
||||
,pullreq_merge_base_sha = :pullreq_merge_base_sha
|
||||
,pullreq_merge_sha = :pullreq_merge_sha
|
||||
,pullreq_merge_check_status = :pullreq_merge_check_status
|
||||
,pullreq_merge_conflicts = :pullreq_merge_conflicts
|
||||
,pullreq_rebase_check_status = :pullreq_rebase_check_status
|
||||
,pullreq_rebase_conflicts = :pullreq_rebase_conflicts
|
||||
,pullreq_commit_count = :pullreq_commit_count
|
||||
,pullreq_file_count = :pullreq_file_count
|
||||
,pullreq_additions = :pullreq_additions
|
||||
|
@ -387,10 +398,12 @@ func (s *PullReqStore) ResetMergeCheckStatus(
|
|||
SET
|
||||
pullreq_updated = $1
|
||||
,pullreq_version = pullreq_version + 1
|
||||
,pullreq_merge_check_status = $2
|
||||
,pullreq_merge_target_sha = NULL
|
||||
,pullreq_merge_sha = NULL
|
||||
,pullreq_merge_check_status = $2
|
||||
,pullreq_merge_conflicts = NULL
|
||||
,pullreq_rebase_check_status = $2
|
||||
,pullreq_rebase_conflicts = NULL
|
||||
,pullreq_commit_count = NULL
|
||||
,pullreq_file_count = NULL
|
||||
,pullreq_additions = NULL
|
||||
|
@ -703,42 +716,47 @@ func (s *PullReqStore) applyFilter(stmt *squirrel.SelectBuilder, opts *types.Pul
|
|||
}
|
||||
|
||||
func mapPullReq(pr *pullReq) *types.PullReq {
|
||||
var mergeConflicts []string
|
||||
var mergeConflicts, rebaseConflicts []string
|
||||
if pr.MergeConflicts.Valid {
|
||||
mergeConflicts = strings.Split(pr.MergeConflicts.String, "\n")
|
||||
}
|
||||
if pr.RebaseConflicts.Valid {
|
||||
rebaseConflicts = strings.Split(pr.RebaseConflicts.String, "\n")
|
||||
}
|
||||
|
||||
return &types.PullReq{
|
||||
ID: pr.ID,
|
||||
Version: pr.Version,
|
||||
Number: pr.Number,
|
||||
CreatedBy: pr.CreatedBy,
|
||||
Created: pr.Created,
|
||||
Updated: pr.Updated,
|
||||
Edited: pr.Edited, // TODO: When we remove the DB column, make Edited equal to Updated
|
||||
Closed: pr.Closed.Ptr(),
|
||||
State: pr.State,
|
||||
IsDraft: pr.IsDraft,
|
||||
CommentCount: pr.CommentCount,
|
||||
UnresolvedCount: pr.UnresolvedCount,
|
||||
Title: pr.Title,
|
||||
Description: pr.Description,
|
||||
SourceRepoID: pr.SourceRepoID,
|
||||
SourceBranch: pr.SourceBranch,
|
||||
SourceSHA: pr.SourceSHA,
|
||||
TargetRepoID: pr.TargetRepoID,
|
||||
TargetBranch: pr.TargetBranch,
|
||||
ActivitySeq: pr.ActivitySeq,
|
||||
MergedBy: pr.MergedBy.Ptr(),
|
||||
Merged: pr.Merged.Ptr(),
|
||||
MergeMethod: (*enum.MergeMethod)(pr.MergeMethod.Ptr()),
|
||||
MergeCheckStatus: pr.MergeCheckStatus,
|
||||
MergeTargetSHA: pr.MergeTargetSHA.Ptr(),
|
||||
MergeBaseSHA: pr.MergeBaseSHA,
|
||||
MergeSHA: pr.MergeSHA.Ptr(),
|
||||
MergeConflicts: mergeConflicts,
|
||||
Author: types.PrincipalInfo{},
|
||||
Merger: nil,
|
||||
ID: pr.ID,
|
||||
Version: pr.Version,
|
||||
Number: pr.Number,
|
||||
CreatedBy: pr.CreatedBy,
|
||||
Created: pr.Created,
|
||||
Updated: pr.Updated,
|
||||
Edited: pr.Edited, // TODO: When we remove the DB column, make Edited equal to Updated
|
||||
Closed: pr.Closed.Ptr(),
|
||||
State: pr.State,
|
||||
IsDraft: pr.IsDraft,
|
||||
CommentCount: pr.CommentCount,
|
||||
UnresolvedCount: pr.UnresolvedCount,
|
||||
Title: pr.Title,
|
||||
Description: pr.Description,
|
||||
SourceRepoID: pr.SourceRepoID,
|
||||
SourceBranch: pr.SourceBranch,
|
||||
SourceSHA: pr.SourceSHA,
|
||||
TargetRepoID: pr.TargetRepoID,
|
||||
TargetBranch: pr.TargetBranch,
|
||||
ActivitySeq: pr.ActivitySeq,
|
||||
MergedBy: pr.MergedBy.Ptr(),
|
||||
Merged: pr.Merged.Ptr(),
|
||||
MergeMethod: (*enum.MergeMethod)(pr.MergeMethod.Ptr()),
|
||||
MergeCheckStatus: pr.MergeCheckStatus,
|
||||
MergeTargetSHA: pr.MergeTargetSHA.Ptr(),
|
||||
MergeBaseSHA: pr.MergeBaseSHA,
|
||||
MergeSHA: pr.MergeSHA.Ptr(),
|
||||
MergeConflicts: mergeConflicts,
|
||||
RebaseCheckStatus: pr.RebaseCheckStatus,
|
||||
RebaseConflicts: rebaseConflicts,
|
||||
Author: types.PrincipalInfo{},
|
||||
Merger: nil,
|
||||
Stats: types.PullReqStats{
|
||||
Conversations: pr.CommentCount,
|
||||
UnresolvedCount: pr.UnresolvedCount,
|
||||
|
@ -754,39 +772,42 @@ func mapPullReq(pr *pullReq) *types.PullReq {
|
|||
|
||||
func mapInternalPullReq(pr *types.PullReq) *pullReq {
|
||||
mergeConflicts := strings.Join(pr.MergeConflicts, "\n")
|
||||
rebaseConflicts := strings.Join(pr.RebaseConflicts, "\n")
|
||||
m := &pullReq{
|
||||
ID: pr.ID,
|
||||
Version: pr.Version,
|
||||
Number: pr.Number,
|
||||
CreatedBy: pr.CreatedBy,
|
||||
Created: pr.Created,
|
||||
Updated: pr.Updated,
|
||||
Edited: pr.Edited, // TODO: When we remove the DB column, make Edited equal to Updated
|
||||
Closed: null.IntFromPtr(pr.Closed),
|
||||
State: pr.State,
|
||||
IsDraft: pr.IsDraft,
|
||||
CommentCount: pr.CommentCount,
|
||||
UnresolvedCount: pr.UnresolvedCount,
|
||||
Title: pr.Title,
|
||||
Description: pr.Description,
|
||||
SourceRepoID: pr.SourceRepoID,
|
||||
SourceBranch: pr.SourceBranch,
|
||||
SourceSHA: pr.SourceSHA,
|
||||
TargetRepoID: pr.TargetRepoID,
|
||||
TargetBranch: pr.TargetBranch,
|
||||
ActivitySeq: pr.ActivitySeq,
|
||||
MergedBy: null.IntFromPtr(pr.MergedBy),
|
||||
Merged: null.IntFromPtr(pr.Merged),
|
||||
MergeMethod: null.StringFromPtr((*string)(pr.MergeMethod)),
|
||||
MergeCheckStatus: pr.MergeCheckStatus,
|
||||
MergeTargetSHA: null.StringFromPtr(pr.MergeTargetSHA),
|
||||
MergeBaseSHA: pr.MergeBaseSHA,
|
||||
MergeSHA: null.StringFromPtr(pr.MergeSHA),
|
||||
MergeConflicts: null.NewString(mergeConflicts, mergeConflicts != ""),
|
||||
CommitCount: null.IntFromPtr(pr.Stats.Commits),
|
||||
FileCount: null.IntFromPtr(pr.Stats.FilesChanged),
|
||||
Additions: null.IntFromPtr(pr.Stats.Additions),
|
||||
Deletions: null.IntFromPtr(pr.Stats.Deletions),
|
||||
ID: pr.ID,
|
||||
Version: pr.Version,
|
||||
Number: pr.Number,
|
||||
CreatedBy: pr.CreatedBy,
|
||||
Created: pr.Created,
|
||||
Updated: pr.Updated,
|
||||
Edited: pr.Edited, // TODO: When we remove the DB column, make Edited equal to Updated
|
||||
Closed: null.IntFromPtr(pr.Closed),
|
||||
State: pr.State,
|
||||
IsDraft: pr.IsDraft,
|
||||
CommentCount: pr.CommentCount,
|
||||
UnresolvedCount: pr.UnresolvedCount,
|
||||
Title: pr.Title,
|
||||
Description: pr.Description,
|
||||
SourceRepoID: pr.SourceRepoID,
|
||||
SourceBranch: pr.SourceBranch,
|
||||
SourceSHA: pr.SourceSHA,
|
||||
TargetRepoID: pr.TargetRepoID,
|
||||
TargetBranch: pr.TargetBranch,
|
||||
ActivitySeq: pr.ActivitySeq,
|
||||
MergedBy: null.IntFromPtr(pr.MergedBy),
|
||||
Merged: null.IntFromPtr(pr.Merged),
|
||||
MergeMethod: null.StringFromPtr((*string)(pr.MergeMethod)),
|
||||
MergeCheckStatus: pr.MergeCheckStatus,
|
||||
MergeTargetSHA: null.StringFromPtr(pr.MergeTargetSHA),
|
||||
MergeBaseSHA: pr.MergeBaseSHA,
|
||||
MergeSHA: null.StringFromPtr(pr.MergeSHA),
|
||||
MergeConflicts: null.NewString(mergeConflicts, mergeConflicts != ""),
|
||||
RebaseCheckStatus: pr.RebaseCheckStatus,
|
||||
RebaseConflicts: null.NewString(rebaseConflicts, rebaseConflicts != ""),
|
||||
CommitCount: null.IntFromPtr(pr.Stats.Commits),
|
||||
FileCount: null.IntFromPtr(pr.Stats.FilesChanged),
|
||||
Additions: null.IntFromPtr(pr.Stats.Additions),
|
||||
Deletions: null.IntFromPtr(pr.Stats.Deletions),
|
||||
}
|
||||
|
||||
return m
|
||||
|
|
41
git/merge.go
41
git/merge.go
|
@ -26,8 +26,6 @@ import (
|
|||
"github.com/harness/gitness/git/hook"
|
||||
"github.com/harness/gitness/git/merge"
|
||||
"github.com/harness/gitness/git/sha"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// MergeParams is input structure object for merging operation.
|
||||
|
@ -172,16 +170,6 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||
}
|
||||
}
|
||||
|
||||
// logger
|
||||
|
||||
log := log.Ctx(ctx).With().
|
||||
Str("repo_uid", params.RepoUID).
|
||||
Str("head", params.HeadBranch).
|
||||
Str("base", params.BaseBranch).
|
||||
Str("method", string(mergeMethod)).
|
||||
Str("ref", refPath).
|
||||
Logger()
|
||||
|
||||
// find the commit SHAs
|
||||
|
||||
baseCommitSHA, err := s.git.GetFullCommitID(ctx, repoPath, params.BaseBranch)
|
||||
|
@ -225,35 +213,6 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
|||
return MergeOutput{}, fmt.Errorf("failed to find commit count for merge check: %w", err)
|
||||
}
|
||||
|
||||
// handle simple merge check
|
||||
|
||||
if params.RefType == enum.RefTypeUndefined && params.Method != enum.MergeMethodRebase {
|
||||
// Merge method rebase can result in conflicts even if other methods do not.
|
||||
// This is because commits are rebased one by one. And this is why for other merge method we just return
|
||||
// list of conflicts, but for rebase we proceed with the rebasing (even if no ref would be updated).
|
||||
|
||||
_, _, conflicts, err := merge.FindConflicts(ctx, repoPath, baseCommitSHA.String(), headCommitSHA.String())
|
||||
if err != nil {
|
||||
return MergeOutput{}, errors.Internal(err,
|
||||
"Merge check failed to find conflicts between commits %s and %s",
|
||||
baseCommitSHA.String(), headCommitSHA.String())
|
||||
}
|
||||
|
||||
log.Debug().Msg("merged check completed")
|
||||
|
||||
return MergeOutput{
|
||||
BaseSHA: baseCommitSHA,
|
||||
HeadSHA: headCommitSHA,
|
||||
MergeBaseSHA: mergeBaseCommitSHA,
|
||||
MergeSHA: sha.None,
|
||||
CommitCount: commitCount,
|
||||
ChangedFileCount: shortStat.Files,
|
||||
Additions: shortStat.Additions,
|
||||
Deletions: shortStat.Deletions,
|
||||
ConflictFiles: conflicts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// author and committer
|
||||
|
||||
now := time.Now().UTC()
|
||||
|
|
|
@ -51,11 +51,14 @@ type PullReq struct {
|
|||
Merged *int64 `json:"merged"`
|
||||
MergeMethod *enum.MergeMethod `json:"merge_method"`
|
||||
|
||||
MergeCheckStatus enum.MergeCheckStatus `json:"merge_check_status"`
|
||||
MergeTargetSHA *string `json:"merge_target_sha"`
|
||||
MergeBaseSHA string `json:"merge_base_sha"`
|
||||
MergeSHA *string `json:"-"` // TODO: either remove or ensure it's being set (merge dry-run)
|
||||
MergeConflicts []string `json:"merge_conflicts,omitempty"`
|
||||
MergeTargetSHA *string `json:"merge_target_sha"`
|
||||
MergeBaseSHA string `json:"merge_base_sha"`
|
||||
MergeSHA *string `json:"-"` // TODO: either remove or ensure it's being set (merge dry-run)
|
||||
|
||||
MergeCheckStatus enum.MergeCheckStatus `json:"merge_check_status"`
|
||||
MergeConflicts []string `json:"merge_conflicts,omitempty"`
|
||||
RebaseCheckStatus enum.MergeCheckStatus `json:"rebase_check_status"`
|
||||
RebaseConflicts []string `json:"rebase_conflicts,omitempty"`
|
||||
|
||||
Author PrincipalInfo `json:"author"`
|
||||
Merger *PrincipalInfo `json:"merger"`
|
||||
|
@ -64,6 +67,53 @@ type PullReq struct {
|
|||
Labels []*LabelPullReqAssignmentInfo `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
func (pr *PullReq) UpdateMergeOutcome(method enum.MergeMethod, conflictFiles []string) {
|
||||
switch method {
|
||||
case enum.MergeMethodMerge, enum.MergeMethodSquash:
|
||||
if len(conflictFiles) > 0 {
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
|
||||
pr.MergeConflicts = conflictFiles
|
||||
} else {
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.MergeConflicts = nil
|
||||
}
|
||||
case enum.MergeMethodRebase:
|
||||
if len(conflictFiles) > 0 {
|
||||
pr.RebaseCheckStatus = enum.MergeCheckStatusConflict
|
||||
pr.RebaseConflicts = conflictFiles
|
||||
} else {
|
||||
pr.RebaseCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.RebaseConflicts = nil
|
||||
}
|
||||
case enum.MergeMethodFastForward:
|
||||
// fast-forward merge can't have conflicts
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *PullReq) MarkAsMergeUnchecked() {
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
|
||||
pr.MergeConflicts = nil
|
||||
pr.RebaseCheckStatus = enum.MergeCheckStatusUnchecked
|
||||
pr.RebaseConflicts = nil
|
||||
}
|
||||
|
||||
func (pr *PullReq) MarkAsMergeable() {
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.MergeConflicts = nil
|
||||
}
|
||||
|
||||
func (pr *PullReq) MarkAsRebaseable() {
|
||||
pr.RebaseCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.RebaseConflicts = nil
|
||||
}
|
||||
|
||||
func (pr *PullReq) MarkAsMerged() {
|
||||
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.MergeConflicts = nil
|
||||
pr.RebaseCheckStatus = enum.MergeCheckStatusMergeable
|
||||
pr.RebaseConflicts = nil
|
||||
}
|
||||
|
||||
// DiffStats shows total number of commits and modified files.
|
||||
type DiffStats struct {
|
||||
Commits *int64 `json:"commits,omitempty"`
|
||||
|
@ -201,6 +251,7 @@ type MergeResponse struct {
|
|||
|
||||
// values only returned on dryrun
|
||||
DryRun bool `json:"dry_run,omitempty"`
|
||||
Mergeable bool `json:"mergeable,omitempty"`
|
||||
ConflictFiles []string `json:"conflict_files,omitempty"`
|
||||
AllowedMethods []enum.MergeMethod `json:"allowed_methods,omitempty"`
|
||||
MinimumRequiredApprovalsCount int `json:"minimum_required_approvals_count,omitempty"`
|
||||
|
|
Loading…
Reference in New Issue