Skip to content

Commit 30729a2

Browse files
committed
Prevent Git preferences from hiding review changes
1 parent 537199e commit 30729a2

3 files changed

Lines changed: 11 additions & 3 deletions

File tree

‎docs/implementation/build-step-1.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Started from PR #1 and updated to its design commit `2229e88`. Work follows the
2727

2828
`npm test` runs schema fixtures and real Git repositories: the documented invalid plans; projected add/edit/rename/delete chains; bad dependencies/paths; malformed suggestions; stale revisions; two owners in one hunk; forged trailers; out-of-scope changes; pure deletions; overlapping edits; reverted work; all six non-text change kinds; literal filenames; clean rebases with remapped ledger; stale checks/dependents; whitespace/context changes; assignment and duplicate-copy expiry; and rename provenance when final rename detection is lost.
2929

30-
A focused independent review found BOM-only changes could disappear because the default UTF-8 decoder strips the mark. A real-Git regression first failed, then passed with BOM-preserving decoding. Invalid UTF-8 filenames fail instead of being silently replaced.
30+
A focused independent review found BOM-only changes could disappear because the default UTF-8 decoder strips the mark. A real-Git regression first failed, then passed with BOM-preserving decoding. Invalid UTF-8 filenames fail instead of being silently replaced. A separate failing fixture showed `diff.ignoreSubmodules=all` could hide gitlinks; reads now force submodule visibility and repository-wide paths, with regression coverage for relative-diff settings too.
3131

3232
The rename case first failed (moved lines became Unplanned), then passed after the provenance fix. `npm run typecheck` checks all source and tests. The public examples and shared schema definitions are checked on every test run.
3333

‎git/history.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export function readHistory(repo: string, baseRef: string, headRef = 'HEAD'): Hi
3333
return { oid, mode, text: blobs.get(oid)! };
3434
};
3535
const diff = (from: string, to: string, contexts: boolean): FileDelta[] => {
36-
const raw = run('diff', '--raw', '-z', '--no-abbrev', '--no-ext-diff', '--no-textconv', '-M', from, to, '--');
36+
const raw = run('diff', '--ignore-submodules=none', '--no-relative', '--raw', '-z', '--no-abbrev', '--no-ext-diff', '--no-textconv', '-M', from, to, '--');
3737
const fields = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw).split('\0');
3838
const result: FileDelta[] = [];
3939
for (let i = 0; i < fields.length && fields[i];) {
@@ -48,7 +48,7 @@ export function readHistory(repo: string, baseRef: string, headRef = 'HEAD'): Hi
4848
if (contexts && (before?.text !== null || after?.text !== null)) {
4949
const paths = [...new Set([oldPath, newPath].filter((path): path is string => path !== null))];
5050
// Literal pathspecs preserve filenames containing Git pathspec metacharacters.
51-
const patch = run('diff', '--no-ext-diff', '--no-textconv', '--no-color', '--unified=0', '-M', from, to, '--', ...paths.map(path => `:(literal)${path}`)).toString();
51+
const patch = run('diff', '--ignore-submodules=none', '--no-relative', '--no-ext-diff', '--no-textconv', '--no-color', '--unified=0', '-M', from, to, '--', ...paths.map(path => `:(literal)${path}`)).toString();
5252
for (const line of patch.split('\n')) {
5353
const hunk = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/.exec(line);
5454
if (hunk) ranges.push({ oldStart: +hunk[1]!, oldCount: +(hunk[2] ?? 1), newStart: +hunk[3]!, newCount: +(hunk[4] ?? 1), name: hunk[5]!.trim() });

‎test/history.test.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ it('represents binary, executable, empty, rename, symlink and submodule changes'
5353
renameSync(join(f.dir, 'rename.txt'), join(f.dir, 'renamed.txt')); symlinkSync('a.txt', join(f.dir, 'link'));
5454
f.git('add', '-A'); f.git('update-index', '--add', '--cacheinfo', `160000,${f.base},submodule`);
5555
f.git('commit', '-m', 'File changes'); f.ledger.set(f.git('rev-parse', 'HEAD'), 'P1');
56+
f.git('config', 'diff.ignoreSubmodules', 'all');
5657
const cards = f.segments().filter(s => s.kind === 'file');
5758
expect(cards.map(s => s.path).sort()).toEqual(['a.txt', 'binary.dat', 'empty', 'link', 'renamed.txt', 'submodule']);
5859
expect(cards.every(s => s.row === 'P1')).toBe(true);
@@ -128,3 +129,10 @@ it('does not hide a UTF-8 byte-order-mark-only change', () => {
128129
const f = fixture(); f.write('a.txt', '\uFEFFone\ntwo\nthree\n'); f.commit('P1');
129130
expect(f.segments().some(s => s.operation === '+' && s.content.startsWith('\uFEFF'))).toBe(true);
130131
});
132+
it('reads the whole repository even when called from a subdirectory with relative diffs configured', () => {
133+
const f = fixture({ 'a.txt': 'before\n', 'sub/b.txt': 'before\n' });
134+
f.write('a.txt', 'after\n'); f.write('sub/b.txt', 'after\n'); f.commit('P1');
135+
f.git('config', 'diff.relative', 'true');
136+
const parts = linkHistory(f.plan, readHistory(join(f.dir, 'sub'), f.base), f.ledger);
137+
expect(new Set(parts.map(s => s.path))).toEqual(new Set(['a.txt', 'sub/b.txt']));
138+
});

0 commit comments

Comments
 (0)