Release ForgeFlow 0.6.0

This commit is contained in:
NuklearRabbit
2026-07-25 05:59:07 +02:00
parent cf1f67a823
commit 9d3933c878
48 changed files with 2208 additions and 466 deletions
+65
View File
@@ -178,3 +178,68 @@ test('stages a large Windows-sized partial selection through NUL-delimited stdin
assert.equal(status.counts.staged, names.length);
assert.equal(status.counts.unstaged, 0);
});
test('detects and removes a stale HEAD.lock while skipping Git object storage', async (t) => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'forgeflow-head-lock-'));
t.after(() => fs.rm(root, { recursive: true, force: true }));
await git(['init'], root);
await git(['config', 'user.name', 'ForgeFlow Test'], root);
await git(['config', 'user.email', 'forgeflow@example.invalid'], root);
await fs.writeFile(path.join(root, 'README.md'), 'lock test\n');
await git(['add', '.'], root);
await git(['commit', '-m', 'Initial'], root);
const headLock = path.join(root, '.git', 'HEAD.lock');
const ignoredObjectLock = path.join(root, '.git', 'objects', 'fake.lock');
await fs.writeFile(headLock, 'stale');
await fs.writeFile(ignoredObjectLock, 'not a repository mutation lock');
const old = new Date(Date.now() - 60_000);
await fs.utimes(headLock, old, old);
const service = new GitService();
const report = await service.listGitLocks(root);
assert.deepEqual(report.locks.map((item) => item.name), ['HEAD.lock']);
const repaired = await service.repairStaleGitLocks(root, { minimumAgeMs: 0, allowWithoutProcessProbe: true });
assert.equal(repaired.removed.length, 1);
await assert.rejects(() => fs.stat(headLock), (error) => error.code === 'ENOENT');
assert.ok(await fs.stat(ignoredObjectLock));
});
test('repairs a diverged branch by creating a safety branch before resetting to upstream', async (t) => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'forgeflow-diverged-'));
t.after(() => fs.rm(root, { recursive: true, force: true }));
const remote = path.join(root, 'remote.git');
const working = path.join(root, 'working');
const other = path.join(root, 'other');
await git(['init', '--bare', remote], root);
await git(['clone', remote, working], root);
await git(['config', 'user.name', 'ForgeFlow Test'], working);
await git(['config', 'user.email', 'forgeflow@example.invalid'], working);
await fs.writeFile(path.join(working, 'README.md'), 'initial\n');
await git(['add', '.'], working);
await git(['commit', '-m', 'Initial'], working);
await git(['branch', '-M', 'main'], working);
await git(['push', '-u', 'origin', 'main'], working);
await git(['clone', remote, other], root);
await git(['config', 'user.name', 'Other Test'], other);
await git(['config', 'user.email', 'other@example.invalid'], other);
await git(['checkout', 'main'], other);
await fs.writeFile(path.join(other, 'remote.txt'), 'remote\n');
await git(['add', '.'], other);
await git(['commit', '-m', 'Remote commit'], other);
await git(['push', 'origin', 'main'], other);
await fs.writeFile(path.join(working, 'local.txt'), 'local\n');
await git(['add', '.'], working);
await git(['commit', '-m', 'Local commit'], working);
const localBefore = (await git(['rev-parse', 'HEAD'], working)).stdout.trim();
const service = new GitService();
const scan = await service.reconcile(working);
assert.equal(scan.status.branch.ahead, 1);
assert.equal(scan.status.branch.behind, 1);
assert.ok(scan.recommendations.some((item) => item.action === 'backup-reset'));
const repaired = await service.repairSync(working, 'backup-reset');
assert.match(repaired.backupBranch, /^forgeflow\/backup-main-/);
assert.equal(repaired.status.branch.ahead, 0);
assert.equal(repaired.status.branch.behind, 0);
const backupSha = (await git(['rev-parse', repaired.backupBranch], working)).stdout.trim();
assert.equal(backupSha, localBefore);
});