| ... | @@ -1374,6 +1374,92 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { | ... | @@ -1374,6 +1374,92 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 1374 | }), | 1374 | }), |
| 1375 | ); | 1375 | ); |
| 1376 | | 1376 | |
| | 1377 | it.effect("checks out submodules in a new worktree", () => |
| | 1378 | Effect.gen(function* () { |
| | 1379 | const fileSystem = yield* FileSystem.FileSystem; |
| | 1380 | const pathService = yield* Path.Path; |
| | 1381 | |
| | 1382 | // Git refuses `file:` submodule transports by default (CVE-2022-39253) |
| | 1383 | // and ignores repo-level config for it, so a local fixture needs the |
| | 1384 | // env allowance. Real submodules are https/ssh and need none of this. |
| | 1385 | const previousAllowedProtocol = process.env.GIT_ALLOW_PROTOCOL; |
| | 1386 | process.env.GIT_ALLOW_PROTOCOL = "file"; |
| | 1387 | yield* Effect.addFinalizer(() => |
| | 1388 | Effect.sync(() => { |
| | 1389 | if (previousAllowedProtocol === undefined) { |
| | 1390 | delete process.env.GIT_ALLOW_PROTOCOL; |
| | 1391 | } else { |
| | 1392 | process.env.GIT_ALLOW_PROTOCOL = previousAllowedProtocol; |
| | 1393 | } |
| | 1394 | }), |
| | 1395 | ); |
| | 1396 | |
| | 1397 | // A real submodule: `git worktree add` leaves these empty, which is |
| | 1398 | // what silently strips shared tooling out of every new worktree. |
| | 1399 | const submoduleRepo = yield* makeTmpDir("git-submodule-"); |
| | 1400 | yield* initRepoWithCommit(submoduleRepo); |
| | 1401 | yield* writeTextFile(submoduleRepo, "SHARED.md", "# shared\n"); |
| | 1402 | yield* git(submoduleRepo, ["add", "."]); |
| | 1403 | yield* git(submoduleRepo, ["commit", "-m", "shared"]); |
| | 1404 | |
| | 1405 | const cwd = yield* makeTmpDir(); |
| | 1406 | const { initialBranch } = yield* initRepoWithCommit(cwd); |
| | 1407 | yield* git(cwd, ["submodule", "add", submoduleRepo, "shared"]); |
| | 1408 | yield* git(cwd, ["commit", "-m", "add submodule"]); |
| | 1409 | |
| | 1410 | const worktreePath = pathService.join( |
| | 1411 | yield* makeTmpDir("git-worktrees-"), |
| | 1412 | "submodule-worktree", |
| | 1413 | ); |
| | 1414 | const driver = yield* GitVcsDriver.GitVcsDriver; |
| | 1415 | yield* driver.createWorktree({ |
| | 1416 | cwd, |
| | 1417 | path: worktreePath, |
| | 1418 | refName: initialBranch, |
| | 1419 | newRefName: "feature/submodules", |
| | 1420 | }); |
| | 1421 | |
| | 1422 | assert.equal( |
| | 1423 | yield* fileSystem.exists(pathService.join(worktreePath, "shared", "SHARED.md")), |
| | 1424 | true, |
| | 1425 | ); |
| | 1426 | }), |
| | 1427 | ); |
| | 1428 | |
| | 1429 | it.effect("still creates the worktree when submodule checkout fails", () => |
| | 1430 | Effect.gen(function* () { |
| | 1431 | const fileSystem = yield* FileSystem.FileSystem; |
| | 1432 | const pathService = yield* Path.Path; |
| | 1433 | |
| | 1434 | const cwd = yield* makeTmpDir(); |
| | 1435 | const { initialBranch } = yield* initRepoWithCommit(cwd); |
| | 1436 | // Points at a repository that does not exist, so the checkout fails the |
| | 1437 | // way an unreachable private remote would. Creation must still succeed. |
| | 1438 | yield* writeTextFile( |
| | 1439 | cwd, |
| | 1440 | ".gitmodules", |
| | 1441 | '[submodule "missing"]\n\tpath = missing\n\turl = /nonexistent/repo.git\n', |
| | 1442 | ); |
| | 1443 | yield* git(cwd, ["add", "."]); |
| | 1444 | yield* git(cwd, ["commit", "-m", "add unreachable submodule"]); |
| | 1445 | |
| | 1446 | const worktreePath = pathService.join( |
| | 1447 | yield* makeTmpDir("git-worktrees-"), |
| | 1448 | "broken-submodule-worktree", |
| | 1449 | ); |
| | 1450 | const driver = yield* GitVcsDriver.GitVcsDriver; |
| | 1451 | const created = yield* driver.createWorktree({ |
| | 1452 | cwd, |
| | 1453 | path: worktreePath, |
| | 1454 | refName: initialBranch, |
| | 1455 | newRefName: "feature/broken-submodules", |
| | 1456 | }); |
| | 1457 | |
| | 1458 | assert.equal(created.worktree.path, worktreePath); |
| | 1459 | assert.equal(yield* fileSystem.exists(worktreePath), true); |
| | 1460 | }), |
| | 1461 | ); |
| | 1462 | |
| 1377 | it.effect("creates and removes a worktree for a new refName", () => | 1463 | it.effect("creates and removes a worktree for a new refName", () => |
| 1378 | Effect.gen(function* () { | 1464 | Effect.gen(function* () { |
| 1379 | const cwd = yield* makeTmpDir(); | 1465 | const cwd = yield* makeTmpDir(); |