diff --git a/bin/gstack-wtree b/bin/gstack-wtree index a843f75d4..766e40072 100755 --- a/bin/gstack-wtree +++ b/bin/gstack-wtree @@ -7,7 +7,8 @@ # that index. An untracked `.bun/` folder at the repository root (Bun's install # cache, which a test run can create) is left out so a passing run does not # look like a mid-run edit (GST-421); tracked files under it still count, and a -# tree without that folder hashes exactly as a plain `git add -A` would. +# tree without that folder (or with it gitignored) hashes exactly as a plain +# `git add -A` would. # Properties that make this the right staleness fingerprint, vs # `git rev-parse HEAD^{tree}`: # @@ -87,7 +88,12 @@ if [ -n "$REAL_INDEX" ] && [ -f "$REAL_INDEX" ] && cp "$REAL_INDEX" "$TMPIDX" 2> else git -C "$TOP" read-tree HEAD 2>/dev/null || exit 1 fi -if ! ERR=$(git -C "$TOP" add -u 2>&1 >/dev/null) || ! ERR=$(git -C "$TOP" add -A -- . ':(exclude,top).bun' 2>&1 >/dev/null); then +# The exclude is added only when the root .bun/ exists and is not ignored: an +# exclude pathspec naming an ignored path makes `git add -A` exit 1 (GST-449), +# and an ignored .bun/ stays out of a plain `git add -A` anyway. +EXCLUDE_BUN="" +if [ -e "$TOP/.bun" ] && ! git -C "$TOP" check-ignore -q .bun 2>/dev/null; then EXCLUDE_BUN=':(exclude,top).bun'; fi +if ! ERR=$(git -C "$TOP" add -u 2>&1 >/dev/null) || ! ERR=$(git -C "$TOP" add -A -- . ${EXCLUDE_BUN:+"$EXCLUDE_BUN"} 2>&1 >/dev/null); then echo "gstack-wtree: cannot stage the working tree: $(printf '%s' "$ERR" | head -1)" >&2 exit 1 fi diff --git a/changelog.d/gsta195-wtree-ignored-bun.md b/changelog.d/gsta195-wtree-ignored-bun.md new file mode 100644 index 000000000..f76824537 --- /dev/null +++ b/changelog.d/gsta195-wtree-ignored-bun.md @@ -0,0 +1,7 @@ +**`gstack-wtree` works again in a repository that gitignores `.bun/`.** Since GST-421, its `git add -A` carried an exclude pathspec naming `.bun`, and git refuses a pathspec naming an ignored path with exit 1. Every fingerprint call then failed, which broke `gstack-review-log --start`, the evidence checks and /ship in harness-evals (HARN-1, HARN-7, harness-evals#148; GST-449). + +### Fixed +- **The `.bun/` exclude applies only when the root `.bun/` exists and is not ignored.** An ignored `.bun/` already stays out of `git add -A`, so the fingerprint is the same either way. + +### Tests +- `test/evidence.test.ts`: with `.bun/` or `/.bun` in `.gitignore`, `gstack-wtree` prints a tree and a lane that writes `.bun/` grades FRESH. The existing unignored `.bun/` cases still pass. diff --git a/test/evidence.test.ts b/test/evidence.test.ts index 479095f6a..56e7bc1c0 100644 --- a/test/evidence.test.ts +++ b/test/evidence.test.ts @@ -304,6 +304,29 @@ describe('gstack-evidence check', () => { expect(chk.status).toBe(0); }); + // Value: protects=a repository that gitignores .bun/ still gets a fingerprint (GST-449): an exclude pathspec naming an ignored path made + // `git add -A` exit 1, so gstack-wtree failed and every review-log start and evidence check lost its fingerprint; fails_when=gstack-wtree + // exits non-zero or the lane records no wtree with .bun/ (or /.bun) in .gitignore; why_new=the GST-421 test covered only an unignored + // .bun/ (harness-evals#148); seam=none; no_claim=Windows git + test('GST-449: with .bun/ gitignored, gstack-wtree still fingerprints and a lane that writes .bun/ grades FRESH', () => { + for (const pattern of ['.bun/', '/.bun']) { + fs.writeFileSync(path.join(repoDir, '.gitignore'), `${pattern}\n`); + git('add .gitignore'); + git(`commit -q -m "ignore ${pattern}"`); + fs.mkdirSync(path.join(repoDir, '.bun'), { recursive: true }); + fs.writeFileSync(path.join(repoDir, '.bun', 'x'), 'cache\n'); + const wt = runBin(path.join(ROOT, 'bin', 'gstack-wtree'), [], { cwd: repoDir }); + expect(wt.stderr).toBe(''); + expect(wt.status).toBe(0); + expect(wt.stdout.trim()).toMatch(/^[0-9a-f]{40}$/); + const r = run(['run', '--label', 'tests', '--', 'echo cache2 > .bun/y && echo green']); + expect(r.status).toBe(0); + expect(records().pop().wtree).toMatch(/^[0-9a-f]{40}$/); + expect(run(['check', '--label', 'tests']).stdout).toContain('EVIDENCE: FRESH label=tests'); + fs.rmSync(path.join(repoDir, '.bun'), { recursive: true, force: true }); + } + }); + test('GST-421: a lane that writes untracked source grades STALE and the reason names the path', () => { const r = run(['run', '--label', 'tests', '--', 'mkdir -p src && echo "export {}" > src/new.ts && echo green']); expect(r.status).toBe(0);