From 93bed4504ac4036235d5540490ba1b667a338a03 Mon Sep 17 00:00:00 2001 From: jamin-peckham-sp Date: Tue, 21 Jul 2026 11:20:48 -0500 Subject: [PATCH] fix: resolve file compare paths against the git root vscode.workspace.asRelativePath is workspace-folder relative, so nested workspace folders produced wrong working-copy URIs for Compare with Commit/Branch/Tag. Use path.relative to the git repository root instead. Co-authored-by: Cursor --- src/commands/compareFileWithRev.ts | 6 +++-- src/git/relPath.ts | 18 ++++++++++++++ src/test/unit/relPath.test.ts | 39 ++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 src/git/relPath.ts create mode 100644 src/test/unit/relPath.test.ts diff --git a/src/commands/compareFileWithRev.ts b/src/commands/compareFileWithRev.ts index 78e759b..4f05cc5 100644 --- a/src/commands/compareFileWithRev.ts +++ b/src/commands/compareFileWithRev.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import { REV_KINDS, TITLE_PREFIX } from "../constants"; import type { GitRepo } from "../git/GitRepo"; +import { toRepoRelPath } from "../git/relPath"; import type { Sha } from "../git/types"; import type { Result } from "../result"; import { err, ok } from "../result"; @@ -69,6 +70,7 @@ const handler = async ({ void vscode.window.showWarningMessage(NOT_IN_REPO); return; } + const repoRoot = vsRepo.rootUri.fsPath; const repo = buildRepo(deps.runner, vsRepo); const sha = await pickShaForFile({ repo, source, output: deps.output }); if (!sha.ok) { @@ -77,8 +79,8 @@ const handler = async ({ await openDiff({ revA: sideAFromSha(sha.value), revB: { kind: REV_KINDS.workingCopy }, - repoRoot: vsRepo.rootUri.fsPath, - relPath: vscode.workspace.asRelativePath(target, false), + repoRoot, + relPath: toRepoRelPath({ repoRoot, fileFsPath: target.fsPath }), }); }; diff --git a/src/git/relPath.ts b/src/git/relPath.ts new file mode 100644 index 0000000..a8a57ce --- /dev/null +++ b/src/git/relPath.ts @@ -0,0 +1,18 @@ +import * as path from "node:path"; + +const GIT_PATH_SEP = "/"; + +/** + * File path relative to the git repository root, with forward slashes. + * + * Must NOT use workspace-folder-relative paths: the VS Code workspace folder + * can be a subdirectory of the git root (nested package / multi-root), and + * joining that with the git root produces a wrong absolute path. + */ +export const toRepoRelPath = ({ + repoRoot, + fileFsPath, +}: { + repoRoot: string; + fileFsPath: string; +}): string => path.relative(repoRoot, fileFsPath).split(path.sep).join(GIT_PATH_SEP); diff --git a/src/test/unit/relPath.test.ts b/src/test/unit/relPath.test.ts new file mode 100644 index 0000000..dc00b5a --- /dev/null +++ b/src/test/unit/relPath.test.ts @@ -0,0 +1,39 @@ +import { strict as assert } from "node:assert"; +import * as path from "node:path"; +import { toRepoRelPath } from "../../git/relPath"; + +describe("toRepoRelPath", () => { + it("keeps every segment under the git root when the workspace folder is a nested package", () => { + const repoRoot = "/repo"; + const fileFsPath = "/repo/scripts/pkg/pkg/module.py"; + + // Reproduce the product bug: asRelativePath(workspace=.../scripts/pkg) + // yields "pkg/module.py", then path.join(gitRoot, that) is wrong. + const nestedWorkspace = "/repo/scripts/pkg"; + const workspaceRel = path.relative(nestedWorkspace, fileFsPath); + assert.equal(workspaceRel, "pkg/module.py"); + assert.equal(path.join(repoRoot, workspaceRel), "/repo/pkg/module.py"); + + assert.equal(toRepoRelPath({ repoRoot, fileFsPath }), "scripts/pkg/pkg/module.py"); + }); + + it("returns a single-segment path for a file at the repo root", () => { + assert.equal( + toRepoRelPath({ + repoRoot: "/repo", + fileFsPath: "/repo/README.md", + }), + "README.md" + ); + }); + + it("preserves nested directories under the repo root", () => { + assert.equal( + toRepoRelPath({ + repoRoot: "/repo", + fileFsPath: "/repo/dir/c.txt", + }), + "dir/c.txt" + ); + }); +});