Skip to content

Commit 8a3b4e8

Browse files
committed
test: add git worktree tests for top-level and read
Add comprehensive tests for git worktree support: - Test toplevel() works correctly in worktrees - Test toplevel() works from subdirectories in worktrees - Test reading edit commit message from worktree
1 parent 2899525 commit 8a3b4e8

File tree

4 files changed

+185
-2
lines changed

4 files changed

+185
-2
lines changed

@commitlint/read/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@
3838
"devDependencies": {
3939
"@commitlint/test": "^20.0.0",
4040
"@commitlint/utils": "^20.0.0",
41+
"@types/fs-extra": "^11.0.3",
4142
"@types/git-raw-commits": "^2.0.3",
42-
"@types/minimist": "^1.2.4"
43+
"@types/minimist": "^1.2.4",
44+
"@types/tmp": "^0.2.5",
45+
"fs-extra": "^11.0.0",
46+
"tmp": "^0.2.1"
4347
},
4448
"dependencies": {
4549
"@commitlint/top-level": "^20.0.0",

@commitlint/read/src/read.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { test, expect } from "vitest";
22
import fs from "fs/promises";
3+
import fsExtra from "fs-extra";
34
import path from "node:path";
45
import { git } from "@commitlint/test";
56
import { x } from "tinyexec";
7+
import tmp from "tmp";
68

79
import read from "./read.js";
810

@@ -150,3 +152,49 @@ test("should not read any commits when there are no tags", async () => {
150152

151153
expect(result).toHaveLength(0);
152154
});
155+
156+
test("get edit commit message from git worktree", async () => {
157+
const tmpDir = tmp.dirSync({ keep: false, unsafeCleanup: true });
158+
const mainRepoDir = path.join(tmpDir.name, "main");
159+
const worktreeDir = path.join(tmpDir.name, "worktree");
160+
161+
// Initialize main repo
162+
await fsExtra.mkdirp(mainRepoDir);
163+
await x("git", ["init"], { nodeOptions: { cwd: mainRepoDir } });
164+
await x("git", ["config", "user.email", "test@example.com"], {
165+
nodeOptions: { cwd: mainRepoDir },
166+
});
167+
await x("git", ["config", "user.name", "test"], {
168+
nodeOptions: { cwd: mainRepoDir },
169+
});
170+
await x("git", ["config", "commit.gpgsign", "false"], {
171+
nodeOptions: { cwd: mainRepoDir },
172+
});
173+
174+
// Create initial commit in main repo
175+
await fs.writeFile(path.join(mainRepoDir, "file.txt"), "content");
176+
await x("git", ["add", "."], { nodeOptions: { cwd: mainRepoDir } });
177+
await x("git", ["commit", "-m", "initial"], {
178+
nodeOptions: { cwd: mainRepoDir },
179+
});
180+
181+
// Create a branch and worktree
182+
await x("git", ["branch", "worktree-branch"], {
183+
nodeOptions: { cwd: mainRepoDir },
184+
});
185+
await x("git", ["worktree", "add", worktreeDir, "worktree-branch"], {
186+
nodeOptions: { cwd: mainRepoDir },
187+
});
188+
189+
// Make a commit in the worktree
190+
await fs.writeFile(path.join(worktreeDir, "worktree-file.txt"), "worktree");
191+
await x("git", ["add", "."], { nodeOptions: { cwd: worktreeDir } });
192+
await x("git", ["commit", "-m", "worktree commit"], {
193+
nodeOptions: { cwd: worktreeDir },
194+
});
195+
196+
// Read the edit commit message from the worktree
197+
const expected = ["worktree commit\n\n"];
198+
const actual = await read({ edit: true, cwd: worktreeDir });
199+
expect(actual).toEqual(expected);
200+
});

@commitlint/top-level/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@
3636
},
3737
"license": "MIT",
3838
"devDependencies": {
39-
"@commitlint/utils": "^20.0.0"
39+
"@commitlint/utils": "^20.0.0",
40+
"@types/fs-extra": "^11.0.3",
41+
"@types/tmp": "^0.2.5",
42+
"fs-extra": "^11.0.0",
43+
"tmp": "^0.2.1"
4044
},
4145
"gitHead": "e82f05a737626bb69979d14564f5ff601997f679"
4246
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { test, expect, describe } from "vitest";
2+
import path from "node:path";
3+
import fs from "fs-extra";
4+
import tmp from "tmp";
5+
import { execFile } from "node:child_process";
6+
import { promisify } from "node:util";
7+
import { realpathSync } from "node:fs";
8+
9+
import toplevel from "./index.js";
10+
11+
const execFileAsync = promisify(execFile);
12+
13+
/**
14+
* Normalize a path for cross-platform comparison.
15+
* On Windows, tmp paths may use short names (e.g., RUNNER~1) while git returns long names.
16+
* This resolves symlinks and normalizes the path format.
17+
*/
18+
function normalizePath(p: string): string {
19+
return realpathSync(p).replace(/\\/g, "/");
20+
}
21+
22+
async function initGitRepo(cwd: string): Promise<void> {
23+
await execFileAsync("git", ["init"], { cwd });
24+
await execFileAsync("git", ["config", "user.email", "test@example.com"], {
25+
cwd,
26+
});
27+
await execFileAsync("git", ["config", "user.name", "test"], { cwd });
28+
await execFileAsync("git", ["config", "commit.gpgsign", "false"], { cwd });
29+
}
30+
31+
describe("toplevel", () => {
32+
test("should return git root for a regular repository", async () => {
33+
const tmpDir = tmp.dirSync({ keep: false, unsafeCleanup: true });
34+
const repoDir = tmpDir.name;
35+
36+
await initGitRepo(repoDir);
37+
38+
const result = await toplevel(repoDir);
39+
expect(normalizePath(result!)).toBe(normalizePath(repoDir));
40+
});
41+
42+
test("should return git root from a subdirectory", async () => {
43+
const tmpDir = tmp.dirSync({ keep: false, unsafeCleanup: true });
44+
const repoDir = tmpDir.name;
45+
46+
await initGitRepo(repoDir);
47+
48+
const subDir = path.join(repoDir, "sub", "dir");
49+
await fs.mkdirp(subDir);
50+
51+
const result = await toplevel(subDir);
52+
expect(normalizePath(result!)).toBe(normalizePath(repoDir));
53+
});
54+
55+
test("should return undefined for a non-git directory", async () => {
56+
const tmpDir = tmp.dirSync({ keep: false, unsafeCleanup: true });
57+
58+
const result = await toplevel(tmpDir.name);
59+
expect(result).toBeUndefined();
60+
});
61+
62+
test("should work with git worktrees", async () => {
63+
const tmpDir = tmp.dirSync({ keep: false, unsafeCleanup: true });
64+
const mainRepoDir = path.join(tmpDir.name, "main");
65+
const worktreeDir = path.join(tmpDir.name, "worktree");
66+
67+
await fs.mkdirp(mainRepoDir);
68+
await initGitRepo(mainRepoDir);
69+
70+
// Create an initial commit (required for worktree)
71+
await fs.writeFile(path.join(mainRepoDir, "file.txt"), "content");
72+
await execFileAsync("git", ["add", "."], { cwd: mainRepoDir });
73+
await execFileAsync("git", ["commit", "-m", "initial"], {
74+
cwd: mainRepoDir,
75+
});
76+
77+
// Create a new branch for the worktree
78+
await execFileAsync("git", ["branch", "worktree-branch"], {
79+
cwd: mainRepoDir,
80+
});
81+
82+
// Create the worktree
83+
await execFileAsync(
84+
"git",
85+
["worktree", "add", worktreeDir, "worktree-branch"],
86+
{ cwd: mainRepoDir },
87+
);
88+
89+
// toplevel should return the worktree directory, not the main repo
90+
const result = await toplevel(worktreeDir);
91+
expect(normalizePath(result!)).toBe(normalizePath(worktreeDir));
92+
});
93+
94+
test("should work from a subdirectory of a git worktree", async () => {
95+
const tmpDir = tmp.dirSync({ keep: false, unsafeCleanup: true });
96+
const mainRepoDir = path.join(tmpDir.name, "main");
97+
const worktreeDir = path.join(tmpDir.name, "worktree");
98+
99+
await fs.mkdirp(mainRepoDir);
100+
await initGitRepo(mainRepoDir);
101+
102+
// Create an initial commit
103+
await fs.writeFile(path.join(mainRepoDir, "file.txt"), "content");
104+
await execFileAsync("git", ["add", "."], { cwd: mainRepoDir });
105+
await execFileAsync("git", ["commit", "-m", "initial"], {
106+
cwd: mainRepoDir,
107+
});
108+
109+
// Create a new branch and worktree
110+
await execFileAsync("git", ["branch", "worktree-branch"], {
111+
cwd: mainRepoDir,
112+
});
113+
await execFileAsync(
114+
"git",
115+
["worktree", "add", worktreeDir, "worktree-branch"],
116+
{ cwd: mainRepoDir },
117+
);
118+
119+
// Create a subdirectory in the worktree
120+
const subDir = path.join(worktreeDir, "sub", "dir");
121+
await fs.mkdirp(subDir);
122+
123+
// toplevel from subdirectory should return the worktree root
124+
const result = await toplevel(subDir);
125+
expect(normalizePath(result!)).toBe(normalizePath(worktreeDir));
126+
});
127+
});

0 commit comments

Comments
 (0)