From a7215ba18b00b59a4647e0dedcbc619a32669a36 Mon Sep 17 00:00:00 2001 From: Nathan Nutter Date: Thu, 23 Jul 2026 03:22:36 +0000 Subject: [PATCH 1/4] wt create --herdr now implies --no-cd --- README.md | 4 ++-- internal/gitwt/gitwt_generate_zsh.go | 4 ++++ internal/gitwt/gitwt_test.go | 5 +++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6d48e63..ad26156 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Ensure the output directory is on `fpath`, then restart zsh or run `compinit`. The generated function: - routes most commands to `git-wt` (`wt create`, `wt list`, `wt prune`, …) -- after a successful `wt create`, `cd`s into the new worktree (`--no-cd` to stay put) +- after a successful `wt create`, `cd`s into the new worktree (`--no-cd` or `--herdr` to stay put) - provides a shell-only `switch` that `cd`s into a worktree - after a successful `wt remove`, `cd`s to the main worktree @@ -67,7 +67,7 @@ Create a managed worktree for a branch. - If the branch already exists, the worktree is created from that branch. - If the branch does not exist, it is created from the upstream branch; which defaults to the default origin branch but can be set explicity with `--upstream` | `-u`. -- With `--herdr`, also create a [Herdr](https://herdr.dev) workspace whose `--cwd` is the new worktree and whose `--label` is the worktree name (branch name). Requires `herdr` on `PATH` and a running Herdr server. +- With `--herdr`, also create a [Herdr](https://herdr.dev) workspace whose `--cwd` is the new worktree and whose `--label` is the worktree name (branch name). `wt create --herdr` implies `--no-cd`. Requires `herdr` on `PATH` and a running Herdr server. Example: diff --git a/internal/gitwt/gitwt_generate_zsh.go b/internal/gitwt/gitwt_generate_zsh.go index 657568c..6da634f 100644 --- a/internal/gitwt/gitwt_generate_zsh.go +++ b/internal/gitwt/gitwt_generate_zsh.go @@ -93,6 +93,10 @@ func (x *zshCommandOptions) writeFunctionFile(target string) error { --no-cd) no_cd=1 ;; + --herdr) + no_cd=1 + forward+=("$arg") + ;; -u|--upstream) forward+=("$arg") skip_next=1 diff --git a/internal/gitwt/gitwt_test.go b/internal/gitwt/gitwt_test.go index 75dc39c..d06191b 100644 --- a/internal/gitwt/gitwt_test.go +++ b/internal/gitwt/gitwt_test.go @@ -462,6 +462,8 @@ func TestGenerateZshGeneratesWrapperFunctionAndCompletion(t *testing.T) { "case \"$1\" in", "create)", "--no-cd)", + "--herdr)", + "no_cd=1", "command git-wt create \"${forward[@]}\"", "switch)", "remove)", @@ -483,6 +485,9 @@ func TestGenerateZshGeneratesWrapperFunctionAndCompletion(t *testing.T) { if strings.Contains(functionText, "${name//\\//.}") || strings.Contains(functionText, "${arg//\\//.}") { t.Fatalf("function still normalizes slashes in paths:\n%s", functionText) } + if !strings.Contains(functionText, "--herdr)\n no_cd=1\n forward+=(\"$arg\")") { + t.Fatalf("function does not make --herdr imply --no-cd:\n%s", functionText) + } completionText := string(completionContent) for _, want := range []string{ From 8d9d77c8cea1c065427b9fbeee19335dcf7e94ba Mon Sep 17 00:00:00 2001 From: Nathan Nutter Date: Thu, 23 Jul 2026 04:00:45 +0000 Subject: [PATCH 2/4] Fix tab completion for `wt create` --- internal/gitwt/gitwt_generate_zsh.go | 10 ++++++++++ internal/gitwt/gitwt_test.go | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/internal/gitwt/gitwt_generate_zsh.go b/internal/gitwt/gitwt_generate_zsh.go index 6da634f..36aa242 100644 --- a/internal/gitwt/gitwt_generate_zsh.go +++ b/internal/gitwt/gitwt_generate_zsh.go @@ -220,6 +220,16 @@ _` + x.name + `() { fi case $words[2] in + create) + shift words + (( CURRENT-- )) + _arguments \ + '--no-cd[Create without changing directories]' \ + '--herdr[Also create a Herdr workspace for the new worktree]' \ + '(-u --upstream)'{-u,--upstream}'[Upstream branch]:upstream branch:' \ + '(-h --help)'{-h,--help}'[help for create]' \ + '1:worktree name:' + ;; switch|remove) if ! git rev-parse --is-inside-work-tree 1>/dev/null 2>/dev/null; then return 1 diff --git a/internal/gitwt/gitwt_test.go b/internal/gitwt/gitwt_test.go index d06191b..d2c57c0 100644 --- a/internal/gitwt/gitwt_test.go +++ b/internal/gitwt/gitwt_test.go @@ -496,6 +496,10 @@ func TestGenerateZshGeneratesWrapperFunctionAndCompletion(t *testing.T) { "remove:Remove a managed Git worktree", "create:Create a managed Git worktree", "case $words[2] in", + "create)", + "--no-cd[Create without changing directories]", + "--herdr[Also create a Herdr workspace for the new worktree]", + "'(-u --upstream)'{-u,--upstream}'[Upstream branch]:upstream branch:'", "switch|remove)", "worktrees=(main)", "/.git/wt/", From d373aaad9f5550f0e2a1d610b6f93c4f4e7da015 Mon Sep 17 00:00:00 2001 From: Nathan Nutter Date: Fri, 24 Jul 2026 03:56:14 +0000 Subject: [PATCH 3/4] Allow -r for --herdr --- README.md | 8 ++++---- internal/gitwt/gitwt_create.go | 2 +- internal/gitwt/gitwt_generate_zsh.go | 4 ++-- internal/gitwt/gitwt_test.go | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index ad26156..b370b94 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Ensure the output directory is on `fpath`, then restart zsh or run `compinit`. The generated function: - routes most commands to `git-wt` (`wt create`, `wt list`, `wt prune`, …) -- after a successful `wt create`, `cd`s into the new worktree (`--no-cd` or `--herdr` to stay put) +- after a successful `wt create`, `cd`s into the new worktree (`--no-cd` or `-r` | `--herdr` to stay put) - provides a shell-only `switch` that `cd`s into a worktree - after a successful `wt remove`, `cd`s to the main worktree @@ -66,15 +66,15 @@ Set this **before** `source <(carapace _carapace)`. You may need `carapace --cle Create a managed worktree for a branch. - If the branch already exists, the worktree is created from that branch. -- If the branch does not exist, it is created from the upstream branch; which defaults to the default origin branch but can be set explicity with `--upstream` | `-u`. -- With `--herdr`, also create a [Herdr](https://herdr.dev) workspace whose `--cwd` is the new worktree and whose `--label` is the worktree name (branch name). `wt create --herdr` implies `--no-cd`. Requires `herdr` on `PATH` and a running Herdr server. +- If the branch does not exist, it is created from the branch pointed at by `origin/HEAD`; set it explicitly with `--upstream` | `-u`. +- With `-r` | `--herdr`, also create a [Herdr](https://herdr.dev) workspace whose `--cwd` is the new worktree and whose `--label` is the worktree name (branch name). `wt create -r` implies `--no-cd`. Requires `herdr` on `PATH` and a running Herdr server. Example: ```bash git-wt create feature/login git-wt create -u origin/v1.2 hotfix/1.2.1 -git-wt create --herdr feature/login +git-wt create -r feature/login ``` ### `git-wt list` diff --git a/internal/gitwt/gitwt_create.go b/internal/gitwt/gitwt_create.go index c320a46..ee6cd5a 100644 --- a/internal/gitwt/gitwt_create.go +++ b/internal/gitwt/gitwt_create.go @@ -23,7 +23,7 @@ func NewCreateCommand() *cobra.Command { } command.Flags().StringVarP(&options.upstream, "upstream", "u", "", "Upstream branch") - command.Flags().BoolVar(&options.herdr, "herdr", false, "Also create a Herdr workspace for the new worktree") + command.Flags().BoolVarP(&options.herdr, "herdr", "r", false, "Also create a Herdr workspace for the new worktree") return command } diff --git a/internal/gitwt/gitwt_generate_zsh.go b/internal/gitwt/gitwt_generate_zsh.go index 36aa242..7892ec9 100644 --- a/internal/gitwt/gitwt_generate_zsh.go +++ b/internal/gitwt/gitwt_generate_zsh.go @@ -93,7 +93,7 @@ func (x *zshCommandOptions) writeFunctionFile(target string) error { --no-cd) no_cd=1 ;; - --herdr) + -r|--herdr) no_cd=1 forward+=("$arg") ;; @@ -225,7 +225,7 @@ _` + x.name + `() { (( CURRENT-- )) _arguments \ '--no-cd[Create without changing directories]' \ - '--herdr[Also create a Herdr workspace for the new worktree]' \ + '(-r --herdr)'{-r,--herdr}'[Also create a Herdr workspace for the new worktree]' \ '(-u --upstream)'{-u,--upstream}'[Upstream branch]:upstream branch:' \ '(-h --help)'{-h,--help}'[help for create]' \ '1:worktree name:' diff --git a/internal/gitwt/gitwt_test.go b/internal/gitwt/gitwt_test.go index d2c57c0..3097c33 100644 --- a/internal/gitwt/gitwt_test.go +++ b/internal/gitwt/gitwt_test.go @@ -93,9 +93,9 @@ func TestCreateWithHerdrInvokesHerdrWorkspaceCreate(t *testing.T) { logPath := filepath.Join(t.TempDir(), "herdr.log") installFakeHerdr(t, logPath, 0) - result := testRepository.runGitWT(t, "create", "--herdr", branchName) + result := testRepository.runGitWT(t, "create", "-r", branchName) if result.err != nil { - t.Fatalf("create --herdr failed: %v\n%s", result.err, result.stderr) + t.Fatalf("create -r failed: %v\n%s", result.err, result.stderr) } testRepository.assertPathPresent(t, testRepository.worktreePath(branchName)) if !strings.Contains(result.stderr, "created herdr workspace "+branchName) { @@ -462,7 +462,7 @@ func TestGenerateZshGeneratesWrapperFunctionAndCompletion(t *testing.T) { "case \"$1\" in", "create)", "--no-cd)", - "--herdr)", + "-r|--herdr)", "no_cd=1", "command git-wt create \"${forward[@]}\"", "switch)", @@ -485,7 +485,7 @@ func TestGenerateZshGeneratesWrapperFunctionAndCompletion(t *testing.T) { if strings.Contains(functionText, "${name//\\//.}") || strings.Contains(functionText, "${arg//\\//.}") { t.Fatalf("function still normalizes slashes in paths:\n%s", functionText) } - if !strings.Contains(functionText, "--herdr)\n no_cd=1\n forward+=(\"$arg\")") { + if !strings.Contains(functionText, "-r|--herdr)\n no_cd=1\n forward+=(\"$arg\")") { t.Fatalf("function does not make --herdr imply --no-cd:\n%s", functionText) } @@ -498,7 +498,7 @@ func TestGenerateZshGeneratesWrapperFunctionAndCompletion(t *testing.T) { "case $words[2] in", "create)", "--no-cd[Create without changing directories]", - "--herdr[Also create a Herdr workspace for the new worktree]", + "'(-r --herdr)'{-r,--herdr}'[Also create a Herdr workspace for the new worktree]'", "'(-u --upstream)'{-u,--upstream}'[Upstream branch]:upstream branch:'", "switch|remove)", "worktrees=(main)", From 2789a48472fca3bcd862082e79144c9b868df796 Mon Sep 17 00:00:00 2001 From: Nathan Nutter Date: Fri, 24 Jul 2026 03:56:28 +0000 Subject: [PATCH 4/4] Add a test case for using origin/HEAD as default upstream --- internal/gitwt/gitwt_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/internal/gitwt/gitwt_test.go b/internal/gitwt/gitwt_test.go index 3097c33..f811a85 100644 --- a/internal/gitwt/gitwt_test.go +++ b/internal/gitwt/gitwt_test.go @@ -86,6 +86,38 @@ func TestCreateSucceedsWithWorktreeConfig(t *testing.T) { } } +func TestCreateUsesOriginHeadAsDefaultUpstream(t *testing.T) { + const defaultBranch = "default" + const branchName = "feature/origin-head" + const fileName = "default.txt" + const fileContents = "default branch\n" + + testRepository := newTestRepository(t) + runGitCommand(t, testRepository.mainPath, "checkout", "-b", defaultBranch, remoteName+"/main") + testRepository.writeFile(t, filepath.Join(testRepository.mainPath, fileName), fileContents) + runGitCommand(t, testRepository.mainPath, "add", fileName) + runGitCommand(t, testRepository.mainPath, "commit", "-m", "default branch") + runGitCommand(t, testRepository.mainPath, "push", "-u", remoteName, defaultBranch) + runGitCommand(t, testRepository.mainPath, "checkout", "main") + runGitCommand(t, testRepository.mainPath, "remote", "set-head", remoteName, defaultBranch) + + result := testRepository.runGitWT(t, "create", branchName) + if result.err != nil { + t.Fatalf("create failed: %v\n%s", result.err, result.stderr) + } + + createdCommit := strings.TrimSpace(runGitCommand(t, testRepository.mainPath, "rev-parse", branchName)) + upstreamCommit := strings.TrimSpace(runGitCommand(t, testRepository.mainPath, "rev-parse", remoteName+"/"+defaultBranch)) + if createdCommit != upstreamCommit { + t.Fatalf("created branch commit = %s, want %s", createdCommit, upstreamCommit) + } + + upstream := strings.TrimSpace(runGitCommand(t, testRepository.mainPath, "rev-parse", "--abbrev-ref", branchName+"@{upstream}")) + if upstream != remoteName+"/"+defaultBranch { + t.Fatalf("created branch upstream = %q, want %q", upstream, remoteName+"/"+defaultBranch) + } +} + func TestCreateWithHerdrInvokesHerdrWorkspaceCreate(t *testing.T) { const branchName = "feature/herdr"