fix(git-sync): self-heal a missing 'main' branch that wedged a space (D3-N1)
Ref-store damage (a deleted refs/heads/main, an interrupted ref update) can leave an
existing vault repo without a 'main' branch. The cycle's ensureBranch('docmost','main')
+ checkout then throw every poll ("pathspec 'main' did not match"), wedging the space
forever with no self-heal — ensureRepo only creates branches on a FRESH git init
(found via web-test corruption charter, reproduced deterministically).
Add VaultGit.ensureMainBranch() and call it in the cycle preflight (after
clearStaleGitLocks, before the branch setup): if 'main' is missing, re-create it from
the 'docmost' mirror branch (they track each other) else from HEAD. Same
wedge-forever family as D3-N3.
Verified on the stand: deleting refs/heads/main now self-heals (main restored, the
edit reaches the vault, 0 pathspec errors) — was wedged forever. Unit test (real temp
repo: delete main -> ensureMainBranch restores it from docmost). git-sync suite green (708).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,7 @@ function fakeVault(overrides: Record<string, any> = {}) {
|
||||
assertGitAvailable: rec("assertGitAvailable"),
|
||||
ensureRepo: rec("ensureRepo"),
|
||||
clearStaleGitLocks: rec("clearStaleGitLocks"),
|
||||
ensureMainBranch: rec("ensureMainBranch"),
|
||||
isMergeInProgress: vi.fn(async () => false),
|
||||
ensureBranch: rec("ensureBranch"),
|
||||
checkout: rec("checkout"),
|
||||
|
||||
@@ -166,6 +166,32 @@ describe('VaultGit (integration; temp repo)', () => {
|
||||
await expect(git.clearStaleGitLocks()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('ensureMainBranch restores a deleted main from the docmost mirror (bug D3-N1)', async () => {
|
||||
if (!available) return;
|
||||
const vault = await freshDir();
|
||||
const git = new VaultGit(vault);
|
||||
await git.ensureRepo();
|
||||
await git.ensureBranch('docmost', 'main');
|
||||
|
||||
// Ref damage: delete refs/heads/main (git refuses to delete the current
|
||||
// branch, so move HEAD to docmost first — simulating a lost main ref).
|
||||
await execFileAsync('git', ['symbolic-ref', 'HEAD', 'refs/heads/docmost'], {
|
||||
cwd: vault,
|
||||
});
|
||||
await execFileAsync('git', ['branch', '-D', 'main'], { cwd: vault });
|
||||
await expect(
|
||||
execFileAsync('git', ['rev-parse', '--verify', 'main'], { cwd: vault }),
|
||||
).rejects.toThrow();
|
||||
|
||||
// The preflight re-creates main (from docmost).
|
||||
await git.ensureMainBranch();
|
||||
await expect(
|
||||
execFileAsync('git', ['rev-parse', '--verify', 'main'], { cwd: vault }),
|
||||
).resolves.toBeDefined();
|
||||
// Idempotent when main already exists.
|
||||
await expect(git.ensureMainBranch()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('ensureRepo neutralizes correctness-affecting LOCAL config', async () => {
|
||||
if (!available) return;
|
||||
const vault = await freshDir();
|
||||
|
||||
@@ -41,6 +41,7 @@ function makePushGit(opts: {
|
||||
assertGitAvailable: vi.fn(async () => {}),
|
||||
ensureRepo: vi.fn(async () => {}),
|
||||
clearStaleGitLocks: vi.fn(async () => {}),
|
||||
ensureMainBranch: vi.fn(async () => {}),
|
||||
isMergeInProgress: vi.fn(async () => false), // NO merge in progress
|
||||
checkout: vi.fn(async () => {}),
|
||||
stageAll: vi.fn(async () => {}),
|
||||
@@ -344,6 +345,7 @@ function fakeVault(overrides: Record<string, any> = {}) {
|
||||
assertGitAvailable: rec('assertGitAvailable'),
|
||||
ensureRepo: rec('ensureRepo'),
|
||||
clearStaleGitLocks: rec('clearStaleGitLocks'),
|
||||
ensureMainBranch: rec('ensureMainBranch'),
|
||||
isMergeInProgress: vi.fn(async () => false),
|
||||
ensureBranch: rec('ensureBranch'),
|
||||
checkout: rec('checkout'),
|
||||
|
||||
Reference in New Issue
Block a user