|
3 | 3 | // Covered scenarios: |
4 | 4 | // - List operation: supported in both bare root and worktrees from bare repos |
5 | 5 | // - Add/switch operations: supported in both bare root and worktrees from bare repos |
6 | | -// - Delete operation: not yet supported, should return errors |
| 6 | +// - Delete operation: supported from bare-derived worktrees; bare entry itself is protected |
7 | 7 | package e2e |
8 | 8 |
|
9 | 9 | import ( |
| 10 | + "bytes" |
10 | 11 | "encoding/json" |
11 | 12 | "os" |
12 | 13 | "path/filepath" |
@@ -392,41 +393,202 @@ func TestE2E_BareRepository(t *testing.T) { |
392 | 393 | } |
393 | 394 | }) |
394 | 395 |
|
395 | | - // --- Tests for operations that still don't support bare repositories --- |
| 396 | +} |
| 397 | + |
| 398 | +// TestE2E_BareDelete tests delete operations in bare repository environments. |
| 399 | +func TestE2E_BareDelete(t *testing.T) { |
| 400 | + t.Parallel() |
| 401 | + binPath := buildBinary(t) |
| 402 | + |
| 403 | + // --- Success: delete linked worktree from bare root --- |
| 404 | + |
| 405 | + t.Run("bare_root_safe_delete", func(t *testing.T) { |
| 406 | + t.Parallel() |
| 407 | + bareRepo := testutil.NewBareTestRepo(t) |
| 408 | + wtPath := createBareWorktree(t, binPath, bareRepo.Root, "feature-del") |
| 409 | + |
| 410 | + // Safe delete from bare root |
| 411 | + out, err := runGitWt(t, binPath, bareRepo.Root, "-d", "feature-del") |
| 412 | + if err != nil { |
| 413 | + t.Fatalf("git-wt -d failed: %v\noutput: %s", err, out) |
| 414 | + } |
| 415 | + |
| 416 | + // Worktree should be deleted |
| 417 | + if _, err := os.Stat(wtPath); !os.IsNotExist(err) { |
| 418 | + t.Error("worktree should have been deleted") |
| 419 | + } |
| 420 | + if !strings.Contains(out, "Deleted") { |
| 421 | + t.Errorf("output should contain 'Deleted', got: %s", out) |
| 422 | + } |
| 423 | + }) |
| 424 | + |
| 425 | + t.Run("bare_root_force_delete", func(t *testing.T) { |
| 426 | + t.Parallel() |
| 427 | + bareRepo := testutil.NewBareTestRepo(t) |
| 428 | + wtPath := createBareWorktree(t, binPath, bareRepo.Root, "unmerged-del") |
| 429 | + commitUnmergedChange(t, wtPath) |
| 430 | + |
| 431 | + // Force delete from bare root |
| 432 | + out, err := runGitWt(t, binPath, bareRepo.Root, "-D", "unmerged-del") |
| 433 | + if err != nil { |
| 434 | + t.Fatalf("git-wt -D failed: %v\noutput: %s", err, out) |
| 435 | + } |
396 | 436 |
|
397 | | - t.Run("direct_bare_delete", func(t *testing.T) { |
| 437 | + // Worktree should be deleted |
| 438 | + if _, err := os.Stat(wtPath); !os.IsNotExist(err) { |
| 439 | + t.Error("worktree should have been force deleted") |
| 440 | + } |
| 441 | + }) |
| 442 | + |
| 443 | + // --- Error: attempting to delete bare entry itself --- |
| 444 | + |
| 445 | + t.Run("bare_root_delete_self", func(t *testing.T) { |
398 | 446 | t.Parallel() |
399 | 447 | bareRepo := testutil.NewBareTestRepo(t) |
400 | 448 |
|
401 | | - // Run git-wt with -d flag (delete mode) inside the bare repo |
| 449 | + // Try to delete main (the bare entry's branch) from bare root |
402 | 450 | out, err := runGitWt(t, binPath, bareRepo.Root, "-d", "main") |
403 | 451 | if err == nil { |
404 | | - t.Fatalf("expected error for bare repository, but succeeded with output: %s", out) |
| 452 | + t.Fatalf("expected error when deleting bare entry, but succeeded with output: %s", out) |
405 | 453 | } |
406 | | - if !strings.Contains(out, "bare") { |
407 | | - t.Errorf("error message should mention 'bare', got: %s", out) |
| 454 | + if !strings.Contains(out, "bare repository entry") { |
| 455 | + t.Errorf("error message should mention 'bare repository entry', got: %s", out) |
408 | 456 | } |
409 | 457 | }) |
410 | 458 |
|
411 | | - t.Run("worktree_from_bare_delete", func(t *testing.T) { |
| 459 | + t.Run("bare_root_delete_self_dot", func(t *testing.T) { |
412 | 460 | t.Parallel() |
413 | 461 | bareRepo := testutil.NewBareTestRepo(t) |
414 | 462 |
|
415 | | - // Create a worktree from the bare repo |
416 | | - wtPath := filepath.Join(bareRepo.ParentDir(), "wt-main") |
417 | | - cmd := exec.Command("git", "-C", bareRepo.Root, "worktree", "add", wtPath, "main") |
418 | | - if out, err := cmd.CombinedOutput(); err != nil { |
419 | | - t.Fatalf("git worktree add failed: %v\noutput: %s", err, out) |
| 463 | + // Try to delete "." (current directory = bare root) from bare root |
| 464 | + out, err := runGitWt(t, binPath, bareRepo.Root, "-d", ".") |
| 465 | + if err == nil { |
| 466 | + t.Fatalf("expected error when deleting bare entry via '.', but succeeded with output: %s", out) |
| 467 | + } |
| 468 | + if !strings.Contains(out, "bare repository entry") { |
| 469 | + t.Errorf("error message should mention 'bare repository entry', got: %s", out) |
| 470 | + } |
| 471 | + }) |
| 472 | + |
| 473 | + // --- Success: delete from bare-derived worktree --- |
| 474 | + |
| 475 | + t.Run("bare_worktree_delete_other", func(t *testing.T) { |
| 476 | + t.Parallel() |
| 477 | + bareRepo := testutil.NewBareTestRepo(t) |
| 478 | + wtPathA := createBareWorktree(t, binPath, bareRepo.Root, "wt-a") |
| 479 | + wtPathB := createBareWorktree(t, binPath, bareRepo.Root, "wt-b") |
| 480 | + |
| 481 | + // From worktree B, delete worktree A |
| 482 | + out, err := runGitWt(t, binPath, wtPathB, "-d", "wt-a") |
| 483 | + if err != nil { |
| 484 | + t.Fatalf("git-wt -d failed: %v\noutput: %s", err, out) |
| 485 | + } |
| 486 | + |
| 487 | + // Worktree A should be deleted |
| 488 | + if _, err := os.Stat(wtPathA); !os.IsNotExist(err) { |
| 489 | + t.Error("worktree A should have been deleted") |
| 490 | + } |
| 491 | + // Worktree B should still exist |
| 492 | + if _, err := os.Stat(wtPathB); os.IsNotExist(err) { |
| 493 | + t.Error("worktree B should still exist") |
| 494 | + } |
| 495 | + }) |
| 496 | + |
| 497 | + t.Run("bare_worktree_delete_current", func(t *testing.T) { |
| 498 | + t.Parallel() |
| 499 | + bareRepo := testutil.NewBareTestRepo(t) |
| 500 | + wtPath := createBareWorktree(t, binPath, bareRepo.Root, "current-del") |
| 501 | + |
| 502 | + // Delete current worktree from inside it (with shell integration) |
| 503 | + cmd := exec.Command(binPath, "-D", "current-del") |
| 504 | + cmd.Dir = wtPath |
| 505 | + cmd.Env = append(os.Environ(), "GIT_WT_SHELL_INTEGRATION=1") |
| 506 | + var stdoutBuf, stderrBuf bytes.Buffer |
| 507 | + cmd.Stdout = &stdoutBuf |
| 508 | + cmd.Stderr = &stderrBuf |
| 509 | + if err := cmd.Run(); err != nil { |
| 510 | + t.Fatalf("git-wt -D failed: %v\nstderr: %s", err, stderrBuf.String()) |
| 511 | + } |
| 512 | + |
| 513 | + // Worktree should be deleted |
| 514 | + if _, err := os.Stat(wtPath); !os.IsNotExist(err) { |
| 515 | + t.Error("worktree should have been deleted") |
| 516 | + } |
| 517 | + |
| 518 | + // Shell integration should output bare root path |
| 519 | + assertLastLine(t, stdoutBuf.String(), bareRepo.Root) |
| 520 | + }) |
| 521 | + |
| 522 | + // --- Delete last worktree, cd back to bare root --- |
| 523 | + |
| 524 | + t.Run("bare_delete_last_worktree_cd", func(t *testing.T) { |
| 525 | + t.Parallel() |
| 526 | + bareRepo := testutil.NewBareTestRepo(t) |
| 527 | + wtPath := createBareWorktree(t, binPath, bareRepo.Root, "only-wt") |
| 528 | + |
| 529 | + // Delete from inside the worktree (with shell integration) |
| 530 | + cmd := exec.Command(binPath, "-D", "only-wt") |
| 531 | + cmd.Dir = wtPath |
| 532 | + cmd.Env = append(os.Environ(), "GIT_WT_SHELL_INTEGRATION=1") |
| 533 | + var stdoutBuf, stderrBuf bytes.Buffer |
| 534 | + cmd.Stdout = &stdoutBuf |
| 535 | + cmd.Stderr = &stderrBuf |
| 536 | + if err := cmd.Run(); err != nil { |
| 537 | + t.Fatalf("git-wt -D failed: %v\nstderr: %s", err, stderrBuf.String()) |
| 538 | + } |
| 539 | + |
| 540 | + // Worktree should be deleted |
| 541 | + if _, err := os.Stat(wtPath); !os.IsNotExist(err) { |
| 542 | + t.Error("worktree should have been deleted") |
420 | 543 | } |
421 | | - t.Cleanup(func() { os.RemoveAll(wtPath) }) |
422 | 544 |
|
423 | | - // Run git-wt with -d flag (delete mode) inside the worktree |
424 | | - out, err := runGitWt(t, binPath, wtPath, "-d", "main") |
| 545 | + // Shell integration should output bare root path |
| 546 | + assertLastLine(t, stdoutBuf.String(), bareRepo.Root) |
| 547 | + }) |
| 548 | + |
| 549 | + // --- Error: modified files --- |
| 550 | + |
| 551 | + t.Run("bare_worktree_delete_modified", func(t *testing.T) { |
| 552 | + t.Parallel() |
| 553 | + bareRepo := testutil.NewBareTestRepo(t) |
| 554 | + wtPath := createBareWorktree(t, binPath, bareRepo.Root, "mod-wt") |
| 555 | + |
| 556 | + // Add an untracked file |
| 557 | + if err := os.WriteFile(filepath.Join(wtPath, "untracked.txt"), []byte("content"), 0600); err != nil { |
| 558 | + t.Fatalf("failed to create untracked file: %v", err) |
| 559 | + } |
| 560 | + |
| 561 | + // Safe delete should fail |
| 562 | + out, err := runGitWt(t, binPath, bareRepo.Root, "-d", "mod-wt") |
425 | 563 | if err == nil { |
426 | | - t.Fatalf("expected error for worktree from bare repo, but succeeded with output: %s", out) |
| 564 | + t.Fatal("git-wt -d should fail when worktree has untracked files") |
| 565 | + } |
| 566 | + if !strings.Contains(out, "use -D to force deletion") { |
| 567 | + t.Errorf("error should suggest '-D to force deletion', got: %s", out) |
427 | 568 | } |
428 | | - if !strings.Contains(out, "bare") { |
429 | | - t.Errorf("error message should mention 'bare', got: %s", out) |
| 569 | + |
| 570 | + // Worktree should still exist |
| 571 | + if _, err := os.Stat(wtPath); os.IsNotExist(err) { |
| 572 | + t.Error("worktree should NOT have been deleted") |
| 573 | + } |
| 574 | + }) |
| 575 | + |
| 576 | + // --- dotgit bare variant --- |
| 577 | + |
| 578 | + t.Run("dotgit_bare_delete", func(t *testing.T) { |
| 579 | + t.Parallel() |
| 580 | + bareRepo := testutil.NewDotGitBareTestRepo(t) |
| 581 | + wtPath := createBareWorktree(t, binPath, bareRepo.Root, "dotgit-del") |
| 582 | + |
| 583 | + // Safe delete from bare root |
| 584 | + out, err := runGitWt(t, binPath, bareRepo.Root, "-d", "dotgit-del") |
| 585 | + if err != nil { |
| 586 | + t.Fatalf("git-wt -d failed: %v\noutput: %s", err, out) |
| 587 | + } |
| 588 | + |
| 589 | + // Worktree should be deleted |
| 590 | + if _, err := os.Stat(wtPath); !os.IsNotExist(err) { |
| 591 | + t.Error("worktree should have been deleted") |
430 | 592 | } |
431 | 593 | }) |
432 | 594 | } |
0 commit comments