Skip to content

Commit 983ad06

Browse files
authored
Merge pull request #14 from bkildow/feature/main-branch-config
Add configurable main_branch to .worktree.yml
2 parents 6ee9f47 + 1e691cc commit 983ad06

8 files changed

Lines changed: 40 additions & 26 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ parallel_teardown:
257257
|-------|-------------|---------|
258258
| `version` | Config version | `1` |
259259
| `git_dir` | Path to bare repository | `.bare` |
260-
| `main_branch` | Primary branch (protected from removal, used as base for new branches) | `main` |
260+
| `main_branch` | Primary branch (branch ref protected from deletion, used as base for new branches) | `main` |
261261
| `editor` | Preferred editor binary name | (auto-detect) |
262262
| `setup` | Commands to run sequentially after creating a worktree | `[]` |
263263
| `parallel_setup` | Commands to run concurrently after serial setup hooks | `[]` |

cmd/agents.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ Fields:
174174
- version: Config version (always 1)
175175
- git_dir: Path to git directory (.bare for cloned, .git for initialized)
176176
- worktree_dir: Directory for worktrees (default: worktrees)
177-
- main_branch: Primary branch, protected from removal and used as base for new branches (default: main)
177+
- main_branch: Primary branch, branch ref protected from deletion and used as base for new branches (default: main)
178178
- editor: Preferred editor binary name (default: auto-detect)
179179
- setup: Commands run sequentially after creating a worktree
180180
- parallel_setup: Commands run concurrently after setup completes

cmd/clone.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,9 @@ func runClone(cmd *cobra.Command, args []string) error {
7272
return err
7373
}
7474

75-
// Detect the remote's default branch
76-
ui.Step("Detecting default branch")
77-
detectedBranch, err := runner.GetDefaultBranch(ctx)
78-
if err != nil {
79-
ui.Warning("Could not detect default branch, defaulting to 'main'")
80-
detectedBranch = config.DefaultMainBranch
81-
}
82-
8375
// Create scaffold directories
8476
cfg := config.DefaultConfig()
85-
cfg.MainBranch = detectedBranch
77+
cfg.MainBranch = detectDefaultBranch(ctx, runner)
8678
ui.Step("Creating project scaffold")
8779
if err := project.CreateScaffold(projectRoot, &cfg, dry); err != nil {
8880
return err

cmd/init.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,7 @@ func runInit(cmd *cobra.Command, args []string) error {
5353
// Detect the repository's default branch
5454
gitDir := filepath.Join(projectRoot, cfg.GitDir)
5555
initRunner := git.NewRunner(gitDir, dry)
56-
detectedBranch, err := initRunner.GetDefaultBranch(cmd.Context())
57-
if err != nil {
58-
ui.Warning("Could not detect default branch, defaulting to 'main'")
59-
detectedBranch = config.DefaultMainBranch
60-
}
61-
cfg.MainBranch = detectedBranch
56+
cfg.MainBranch = detectDefaultBranch(cmd.Context(), initRunner)
6257

6358
// Create scaffold directories
6459
ui.Step("Creating project scaffold")

cmd/project.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cmd
22

33
import (
4+
"context"
45
"errors"
56
"os"
67
"path/filepath"
@@ -63,6 +64,17 @@ func filterManagedWorktrees(worktrees []git.WorktreeInfo, projectRoot string) []
6364
return filtered
6465
}
6566

67+
// detectDefaultBranch asks git for the remote's default branch and falls back
68+
// to config.DefaultMainBranch on error.
69+
func detectDefaultBranch(ctx context.Context, runner git.Git) string {
70+
branch, err := runner.GetDefaultBranch(ctx)
71+
if err != nil {
72+
ui.Warning("Could not detect default branch, defaulting to 'main'")
73+
return config.DefaultMainBranch
74+
}
75+
return branch
76+
}
77+
6678
// resolvePathBest tries to resolve symlinks; falls back to filepath.Clean.
6779
func resolvePathBest(p string) string {
6880
if resolved, err := filepath.EvalSymlinks(p); err == nil {

cmd/remove.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func runRemove(cmd *cobra.Command, args []string) error {
3939
}
4040

4141
filtered := filterManagedWorktrees(worktrees, projectRoot)
42+
mainBranch := cfg.MainBranchOrDefault()
4243

4344
if len(filtered) == 0 {
4445
return fmt.Errorf("no worktrees found")
@@ -78,11 +79,6 @@ func runRemove(cmd *cobra.Command, args []string) error {
7879
}
7980
}
8081

81-
// Protect the main branch from accidental removal.
82-
if selected.Branch == cfg.MainBranchOrDefault() {
83-
return fmt.Errorf("cannot remove the main branch worktree (%s)", selected.Branch)
84-
}
85-
8682
force, _ := cmd.Flags().GetBool("force")
8783

8884
if !force {
@@ -108,13 +104,22 @@ func runRemove(cmd *cobra.Command, args []string) error {
108104
}
109105
}
110106

111-
ui.Step("Removing worktree: " + selected.Branch)
107+
isMainBranch := selected.Branch == mainBranch
108+
109+
if isMainBranch {
110+
ui.Step("Removing worktree: " + selected.Branch + " (branch preserved in bare repo)")
111+
} else {
112+
ui.Step("Removing worktree: " + selected.Branch)
113+
}
114+
112115
if err := runner.WorktreeRemove(ctx, selected.Path, force); err != nil {
113116
return err
114117
}
115118

116-
if err := runner.BranchDelete(ctx, selected.Branch, false); err != nil {
117-
ui.Warning("Could not delete branch: " + err.Error())
119+
if !isMainBranch {
120+
if err := runner.BranchDelete(ctx, selected.Branch, false); err != nil {
121+
ui.Warning("Could not delete branch: " + err.Error())
122+
}
118123
}
119124

120125
ui.Success("Removed worktree: " + selected.Branch)

e2e/testdata/add_remove.txtar

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,13 @@ stderr 'Removed worktree'
3131
# List should show only main
3232
exec wt list
3333
stderr 'main'
34+
35+
# Remove the main branch worktree (should succeed, branch preserved)
36+
exec wt remove main
37+
stderr 'branch preserved'
38+
stderr 'Removed worktree'
39+
! exists worktrees/main
40+
41+
# Re-add the main branch worktree
42+
exec wt add --skip-setup main
43+
exists worktrees/main

internal/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func renderAnnotatedConfig(cfg *Config) string {
126126
fmt.Fprintf(&b, "shared_dir: %s\n", DefaultSharedDir)
127127
}
128128

129-
b.WriteString("\n# The primary branch of the repository (used as base for new branches, protected from removal)\n")
129+
b.WriteString("\n# The primary branch of the repository (used as base for new branches, branch ref protected from deletion)\n")
130130
if cfg != nil && cfg.MainBranch != "" {
131131
fmt.Fprintf(&b, "main_branch: %s\n", cfg.MainBranch)
132132
} else {

0 commit comments

Comments
 (0)