test: preserve branch when recovering bare git repo (#8803)
* test: preserve branch when recovering bare git repo * Replaced the standalone ensureMountClone + gitRun in Phase 5 with a new resetToCommitWithRecovery function that mirrors the existing pullFromCommitWithRecovery pattern
This commit is contained in:
@@ -122,8 +122,7 @@ func testGitCloneAndPull(t *testing.T, mountPoint, localDir string) {
|
|||||||
|
|
||||||
// ---- Phase 5: Reset to older revision in on-mount clone ----
|
// ---- Phase 5: Reset to older revision in on-mount clone ----
|
||||||
t.Log("Phase 5: reset to older revision on mount clone")
|
t.Log("Phase 5: reset to older revision on mount clone")
|
||||||
ensureMountClone(t, bareRepo, mountClone)
|
resetToCommitWithRecovery(t, bareRepo, localClone, mountClone, commit2)
|
||||||
gitRun(t, mountClone, "reset", "--hard", commit2)
|
|
||||||
|
|
||||||
resetHead := gitOutput(t, mountClone, "rev-parse", "HEAD")
|
resetHead := gitOutput(t, mountClone, "rev-parse", "HEAD")
|
||||||
assert.Equal(t, commit2, resetHead, "should be at commit 2")
|
assert.Equal(t, commit2, resetHead, "should be at commit 2")
|
||||||
@@ -324,13 +323,21 @@ func tryEnsureBareRepo(bareRepo, localClone string) error {
|
|||||||
if _, err := os.Stat(filepath.Join(bareRepo, "HEAD")); err == nil {
|
if _, err := os.Stat(filepath.Join(bareRepo, "HEAD")); err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
branch, err := tryGitCommand(localClone, "rev-parse", "--abbrev-ref", "HEAD")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("detect local branch: %w", err)
|
||||||
|
}
|
||||||
os.RemoveAll(bareRepo)
|
os.RemoveAll(bareRepo)
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
if _, err := tryGitCommand("", "init", "--bare", bareRepo); err != nil {
|
if _, err := tryGitCommand("", "init", "--bare", bareRepo); err != nil {
|
||||||
return fmt.Errorf("re-init bare repo: %w", err)
|
return fmt.Errorf("re-init bare repo: %w", err)
|
||||||
}
|
}
|
||||||
if _, err := tryGitCommand(localClone, "push", "--force", bareRepo, "master"); err != nil {
|
refSpec := fmt.Sprintf("%s:refs/heads/%s", branch, branch)
|
||||||
return fmt.Errorf("re-push to bare repo: %w", err)
|
if _, err := tryGitCommand(localClone, "push", "--force", bareRepo, refSpec); err != nil {
|
||||||
|
return fmt.Errorf("re-push branch %s to bare repo: %w", branch, err)
|
||||||
|
}
|
||||||
|
if _, err := tryGitCommand("", "--git-dir="+bareRepo, "symbolic-ref", "HEAD", "refs/heads/"+branch); err != nil {
|
||||||
|
return fmt.Errorf("set bare repo HEAD to %s: %w", branch, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -385,6 +392,50 @@ func pullFromCommitWithRecovery(t *testing.T, bareRepo, localClone, cloneDir, fr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resetToCommitWithRecovery resets the on-mount clone to a given commit. If
|
||||||
|
// the FUSE mount loses the directory after a failed reset (the kernel dcache
|
||||||
|
// can drop the entry after "Could not write new index file"), it re-clones
|
||||||
|
// from the bare repo and retries.
|
||||||
|
func resetToCommitWithRecovery(t *testing.T, bareRepo, localClone, mountClone, commit string) {
|
||||||
|
t.Helper()
|
||||||
|
const maxAttempts = 3
|
||||||
|
var lastErr error
|
||||||
|
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||||
|
if err := tryEnsureBareRepo(bareRepo, localClone); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
t.Logf("reset recovery attempt %d: ensure bare repo: %v", attempt, err)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := tryEnsureMountClone(bareRepo, mountClone); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
t.Logf("reset recovery attempt %d: ensure mount clone: %v", attempt, err)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := tryGitCommand(mountClone, "reset", "--hard", commit); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
if attempt < maxAttempts {
|
||||||
|
t.Logf("reset recovery attempt %d: %v — removing clone for re-create", attempt, err)
|
||||||
|
os.RemoveAll(mountClone)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !waitForDirEventually(t, mountClone, 5*time.Second) {
|
||||||
|
lastErr = fmt.Errorf("clone dir %s did not recover after reset", mountClone)
|
||||||
|
if attempt < maxAttempts {
|
||||||
|
t.Logf("reset recovery attempt %d: %v", attempt, lastErr)
|
||||||
|
os.RemoveAll(mountClone)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, lastErr, "git reset --hard %s failed after %d recovery attempts", commit, maxAttempts)
|
||||||
|
}
|
||||||
|
|
||||||
func tryPullFromCommit(t *testing.T, bareRepo, localClone, cloneDir, fromCommit string) error {
|
func tryPullFromCommit(t *testing.T, bareRepo, localClone, cloneDir, fromCommit string) error {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
// The bare repo lives on the FUSE mount and can also vanish.
|
// The bare repo lives on the FUSE mount and can also vanish.
|
||||||
@@ -437,3 +488,36 @@ func leftPad(n, width int) string {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTryEnsureBareRepoPreservesCurrentBranch(t *testing.T) {
|
||||||
|
tempDir, err := os.MkdirTemp("", "git_bare_recovery_")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(tempDir)
|
||||||
|
|
||||||
|
bareRepo := filepath.Join(tempDir, "repo.git")
|
||||||
|
localClone := filepath.Join(tempDir, "clone")
|
||||||
|
restoredClone := filepath.Join(tempDir, "restored")
|
||||||
|
|
||||||
|
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 recovery\n")
|
||||||
|
gitRun(t, localClone, "add", "README.md")
|
||||||
|
gitRun(t, localClone, "commit", "-m", "initial commit")
|
||||||
|
|
||||||
|
branch := gitOutput(t, localClone, "rev-parse", "--abbrev-ref", "HEAD")
|
||||||
|
require.NotEmpty(t, branch)
|
||||||
|
|
||||||
|
require.NoError(t, os.RemoveAll(bareRepo))
|
||||||
|
require.NoError(t, tryEnsureBareRepo(bareRepo, localClone))
|
||||||
|
|
||||||
|
headRef := gitOutput(t, "", "--git-dir="+bareRepo, "symbolic-ref", "--short", "HEAD")
|
||||||
|
assert.Equal(t, branch, headRef, "recovered bare repo should keep the current branch as HEAD")
|
||||||
|
|
||||||
|
gitRun(t, "", "clone", bareRepo, restoredClone)
|
||||||
|
restoredHead := gitOutput(t, restoredClone, "rev-parse", "--abbrev-ref", "HEAD")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user