Skip to content

Commit 51da9a8

Browse files
committed
fix: Windows path handling with Cygwin, WSL, Git Bash
1 parent 1de9e8a commit 51da9a8

6 files changed

Lines changed: 42 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ All notable user-visible changes to Hunk are documented in this file.
1010

1111
### Fixed
1212

13+
- Fixed Windows launches from Cygwin, Git Bash, and WSL-style VCS paths by normalizing Unix-style repo roots before reusing them as subprocess working directories or filesystem roots.
14+
1315
## [0.15.1] - 2026-06-09
1416

1517
### Fixed

src/core/git.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { join } from "node:path";
33
import { HunkUserError } from "./errors";
44
import { escapeUntrackedPatchPath } from "./patch/normalize";
55
import type { VcsCommandInput, ShowCommandInput, StashShowCommandInput } from "./types";
6+
import { normalizeWindowsPath } from "../lib/windowsPath";
67

78
export type GitBackedInput = VcsCommandInput | ShowCommandInput | StashShowCommandInput;
89

@@ -642,11 +643,12 @@ export function resolveGitRepoRoot(
642643
input: GitBackedInput,
643644
options: Omit<RunGitTextOptions, "input" | "args"> = {},
644645
) {
645-
return runGitText({
646+
const repoRoot = runGitText({
646647
input,
647648
args: ["rev-parse", "--show-toplevel"],
648649
...options,
649650
}).trim();
651+
return normalizeWindowsPath(repoRoot);
650652
}
651653

652654
/** Resolve one commit-ish ref to the exact commit object used for later blob reads. */

src/core/jj.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { HunkUserError } from "./errors";
22
import type { VcsCommandInput, ShowCommandInput } from "./types";
3+
import { normalizeWindowsPath } from "../lib/windowsPath";
34

45
export type JjBackedInput = VcsCommandInput | ShowCommandInput;
56

@@ -182,9 +183,10 @@ export function resolveJjRepoRoot(
182183
input: JjBackedInput,
183184
options: Omit<RunJjTextOptions, "input" | "args"> = {},
184185
) {
185-
return runJjText({
186+
const repoRoot = runJjText({
186187
input,
187188
args: ["root"],
188189
...options,
189190
}).trim();
191+
return normalizeWindowsPath(repoRoot);
190192
}

src/core/sl.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from "node:fs";
22
import { join } from "node:path";
33
import { HunkUserError } from "./errors";
44
import type { VcsCommandInput, ShowCommandInput } from "./types";
5+
import { normalizeWindowsPath } from "../lib/windowsPath";
56

67
export type SlBackedInput = VcsCommandInput | ShowCommandInput;
78

@@ -265,9 +266,10 @@ export function resolveSlRepoRoot(
265266
input: SlBackedInput,
266267
options: Omit<RunSlTextOptions, "input" | "args"> = {},
267268
) {
268-
return runSlText({
269+
const repoRoot = runSlText({
269270
input,
270271
args: ["root"],
271272
...options,
272273
}).trim();
274+
return normalizeWindowsPath(repoRoot);
273275
}

src/lib/windowsPath.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { describe, expect, test } from "bun:test";
2+
import { normalizeWindowsPath } from "./windowsPath";
3+
4+
describe("normalizeWindowsPath", () => {
5+
test("normalizes Unix-style Windows paths for native subprocess cwd", () => {
6+
expect(normalizeWindowsPath("/cygdrive/c/work/repo", "win32")).toBe("C:\\work\\repo");
7+
expect(normalizeWindowsPath("/c/work/repo", "win32")).toBe("C:\\work\\repo");
8+
expect(normalizeWindowsPath("/mnt/c/work/repo", "win32")).toBe("C:\\work\\repo");
9+
expect(normalizeWindowsPath("/c:/work/repo", "win32")).toBe("C:\\work\\repo");
10+
expect(normalizeWindowsPath("/home/project", "win32")).toBe("/home/project");
11+
expect(normalizeWindowsPath("/cygdrive/c/work/repo", "linux")).toBe("/cygdrive/c/work/repo");
12+
});
13+
});

src/lib/windowsPath.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/** Convert Unix-style Windows paths to native paths usable as Bun cwd. */
2+
export function normalizeWindowsPath(path: string, platform = process.platform) {
3+
if (platform !== "win32") {
4+
return path;
5+
}
6+
7+
const normalized = path
8+
// Some Windows tools can report slash-prefixed drive paths as `/C:/...`.
9+
.replace(/^\/([a-zA-Z]):(?:[\\/]|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
10+
// Git Bash/MSYS2 commonly reports drive paths as `/c/...`.
11+
.replace(/^\/([a-zA-Z])(?:[\\/]|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
12+
// Cygwin commonly reports drive paths as `/cygdrive/c/...`.
13+
.replace(/^\/cygdrive\/([a-zA-Z])(?:[\\/]|$)/, (_, drive) => `${drive.toUpperCase()}:/`)
14+
// WSL-style paths are commonly reported as `/mnt/c/...`.
15+
.replace(/^\/mnt\/([a-zA-Z])(?:[\\/]|$)/, (_, drive) => `${drive.toUpperCase()}:/`);
16+
17+
return normalized === path ? path : normalized.replaceAll("/", "\\");
18+
}

0 commit comments

Comments
 (0)