mirror of https://github.com/harness/drone.git
feat: unrealted histories error handling (#2642)
* comments added * return proper status and message for unrelated history * code cleanup * requested changes * feat: unrealted histories error handlingCODE-2402
parent
88485ade75
commit
61c794da0b
|
@ -21,6 +21,7 @@ import (
|
||||||
"github.com/harness/gitness/app/api/controller"
|
"github.com/harness/gitness/app/api/controller"
|
||||||
"github.com/harness/gitness/app/auth"
|
"github.com/harness/gitness/app/auth"
|
||||||
"github.com/harness/gitness/git"
|
"github.com/harness/gitness/git"
|
||||||
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/types/enum"
|
"github.com/harness/gitness/types/enum"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,6 +58,14 @@ func (c *Controller) MergeCheck(
|
||||||
HeadBranch: info.HeadRef,
|
HeadBranch: info.HeadRef,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// git.Merge works with commits and error is not user-friendly
|
||||||
|
// and here we are modify base-ref and head-ref with user input
|
||||||
|
// values.
|
||||||
|
if uErr := api.AsUnrelatedHistoriesError(err); uErr != nil {
|
||||||
|
uErr.BaseRef = info.BaseRef
|
||||||
|
uErr.HeadRef = info.HeadRef
|
||||||
|
return MergeCheck{}, uErr
|
||||||
|
}
|
||||||
return MergeCheck{}, fmt.Errorf("merge check execution failed: %w", err)
|
return MergeCheck{}, fmt.Errorf("merge check execution failed: %w", err)
|
||||||
}
|
}
|
||||||
if len(mergeOutput.ConflictFiles) > 0 {
|
if len(mergeOutput.ConflictFiles) > 0 {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/harness/gitness/app/api/controller/repo"
|
"github.com/harness/gitness/app/api/controller/repo"
|
||||||
"github.com/harness/gitness/app/api/render"
|
"github.com/harness/gitness/app/api/render"
|
||||||
"github.com/harness/gitness/app/api/request"
|
"github.com/harness/gitness/app/api/request"
|
||||||
|
"github.com/harness/gitness/app/api/usererror"
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
gittypes "github.com/harness/gitness/git/api"
|
gittypes "github.com/harness/gitness/git/api"
|
||||||
)
|
)
|
||||||
|
@ -110,6 +111,13 @@ func HandleDiffStats(repoCtrl *repo.Controller) http.HandlerFunc {
|
||||||
path := request.GetOptionalRemainderFromPath(r)
|
path := request.GetOptionalRemainderFromPath(r)
|
||||||
|
|
||||||
output, err := repoCtrl.DiffStats(ctx, session, repoRef, path)
|
output, err := repoCtrl.DiffStats(ctx, session, repoRef, path)
|
||||||
|
if uErr := gittypes.AsUnrelatedHistoriesError(err); uErr != nil {
|
||||||
|
render.JSON(w, http.StatusOK, &usererror.Error{
|
||||||
|
Message: uErr.Error(),
|
||||||
|
Values: uErr.Map(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
render.TranslatedUserError(ctx, w, err)
|
render.TranslatedUserError(ctx, w, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -22,7 +22,9 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/harness/gitness/app/api/usererror"
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/types"
|
"github.com/harness/gitness/types"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
@ -75,6 +77,15 @@ func JSONArrayDynamic[T comparable](ctx context.Context, w http.ResponseWriter,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// based on discussion unrelated histories error should return
|
||||||
|
// StatusOK on diff.
|
||||||
|
if uErr := api.AsUnrelatedHistoriesError(err); uErr != nil {
|
||||||
|
JSON(w, http.StatusOK, &usererror.Error{
|
||||||
|
Message: uErr.Error(),
|
||||||
|
Values: uErr.Map(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
// User canceled the request - no need to do anything
|
// User canceled the request - no need to do anything
|
||||||
if errors.Is(err, context.Canceled) {
|
if errors.Is(err, context.Canceled) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/harness/gitness/app/services/webhook"
|
"github.com/harness/gitness/app/services/webhook"
|
||||||
"github.com/harness/gitness/blob"
|
"github.com/harness/gitness/blob"
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
|
"github.com/harness/gitness/git/api"
|
||||||
"github.com/harness/gitness/lock"
|
"github.com/harness/gitness/lock"
|
||||||
"github.com/harness/gitness/store"
|
"github.com/harness/gitness/store"
|
||||||
"github.com/harness/gitness/types/check"
|
"github.com/harness/gitness/types/check"
|
||||||
|
@ -37,6 +38,7 @@ func Translate(ctx context.Context, err error) *Error {
|
||||||
rError *Error
|
rError *Error
|
||||||
checkError *check.ValidationError
|
checkError *check.ValidationError
|
||||||
appError *errors.Error
|
appError *errors.Error
|
||||||
|
unrelatedHistoriesErr *api.UnrelatedHistoriesError
|
||||||
maxBytesErr *http.MaxBytesError
|
maxBytesErr *http.MaxBytesError
|
||||||
codeOwnersTooLargeError *codeowners.TooLargeError
|
codeOwnersTooLargeError *codeowners.TooLargeError
|
||||||
codeOwnersFileParseError *codeowners.FileParseError
|
codeOwnersFileParseError *codeowners.FileParseError
|
||||||
|
@ -96,6 +98,12 @@ func Translate(ctx context.Context, err error) *Error {
|
||||||
appError.Message,
|
appError.Message,
|
||||||
appError.Details,
|
appError.Details,
|
||||||
)
|
)
|
||||||
|
case errors.As(err, &unrelatedHistoriesErr):
|
||||||
|
return NewWithPayload(
|
||||||
|
http.StatusBadRequest,
|
||||||
|
err.Error(),
|
||||||
|
unrelatedHistoriesErr.Map(),
|
||||||
|
)
|
||||||
|
|
||||||
// webhook errors
|
// webhook errors
|
||||||
case errors.Is(err, webhook.ErrWebhookNotRetriggerable):
|
case errors.Is(err, webhook.ErrWebhookNotRetriggerable):
|
||||||
|
|
|
@ -19,7 +19,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/harness/gitness/errors"
|
"github.com/harness/gitness/errors"
|
||||||
"github.com/harness/gitness/git/enum"
|
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -133,33 +132,64 @@ func processGitErrorf(err error, format string, args ...interface{}) error {
|
||||||
return errors.NotFound("repository not found")
|
return errors.NotFound("repository not found")
|
||||||
case strings.Contains(err.Error(), "reference already exists"):
|
case strings.Contains(err.Error(), "reference already exists"):
|
||||||
return errors.Conflict("reference already exists")
|
return errors.Conflict("reference already exists")
|
||||||
|
case strings.Contains(err.Error(), "no merge base"):
|
||||||
|
if len(args) >= 2 {
|
||||||
|
return &UnrelatedHistoriesError{
|
||||||
|
BaseRef: strings.TrimSpace(args[0].(string)),
|
||||||
|
HeadRef: strings.TrimSpace(args[1].(string)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &UnrelatedHistoriesError{}
|
||||||
default:
|
default:
|
||||||
return fallbackErr
|
return fallbackErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergeUnrelatedHistoriesError represents an error if merging fails due to unrelated histories.
|
type UnrelatedHistoriesError struct {
|
||||||
type MergeUnrelatedHistoriesError struct {
|
BaseRef string
|
||||||
Method enum.MergeMethod
|
HeadRef string
|
||||||
StdOut string
|
|
||||||
StdErr string
|
|
||||||
Err error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsMergeUnrelatedHistoriesError(err error) bool {
|
func (e *UnrelatedHistoriesError) Map() map[string]any {
|
||||||
return errors.Is(err, &MergeUnrelatedHistoriesError{})
|
return map[string]any{
|
||||||
|
"base_ref": e.BaseRef,
|
||||||
|
"head_ref": e.HeadRef,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *MergeUnrelatedHistoriesError) Error() string {
|
func (e *UnrelatedHistoriesError) Is(err error) bool {
|
||||||
return fmt.Sprintf("Merge UnrelatedHistories Error: %v: %s\n%s", e.Err, e.StdErr, e.StdOut)
|
var target *UnrelatedHistoriesError
|
||||||
|
ok := errors.As(err, &target)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return target.BaseRef == e.BaseRef && target.HeadRef == e.HeadRef
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *MergeUnrelatedHistoriesError) Unwrap() error {
|
func (e *UnrelatedHistoriesError) Error() string {
|
||||||
return e.Err
|
if e.BaseRef == "" || e.HeadRef == "" {
|
||||||
|
return "unrelated commit histories error"
|
||||||
|
}
|
||||||
|
// remove branch and tag prefixes, original remains in struct fields
|
||||||
|
// we just need to remove first occurrence.
|
||||||
|
baseRef := strings.TrimPrefix(e.BaseRef, BranchPrefix)
|
||||||
|
baseRef = strings.TrimPrefix(baseRef, TagPrefix)
|
||||||
|
headRef := strings.TrimPrefix(e.HeadRef, BranchPrefix)
|
||||||
|
headRef = strings.TrimPrefix(headRef, TagPrefix)
|
||||||
|
return fmt.Sprintf("%s and %s have entirely different commit histories.", baseRef, headRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint:errorlint // the purpose of this method is to check whether the target itself if of this type.
|
// IsUnrelatedHistoriesError checks if an error is a UnrelatedHistoriesError.
|
||||||
func (e *MergeUnrelatedHistoriesError) Is(target error) bool {
|
func IsUnrelatedHistoriesError(err error) bool {
|
||||||
_, ok := target.(*MergeUnrelatedHistoriesError)
|
return AsUnrelatedHistoriesError(err) != nil
|
||||||
return ok
|
}
|
||||||
|
|
||||||
|
func AsUnrelatedHistoriesError(err error) *UnrelatedHistoriesError {
|
||||||
|
var target *UnrelatedHistoriesError
|
||||||
|
ok := errors.As(err, &target)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return target
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,15 +56,26 @@ func (g *Git) GetMergeBase(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stdout := &bytes.Buffer{}
|
|
||||||
cmd := command.New("merge-base",
|
cmd := command.New("merge-base",
|
||||||
command.WithArg(base, head),
|
command.WithArg(base, head),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
stdout, stderr := new(bytes.Buffer), new(bytes.Buffer)
|
||||||
err := cmd.Run(ctx,
|
err := cmd.Run(ctx,
|
||||||
command.WithDir(repoPath),
|
command.WithDir(repoPath),
|
||||||
command.WithStdout(stdout),
|
command.WithStdout(stdout),
|
||||||
|
command.WithStderr(stderr),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// git merge-base rev1 rev2
|
||||||
|
// if there is unrelated history then stderr is empty with
|
||||||
|
// exit code 1. This cannot be handled in processGitErrorf because stderr is empty.
|
||||||
|
if command.AsError(err).IsExitCode(1) && stderr.Len() == 0 {
|
||||||
|
return sha.None, "", &UnrelatedHistoriesError{
|
||||||
|
BaseRef: base,
|
||||||
|
HeadRef: head,
|
||||||
|
}
|
||||||
|
}
|
||||||
return sha.None, "", processGitErrorf(err, "failed to get merge-base [%s, %s]", base, head)
|
return sha.None, "", processGitErrorf(err, "failed to get merge-base [%s, %s]", base, head)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -184,12 +184,12 @@ func (s *Service) Merge(ctx context.Context, params *MergeParams) (MergeOutput,
|
||||||
|
|
||||||
baseCommitSHA, err := s.git.GetFullCommitID(ctx, repoPath, params.BaseBranch)
|
baseCommitSHA, err := s.git.GetFullCommitID(ctx, repoPath, params.BaseBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MergeOutput{}, fmt.Errorf("failed to get merge base branch commit SHA: %w", err)
|
return MergeOutput{}, fmt.Errorf("failed to get base branch commit SHA: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headCommitSHA, err := s.git.GetFullCommitID(ctx, repoPath, params.HeadBranch)
|
headCommitSHA, err := s.git.GetFullCommitID(ctx, repoPath, params.HeadBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return MergeOutput{}, fmt.Errorf("failed to get merge base branch commit SHA: %w", err)
|
return MergeOutput{}, fmt.Errorf("failed to get head branch commit SHA: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !params.HeadExpectedSHA.IsEmpty() && !params.HeadExpectedSHA.Equal(headCommitSHA) {
|
if !params.HeadExpectedSHA.IsEmpty() && !params.HeadExpectedSHA.Equal(headCommitSHA) {
|
||||||
|
|
Loading…
Reference in New Issue