fix(workspace-changes): count an appended line as one addition
The file tools persist both sides of a hunk without a trailing newline, so re-diffing an append read the previous last line as rewritten and reported +2 -1 for +1 -0. Both sides are line-terminated before the diff, the rule the per-call diff card already applies.
This commit is contained in:
1 parent
8f0cdc9a98
commit
da6e3f9edf
3 files changed
+19
-3
No files matched your search
@@ -39,10 +39,19 @@ export function parseNumstat(output: string): NumstatEntry[] {
|
||||
return entries
|
||||
}
|
||||
|
||||
/**
|
||||
* A side's text with every line terminated, so the last line compares by
|
||||
* content alone: the file tools persist hunk sides without a trailing newline,
|
||||
* and the same rule reads empty text as no lines rather than one empty line.
|
||||
*/
|
||||
function terminated(text: string): string {
|
||||
return text === '' || text.endsWith('\n') ? text : `${text}\n`
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum the added and deleted lines over recorded hunks. Context lines appear on
|
||||
* both sides of a hunk and cancel out; a hunk without prior text counts every
|
||||
* line as added.
|
||||
* line as added; a trailing newline never counts as a changed line.
|
||||
* @param diffs - the applied hunks a file tool persisted with its result.
|
||||
* @returns line totals for one file.
|
||||
*/
|
||||
@@ -50,7 +59,7 @@ export function hunkLineCounts(diffs: readonly FileDiff[]): { added: number; del
|
||||
let added = 0
|
||||
let deleted = 0
|
||||
for (const diff of diffs) {
|
||||
const patch = structuredPatch('', '', diff.oldText ?? '', diff.newText)
|
||||
const patch = structuredPatch('', '', terminated(diff.oldText ?? ''), terminated(diff.newText))
|
||||
for (const hunk of patch.hunks) {
|
||||
for (const line of hunk.lines) {
|
||||
if (line.startsWith('+')) added += 1
|
||||
|
||||
@@ -30,6 +30,13 @@ describe('hunkLineCounts', () => {
|
||||
{ path: 'a', oldText: null, newText: 'x\ny\n' },
|
||||
])).toEqual({ added: 4, deleted: 1 })
|
||||
})
|
||||
|
||||
it('appends a line after an unterminated last line without counting that line as rewritten', () => {
|
||||
// The file tools persist hunk sides without a trailing newline; an append reads +1 -0, not +2 -1.
|
||||
expect(hunkLineCounts([{ path: 'a', oldText: 'last', newText: 'last\nadded' }])).toEqual({ added: 1, deleted: 0 })
|
||||
expect(hunkLineCounts([{ path: 'a', oldText: 'last\n', newText: 'last\nadded\n' }])).toEqual({ added: 1, deleted: 0 })
|
||||
expect(hunkLineCounts([{ path: 'a', oldText: 'gone', newText: '' }])).toEqual({ added: 0, deleted: 1 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('fileDiffsOf', () => {
|
||||
|
||||
@@ -259,7 +259,7 @@ describe('workspace-changes without a repository', () => {
|
||||
expect(changes(ctx, session)).toEqual([{
|
||||
turn: 1, cwd, total: 2,
|
||||
files: [
|
||||
{ path: 'existing.txt', display: 'existing.txt', added: 4, deleted: 2 },
|
||||
{ path: 'existing.txt', display: 'existing.txt', added: 3, deleted: 1 },
|
||||
{ path: join(await realpath(outside), 'note.txt'), display: `~/${outside.slice(homedir().length + 1)}/note.txt`, added: 2, deleted: 0 },
|
||||
],
|
||||
}])
|
||||
|
||||
Reference in new issue
Block a user