From d8a4f1aee7e8f86d5c03dabb03f2c947c05760d9 Mon Sep 17 00:00:00 2001 From: Kent Rancourt <kent.rancourt@gmail.com> Date: Wed, 11 Dec 2024 13:39:37 -0500 Subject: [PATCH] make git push detect non-fast-forwards Signed-off-by: Kent Rancourt <kent.rancourt@gmail.com> --- internal/controller/git/errors.go | 10 +++++++ internal/controller/git/errors_test.go | 37 +++++++++++++++++++++++++- internal/controller/git/work_tree.go | 11 +++++++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/internal/controller/git/errors.go b/internal/controller/git/errors.go index 440280cf21..fea260bc73 100644 --- a/internal/controller/git/errors.go +++ b/internal/controller/git/errors.go @@ -12,3 +12,13 @@ var ErrMergeConflict = errors.New("merge conflict") func IsMergeConflict(err error) bool { return errors.Is(err, ErrMergeConflict) } + +// ErrNonFastForward is returned when a push is rejected because it is not a +// fast-forward or needs to be fetched first. +var ErrNonFastForward = errors.New("non-fast-forward") + +// IsNonFastForward returns true if the error is a non-fast-forward or wraps one +// and false otherwise. +func IsNonFastForward(err error) bool { + return errors.Is(err, ErrNonFastForward) +} diff --git a/internal/controller/git/errors_test.go b/internal/controller/git/errors_test.go index 6facdba44d..f92a720929 100644 --- a/internal/controller/git/errors_test.go +++ b/internal/controller/git/errors_test.go @@ -20,7 +20,7 @@ func TestIsMergeConflict(t *testing.T) { expected: false, }, { - name: "not a a merge conflict", + name: "not a merge conflict", err: errors.New("something went wrong"), expected: false, }, @@ -42,3 +42,38 @@ func TestIsMergeConflict(t *testing.T) { }) } } + +func TestIsNonFastForward(t *testing.T) { + testCases := []struct { + name string + err error + expected bool + }{ + { + name: "nil error", + err: nil, + expected: false, + }, + { + name: "not a non-fast-forward error", + err: errors.New("something went wrong"), + expected: false, + }, + { + name: "a non-fast-forward error", + err: ErrNonFastForward, + expected: true, + }, + { + name: "a wrapped fast forward error", + err: fmt.Errorf("an error occurred: %w", ErrNonFastForward), + expected: true, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + actual := IsNonFastForward(testCase.err) + require.Equal(t, testCase.expected, actual) + }) + } +} diff --git a/internal/controller/git/work_tree.go b/internal/controller/git/work_tree.go index 79f37e5952..214691b437 100644 --- a/internal/controller/git/work_tree.go +++ b/internal/controller/git/work_tree.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "regexp" "strings" "time" @@ -516,6 +517,11 @@ type PushOptions struct { PullRebase bool } +// https://regex101.com/r/aNYjHP/1 +// +// nolint: lll +var nonFastForwardRegex = regexp.MustCompile(`(?m)^\s*!\s+\[(?:remote )?rejected].+\((?:non-fast-forward|fetch first|cannot lock ref.*)\)\s*$`) + func (w *workTree) Push(opts *PushOptions) error { if opts == nil { opts = &PushOptions{} @@ -551,7 +557,10 @@ func (w *workTree) Push(opts *PushOptions) error { if opts.Force { args = append(args, "--force") } - if _, err := libExec.Exec(w.buildGitCommand(args...)); err != nil { + if res, err := libExec.Exec(w.buildGitCommand(args...)); err != nil { + if nonFastForwardRegex.MatchString(string(res)) { + return fmt.Errorf("error pushing branch: %w", ErrNonFastForward) + } return fmt.Errorf("error pushing branch: %w", err) } return nil