Skip to content

Commit

Permalink
Fix merge related issues (#2012)
Browse files Browse the repository at this point in the history
  • Loading branch information
johannesHarness authored and Harness committed Apr 25, 2024
1 parent 4748966 commit 86537b2
Show file tree
Hide file tree
Showing 17 changed files with 80 additions and 48 deletions.
2 changes: 1 addition & 1 deletion app/api/controller/pullreq/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func (c *Controller) Merge(
pr.MergeCheckStatus = enum.MergeCheckStatusMergeable
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
pr.MergeSHA = ptr.String(mergeOutput.MergeSHA.String())
pr.MergeSHA = nil // dry-run doesn't create a merge commit so output is empty.
pr.MergeConflicts = nil
}
pr.Stats.DiffStats = types.NewDiffStats(mergeOutput.CommitCount, mergeOutput.ChangedFileCount)
Expand Down
1 change: 1 addition & 0 deletions app/api/controller/pullreq/pr_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ func (c *Controller) State(ctx context.Context,
pr.MergeCheckStatus = enum.MergeCheckStatusUnchecked
pr.MergeSHA = nil
pr.MergeConflicts = nil
pr.MergeTargetSHA = nil
pr.Closed = &pr.Edited
case changeReopen:
pr.SourceSHA = sourceSHA
Expand Down
2 changes: 1 addition & 1 deletion app/services/pullreq/handlers_branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func (s *Service) triggerPREventOnBranchUpdate(ctx context.Context,
// and need to run mergeable check even nothing was changed on feature1, same applies to main if someone
// push new commit to main then develop should merge status should be unchecked.
if branch, err := getBranchFromRef(event.Payload.Ref); err == nil {
err = s.pullreqStore.UpdateMergeCheckStatus(ctx, event.Payload.RepoID, branch, enum.MergeCheckStatusUnchecked)
err = s.pullreqStore.ResetMergeCheckStatus(ctx, event.Payload.RepoID, branch)
if err != nil {
return err
}
Expand Down
22 changes: 7 additions & 15 deletions app/services/pullreq/handlers_mergeable.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import (

const (
cancelMergeCheckKey = "cancel_merge_check_for_sha"
nilSHA = "0000000000000000000000000000000000000000"
)

// mergeCheckOnCreated handles pull request Created events.
Expand All @@ -48,7 +47,7 @@ func (s *Service) mergeCheckOnCreated(ctx context.Context,
ctx,
event.Payload.TargetRepoID,
event.Payload.Number,
nilSHA,
sha.Nil.String(),
event.Payload.SourceSHA,
)
}
Expand Down Expand Up @@ -76,7 +75,7 @@ func (s *Service) mergeCheckOnReopen(ctx context.Context,
ctx,
event.Payload.TargetRepoID,
event.Payload.Number,
"",
sha.None.String(),
event.Payload.SourceSHA,
)
}
Expand Down Expand Up @@ -134,16 +133,6 @@ func (s *Service) updateMergeData(
return fmt.Errorf("failed to get pull request number %d: %w", prNum, err)
}

return s.updateMergeDataInner(ctx, pr, oldSHA, newSHA)
}

//nolint:funlen // refactor if required.
func (s *Service) updateMergeDataInner(
ctx context.Context,
pr *types.PullReq,
oldSHA string,
newSHA string,
) error {
// TODO: Merge check should not update the merge base.
// TODO: Instead it should accept it as an argument and fail if it doesn't match.
// Then is would not longer be necessary to cancel already active mergeability checks.
Expand Down Expand Up @@ -214,17 +203,20 @@ func (s *Service) updateMergeDataInner(
CommitterDate: &now,
})
if errors.AsStatus(err) == errors.StatusPreconditionFailed {
return events.NewDiscardEventErrorf("Source branch '%s' is not on SHA '%s' anymore.",
return events.NewDiscardEventErrorf("Source branch %q is not on SHA %q anymore.",
pr.SourceBranch, newSHA)
}
if err != nil {
return fmt.Errorf("failed to run git merge with base %q and head %q: %w", pr.TargetBranch, pr.SourceBranch, err)
}

// Update DB in both cases (failure or success)
_, err = s.pullreqStore.UpdateOptLock(ctx, pr, func(pr *types.PullReq) error {
if pr.SourceSHA != newSHA {
return events.NewDiscardEventErrorf("PR SHA %s is newer than %s", pr.SourceSHA, newSHA)
}

if mergeOutput.MergeSHA.IsEmpty() || len(mergeOutput.ConflictFiles) > 0 {
if len(mergeOutput.ConflictFiles) > 0 {
pr.MergeCheckStatus = enum.MergeCheckStatusConflict
pr.MergeBaseSHA = mergeOutput.MergeBaseSHA.String()
pr.MergeTargetSHA = ptr.String(mergeOutput.BaseSHA.String())
Expand Down
4 changes: 2 additions & 2 deletions app/services/repo/reposize.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func worker(ctx context.Context, s *SizeCalculator, wg *sync.WaitGroup, taskCh <
for sizeInfo := range taskCh {
log := log.Ctx(ctx).With().Str("repo_git_uid", sizeInfo.GitUID).Int64("repo_id", sizeInfo.ID).Logger()

log.Debug().Msgf("previous repo size: %d", sizeInfo.Size)
log.Debug().Msgf("previous repo size: %d KiB", sizeInfo.Size)

sizeOut, err := s.git.GetRepositorySize(
ctx,
Expand All @@ -113,6 +113,6 @@ func worker(ctx context.Context, s *SizeCalculator, wg *sync.WaitGroup, taskCh <
continue
}

log.Debug().Msgf("new repo size: %d", sizeOut.Size)
log.Debug().Msgf("new repo size: %d KiB", sizeOut.Size)
}
}
9 changes: 5 additions & 4 deletions app/store/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ type (
// Update the repo details.
Update(ctx context.Context, repo *types.Repository) error

// Update the repo size.
UpdateSize(ctx context.Context, id int64, repoSize int64) error
// UpdateSize updates the size of a specific repository in the database (size is in KiB).
UpdateSize(ctx context.Context, id int64, sizeInKiB int64) error

// Get the repo size.
GetSize(ctx context.Context, id int64) (int64, error)
Expand Down Expand Up @@ -341,8 +341,9 @@ type (
// It will set new values to the ActivitySeq, Version and Updated fields.
UpdateActivitySeq(ctx context.Context, pr *types.PullReq) (*types.PullReq, error)

// Update all PR where target branch points to new SHA
UpdateMergeCheckStatus(ctx context.Context, targetRepo int64, targetBranch string, status enum.MergeCheckStatus) error
// ResetMergeCheckStatus resets the pull request's mergeability status to unchecked
// for all prs with target branch pointing to targetBranch.
ResetMergeCheckStatus(ctx context.Context, targetRepo int64, targetBranch string) error

// Delete the pull request.
Delete(ctx context.Context, id int64) error
Expand Down
17 changes: 10 additions & 7 deletions app/store/database/pullreq.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ func (s *PullReqStore) Update(ctx context.Context, pr *types.PullReq) error {
,pullreq_merge_base_sha = :pullreq_merge_base_sha
,pullreq_merge_sha = :pullreq_merge_sha
,pullreq_merge_conflicts = :pullreq_merge_conflicts
,pullreq_commit_count = :pullreq_commit_count
,pullreq_commit_count = :pullreq_commit_count
,pullreq_file_count = :pullreq_file_count
WHERE pullreq_id = :pullreq_id AND pullreq_version = :pullreq_version - 1`

Expand Down Expand Up @@ -361,20 +361,23 @@ func (s *PullReqStore) UpdateActivitySeq(ctx context.Context, pr *types.PullReq)
})
}

// UpdateMergeCheckStatus updates the pull request's mergeability status
// ResetMergeCheckStatus resets the pull request's mergeability status to unchecked
// for all pr which target branch points to targetBranch.
func (s *PullReqStore) UpdateMergeCheckStatus(
func (s *PullReqStore) ResetMergeCheckStatus(
ctx context.Context,
targetRepo int64,
targetBranch string,
status enum.MergeCheckStatus,
) error {
// NOTE: keep pullreq_merge_base_sha on old value as it's a required field.
const query = `
UPDATE pullreqs
SET
pullreq_updated = $1
,pullreq_merge_check_status = $2
,pullreq_version = pullreq_version + 1
,pullreq_merge_check_status = $2
,pullreq_merge_target_sha = NULL
,pullreq_merge_sha = NULL
,pullreq_merge_conflicts = NULL
,pullreq_commit_count = NULL
,pullreq_file_count = NULL
WHERE pullreq_target_repo_id = $3 AND
Expand All @@ -385,10 +388,10 @@ func (s *PullReqStore) UpdateMergeCheckStatus(

now := time.Now().UnixMilli()

_, err := db.ExecContext(ctx, query, now, status, targetRepo, targetBranch,
_, err := db.ExecContext(ctx, query, now, enum.MergeCheckStatusUnchecked, targetRepo, targetBranch,
enum.PullReqStateClosed, enum.PullReqStateMerged)
if err != nil {
return database.ProcessSQLErrorf(ctx, err, "Failed to update mergeable status check %s in pull requests", status)
return database.ProcessSQLErrorf(ctx, err, "Failed to reset mergeable status check in pull requests")
}

return nil
Expand Down
6 changes: 3 additions & 3 deletions app/store/database/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,11 +349,11 @@ func (s *RepoStore) Update(ctx context.Context, repo *types.Repository) error {
return nil
}

// UpdateSize updates the size of a specific repository in the database.
func (s *RepoStore) UpdateSize(ctx context.Context, id int64, size int64) error {
// UpdateSize updates the size of a specific repository in the database (size is in KiB).
func (s *RepoStore) UpdateSize(ctx context.Context, id int64, sizeInKiB int64) error {
stmt := database.Builder.
Update("repositories").
Set("repo_size", size).
Set("repo_size", sizeInKiB).
Set("repo_size_updated", time.Now().UnixMilli()).
Where("repo_id = ? AND repo_deleted IS NULL", id)

Expand Down
4 changes: 4 additions & 0 deletions errors/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ func (e *Error) Unwrap() error {

// Error implements the error interface.
func (e *Error) Error() string {
if e.Err != nil {
return fmt.Sprintf("%s: %s", e.Message, e.Err)
}

return e.Message
}

Expand Down
1 change: 1 addition & 0 deletions git/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Interface interface {
GetRef(ctx context.Context, params GetRefParams) (GetRefResponse, error)
PathsDetails(ctx context.Context, params PathsDetailsParams) (PathsDetailsOutput, error)

// GetRepositorySize calculates the size of a repo in KiB.
GetRepositorySize(ctx context.Context, params *GetRepositorySizeParams) (*GetRepositorySizeOutput, error)
// UpdateRef creates, updates or deletes a git ref. If the OldValue is defined it must match the reference value
// prior to the call. To remove a ref use the zero ref as the NewValue. To require the creation of a new one and
Expand Down
4 changes: 2 additions & 2 deletions git/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,10 +293,10 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
mergeMsg,
mergeBaseCommitSHA, baseCommitSHA, headCommitSHA)
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)
}
if len(conflicts) != 0 {
if len(conflicts) > 0 {
return MergeOutput{
BaseSHA: baseCommitSHA,
HeadSHA: headCommitSHA,
Expand Down
8 changes: 6 additions & 2 deletions git/merge/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/harness/gitness/errors"
"github.com/harness/gitness/git/command"
"github.com/harness/gitness/git/sharedrepo"

"github.com/rs/zerolog/log"
)
Expand Down Expand Up @@ -72,11 +73,14 @@ func FindConflicts(
"Failed to find conflicts between %s and %s: Operation blocked. Status=%d", base, head, status)
}

treeSHA = lines[1]
if status == 1 {
return true, lines[1], nil, nil // all good, merge possible, no conflicts found
return true, treeSHA, nil, nil // all good, merge possible, no conflicts found
}

return false, lines[1], lines[2:], nil // conflict found, list of conflicted files returned
conflicts = sharedrepo.CleanupMergeConflicts(lines[2:])

return false, treeSHA, conflicts, nil // conflict found, list of conflicted files returned
}

// CommitCount returns number of commits between the two git revisions.
Expand Down
14 changes: 10 additions & 4 deletions git/merge/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package merge

import (
"context"
"errors"
"fmt"

"github.com/harness/gitness/git/api"
Expand All @@ -26,6 +27,11 @@ import (
"github.com/rs/zerolog/log"
)

var (
// errConflict is used to error out of sharedrepo Run method without erroring out of merge in case of conflicts.
errConflict = errors.New("conflict")
)

// Func represents a merge method function. The concrete merge implementation functions must have this signature.
type Func func(
ctx context.Context,
Expand Down Expand Up @@ -93,7 +99,7 @@ func mergeInternal(
}

if len(conflicts) > 0 {
return nil
return errConflict
}

parents := make([]sha.SHA, 0, 2)
Expand All @@ -113,7 +119,7 @@ func mergeInternal(

return nil
})
if err != nil {
if err != nil && !errors.Is(err, errConflict) {
return sha.None, nil, fmt.Errorf("merge method=merge squash=%t: %w", squash, err)
}

Expand Down Expand Up @@ -173,7 +179,7 @@ func Rebase(
return fmt.Errorf("failed to merge tree in rebase merge: %w", err)
}
if len(conflicts) > 0 {
return nil
return errConflict
}

// Drop any commit which after being rebased would be empty.
Expand Down Expand Up @@ -203,7 +209,7 @@ func Rebase(

return nil
})
if err != nil {
if err != nil && !errors.Is(err, errConflict) {
return sha.None, nil, fmt.Errorf("merge method=rebase: %w", err)
}

Expand Down
1 change: 1 addition & 0 deletions git/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ type GetRepositorySizeParams struct {
}

type GetRepositorySizeOutput struct {
// Total size of the repository in KiB.
Size int64
}

Expand Down
17 changes: 16 additions & 1 deletion git/sharedrepo/sharedrepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,12 +337,27 @@ func (r *SharedRepo) MergeTree(
log.Ctx(ctx).Err(err).Str("output", output).Msg("unexpected output of merge-tree in shared repo")
return sha.None, nil, fmt.Errorf("unexpected output of merge-tree in shared repo: %w", err)
}
return sha.Must(lines[0]), lines[1:], nil

treeSHA := sha.Must(lines[0])
conflicts := CleanupMergeConflicts(lines[1:])

return treeSHA, conflicts, nil
}

return sha.None, nil, fmt.Errorf("failed to merge-tree in shared repo: %w", err)
}

func CleanupMergeConflicts(conflicts []string) []string {
out := make([]string, 0, len(conflicts))
for _, conflict := range conflicts {
conflict = strings.TrimSpace(conflict)
if conflict != "" {
out = append(out, conflict)
}
}
return out
}

// CommitTree creates a commit from a given tree for the user with provided message.
func (r *SharedRepo) CommitTree(
ctx context.Context,
Expand Down
2 changes: 1 addition & 1 deletion types/pullreq.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ type PullReq struct {
MergeCheckStatus enum.MergeCheckStatus `json:"merge_check_status"`
MergeTargetSHA *string `json:"merge_target_sha"`
MergeBaseSHA string `json:"merge_base_sha"`
MergeSHA *string `json:"merge_sha"`
MergeSHA *string `json:"-"` // TODO: either remove or ensure it's being set (merge dry-run)
MergeConflicts []string `json:"merge_conflicts,omitempty"`

Author PrincipalInfo `json:"author"`
Expand Down
14 changes: 9 additions & 5 deletions types/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ type Repository struct {
Updated int64 `json:"updated" yaml:"updated"`
Deleted *int64 `json:"deleted,omitempty" yaml:"deleted"`

Size int64 `json:"size" yaml:"size"`
// Size of the repository in KiB.
Size int64 `json:"size" yaml:"size"`
// SizeUpdated is the time when the Size was last updated.
SizeUpdated int64 `json:"size_updated" yaml:"size_updated"`

GitUID string `json:"-" yaml:"-"`
Expand Down Expand Up @@ -81,10 +83,12 @@ func (r Repository) MarshalJSON() ([]byte, error) {
}

type RepositorySizeInfo struct {
ID int64 `json:"id"`
GitUID string `json:"git_uid"`
Size int64 `json:"size"`
SizeUpdated int64 `json:"size_updated"`
ID int64 `json:"id"`
GitUID string `json:"git_uid"`
// Size of the repository in KiB.
Size int64 `json:"size"`
// SizeUpdated is the time when the Size was last updated.
SizeUpdated int64 `json:"size_updated"`
}

func (r Repository) GetGitUID() string {
Expand Down

0 comments on commit 86537b2

Please sign in to comment.