test: recover initial FUSE git clone on mount
This commit is contained in:
@@ -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")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user