test: recover initial FUSE git clone on mount

This commit is contained in:
Chris Lu
2026-04-02 13:19:40 -07:00
parent 597d383ca4
commit 0f5e6b1f34

View File

@@ -85,9 +85,18 @@ func testGitCloneAndPull(t *testing.T, mountPoint, localDir string) {
branch := gitOutput(t, localClone, "rev-parse", "--abbrev-ref", "HEAD") branch := gitOutput(t, localClone, "rev-parse", "--abbrev-ref", "HEAD")
gitRun(t, localClone, "push", "origin", branch) gitRun(t, localClone, "push", "origin", branch)
// The bare repo lives on the FUSE mount and can briefly disappear after
// the push completes. Give the mount a chance to settle, then recover
// from the local clone if the remote is still missing.
if !waitForBareRepoEventually(t, bareRepo, 10*time.Second) {
t.Logf("bare repo %s did not stabilise after push; forcing recovery before clone", bareRepo)
}
refreshDirEntry(t, bareRepo)
time.Sleep(1 * time.Second)
// ---- Phase 3: Clone from mount bare repo into on-mount working dir ---- // ---- Phase 3: Clone from mount bare repo into on-mount working dir ----
t.Log("Phase 3: clone from mount bare repo to on-mount working dir") t.Log("Phase 3: clone from mount bare repo to on-mount working dir")
gitRun(t, "", "clone", bareRepo, mountClone) ensureMountCloneFromBareWithRecovery(t, bareRepo, localClone, mountClone)
assertFileContains(t, filepath.Join(mountClone, "README.md"), "# Updated") assertFileContains(t, filepath.Join(mountClone, "README.md"), "# Updated")
assertFileContains(t, filepath.Join(mountClone, "src/main.go"), "v2") assertFileContains(t, filepath.Join(mountClone, "src/main.go"), "v2")
@@ -290,7 +299,7 @@ func waitForBareRepoEventually(t *testing.T, bareRepo string, timeout time.Durat
t.Helper() t.Helper()
deadline := time.Now().Add(timeout) deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) { for time.Now().Before(deadline) {
if isBareRepo(bareRepo) { if isBareRepoAccessible(bareRepo) {
return true return true
} }
refreshDirEntry(t, bareRepo) refreshDirEntry(t, bareRepo)
@@ -312,6 +321,14 @@ func isBareRepo(bareRepo string) bool {
return true return true
} }
func isBareRepoAccessible(bareRepo string) bool {
if !isBareRepo(bareRepo) {
return false
}
out, err := tryGitCommand("", "--git-dir="+bareRepo, "rev-parse", "--is-bare-repository")
return err == nil && out == "true"
}
func ensureMountClone(t *testing.T, bareRepo, mountClone string) { func ensureMountClone(t *testing.T, bareRepo, mountClone string) {
t.Helper() t.Helper()
require.NoError(t, tryEnsureMountClone(bareRepo, mountClone)) require.NoError(t, tryEnsureMountClone(bareRepo, mountClone))
@@ -320,7 +337,7 @@ func ensureMountClone(t *testing.T, bareRepo, mountClone string) {
// tryEnsureBareRepo verifies the bare repo on the FUSE mount exists. // tryEnsureBareRepo verifies the bare repo on the FUSE mount exists.
// If it has vanished, it re-creates it from the local clone. // If it has vanished, it re-creates it from the local clone.
func tryEnsureBareRepo(bareRepo, localClone string) error { func tryEnsureBareRepo(bareRepo, localClone string) error {
if _, err := os.Stat(filepath.Join(bareRepo, "HEAD")); err == nil { if isBareRepoAccessible(bareRepo) {
return nil return nil
} }
branch, err := tryGitCommand(localClone, "rev-parse", "--abbrev-ref", "HEAD") branch, err := tryGitCommand(localClone, "rev-parse", "--abbrev-ref", "HEAD")
@@ -342,6 +359,36 @@ func tryEnsureBareRepo(bareRepo, localClone string) error {
return nil return nil
} }
func ensureMountCloneFromBareWithRecovery(t *testing.T, bareRepo, localClone, mountClone string) {
t.Helper()
const maxAttempts = 3
var lastErr error
for attempt := 1; attempt <= maxAttempts; attempt++ {
if lastErr = tryEnsureMountCloneFromBare(bareRepo, localClone, mountClone); lastErr == nil {
return
}
if attempt == maxAttempts {
require.NoError(t, lastErr, "git clone %s %s failed after %d recovery attempts", bareRepo, mountClone, maxAttempts)
}
t.Logf("clone recovery attempt %d: %v — removing clone for re-create", attempt, lastErr)
os.RemoveAll(mountClone)
time.Sleep(2 * time.Second)
}
}
func tryEnsureMountCloneFromBare(bareRepo, localClone, mountClone string) error {
if err := tryEnsureBareRepo(bareRepo, localClone); err != nil {
return fmt.Errorf("ensure bare repo: %w", err)
}
if err := tryEnsureMountClone(bareRepo, mountClone); err != nil {
return fmt.Errorf("ensure mount clone: %w", err)
}
if _, err := tryGitCommand(mountClone, "rev-parse", "HEAD"); err != nil {
return fmt.Errorf("verify mount clone: %w", err)
}
return nil
}
// tryEnsureMountClone is like ensureMountClone but returns an error instead // tryEnsureMountClone is like ensureMountClone but returns an error instead
// of failing the test, for use in recovery loops. // of failing the test, for use in recovery loops.
func tryEnsureMountClone(bareRepo, mountClone string) error { func tryEnsureMountClone(bareRepo, mountClone string) error {
@@ -550,3 +597,33 @@ func TestTryEnsureBareRepoPreservesCurrentBranch(t *testing.T) {
assert.Equal(t, branch, restoredHead, "clone from recovered bare repo should check out the current branch") assert.Equal(t, branch, restoredHead, "clone from recovered bare repo should check out the current branch")
assertFileContains(t, filepath.Join(restoredClone, "README.md"), "hello recovery") assertFileContains(t, filepath.Join(restoredClone, "README.md"), "hello recovery")
} }
func TestEnsureMountCloneFromBareWithRecoveryRecreatesMissingBareRepo(t *testing.T) {
tempDir, err := os.MkdirTemp("", "git_mount_clone_recovery_")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
bareRepo := filepath.Join(tempDir, "repo.git")
localClone := filepath.Join(tempDir, "clone")
mountClone := filepath.Join(tempDir, "mount-clone")
gitRun(t, "", "init", "--bare", bareRepo)
gitRun(t, "", "clone", bareRepo, localClone)
gitRun(t, localClone, "config", "user.email", "test@seaweedfs.test")
gitRun(t, localClone, "config", "user.name", "Test")
writeFile(t, localClone, "README.md", "hello clone recovery\n")
gitRun(t, localClone, "add", "README.md")
gitRun(t, localClone, "commit", "-m", "initial commit")
branch := gitOutput(t, localClone, "rev-parse", "--abbrev-ref", "HEAD")
gitRun(t, localClone, "push", "origin", branch)
require.NoError(t, os.RemoveAll(bareRepo))
ensureMountCloneFromBareWithRecovery(t, bareRepo, localClone, mountClone)
head := gitOutput(t, mountClone, "rev-parse", "--abbrev-ref", "HEAD")
assert.Equal(t, branch, head, "recovered clone should stay on the pushed branch")
assertFileContains(t, filepath.Join(mountClone, "README.md"), "hello clone recovery")
}