diff --git a/README.md b/README.md index 6d48e63..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` 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). 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 657568c..7892ec9 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 ;; + -r|--herdr) + no_cd=1 + forward+=("$arg") + ;; -u|--upstream) forward+=("$arg") skip_next=1 @@ -216,6 +220,16 @@ _` + x.name + `() { fi case $words[2] in + create) + shift words + (( CURRENT-- )) + _arguments \ + '--no-cd[Create without changing directories]' \ + '(-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:' + ;; 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 75dc39c..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" @@ -93,9 +125,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,6 +494,8 @@ func TestGenerateZshGeneratesWrapperFunctionAndCompletion(t *testing.T) { "case \"$1\" in", "create)", "--no-cd)", + "-r|--herdr)", + "no_cd=1", "command git-wt create \"${forward[@]}\"", "switch)", "remove)", @@ -483,6 +517,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, "-r|--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{ @@ -491,6 +528,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]", + "'(-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)", "/.git/wt/",