* test(#4183): reproduce root phase Fallow base failure * fix(#4183): resolve root phase Fallow base Fall back to the phase root commit when its parent is unresolvable. * chore(#4183): add release note * chore(#4183): bind changeset to fork PR * docs(#4183): describe root fallback precisely * chore(#4183): bind changeset to upstream PR --------- Co-authored-by: CI Rebase Check <ci@gsd-redux> Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
committed by
GitHub
parent
eedb6b5431
commit
d0d542e478
5
.changeset/bold-otters-run.md
Normal file
5
.changeset/bold-otters-run.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
type: Fixed
|
||||||
|
pr: 4215
|
||||||
|
---
|
||||||
|
**Structural pre-pass no longer aborts for phases introduced in the repository's root commit** — Fallow uses the root commit itself when no parent exists instead of receiving an invalid parent revision. (#4183)
|
||||||
@@ -39,7 +39,11 @@ if [ \"$FALLOW_SCOPE\" = \"phase\" ]; then
|
|||||||
# same-numbered phases and tail -1 selected the oldest.
|
# same-numbered phases and tail -1 selected the oldest.
|
||||||
FALLOW_PHASE_START=$(git log --format=\"%H\" --diff-filter=A -- \"${PHASE_DIR}\" 2>/dev/null | tail -1)
|
FALLOW_PHASE_START=$(git log --format=\"%H\" --diff-filter=A -- \"${PHASE_DIR}\" 2>/dev/null | tail -1)
|
||||||
if [ -n \"$FALLOW_PHASE_START\" ]; then
|
if [ -n \"$FALLOW_PHASE_START\" ]; then
|
||||||
FALLOW_BASE=\"${FALLOW_PHASE_START}^\"
|
if git rev-parse \"${FALLOW_PHASE_START}^\" >/dev/null 2>&1; then
|
||||||
|
FALLOW_BASE=\"${FALLOW_PHASE_START}^\"
|
||||||
|
else
|
||||||
|
FALLOW_BASE=\"${FALLOW_PHASE_START}\"
|
||||||
|
fi
|
||||||
FALLOW_SCOPE_ARGS=(--changed-since \"$FALLOW_BASE\")
|
FALLOW_SCOPE_ARGS=(--changed-since \"$FALLOW_BASE\")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -990,6 +990,59 @@ describe('Bug 5 (#3191) — same anchored, portable phase-scope grep at all thre
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'root commit: fallow phase scope uses a resolvable root SHA',
|
||||||
|
SKIP_WIN32,
|
||||||
|
() => {
|
||||||
|
const repo = createTempDir('gsd-4183-fallow-root-');
|
||||||
|
try {
|
||||||
|
gitOrThrow(['init', '-b', 'main'], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS });
|
||||||
|
gitOrThrow(['config', 'user.email', 'test@test.com'], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS });
|
||||||
|
gitOrThrow(['config', 'user.name', 'Test'], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS });
|
||||||
|
gitOrThrow(['config', 'commit.gpgsign', 'false'], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS });
|
||||||
|
|
||||||
|
const phaseFile = path.join(repo, '.planning', 'phases', '06-ctx', 'PLAN.md');
|
||||||
|
fs.mkdirSync(path.dirname(phaseFile), { recursive: true });
|
||||||
|
fs.writeFileSync(phaseFile, '# phase context\n');
|
||||||
|
gitOrThrow(['add', '.planning'], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS });
|
||||||
|
gitOrThrow(['commit', '-m', 'docs(06): initial phase context'], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS });
|
||||||
|
const rootSha = gitOrThrow(['rev-parse', 'HEAD'], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS }).trim();
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(repo, 'index.js'), 'module.exports = 1;\n');
|
||||||
|
gitOrThrow(['add', 'index.js'], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS });
|
||||||
|
gitOrThrow(['commit', '-m', 'feat: add source'], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS });
|
||||||
|
|
||||||
|
const result = runDerivation(repo, extractFallowDerivation(), '06');
|
||||||
|
assert.equal(result.status, 0, `snippet exited ${result.status}; stderr=${result.stderr}`);
|
||||||
|
const fallowBase = parseSentinel(result.stdout, 'FALLOW_BASE');
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
fallowBase,
|
||||||
|
[rootSha],
|
||||||
|
`root-parent FALLOW_BASE regression: expected ${rootSha}, got ${JSON.stringify(fallowBase)}`,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
gitOrThrow(['rev-parse', '--verify', `${fallowBase[0]}^{commit}`], { cwd: repo, timeoutMs: GIT_TIMEOUT_MS }).trim(),
|
||||||
|
rootSha,
|
||||||
|
'FALLOW_BASE must resolve to the root commit',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (process.env.CI) {
|
||||||
|
const { requireFallowBinary } = require('../gsd-core/bin/lib/fallow-runner.cjs');
|
||||||
|
const { execTool } = require('../gsd-core/bin/lib/shell-command-projection.cjs');
|
||||||
|
const audit = execTool(
|
||||||
|
requireFallowBinary({ cwd: ROOT, envPath: '' }),
|
||||||
|
['audit', '--changed-since', fallowBase[0], '--format', 'json'],
|
||||||
|
{ cwd: repo, timeout: 120000 },
|
||||||
|
);
|
||||||
|
assert.ok([0, 1].includes(audit.exitCode), `fallow root audit exit=${audit.exitCode}; stderr=${audit.stderr}`);
|
||||||
|
console.log(`fallow-root-audit normal-exit=${audit.exitCode}`);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cleanup(repo);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'T5: with no genuine phase scope commit, every derivation yields NO base (fail-closed preserved)',
|
'T5: with no genuine phase scope commit, every derivation yields NO base (fail-closed preserved)',
|
||||||
SKIP_WIN32,
|
SKIP_WIN32,
|
||||||
|
|||||||
Reference in New Issue
Block a user