mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-10 02:30:39 +01:00
fix(review): receipt write gate-scans whenever the base declares review_exclude_paths; collectLive never re-qualifies origin/<base> (#3852 /ship red-team#1, outside-voice#1)
This commit is contained in:
1 parent
557e5c4c5f
commit
d7ad64eae4
4 files changed
+31
-4
No files matched your search
@@ -112,6 +112,18 @@ describe('collectLive: the working tree', () => {
|
||||
expect(c.paths).toEqual(['docs/a.md', 'src/a.ts', 'src/new.ts']);
|
||||
expect(depthOfCommitInput({ ...c, gateTagged: 0 })).toMatchObject({ depth: 'light', runtimeLines: 20 + 10 });
|
||||
});
|
||||
// Value: protects=a remote-qualified base reads that remote's tracking branch by its full name, so a fetched branch called
|
||||
// origin/main cannot become the merge base (an empty diff) or the registry and policy source; fails_when=collectLive tries
|
||||
// refs/remotes/origin/<base> first for origin/main (/ship review of #3852, outside-voice#1); why_new=every other case names a
|
||||
// bare base; seam=none; no_claim=remotes other than origin
|
||||
test('a fetched branch named origin/main never stands in for origin/main', () => {
|
||||
const r = repo({});
|
||||
r.git('update-ref', 'refs/remotes/origin/main', r.baseSha);
|
||||
const head = r.commit({ 'src/a.ts': lines(3) });
|
||||
r.git('update-ref', 'refs/remotes/origin/origin/main', head);
|
||||
const c = collectLive(r.dir, 'origin/main');
|
||||
expect([c.mergeBase, c.registry.sha, c.paths]).toEqual([r.baseSha, r.baseSha, ['src/a.ts']]);
|
||||
});
|
||||
test('no merge base is a collector error the CLI refuses on', () => {
|
||||
const r = repo({});
|
||||
expect(collectLive(r.dir, 'no-such-branch').errors.map(e => e.code)).toEqual(['numstat_failed']);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { EXIT } from '../lib/headless-artifacts';
|
||||
import { KEEP_ARG_MAX_BYTES, readSelection, reviewExclude, tooLargeLine, unreadableLine } from '../lib/review-exclude';
|
||||
import { collectCommit, collectLive } from '../lib/review-collect';
|
||||
import { extractReceipt } from '../lib/ship-receipt';
|
||||
import { bindShippedReceipt } from '../lib/ship-receipt-depth';
|
||||
import { bindShippedReceipt, receiptReviewAtHead } from '../lib/ship-receipt-depth';
|
||||
import { REVIEW_EXCLUDE_KEY_VERSION, isUnavailable } from '../lib/ship-policy';
|
||||
import { loadPolicyAt } from '../lib/ship-policy-at';
|
||||
import { cleanup, commit, makeFixtureRepo, runBin, write, type FixtureRepo } from './helpers/pregate-fixture';
|
||||
@@ -253,6 +253,20 @@ describe('the receipt binds the selection the reviewers read', () => {
|
||||
const receiptIn = (body: string) => extractReceipt(fs.readFileSync(body, 'utf8')).receipt!;
|
||||
const writeReceipt = (repo: FixtureRepo, body: string, extra: string[]) =>
|
||||
runBin(repo, 'gstack-ship-receipt', ['write', '--pr', '7', '--base', 'main', '--version', '1.0.0', '--session-kind', 'unattended', '--body', body, ...extra]);
|
||||
// Value: protects=a docs-only range whose only file sits under a base pattern and carries a gate tag binds the same selection at
|
||||
// plan and receipt write, so the receipt can be written; fails_when=receiptReviewAtHead skips the gate scan when the paths are
|
||||
// docs-class and excludes the file the plan kept as gate-tagged (/ship review of #3852, red-team#1: digests 652d0aa8 vs
|
||||
// 6f33bd56, RECEIPT_INVALID with no fix); why_new=every other receipt case has a code path in its range; seam=gstack-review-plan
|
||||
// --json and receiptReviewAtHead on one head; no_claim=the gate scan's own tag rules
|
||||
test('a docs-only range with a gate-tagged file under a pattern binds the plan\'s digest at receipt write', () => {
|
||||
const repo = makeFixtureRepo({ prefix: 'gstack-exclude-docs-', baseFiles: { '.gstack/ship-policy.json': policyText(['testdata/**']) } });
|
||||
repos.push(repo);
|
||||
const head = commit(repo, { 'testdata/expected.txt': 'expected output\n' });
|
||||
const planned = JSON.parse(runBin(repo, 'gstack-review-plan', ['--base', 'main', '--json']).out).review_exclude;
|
||||
expect(planned.kept_files).toEqual([{ path: 'testdata/expected.txt', reason: 'gate-tagged' }]);
|
||||
const { exclude } = receiptReviewAtHead(repo.work, 'main', path.join(import.meta.dir, '..', 'bin'), head);
|
||||
expect(exclude.state === 'active' ? exclude.record.digest : exclude.state).toBe(planned.digest);
|
||||
});
|
||||
// Value: protects=a receipt names the selection the rendered briefs read, a base merge that leaves the policy alone still
|
||||
// carries, and a policy change on the base (adding or removing a pattern) needs a new review, not a write; fails_when=write
|
||||
// records a digest the briefs never named, carry compares the base sha, or a removed exclusion clears by writing the old
|
||||
|
||||
Reference in new issue
Block a user