Skip to content

fix: correctly handle renamed files in the commit view (list + diffs)#189

Open
rubensa wants to merge 2 commits into
phil294:masterfrom
rubensa:fix/renamed-file-diffs
Open

fix: correctly handle renamed files in the commit view (list + diffs)#189
rubensa wants to merge 2 commits into
phil294:masterfrom
rubensa:fix/renamed-file-diffs

Conversation

@rubensa

@rubensa rubensa commented Jul 1, 2026

Copy link
Copy Markdown

Fixes #187. Fixes #131.

Supersedes #188 — that PR fixed only the file-list parsing (#187). While
testing it I found that once renamed files finally appear in the list, opening
their diff still breaks (#131), because the two bugs share the same root cause:
renames aren't tracked through to the paths used per revision. This PR fixes
both, so I'll close #188 in favour of it.

This is two commits; they can be reviewed independently.


Commit 1 — parse git's compact {old => new} rename notation (#187)

When a commit renames a directory, git reports the moved files with its
compact rename notation, e.g. src/{old => new}/file.txt. The file-list
parser (git_numstat_summary_to_changes_array in
web/src/components/CommitDiff.vue, fed by git show --numstat --summary) only
understood the full-path form old => new.

For a numstat row like src/{old => new}/file.txt it did:

path = path.split(' => ')[0]?.replaceAll('{', '') || ''
// "src/{old => new}/file.txt"
//   .split(' => ')[0]   -> "src/{old"
//   .replaceAll('{','') -> "src/old"   <-- truncated at the brace; "/file.txt" is lost

So every file renamed under the same directory collapsed onto the same
key (src/old) and the entries overwrote each other in the accumulator — only
the last survived. The --summary rename branch had the same assumption, so it
couldn't reconcile either. Result: files silently missing from the commit's
file list, and a basename that also appeared as a plain add/delete pair could
show up duplicated.

Fix: a small split_rename() helper expands git's compact form
prefix/{old => new}/suffix (and the degenerate {old => new},
dir/{ => new}/f, dir/{old => }/f, and plain old => new) into the real
old/new paths, and the entries are keyed on the real old path in both the
numstat branch and the --summary rename branch.

Commit 2 — use old/new paths for renamed files in commit diffs (#131)

Once #187 makes renamed files appear in the list, clicking one opened an empty
diff (and "View File at this Revision" opened an empty editor). A renamed file's
old name exists only in the parent commit and its new name only in the
child, but the handlers used a single path for both revisions:

uris: [
  `${hash1.value}:${filepath}`,   // parent : path
  `${hash2(filepath)}:${filepath}`, // child : SAME path  <- absent here for a rename
]

So the child side resolved a path that doesn't exist in that commit and rendered
empty.

Fix: track new_path on each FileDiff, and use the old path at
hash1 and the new path at hash2 in show_diff / show_multi_diff /
view_rev, and the new path for open_file / show_file. Non-renamed
files are unaffected because new_path === path for them.


Verification

Built and type-checked locally against a fresh checkout:

  • npm run build (vite + esbuild) — passes.
  • vue-tsc --noEmitno new errors from either change (the one remaining
    CommitDiff.vue diagnostic is pre-existing and present on the unmodified
    tree, just line-shifted).
  • Parser check on a directory rename (src/old/src/new/): before only
    1 of 2 renamed files appeared (both collapsed onto one key); after both
    appear with correct old/new paths. The loss scales with the directory — an
    N-file rename previously collapsed all N brace entries onto a single key.
  • Diff-URI check on a rename: before the child side reused the old path and
    the pane was empty; after it resolves parent:old ↔ child:new and both
    panes populate. Modified/created/deleted files produce identical URIs to
    before.

Note: npm run lint currently crashes in a fresh checkout on
src/global.d.ts (a @typescript-eslint/TS version issue, unrelated to this
change), so I couldn't run the lint gate — the type-check and build above
cover this file.

Reproduce the original bugs

git init demo && cd demo
mkdir -p src/old
printf 'a\n'    > src/old/keep.txt
printf 'a\nb\n' > src/old/edit.txt
git add -A && git commit -m init
git mv src/old src/new                 # rename the directory (both files move)
git commit -am "rename dir"

Open the rename dir commit:

rubensa added 2 commits July 1, 2026 17:41
…list

Files renamed under a renamed directory collapsed onto a single key and
overwrote each other, so only some appeared in the file list (and a plain
add/delete sibling could show duplicated). Add split_rename() to expand the
brace form to the real old/new paths instead of truncating at the '{'.
A renamed file's old name exists only in the parent commit and its new
name only in the child, but the diff/view/open handlers used a single
path for both revisions. For a rename that meant the right-hand pane
(and "View File at this Revision") resolved a path absent from that
commit and rendered empty.

Track new_path on each FileDiff and use the old path for hash1 and the
new path for hash2 in show_diff/show_multi_diff/view_rev, and the new
path for open_file/show_file. Non-renamed files are unaffected
(new_path === path).

Fixes phil294#131.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant