Merge branch 'codex/pr-8889'

# Conflicts:
#	test/fuse_integration/git_operations_test.go
This commit is contained in:
Chris Lu
2026-04-02 13:21:03 -07:00

View File

@@ -85,16 +85,19 @@ 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)
// Wait for pushed objects to become visible through the FUSE mount. // The bare repo lives on the FUSE mount and can briefly disappear after
// After git push, pack objects may not be immediately consistent on // the push completes, and pushed pack objects may not be immediately
// the FUSE layer, causing clone to fail with "nonexistent object". // consistent on the FUSE layer. Give the mount a chance to settle, then
waitForBareRepoEventually(t, bareRepo, 10*time.Second) // 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) refreshDirEntry(t, bareRepo)
time.Sleep(1 * time.Second) 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")
@@ -297,7 +300,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)
@@ -319,6 +322,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))
@@ -327,7 +338,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")
@@ -349,6 +360,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 {
@@ -557,3 +598,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")
}