Skip to content

Commit 537199e

Browse files
committed
Preserve BOM changes in Git review evidence
1 parent c9e5260 commit 537199e

3 files changed

Lines changed: 9 additions & 2 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ 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.
31+
3032
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.
3133

3234
## Remaining gates

‎git/history.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ export function readHistory(repo: string, baseRef: string, headRef = 'HEAD'): Hi
2727
if (!blobs.has(oid)) {
2828
const data = run('cat-file', 'blob', oid);
2929
let text: string | null = null;
30-
if (!data.includes(0)) { try { text = new TextDecoder('utf-8', { fatal: true }).decode(data); } catch { /* binary */ } }
30+
if (!data.includes(0)) { try { text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(data); } catch { /* binary */ } }
3131
blobs.set(oid, text);
3232
}
3333
return { oid, mode, text: blobs.get(oid)! };
3434
};
3535
const diff = (from: string, to: string, contexts: boolean): FileDelta[] => {
36-
const fields = run('diff', '--raw', '-z', '--no-abbrev', '--no-ext-diff', '--no-textconv', '-M', from, to, '--').toString().split('\0');
36+
const raw = run('diff', '--raw', '-z', '--no-abbrev', '--no-ext-diff', '--no-textconv', '-M', from, to, '--');
37+
const fields = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(raw).split('\0');
3738
const result: FileDelta[] = [];
3839
for (let i = 0; i < fields.length && fields[i];) {
3940
const match = /^:(\d+) (\d+) ([0-9a-f]+) ([0-9a-f]+) ([A-Z])\d*$/.exec(fields[i++]!);

‎test/history.test.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,7 @@ it('attributes unchanged moved lines when final rename detection is lost', () =>
124124
const parts = f.segments();
125125
expect(parts.filter(s => s.kind === 'text').every(s => s.row !== 'Unplanned')).toBe(true);
126126
});
127+
it('does not hide a UTF-8 byte-order-mark-only change', () => {
128+
const f = fixture(); f.write('a.txt', '\uFEFFone\ntwo\nthree\n'); f.commit('P1');
129+
expect(f.segments().some(s => s.operation === '+' && s.content.startsWith('\uFEFF'))).toBe(true);
130+
});

0 commit comments

Comments
 (0)