| ... | @@ -194,15 +194,19 @@ describe("git integration", () => { | ... | @@ -194,15 +194,19 @@ describe("git integration", () => { |
| 194 | // ── createGitWorktree + removeGitWorktree ── | 194 | // ── createGitWorktree + removeGitWorktree ── |
| 195 | | 195 | |
| 196 | describe("createGitWorktree", () => { | 196 | describe("createGitWorktree", () => { |
| 197 | it("creates a worktree directory at the expected path", async () => { | 197 | it("creates a worktree with a new branch from the base branch", async () => { |
| 198 | await using tmp = await makeTmpDir(); | 198 | await using tmp = await makeTmpDir(); |
| 199 | await initRepoWithCommit(tmp.path); | 199 | await initRepoWithCommit(tmp.path); |
| 200 | await createGitBranch({ cwd: tmp.path, branch: "wt-branch" }); | | |
| 201 | | 200 | |
| 202 | const wtPath = path.join(tmp.path, "worktree-out"); | 201 | const wtPath = path.join(tmp.path, "worktree-out"); |
| | 202 | const currentBranch = (await listGitBranches({ cwd: tmp.path })).branches.find( |
| | 203 | (b) => b.current, |
| | 204 | )!.name; |
| | 205 | |
| 203 | const result = await createGitWorktree({ | 206 | const result = await createGitWorktree({ |
| 204 | cwd: tmp.path, | 207 | cwd: tmp.path, |
| 205 | branch: "wt-branch", | 208 | branch: currentBranch, |
| | 209 | newBranch: "wt-branch", |
| 206 | path: wtPath, | 210 | path: wtPath, |
| 207 | }); | 211 | }); |
| 208 | | 212 | |
| ... | @@ -215,37 +219,44 @@ describe("git integration", () => { | ... | @@ -215,37 +219,44 @@ describe("git integration", () => { |
| 215 | await removeGitWorktree({ cwd: tmp.path, path: wtPath }); | 219 | await removeGitWorktree({ cwd: tmp.path, path: wtPath }); |
| 216 | }); | 220 | }); |
| 217 | | 221 | |
| 218 | it("worktree has the correct branch checked out", async () => { | 222 | it("worktree has the new branch checked out", async () => { |
| 219 | await using tmp = await makeTmpDir(); | 223 | await using tmp = await makeTmpDir(); |
| 220 | await initRepoWithCommit(tmp.path); | 224 | await initRepoWithCommit(tmp.path); |
| 221 | await createGitBranch({ cwd: tmp.path, branch: "wt-check" }); | | |
| 222 | | 225 | |
| 223 | const wtPath = path.join(tmp.path, "wt-check-dir"); | 226 | const wtPath = path.join(tmp.path, "wt-check-dir"); |
| | 227 | const currentBranch = (await listGitBranches({ cwd: tmp.path })).branches.find( |
| | 228 | (b) => b.current, |
| | 229 | )!.name; |
| | 230 | |
| 224 | await createGitWorktree({ | 231 | await createGitWorktree({ |
| 225 | cwd: tmp.path, | 232 | cwd: tmp.path, |
| 226 | branch: "wt-check", | 233 | branch: currentBranch, |
| | 234 | newBranch: "wt-check", |
| 227 | path: wtPath, | 235 | path: wtPath, |
| 228 | }); | 236 | }); |
| 229 | | 237 | |
| 230 | // Verify the worktree is on the right branch | 238 | // Verify the worktree is on the new branch |
| 231 | const branchOutput = await git(wtPath, "branch --show-current"); | 239 | const branchOutput = await git(wtPath, "branch --show-current"); |
| 232 | expect(branchOutput).toBe("wt-check"); | 240 | expect(branchOutput).toBe("wt-check"); |
| 233 | | 241 | |
| 234 | await removeGitWorktree({ cwd: tmp.path, path: wtPath }); | 242 | await removeGitWorktree({ cwd: tmp.path, path: wtPath }); |
| 235 | }); | 243 | }); |
| 236 | | 244 | |
| 237 | it("throws when branch is already checked out in main worktree", async () => { | 245 | it("throws when new branch name already exists", async () => { |
| 238 | await using tmp = await makeTmpDir(); | 246 | await using tmp = await makeTmpDir(); |
| 239 | await initRepoWithCommit(tmp.path); | 247 | await initRepoWithCommit(tmp.path); |
| 240 | // Try to create a worktree for the current branch (already checked out) | 248 | await createGitBranch({ cwd: tmp.path, branch: "existing" }); |
| 241 | const branches = await listGitBranches({ cwd: tmp.path }); | | |
| 242 | const currentBranch = branches.branches.find((b) => b.current)!.name; | | |
| 243 | | 249 | |
| 244 | const wtPath = path.join(tmp.path, "wt-conflict"); | 250 | const wtPath = path.join(tmp.path, "wt-conflict"); |
| | 251 | const currentBranch = (await listGitBranches({ cwd: tmp.path })).branches.find( |
| | 252 | (b) => b.current, |
| | 253 | )!.name; |
| | 254 | |
| 245 | await expect( | 255 | await expect( |
| 246 | createGitWorktree({ | 256 | createGitWorktree({ |
| 247 | cwd: tmp.path, | 257 | cwd: tmp.path, |
| 248 | branch: currentBranch, | 258 | branch: currentBranch, |
| | 259 | newBranch: "existing", |
| 249 | path: wtPath, | 260 | path: wtPath, |
| 250 | }), | 261 | }), |
| 251 | ).rejects.toThrow(); | 262 | ).rejects.toThrow(); |
| ... | @@ -254,12 +265,16 @@ describe("git integration", () => { | ... | @@ -254,12 +265,16 @@ describe("git integration", () => { |
| 254 | it("removeGitWorktree cleans up the worktree", async () => { | 265 | it("removeGitWorktree cleans up the worktree", async () => { |
| 255 | await using tmp = await makeTmpDir(); | 266 | await using tmp = await makeTmpDir(); |
| 256 | await initRepoWithCommit(tmp.path); | 267 | await initRepoWithCommit(tmp.path); |
| 257 | await createGitBranch({ cwd: tmp.path, branch: "wt-remove" }); | | |
| 258 | | 268 | |
| 259 | const wtPath = path.join(tmp.path, "wt-remove-dir"); | 269 | const wtPath = path.join(tmp.path, "wt-remove-dir"); |
| | 270 | const currentBranch = (await listGitBranches({ cwd: tmp.path })).branches.find( |
| | 271 | (b) => b.current, |
| | 272 | )!.name; |
| | 273 | |
| 260 | await createGitWorktree({ | 274 | await createGitWorktree({ |
| 261 | cwd: tmp.path, | 275 | cwd: tmp.path, |
| 262 | branch: "wt-remove", | 276 | branch: currentBranch, |
| | 277 | newBranch: "wt-remove", |
| 263 | path: wtPath, | 278 | path: wtPath, |
| 264 | }); | 279 | }); |
| 265 | expect(existsSync(wtPath)).toBe(true); | 280 | expect(existsSync(wtPath)).toBe(true); |
| ... | @@ -284,18 +299,22 @@ describe("git integration", () => { | ... | @@ -284,18 +299,22 @@ describe("git integration", () => { |
| 284 | }); | 299 | }); |
| 285 | }); | 300 | }); |
| 286 | | 301 | |
| 287 | // ── Full flow: worktree creation from selected branch ── | 302 | // ── Full flow: worktree creation from base branch ── |
| 288 | | 303 | |
| 289 | describe("full flow: worktree creation", () => { | 304 | describe("full flow: worktree creation", () => { |
| 290 | it("creates worktree from a non-current branch", async () => { | 305 | it("creates worktree with new branch from current branch", async () => { |
| 291 | await using tmp = await makeTmpDir(); | 306 | await using tmp = await makeTmpDir(); |
| 292 | await initRepoWithCommit(tmp.path); | 307 | await initRepoWithCommit(tmp.path); |
| 293 | await createGitBranch({ cwd: tmp.path, branch: "feature-wt" }); | 308 | |
| | 309 | const currentBranch = (await listGitBranches({ cwd: tmp.path })).branches.find( |
| | 310 | (b) => b.current, |
| | 311 | )!.name; |
| 294 | | 312 | |
| 295 | const wtPath = path.join(tmp.path, "my-worktree"); | 313 | const wtPath = path.join(tmp.path, "my-worktree"); |
| 296 | const result = await createGitWorktree({ | 314 | const result = await createGitWorktree({ |
| 297 | cwd: tmp.path, | 315 | cwd: tmp.path, |
| 298 | branch: "feature-wt", | 316 | branch: currentBranch, |
| | 317 | newBranch: "feature-wt", |
| 299 | path: wtPath, | 318 | path: wtPath, |
| 300 | }); | 319 | }); |
| 301 | | 320 | |
| ... | @@ -305,9 +324,9 @@ describe("git integration", () => { | ... | @@ -305,9 +324,9 @@ describe("git integration", () => { |
| 305 | // Main repo still on original branch | 324 | // Main repo still on original branch |
| 306 | const mainBranches = await listGitBranches({ cwd: tmp.path }); | 325 | const mainBranches = await listGitBranches({ cwd: tmp.path }); |
| 307 | const mainCurrent = mainBranches.branches.find((b) => b.current); | 326 | const mainCurrent = mainBranches.branches.find((b) => b.current); |
| 308 | expect(mainCurrent!.name).not.toBe("feature-wt"); | 327 | expect(mainCurrent!.name).toBe(currentBranch); |
| 309 | | 328 | |
| 310 | // Worktree is on feature-wt | 329 | // Worktree is on the new branch |
| 311 | const wtBranch = await git(wtPath, "branch --show-current"); | 330 | const wtBranch = await git(wtPath, "branch --show-current"); |
| 312 | expect(wtBranch).toBe("feature-wt"); | 331 | expect(wtBranch).toBe("feature-wt"); |
| 313 | | 332 | |
| ... | @@ -356,7 +375,9 @@ describe("git integration", () => { | ... | @@ -356,7 +375,9 @@ describe("git integration", () => { |
| 356 | await git(tmp.path, "commit -m 'diverge'"); | 375 | await git(tmp.path, "commit -m 'diverge'"); |
| 357 | | 376 | |
| 358 | // Go back to default branch | 377 | // Go back to default branch |
| 359 | const defaultBranch = (await git(tmp.path, "rev-parse --abbrev-ref HEAD")).includes("diverged") | 378 | const defaultBranch = (await git(tmp.path, "rev-parse --abbrev-ref HEAD")).includes( |
| | 379 | "diverged", |
| | 380 | ) |
| 360 | ? // we're on diverged, need to find the other one | 381 | ? // we're on diverged, need to find the other one |
| 361 | (await listGitBranches({ cwd: tmp.path })).branches.find( | 382 | (await listGitBranches({ cwd: tmp.path })).branches.find( |
| 362 | (b) => !b.current && b.name !== "diverged", | 383 | (b) => !b.current && b.name !== "diverged", |