| @@ -1374,6 +1374,92 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { · 1373 unmodified lines |
| 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 | 1463 | it.effect("creates and removes a worktree for a new refName", () => |
| 1378 | 1464 | Effect.gen(function* () { |
| 1379 | 1465 | const cwd = yield* makeTmpDir(); |