mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 07:18:03 +01:00
fix(stamp): name branch lines by position; carry and finish accept a kept VERSION claim (GST-979, GST-980)
BranchSide carries the positions of the branch-added lines and stampOnlyLine takes the removed line position, so a base line whose text duplicates a branch line keeps the old-version floor. At equal versions only the branch own lines may move. stampOnlyText and stampedFiles accept a kept claim for those lines, and the carry leaves a stamp file out of the product comparison when its only change since the reviewed head is a stamp on the branch own rows and the base did not touch it. Failing-first: all five new tests fail on main fc292e22f.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
40a3170335
commit
fbf83f11ef
12 files changed
+178
-35
No files matched your search
@@ -225,12 +225,29 @@ describe('carry follow-ups: git floor, named refusal, branch-touched walk, digit
|
||||
const mine = "export const MINE_KEY_VERSION = '1.2.3.0';";
|
||||
const before = `${mine}\nexport const BASE_KEY_VERSION = '1.2.3.0';\n`;
|
||||
const moved = (text: string) => ({ before, after: text, oldVersion: '1.2.4.0', newVersion: '1.2.5.0', templates });
|
||||
const side = { fork: '1.2.3.0', added: new Set([mine]) };
|
||||
const side = { fork: '1.2.3.0', added: new Set([mine]), at: new Set([1]) };
|
||||
expect(stampOnlyText({ ...moved(before.replace(mine, mine.replace('1.2.3.0', '1.2.5.0'))), side })).toBe(true);
|
||||
expect(stampOnlyText(moved(before.replace(mine, mine.replace('1.2.3.0', '1.2.5.0'))))).toBe(false);
|
||||
expect(stampOnlyText({ ...moved(before.replace("BASE_KEY_VERSION = '1.2.3.0'", "BASE_KEY_VERSION = '1.2.5.0'")), side })).toBe(false);
|
||||
});
|
||||
|
||||
// Value: protects=the carry reads the branch's own line by position and, with a kept VERSION claim, accepts only the
|
||||
// branch's own row moved from the fork version (GST-979, GST-980); fails_when=stampOnlyText refuses every change at
|
||||
// equal versions or takes a duplicated base line as the branch's; seam=lib/ship-receipt.ts stampOnlyText
|
||||
test('stampOnlyText reads the branch line by position, and with a kept VERSION moves only the branch\'s own row (GST-979, GST-980)', () => {
|
||||
const templates = ["_KEY_VERSION = '{version}'"];
|
||||
const key = (v: string) => `export const MINE_KEY_VERSION = '${v}';`;
|
||||
const before = `${key('1.2.3.0')}\nexport const BASE_KEY_VERSION = '1.2.3.0';\n${key('1.2.3.0')}\n`;
|
||||
const side = { fork: '1.2.3.0', added: new Set([key('1.2.3.0')]), at: new Set([3]) };
|
||||
const lines = (a: string, c: string) => `${a}\nexport const BASE_KEY_VERSION = '1.2.3.0';\n${c}\n`;
|
||||
const change = (after: string, oldVersion: string, newVersion: string) => stampOnlyText({ before, after, oldVersion, newVersion, templates, side });
|
||||
expect(change(lines(key('1.2.3.0'), key('1.2.5.0')), '1.2.4.0', '1.2.5.0')).toBe(true);
|
||||
expect(change(lines(key('1.2.5.0'), key('1.2.3.0')), '1.2.4.0', '1.2.5.0')).toBe(false);
|
||||
expect(change(lines(key('1.2.3.0'), key('1.2.4.0')), '1.2.4.0', '1.2.4.0')).toBe(true);
|
||||
expect(change(lines(key('1.2.4.0'), key('1.2.3.0')), '1.2.4.0', '1.2.4.0')).toBe(false);
|
||||
expect(stampOnlyText({ before, after: lines(key('1.2.3.0'), key('1.2.4.0')), oldVersion: '1.2.4.0', newVersion: '1.2.4.0', templates })).toBe(false);
|
||||
});
|
||||
|
||||
test('planCarry names at most CARRY_NAMED_PATHS paths', () => {
|
||||
const many = Array.from({ length: CARRY_NAMED_PATHS + 3 }, (_, i) => `src/f${i}.ts`);
|
||||
const plan = planCarry({ from: 'a'.repeat(40), head: 'b'.repeat(40), changed: many.map(p => ({ path: p, class: 'product' as const })), productPatchId: { from: 'x', head: 'y' }, testPatchId: { from: '', head: '' }, changedProduct: many, mergeCommits: 0, rangeDiff: '' });
|
||||
|
||||
@@ -329,4 +329,58 @@ describe('a branch-added row left at the fork version after the base moved (GST-
|
||||
expect(receipt.out).toContain("id: 'base_row'");
|
||||
expect(receipt.out).not.toContain("id: 'mine'");
|
||||
});
|
||||
|
||||
// Value: protects=a base row whose text a branch row duplicates keeps the old-version floor in the tree receipt
|
||||
// (GST-979, the #3862 outside voice); fails_when=the side matches by text, so a hand move of the base copy reads as
|
||||
// same-modulo-stamps; seam=lib/stamp-only.ts branchSides positions
|
||||
test('a base row whose text the branch duplicates still refuses a hand move; the branch copy\'s own move passes (GST-979)', () => {
|
||||
const repo = makeFixtureRepo({ prefix: 'restamp-dup-row-', policy: receiptPolicy(true) }); repos.push(repo);
|
||||
write(repo, NOTICE, notice([row('1.2.3.0', 'base_row')]));
|
||||
repo.git('add', '-A'); repo.git('commit', '-qm', 'notice rows'); repo.git('push', '-q', 'origin', 'main');
|
||||
makePr(repo, 'pr-dup');
|
||||
expect(restamp(repo, ['--version', '1.2.4.0']).status).toBe(EXIT.ok);
|
||||
repo.git('commit', '-qam', 'stamp 1.2.4.0');
|
||||
write(repo, NOTICE, notice([row('1.2.3.0', 'base_row'), '', '', row('1.2.3.0', 'base_row')]));
|
||||
repo.git('commit', '-qam', 'dup: the same row text at the fork version');
|
||||
const gated = repo.git('rev-parse', 'HEAD');
|
||||
expect(restamp(repo, ['--version', '1.2.5.0']).status).toBe(EXIT.ok);
|
||||
expect(readFile(repo, NOTICE)).toBe(notice([row('1.2.3.0', 'base_row'), '', '', row('1.2.5.0', 'base_row')]));
|
||||
repo.git('commit', '-qam', 'stamp dup');
|
||||
const own = runBin(repo, 'gstack-tree-receipt', ['--gated', gated, '--base', 'main']);
|
||||
expect(own.out, own.err).toContain('TREE: same-modulo-stamps');
|
||||
write(repo, NOTICE, notice([row('1.2.5.0', 'base_row'), '', '', row('1.2.5.0', 'base_row')]));
|
||||
repo.git('commit', '-qam', 'move the base copy by hand');
|
||||
const hand = runBin(repo, 'gstack-tree-receipt', ['--gated', gated, '--base', 'main']);
|
||||
expect(hand.out, hand.err).toContain('TREE: changed');
|
||||
});
|
||||
|
||||
// Value: protects=the receipt carry follows a stamp that moves the branch's own fork-version row, the move the writer
|
||||
// makes since GST-963 (GST-980); fails_when=the carry keeps the branch-added row's bytes in both product patch-ids and
|
||||
// refuses RECEIPT_CARRY_PRODUCT_CHANGED; seam=bin/gstack-ship-receipt carry walk
|
||||
test('the carry follows a stamp that moves the branch\'s own fork-version row, and still refuses a product edit beside it (GST-980)', () => {
|
||||
const repo = makeFixtureRepo({ prefix: 'restamp-carry-row-', policy: policy(true) }); repos.push(repo);
|
||||
write(repo, NOTICE, notice([row('1.2.3.0', 'base_row')]));
|
||||
fs.rmSync(path.join(repo.work, 'package-lock.json'));
|
||||
repo.git('add', '-A'); repo.git('commit', '-qm', 'notice rows, no lockfile'); repo.git('push', '-q', 'origin', 'main');
|
||||
makePr(repo, 'pr-carry');
|
||||
expect(restamp(repo, ['--version', '1.2.4.0']).status).toBe(EXIT.ok);
|
||||
repo.git('commit', '-qam', 'stamp 1.2.4.0');
|
||||
write(repo, NOTICE, notice([row('1.2.3.0', 'base_row'), '', row('1.2.3.0', 'mine')]));
|
||||
repo.git('commit', '-qam', 'mine: claims 1.2.4.0 with a row at the fork version');
|
||||
const body = path.join(repo.root, 'body.md');
|
||||
fs.writeFileSync(body, '## Summary\n');
|
||||
const w = runBin(repo, 'gstack-ship-receipt', ['write', '--pr', '3', '--base', 'main', '--session-kind', 'x', '--body', body]);
|
||||
expect(w.status, w.err).toBe(EXIT.ok);
|
||||
expect(restamp(repo, ['--version', '1.2.5.0']).status).toBe(EXIT.ok);
|
||||
expect(readFile(repo, NOTICE)).toBe(notice([row('1.2.3.0', 'base_row'), '', row('1.2.5.0', 'mine')]));
|
||||
repo.git('commit', '-qam', 'stamp 1.2.5.0');
|
||||
const carried = runBin(repo, 'gstack-ship-receipt', ['carry', '--body', body, '--base', 'main']);
|
||||
expect(carried.err).not.toContain('RECEIPT_CARRY_PRODUCT_CHANGED');
|
||||
expect(carried.out, carried.err).toContain('CARRY: carried');
|
||||
write(repo, 'src.ts', 'export const x = 1;\n');
|
||||
repo.git('add', '-A'); repo.git('commit', '-qm', 'a product change after the stamp');
|
||||
const changed = runBin(repo, 'gstack-ship-receipt', ['carry', '--body', body, '--base', 'main']);
|
||||
expect(changed.status).toBe(EXIT.refused);
|
||||
expect(changed.err).toContain('RECEIPT_CARRY_PRODUCT_CHANGED');
|
||||
});
|
||||
});
|
||||
@@ -248,6 +248,24 @@ describe('a restamp after a lost slot (GST-895, GBRA-218, gbrain #8134)', () =>
|
||||
const e = finish(edited.f, edited.reviewed, edited.post, ['--reviewed-base', edited.reviewedBase]);
|
||||
expect([e.status, lineOf(e.stdout, 'STOP')]).toEqual([3, behaviorStop]);
|
||||
});
|
||||
|
||||
// Value: protects=with a kept VERSION claim, the restamp's move of the branch's own fork-version row routes as metadata,
|
||||
// and a base row moved the same way still stops (GST-980); fails_when=stampedFiles returns nothing at equal versions
|
||||
// and finish routes the row as behavior; seam=lib/ship-bin-common.ts stampedFiles
|
||||
test('a kept VERSION claim: the moved fork-version row is metadata; a moved base row stops (GST-980)', () => {
|
||||
const kept = (rows: [string, string][]) => {
|
||||
const f = fx({ '.gstack/ship-policy.json': policy, VERSION: '1.0.0.0\n', 'src/notice.ts': notice(['0.9.0.0', 'old'], ['1.0.0.0', 'base']) },
|
||||
{ VERSION: '1.0.1.0\n', 'src/notice.ts': notice(['0.9.0.0', 'old'], ['1.0.0.0', 'base'], ['1.0.0.0', 'mine']) });
|
||||
const reviewed = wtree(f);
|
||||
const post = snapshot(f);
|
||||
f.write({ 'src/notice.ts': notice(...rows) }); f.commit('stamp, VERSION kept');
|
||||
return finish(f, reviewed, post, ['--reviewed-base', mergeBase(f)]);
|
||||
};
|
||||
const own = kept([['0.9.0.0', 'old'], ['1.0.0.0', 'base'], ['1.0.1.0', 'mine']]);
|
||||
expect(lineOf(own.stdout, 'ROUTE')).toEqual(expect.stringMatching(/ behavior=0 docs=0 metadata=1 tests=0 /));
|
||||
const base = kept([['0.9.0.0', 'old'], ['1.0.1.0', 'base'], ['1.0.0.0', 'mine']]);
|
||||
expect([base.status, lineOf(base.stdout, 'STOP')]).toEqual([3, behaviorStop]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('adjacent base edits (GST-384, #3631, founderstar#441)', () => {
|
||||
|
||||
@@ -105,6 +105,22 @@ describe('tree receipt: stamps, stamp-sensitive gates and negative fixtures', ()
|
||||
expect(width.out).toMatch(/^TREE: changed \(lib\/policy\.ts\)$/m);
|
||||
});
|
||||
|
||||
// Value: protects=the fork-version bound reads the branch's own line by its position, so a base copy of the same text
|
||||
// is still floored at the old version, and when the slot keeps its VERSION only the branch's own lines may move
|
||||
// (GST-979); fails_when=stampOnlyLine matches the branch's lines by text alone or takes the old-version floor at equal
|
||||
// versions; seam=lib/stamp-only.ts stampOnlyLine
|
||||
test('the fork-version bound holds only at the branch line\'s own position, and a kept VERSION moves only the branch\'s own lines (GST-979)', () => {
|
||||
const tpl = ["since: '{version}'"];
|
||||
const row = " { since: '1.2.3.0', id: 'x' },";
|
||||
const moved = row.replace('1.2.3.0', '1.2.5.0');
|
||||
const side = { fork: '1.2.3.0', added: new Set([row]), at: new Set([10]) };
|
||||
expect(stampOnlyLine(row, moved, '1.2.4.0', '1.2.5.0', tpl, side, 10)).toBe(true);
|
||||
expect(stampOnlyLine(row, moved, '1.2.4.0', '1.2.5.0', tpl, side, 2)).toBe(false);
|
||||
const mine = { fork: '1.2.3.0', added: new Set(["mine since: '1.2.3.0'"]), at: new Set([5]) };
|
||||
expect(stampOnlyLine("base since: '1.3.0.0'", "base since: '1.2.4.0'", '1.2.4.0', '1.2.4.0', tpl, mine, 1)).toBe(false);
|
||||
expect(stampOnlyLine("mine since: '1.2.3.0'", "mine since: '1.2.4.0'", '1.2.4.0', '1.2.4.0', tpl, mine, 5)).toBe(true);
|
||||
});
|
||||
|
||||
test('negative fixture: a dependency pin equal to the old version is `changed`, not a stamp, even inside stamp_paths', () => {
|
||||
const repo = fixture();
|
||||
const g = gated(repo);
|
||||
|
||||
Reference in new issue
Block a user