From ae22b20d0761b655ae3beaba2261329f935ab00a Mon Sep 17 00:00:00 2001 From: Sam Dowell Date: Mon, 5 Aug 2024 10:54:27 -0700 Subject: [PATCH] fix: recover when there is unreleased lock file If a previous git invocation crashes, it is possible that an orphaned lock file (e.g. shallow.lock) is left on the filesystem. This previously caused git-sync to crash loop because the lock file is never deleted. This change adds a check in sanityCheckRepo for the existence of a git lock file. If the git lock file exists at this stage, then initRepo will re-initialize the repository. --- main.go | 24 ++++++++++++++++++++++++ main_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/main.go b/main.go index 94280d8..c5600bf 100644 --- a/main.go +++ b/main.go @@ -1227,6 +1227,20 @@ func (git *repoSync) removeStaleWorktrees() (int, error) { return count, nil } +func hasGitLockFile(gitRoot absPath) (string, error) { + gitLockFiles := []string{"shallow.lock"} + for _, lockFile := range gitLockFiles { + lockFilePath := gitRoot.Join(".git", lockFile).String() + _, err := os.Stat(lockFilePath) + if err == nil { + return lockFilePath, nil + } else if !errors.Is(err, os.ErrNotExist) { + return lockFilePath, err + } + } + return "", nil +} + // sanityCheckRepo tries to make sure that the repo dir is a valid git repository. func (git *repoSync) sanityCheckRepo(ctx context.Context) bool { git.log.V(3).Info("sanity-checking git repo", "repo", git.root) @@ -1258,6 +1272,16 @@ func (git *repoSync) sanityCheckRepo(ctx context.Context) bool { return false } + // Check if the repository contains an unreleased lock file. This can happen if + // a previous git invocation crashed. + if lockFile, err := hasGitLockFile(git.root); err != nil { + git.log.Error(err, "error calling stat on file", "path", lockFile) + return false + } else if len(lockFile) > 0 { + git.log.Error(nil, "repo contains lock file", "path", lockFile) + return false + } + return true } diff --git a/main_test.go b/main_test.go index a67d59d..13de2a4 100644 --- a/main_test.go +++ b/main_test.go @@ -424,3 +424,43 @@ func TestTouch(t *testing.T) { t.Errorf("touch(newfile) mtime %v is not after %v", newfileInfo.ModTime(), stamp) } } + +func TestHasGitLockFile(t *testing.T) { + testCases := map[string]struct { + inputFilePath []string + expectLockFile bool + }{ + "missing .git directory": { + expectLockFile: false, + }, + "has git directory but no lock files": { + inputFilePath: []string{".git", "HEAD"}, + expectLockFile: false, + }, + "shallow.lock file": { + inputFilePath: []string{".git", "shallow.lock"}, + expectLockFile: true, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + root := absPath(t.TempDir()) + + if len(tc.inputFilePath) > 0 { + if err := touch(root.Join(tc.inputFilePath...)); err != nil { + t.Fatal(err) + } + } + + lockFile, err := hasGitLockFile(root) + if err != nil { + t.Fatal(err) + } + hasLock := len(lockFile) > 0 + if hasLock != tc.expectLockFile { + t.Fatalf("expected hasGitLockFile to return %v, but got %v", tc.expectLockFile, hasLock) + } + }) + } +}