Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitaly.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSami Hiltunen <shiltunen@gitlab.com>2023-09-12 15:22:52 +0300
committerSami Hiltunen <shiltunen@gitlab.com>2023-09-15 16:42:48 +0300
commit979350b60cc37f5bdde29e33f8dd02a3173335da (patch)
tree3915f7c080d83ae21660d93baa833c6070d82280
parent16cd7e7fa2bb9afdd7061ae6187f739dc2fe120f (diff)
Return specific errors for packed-refs locks
After a crash, the packed-refs file may have either 'packed-refs.lock' or 'packed-refs.new' leftover by the crashed a Git process. These files prevent further operations on the repository prior to cleaning them. Parse the errors indicating this case from the Updater's stderr and return a well-known error so the caller can handle the case.
-rw-r--r--internal/git/updateref/updateref.go15
-rw-r--r--internal/git/updateref/updateref_test.go38
2 files changed, 51 insertions, 2 deletions
diff --git a/internal/git/updateref/updateref.go b/internal/git/updateref/updateref.go
index 4290846c6..9979e7d83 100644
--- a/internal/git/updateref/updateref.go
+++ b/internal/git/updateref/updateref.go
@@ -13,8 +13,13 @@ import (
"gitlab.com/gitlab-org/gitaly/v16/internal/structerr"
)
-// errClosed is returned when accessing an updater that has already been closed.
-var errClosed = errors.New("closed")
+var (
+ // errClosed is returned when accessing an updater that has already been closed.
+ errClosed = errors.New("closed")
+ // ErrPackedRefsLocked indicates an operation failed due to the 'packed-refs' being locked. This is
+ // the case if either `packed-refs.new` or `packed-refs.lock` exists in the repository.
+ ErrPackedRefsLocked = errors.New("packed-refs locked")
+)
// MultipleUpdatesError indicates that a reference cannot have multiple updates
// within the same transaction.
@@ -475,6 +480,7 @@ func (u *Updater) write(format string, args ...interface{}) error {
}
var (
+ packedRefsLockedRegex = regexp.MustCompile(`(packed-refs\.lock': File exists\.\n)|(packed-refs\.new: File exists\n$)`)
refLockedRegex = regexp.MustCompile(`^fatal: (prepare|commit): cannot lock ref '(.+?)': Unable to create '.*': File exists.`)
refInvalidFormatRegex = regexp.MustCompile(`^fatal: invalid ref format: (.*)\n$`)
referenceAlreadyExistsRegex = regexp.MustCompile(`^fatal: .*: cannot lock ref '(.*)': reference already exists\n$`)
@@ -516,6 +522,11 @@ func (u *Updater) parseStderr() error {
return AlreadyLockedError{ReferenceName: string(matches[2])}
}
+ matches = packedRefsLockedRegex.FindSubmatch(stderr)
+ if len(matches) > 1 {
+ return ErrPackedRefsLocked
+ }
+
matches = refInvalidFormatRegex.FindSubmatch(stderr)
if len(matches) > 1 {
return InvalidReferenceFormatError{ReferenceName: string(matches[1])}
diff --git a/internal/git/updateref/updateref_test.go b/internal/git/updateref/updateref_test.go
index c661d87fe..7fdd89b87 100644
--- a/internal/git/updateref/updateref_test.go
+++ b/internal/git/updateref/updateref_test.go
@@ -720,6 +720,44 @@ func TestUpdater_capturesStderr(t *testing.T) {
require.Equal(t, expectedErr, updater.Commit())
}
+func TestUpdater_packedRefsLocked(t *testing.T) {
+ t.Parallel()
+
+ for _, tc := range []struct {
+ desc string
+ runMethod func(u *Updater) error
+ }{
+ {desc: "commit", runMethod: func(u *Updater) error { return u.Commit() }},
+ {desc: "prepare", runMethod: func(u *Updater) error { return u.Prepare() }},
+ } {
+ tc := tc
+ t.Run(tc.desc, func(t *testing.T) {
+ t.Parallel()
+ for _, lockFile := range []string{"packed-refs.lock", "packed-refs.new"} {
+ lockFile := lockFile
+ t.Run(lockFile, func(t *testing.T) {
+ t.Parallel()
+
+ ctx := testhelper.Context(t)
+
+ cfg, _, repoPath, updater := setupUpdater(t, ctx)
+ defer func() { require.Equal(t, ErrPackedRefsLocked, updater.Close()) }()
+
+ // Write a reference an pack it.
+ gittest.WriteCommit(t, cfg, repoPath, gittest.WithBranch("main"))
+ gittest.Exec(t, cfg, "-C", repoPath, "pack-refs", "--all")
+ // Write a lock file file so we can assert a lock related error is raised.
+ require.NoError(t, os.WriteFile(filepath.Join(repoPath, lockFile), nil, fs.ModePerm))
+
+ require.NoError(t, updater.Start())
+ require.NoError(t, updater.Delete("refs/heads/main"))
+ require.Equal(t, ErrPackedRefsLocked, updater.Commit())
+ })
+ }
+ })
+ }
+}
+
func BenchmarkUpdater(b *testing.B) {
ctx := testhelper.Context(b)