Skip to content

Render diffs for committed changes, not just uncommitted ones - #60

Merged
phine-apps merged 1 commit into
phine-apps:mainfrom
wolframarnold:fix/commit-to-commit-rendered-diff
Jul 23, 2026
Merged

Render diffs for committed changes, not just uncommitted ones#60
phine-apps merged 1 commit into
phine-apps:mainfrom
wolframarnold:fix/commit-to-commit-rendered-diff

Conversation

@wolframarnold

Copy link
Copy Markdown
Contributor

Fixes #59.

Opening a committed Markdown file's diff — selecting a commit in the Source Control Graph and clicking one of its changed .md files — showed No Markdown changes are available for this file, even though the diff button appeared in the editor title bar.

Why it happened

The refs identifying each side were discarded before any comparison was resolved:

Location What it did
commandTarget.ts toFileBackedUri Rewrote a git: URI to its file: path, dropping query.ref. The vscode.Uri branch of getCommandTarget applied that rewrite and hardcoded comparisonHint: "auto", so a commit SHA never survived argument parsing.
gitDiffResolver.ts getComparisonHintFromUris Recognized only "" (index), "~" (working tree), and HEAD. GitComparisonHint had no value able to carry a commit at all.
extension.ts showDiff Never read the originalUri / modifiedUri that getCommandTarget already returns — both sides were available and then thrown away.

The comparison fell back to HEAD vs working tree, which for a clean file resolves to cleanHeadToWorkingTree and is rejected by isActionableSingleFileComparison — the message the user saw.

What changed

Deliberately additive: no existing comparison path changes behavior. A comparison is routed to the new code only when a side names a ref that is not "", "~", or HEAD.

  • isRevisionRef distinguishes a commit / tag / branch ref from the three well-known refs the existing modes already express. shortenRef and describeComparisonSide build the labels.
  • getRevisionComparison detects a comparison naming a revision. getActiveDiffTabUriPair recovers the counterpart revision from the focused diff tab via TabInputTextDiff, because the editor title bar passes only the active resource.
  • showRevisionDiff renders the two revisions directly. Content already resolves through the Git extension's content provider (openTextDocument on a git: URI), so only the routing was missing.
  • refersToSameFile guards the tab fallback so an unrelated diff tab cannot hijack a command aimed at another file.
  • Small cleanups: normalizeFsPath is exported and reused instead of duplicating path normalization, and getComparisonHintFromUris now uses the new ref constants instead of bare "" / "~" literals.

Panel titles read Markdown Diff: example.md (086bdd7 ↔ 010c6f6). Full 40-character SHAs are shortened to 7; branch and tag names are shown as-is. Since both sides of a commit diff share a basename, the existing basename labeling would have produced two identical labels.

This also fixes the quieter half of the bug: when the file had uncommitted edits, the command previously showed the working-tree diff instead of the commit diff that was clicked.

Tests

15 new integration tests in commandTarget.test.ts and gitDiffResolver.test.ts, written before the implementation. Suite goes 22 → 47 passing; test:unit stays at 150. pnpm run pretest is clean.

Coverage: ref classification (commit / tag / branch / HEAD / index / working tree / absent), SHA shortening, side labeling, revision-pair detection including every negative case, same-file matching, and reading both sides of a real diff editor opened with vscode.diff.

Separately, I verified the fix end-to-end against a scratch git repository with two commits: both revisions read correctly through the git content provider, and the command produced a panel titled Markdown Diff: example.md (086bdd7 ↔ 010c6f6). With the source changes reverted and that same check kept, no panel is created at all — confirming it reproduces the reported bug. That check is not included here because the test harness does not open a workspace folder, and adding one to runTest.ts felt out of scope for a bug fix.

Notes for review

  • The multi-file diff editor already worked through a different path. When a commit is opened as a multi-file diff, VS Code passes multiDiffEditorOriginalUri / multiFileDiffEditorModifiedUri, which extractComparisonUris already collects — so those land on the argument path rather than the tab fallback. Both are handled.
  • canShowRenderedDiff is left as is, and it is still inconsistent. The editor/title menu shows the button via isInDiffEditor, while the context key stays false for a clean committed file. The title-bar button now works, but the right-click editor context menu still will not offer the command there, because its when clause requires the context key. Fixing that means teaching updateCanShowRenderedDiffContext about revision comparisons too — happy to add it here or in a follow-up, whichever you prefer.
  • No changelog entry, since entries appear to be written per release — tell me if you would like one added.
  • No new user-facing strings, so package.nls.* and the l10n bundles are untouched.

Opening a committed Markdown file's diff - selecting a commit in the
Source Control Graph and clicking one of its changed .md files - showed
"No Markdown changes are available for this file" even though the diff
button appeared in the editor title bar. Reviewing a document change
after the fact is exactly when a rendered diff is most useful, so this
was the one case where the extension could not help.

The refs identifying each side were being discarded before any
comparison was resolved:

- `toFileBackedUri` rewrites a `git:` URI to its `file:` path, dropping
  `query.ref`. `getCommandTarget`'s `vscode.Uri` branch applied that
  rewrite and hardcoded a hint of "auto", so a commit SHA never survived
  argument parsing.
- `getComparisonHintFromUris` only recognized the index (""), the working
  tree ("~"), and HEAD; `GitComparisonHint` had no value able to carry a
  commit at all.
- `showDiff` never read the `originalUri`/`modifiedUri` that
  `getCommandTarget` already returns, so both sides were available and
  then thrown away.

The comparison therefore fell back to HEAD versus working tree, which for
a clean file resolves to `cleanHeadToWorkingTree` and is rejected as not
actionable - the message the user saw.

Changes:

- Add `isRevisionRef` to tell a commit, tag, or branch ref apart from the
  three well-known refs the existing modes already express, plus
  `shortenRef` and `describeComparisonSide` to label each side.
- Add `getRevisionComparison` to detect a comparison naming a revision,
  and `getActiveDiffTabUriPair` to recover the counterpart revision from
  the focused diff tab, since the editor title bar passes only the active
  resource.
- Route such comparisons in `showDiff` to a new `showRevisionDiff`, which
  renders the two revisions directly. Content already resolves through
  the Git extension's content provider, so only the routing was missing.
- Guard the tab fallback with `refersToSameFile` so an unrelated diff tab
  cannot hijack a command aimed at another file.
- Export `normalizeFsPath` and reuse it rather than duplicating path
  normalization, and use the new ref constants in
  `getComparisonHintFromUris`.

This also fixes the quieter half of the bug: when the file had
uncommitted edits the command previously showed the working-tree diff
instead of the commit diff that was clicked.

Fixes phine-apps#59

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@phine-apps
phine-apps merged commit b106880 into phine-apps:main Jul 23, 2026
1 check passed
phine-apps pushed a commit that referenced this pull request Jul 24, 2026
After #60 the title-bar button renders a committed revision diff, but
right-clicking inside that diff editor still did not offer "Show Rich
Markdown Diff" — the button uses the built-in `isInDiffEditor` context,
while the context menu is gated on `rich-markdown-diff.canShowRenderedDiff`,
and the two disagreed.

The context key is computed by `updateRenderedDiffContext`, which — like
the original #59 bug — rebuilds the active editor's `git:` URI as a plain
`file:` URI, dropping the commit ref. The comparison then resolves as
HEAD-vs-working-tree, a committed blob is not dirty, and
`isActionableSingleFileComparison` returns false, so the key stayed off.

- Add `getActiveRevisionComparison`, reusing #60's `getActiveDiffTabUriPair`
  and `getRevisionComparison`, to report the revision diff the active tab
  is showing.
- In `updateRenderedDiffContext`, short-circuit to canShow = true when the
  active tab is a markdown revision diff, before the ref-dropping fallback
  runs. The markdown-path check stops a non-markdown diff in another editor
  group from enabling the key.
- Factor the repeated "set the context key only if it changed, and only if
  this update has not been superseded" logic into `setCanShowRenderedDiff`,
  and use it at the existing call sites.

Fixes #61

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
phine-apps added a commit that referenced this pull request Jul 24, 2026
…diff

Hi @wolframarnold!

Awesome job again! Thank you so much for following up on your previous PR (#60) with this one.

Adding support to trigger the Rendered Diff from the editor context menu makes the workflow smoother when digging through commit history. I also really appreciate the clean implementation of the asynchronous generation guard—that's a very smart way to prevent race conditions.

I've run the test suite and everything looks pristine. Merging this right away.

Thank you for your continued support and for making this project so much better!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants