authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-04 13:33:21-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 20:33:21+00:00
log12b6d026bdfba0cc8438fa5e323a8587d65bd2af
tree942c5f28428ddf1fb448b0001d18a069efbb3a85
parentf083f520fbde1e1bf4332399df47b45c2bd99722
signature Signed by PGP key B5690EEEBB952194

test(server): pin git config for fixtures and compare native realpaths (#9570)

Co-authored-by: Claude Code <noreply@anthropic.com>

4 files changed, 61 insertions(+), 29 deletions(-)

apps/server/src/project/RepositoryIdentityResolver.test.ts+8-4
...@@ -1,3 +1,5 @@...@@ -1,3 +1,5 @@
1// @effect-diagnostics nodeBuiltinImport:off - realpathSync.native resolves Windows 8.3 short names, which the Effect realPath does not.
2import * as NodeFS from "node:fs";
1import * as NodeServices from "@effect/platform-node/NodeServices";3import * as NodeServices from "@effect/platform-node/NodeServices";
2import { expect, it } from "@effect/vitest";4import { expect, it } from "@effect/vitest";
3import * as Duration from "effect/Duration";5import * as Duration from "effect/Duration";
...@@ -131,9 +133,11 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {...@@ -131,9 +133,11 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
131133
132 const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver;134 const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver;
133 const identity = yield* resolver.resolve(cwd);135 const identity = yield* resolver.resolve(cwd);
136 // Native realpath, since git reports the long form of a directory the
137 // temp dir may name by its 8.3 short form on Windows.
134 const resolvedIdentityRoot =138 const resolvedIdentityRoot =
135 identity?.rootPath === undefined ? "" : yield* fileSystem.realPath(identity.rootPath);139 identity?.rootPath === undefined ? "" : NodeFS.realpathSync.native(identity.rootPath);
136 const resolvedCwd = yield* fileSystem.realPath(cwd);140 const resolvedCwd = NodeFS.realpathSync.native(cwd);
137141
138 expect(identity).not.toBeNull();142 expect(identity).not.toBeNull();
139 expect(identity?.canonicalKey).toBe("github.com/t3tools/t3code");143 expect(identity?.canonicalKey).toBe("github.com/t3tools/t3code");
...@@ -161,8 +165,8 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {...@@ -161,8 +165,8 @@ it.layer(NodeServices.layer)("RepositoryIdentityResolverLive", (it) => {
161 const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver;165 const resolver = yield* RepositoryIdentityResolver.RepositoryIdentityResolver;
162 const identity = yield* resolver.resolve(nestedWorkspace);166 const identity = yield* resolver.resolve(nestedWorkspace);
163 const resolvedIdentityRoot =167 const resolvedIdentityRoot =
164 identity?.rootPath === undefined ? "" : yield* fileSystem.realPath(identity.rootPath);168 identity?.rootPath === undefined ? "" : NodeFS.realpathSync.native(identity.rootPath);
165 const resolvedRepoRoot = yield* fileSystem.realPath(repoRoot);169 const resolvedRepoRoot = NodeFS.realpathSync.native(repoRoot);
166170
167 expect(identity).not.toBeNull();171 expect(identity).not.toBeNull();
168 expect(identity?.canonicalKey).toBe("github.com/t3tools/t3code");172 expect(identity?.canonicalKey).toBe("github.com/t3tools/t3code");
apps/server/src/testUtils/gitConfig.setup.ts created+21
...@@ -0,0 +1,21 @@
1// Pins git behaviour for every repository the suite creates, ahead of the
2// host's ~/.gitconfig. Git for Windows installs with core.autocrlf=true,
3// which checks committed LF files out as CRLF and breaks every byte-exact
4// content assertion; a signing key or a non-default init branch on the
5// developer's machine breaks fixtures the same way. Set as environment so
6// each git child the driver spawns sees it without touching the fixtures.
7const entries: ReadonlyArray<readonly [key: string, value: string]> = [
8 ["core.autocrlf", "false"],
9 ["core.filemode", "false"],
10 ["core.longpaths", "true"],
11 ["commit.gpgsign", "false"],
12 ["tag.gpgsign", "false"],
13 ["init.defaultBranch", "main"],
14];
15
16const existing = Number(process.env.GIT_CONFIG_COUNT ?? "0");
17process.env.GIT_CONFIG_COUNT = String(existing + entries.length);
18entries.forEach(([key, value], index) => {
19 process.env[`GIT_CONFIG_KEY_${existing + index}`] = key;
20 process.env[`GIT_CONFIG_VALUE_${existing + index}`] = value;
21});
apps/server/src/vcs/GitVcsDriverCore.test.ts+31-25
...@@ -1,4 +1,7 @@...@@ -1,4 +1,7 @@
1// @effect-diagnostics nodeBuiltinImport:off - realpathSync.native resolves Windows 8.3 short names, which the Effect realPath does not.
2import * as NodeFS from "node:fs";
1import * as NodeServices from "@effect/platform-node/NodeServices";3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
2import { assert, it, describe } from "@effect/vitest";5import { assert, it, describe } from "@effect/vitest";
3import * as Deferred from "effect/Deferred";6import * as Deferred from "effect/Deferred";
4import * as Effect from "effect/Effect";7import * as Effect from "effect/Effect";
...@@ -570,7 +573,6 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () =>...@@ -570,7 +573,6 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () =>
570 const driver = yield* makeGitVcsDriverCore().pipe(573 const driver = yield* makeGitVcsDriverCore().pipe(
571 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, failingFetchSpawner),574 Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, failingFetchSpawner),
572 );575 );
573 const fileSystem = yield* FileSystem.FileSystem;
574 const cwd = yield* makeTmpDir();576 const cwd = yield* makeTmpDir();
575 const remote = yield* makeTmpDir("git-vcs-driver-remote-");577 const remote = yield* makeTmpDir("git-vcs-driver-remote-");
576 const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-");578 const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-");
...@@ -606,9 +608,11 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () =>...@@ -606,9 +608,11 @@ it.effect("backs off failed upstream refreshes across linked worktrees", () =>
606 "rev-parse",608 "rev-parse",
607 "--git-common-dir",609 "--git-common-dir",
608 ])).stdout.trim();610 ])).stdout.trim();
611 // Native realpath, since git reports the long form of a directory the
612 // temp dir may name by its 8.3 short form on Windows.
609 assert.equal(613 assert.equal(
610 yield* fileSystem.realPath(pathService.resolve(cwd, rootCommonDir)),614 NodeFS.realpathSync.native(pathService.resolve(cwd, rootCommonDir)),
611 yield* fileSystem.realPath(pathService.resolve(worktreePath, linkedCommonDir)),615 NodeFS.realpathSync.native(pathService.resolve(worktreePath, linkedCommonDir)),
612 );616 );
613 yield* Ref.set(fetchAttempts, 0);617 yield* Ref.set(fetchAttempts, 0);
614618
...@@ -1376,31 +1380,33 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {...@@ -1376,31 +1380,33 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => {
1376 });1380 });
13771381
1378 describe("worktree operations", () => {1382 describe("worktree operations", () => {
1379 it.effect("preserves newline characters in worktree paths when listing refs", () =>1383 // NTFS rejects a newline in a file name, so there is nothing to preserve there.
1380 Effect.gen(function* () {1384 it.effect.skipIf(HostProcessPlatform.defaultValue() === "win32")(
1381 const cwd = yield* makeTmpDir();1385 "preserves newline characters in worktree paths when listing refs",
1382 yield* initRepoWithCommit(cwd);1386 () =>
1383 const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-");1387 Effect.gen(function* () {
1384 const fileSystem = yield* FileSystem.FileSystem;1388 const cwd = yield* makeTmpDir();
1385 const pathService = yield* Path.Path;1389 yield* initRepoWithCommit(cwd);
1386 const worktreePath = pathService.join(worktreesRoot, "linked\nworktree");1390 const worktreesRoot = yield* makeTmpDir("git-vcs-driver-worktrees-");
1387 const driver = yield* GitVcsDriver.GitVcsDriver;1391 const pathService = yield* Path.Path;
1392 const worktreePath = pathService.join(worktreesRoot, "linked\nworktree");
1393 const driver = yield* GitVcsDriver.GitVcsDriver;
13881394
1389 yield* git(cwd, ["worktree", "add", "-b", "feature/newline-path", worktreePath]);1395 yield* git(cwd, ["worktree", "add", "-b", "feature/newline-path", worktreePath]);
13901396
1391 const refs = yield* driver.listRefs({ cwd, refresh: true });1397 const refs = yield* driver.listRefs({ cwd, refresh: true });
1392 const listedPath = refs.refs.find(1398 const listedPath = refs.refs.find(
1393 (ref) => ref.name === "feature/newline-path",1399 (ref) => ref.name === "feature/newline-path",
1394 )?.worktreePath;1400 )?.worktreePath;
13951401
1396 if (typeof listedPath !== "string") {1402 if (typeof listedPath !== "string") {
1397 return assert.fail("expected the linked branch to include its worktree path");1403 return assert.fail("expected the linked branch to include its worktree path");
1398 }1404 }
1399 assert.equal(1405 assert.equal(
1400 yield* fileSystem.realPath(listedPath),1406 NodeFS.realpathSync.native(listedPath),
1401 yield* fileSystem.realPath(worktreePath),1407 NodeFS.realpathSync.native(worktreePath),
1402 );1408 );
1403 }),1409 }),
1404 );1410 );
14051411
1406 it.effect("checks out submodules in a new worktree", () =>1412 it.effect("checks out submodules in a new worktree", () =>
apps/server/vite.config.ts+1
...@@ -78,6 +78,7 @@ export default mergeConfig(...@@ -78,6 +78,7 @@ export default mergeConfig(
78 // The server suite exercises sqlite, git, temp worktrees, and orchestration78 // The server suite exercises sqlite, git, temp worktrees, and orchestration
79 // runtimes heavily. Running files in parallel introduces load-sensitive flakes.79 // runtimes heavily. Running files in parallel introduces load-sensitive flakes.
80 fileParallelism: false,80 fileParallelism: false,
81 setupFiles: ["./src/testUtils/gitConfig.setup.ts"],
81 // Server integration tests exercise sqlite, git, and orchestration together.82 // Server integration tests exercise sqlite, git, and orchestration together.
82 // Under package-wide runs they can exceed the default budget on loaded CI hosts.83 // Under package-wide runs they can exceed the default budget on loaded CI hosts.
83 hookTimeout: 120_000,84 hookTimeout: 120_000,