| ... | ... | @@ -1698,6 +1698,64 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 1698 | 1698 | }); |
| 1699 | 1699 | |
| 1700 | 1700 | describe("worktree operations", () => { |
| 1701 | it.effect("uses parallel checkout without skipping filters or hooks", () => |
| 1702 | Effect.gen(function* () { |
| 1703 | const cwd = yield* makeTmpDir(); |
| 1704 | const { initialBranch } = yield* initRepoWithCommit(cwd); |
| 1705 | const fs = yield* FileSystem.FileSystem; |
| 1706 | const path = yield* Path.Path; |
| 1707 | const driver = yield* GitVcsDriver.GitVcsDriver; |
| 1708 | yield* git(cwd, ["config", "filter.test.smudge", "sed s/original/filtered/g"]); |
| 1709 | yield* writeTextFile(cwd, ".gitattributes", "asset.txt filter=test\n"); |
| 1710 | yield* writeTextFile(cwd, "asset.txt", "original\n"); |
| 1711 | yield* git(cwd, ["add", "."]); |
| 1712 | yield* git(cwd, ["commit", "-m", "filtered asset"]); |
| 1713 | yield* writeTextFile( |
| 1714 | cwd, |
| 1715 | ".git/hooks/post-checkout", |
| 1716 | "#!/bin/sh\ngit config checkout.workers > checkout-workers\nexit 0\n", |
| 1717 | ); |
| 1718 | yield* fs.chmod(path.join(cwd, ".git/hooks/post-checkout"), 0o755); |
| 1719 | const worktreePath = path.join(yield* makeTmpDir("git-worktrees-"), "parallel"); |
| 1720 | |
| 1721 | yield* driver.createWorktree({ |
| 1722 | cwd, |
| 1723 | path: worktreePath, |
| 1724 | refName: initialBranch, |
| 1725 | newRefName: "feature/parallel", |
| 1726 | baseRefName: initialBranch, |
| 1727 | }); |
| 1728 | |
| 1729 | assert.notInclude(yield* git(cwd, ["worktree", "list", "--porcelain"]), "locked"); |
| 1730 | assert.equal(yield* fs.readFileString(path.join(worktreePath, "checkout-workers")), "0\n"); |
| 1731 | assert.equal(yield* fs.readFileString(path.join(worktreePath, "asset.txt")), "filtered\n"); |
| 1732 | assert.equal( |
| 1733 | yield* git(worktreePath, ["rev-parse", "HEAD"]), |
| 1734 | yield* git(cwd, ["rev-parse", "HEAD"]), |
| 1735 | ); |
| 1736 | assert.equal( |
| 1737 | yield* git(cwd, ["config", "branch.feature/parallel.gh-merge-base"]), |
| 1738 | initialBranch, |
| 1739 | ); |
| 1740 | for (const [configured, expected] of [ |
| 1741 | ["1", "1"], |
| 1742 | ["", "0"], |
| 1743 | ] as const) { |
| 1744 | yield* git(cwd, ["config", "checkout.workers", configured]); |
| 1745 | const configuredPath = path.join(yield* makeTmpDir("git-worktrees-"), "configured"); |
| 1746 | yield* driver.createWorktree({ |
| 1747 | cwd, |
| 1748 | path: configuredPath, |
| 1749 | refName: initialBranch, |
| 1750 | newRefName: `feature/configured-${expected}`, |
| 1751 | }); |
| 1752 | assert.equal( |
| 1753 | yield* fs.readFileString(path.join(configuredPath, "checkout-workers")), |
| 1754 | `${expected}\n`, |
| 1755 | ); |
| 1756 | } |
| 1757 | }), |
| 1758 | ); |
| 1701 | 1759 | it("parses checkout progress lines from git's stderr", () => { |
| 1702 | 1760 | assert.deepStrictEqual(parseGitCheckoutProgressLine("Updating files: 78% (2104/2700)"), { |
| 1703 | 1761 | percent: 78, |
| ... | ... | @@ -1827,11 +1885,11 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 1827 | 1885 | }), |
| 1828 | 1886 | ); |
| 1829 | 1887 | |
| 1830 | | it.effect("reports checkout progress while creating a worktree", () => |
| 1888 | it.effect("reports checkout progress during parallel worktree creation", () => |
| 1831 | 1889 | Effect.gen(function* () { |
| 1832 | 1890 | const cwd = yield* makeTmpDir(); |
| 1833 | 1891 | const { initialBranch } = yield* initRepoWithCommit(cwd); |
| 1834 | | for (let index = 0; index < 5; index += 1) { |
| 1892 | for (let index = 0; index < 200; index += 1) { |
| 1835 | 1893 | yield* writeTextFile(cwd, `file-${index}.txt`, `${index}\n`); |
| 1836 | 1894 | } |
| 1837 | 1895 | yield* git(cwd, ["add", "."]); |
| ... | ... | @@ -1865,7 +1923,7 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 1865 | 1923 | const updates = yield* Ref.get(seen); |
| 1866 | 1924 | assert.isAbove(updates.length, 1); |
| 1867 | 1925 | assert.equal(updates.at(-1)?.percent, 100); |
| 1868 | | assert.equal(updates.at(-1)?.total, 6); |
| 1926 | assert.equal(updates.at(-1)?.total, 201); |
| 1869 | 1927 | const completed = updates.map((update) => update.completed); |
| 1870 | 1928 | assert.deepEqual( |
| 1871 | 1929 | completed, |
| ... | ... | @@ -2098,6 +2156,61 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 2098 | 2156 | }); |
| 2099 | 2157 | |
| 2100 | 2158 | describe("remote operations", () => { |
| 2159 | for (const failure of ["offline", "auth", "timeout"] as const) { |
| 2160 | it.effect(`does not retry a scoped fetch after ${failure}`, () => |
| 2161 | Effect.gen(function* () { |
| 2162 | const cwd = yield* makeTmpDir(); |
| 2163 | const delegate = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 2164 | const started = yield* Deferred.make<void>(); |
| 2165 | const attempts: Array<ReadonlyArray<string>> = []; |
| 2166 | const spawner = ChildProcessSpawner.make((command) => |
| 2167 | Effect.gen(function* () { |
| 2168 | if (!ChildProcess.isStandardCommand(command)) |
| 2169 | return yield* Effect.die("unexpected command"); |
| 2170 | if (command.args[0] !== "fetch") return yield* delegate.spawn(command); |
| 2171 | attempts.push(command.args); |
| 2172 | yield* Deferred.succeed(started, undefined); |
| 2173 | return ChildProcessSpawner.makeHandle({ |
| 2174 | ...makeNonRepositoryHandle(), |
| 2175 | exitCode: |
| 2176 | failure === "timeout" |
| 2177 | ? Effect.never |
| 2178 | : Effect.succeed(ChildProcessSpawner.ExitCode(128)), |
| 2179 | stderr: Stream.encodeText( |
| 2180 | Stream.make( |
| 2181 | failure === "auth" |
| 2182 | ? "fatal: Authentication failed" |
| 2183 | : "fatal: Could not resolve host", |
| 2184 | ), |
| 2185 | ), |
| 2186 | }); |
| 2187 | }), |
| 2188 | ); |
| 2189 | const driver = yield* makeGitVcsDriverCore().pipe( |
| 2190 | Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), |
| 2191 | Effect.provide(ServerConfigLayer), |
| 2192 | ); |
| 2193 | const fetching = yield* driver |
| 2194 | .fetchRemote({ cwd, remoteName: "origin", refName: "main" }) |
| 2195 | .pipe(Effect.result, Effect.forkChild({ startImmediately: true })); |
| 2196 | yield* Deferred.await(started); |
| 2197 | if (failure === "timeout") { |
| 2198 | yield* TestClock.adjust("31 seconds"); |
| 2199 | yield* TestClock.adjust("31 seconds"); |
| 2200 | } |
| 2201 | const result = yield* Fiber.join(fetching); |
| 2202 | assert.isTrue(Result.isFailure(result)); |
| 2203 | assert.equal(attempts.length, 1); |
| 2204 | if (Result.isFailure(result)) { |
| 2205 | assert.equal( |
| 2206 | result.failure.detail, |
| 2207 | failure === "timeout" ? "Git command timed out." : "git fetch origin failed", |
| 2208 | ); |
| 2209 | } |
| 2210 | }), |
| 2211 | ); |
| 2212 | } |
| 2213 | |
| 2101 | 2214 | it.effect("creates a worktree from the latest fetched remote commit", () => |
| 2102 | 2215 | Effect.gen(function* () { |
| 2103 | 2216 | const cwd = yield* makeTmpDir(); |
| ... | ... | @@ -2120,8 +2233,16 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 2120 | 2233 | const remoteHead = yield* git(peer, ["rev-parse", "HEAD"]); |
| 2121 | 2234 | assert.notEqual(beforeFetch, remoteHead); |
| 2122 | 2235 | |
| 2236 | yield* git(peer, ["push", "origin", "HEAD:refs/heads/unrelated"]); |
| 2123 | 2237 | const driver = yield* GitVcsDriver.GitVcsDriver; |
| 2124 | | yield* driver.fetchRemote({ cwd, remoteName: "origin" }); |
| 2238 | yield* driver.fetchRemote({ |
| 2239 | cwd, |
| 2240 | remoteName: "origin", |
| 2241 | refName: `origin/${initialBranch}`, |
| 2242 | }); |
| 2243 | assert.isFalse( |
| 2244 | yield* driver.remoteBranchExists({ cwd, remoteName: "origin", refName: "unrelated" }), |
| 2245 | ); |
| 2125 | 2246 | |
| 2126 | 2247 | assert.equal( |
| 2127 | 2248 | yield* driver.remoteBranchExists({ |
| ... | ... | @@ -2183,6 +2304,11 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { |
| 2183 | 2304 | const status = yield* driver.statusDetails(worktreePath); |
| 2184 | 2305 | assert.equal(status.aheadCount, 0); |
| 2185 | 2306 | assert.equal(status.aheadOfDefaultCount, 0); |
| 2307 | |
| 2308 | yield* driver.fetchRemote({ cwd, remoteName: "origin", refName: "local-only" }); |
| 2309 | assert.isTrue( |
| 2310 | yield* driver.remoteBranchExists({ cwd, remoteName: "origin", refName: "unrelated" }), |
| 2311 | ); |
| 2186 | 2312 | }), |
| 2187 | 2313 | ); |
| 2188 | 2314 | |