Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ 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 branch pointed at by `origin/HEAD`; set it explicitly with `--upstream` | `-u`.
- If the branch does not exist, it is created from the branch pointed at by `origin/HEAD`, or if that is unset from `origin/master` then `origin/main`; set it explicitly with `--upstream` | `-u`.
- When run inside [Herdr](https://herdr.dev) (`HERDR_ENV=1`), automatically create a Herdr workspace whose `--cwd` is the new worktree and whose `--label` is the worktree name (branch name).
- Use `-r` | `--herdr` to create a Herdr workspace explicitly, or `-R` | `--no-herdr` to suppress automatic creation.
- Herdr workspace creation through `wt create` implies `--no-cd`.
Expand Down
44 changes: 41 additions & 3 deletions internal/gitwt/gitwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,51 @@ func TestCreateUsesOriginHeadAsDefaultUpstream(t *testing.T) {
}
}

func TestCreateFailsWhenOriginHeadIsMissing(t *testing.T) {
func TestCreateFallsBackToOriginMasterWhenOriginHeadIsMissing(t *testing.T) {
const branchName = "feature/fallback-master"
const masterBranch = "master"

testRepository := newTestRepository(t)
runGitCommand(t, testRepository.mainPath, "branch", masterBranch, remoteName+"/main")
runGitCommand(t, testRepository.mainPath, "push", remoteName, masterBranch)
runGitCommand(t, testRepository.mainPath, "remote", "set-head", "--delete", remoteName)

result := testRepository.runGitWT(t, "create", branchName)
if result.err != nil {
t.Fatalf("create failed: %v\n%s", result.err, result.stderr)
}

upstream := strings.TrimSpace(runGitCommand(t, testRepository.mainPath, "rev-parse", "--abbrev-ref", branchName+"@{upstream}"))
if upstream != remoteName+"/"+masterBranch {
t.Fatalf("created branch upstream = %q, want %q", upstream, remoteName+"/"+masterBranch)
}
}

func TestCreateFallsBackToOriginMainWhenOriginHeadAndMasterAreMissing(t *testing.T) {
const branchName = "feature/fallback-main"

testRepository := newTestRepository(t)
runGitCommand(t, testRepository.mainPath, "remote", "set-head", "--delete", remoteName)

result := testRepository.runGitWT(t, "create", "feature/missing-origin-head")
result := testRepository.runGitWT(t, "create", branchName)
if result.err != nil {
t.Fatalf("create failed: %v\n%s", result.err, result.stderr)
}

upstream := strings.TrimSpace(runGitCommand(t, testRepository.mainPath, "rev-parse", "--abbrev-ref", branchName+"@{upstream}"))
if upstream != remoteName+"/main" {
t.Fatalf("created branch upstream = %q, want %q", upstream, remoteName+"/main")
}
}

func TestCreateFailsWhenOriginHeadAndCommonDefaultsAreMissing(t *testing.T) {
testRepository := newTestRepository(t)
runGitCommand(t, testRepository.mainPath, "remote", "set-head", "--delete", remoteName)
runGitCommand(t, testRepository.mainPath, "update-ref", "-d", "refs/remotes/origin/main")

result := testRepository.runGitWT(t, "create", "feature/missing-default-upstream")
if result.err == nil {
t.Fatal("create succeeded without origin/HEAD")
t.Fatal("create succeeded without origin/HEAD, origin/master, or origin/main")
}
if !strings.Contains(result.err.Error(), "resolve origin/HEAD") {
t.Fatalf("create error = %q, want origin/HEAD resolution error", result.err)
Expand Down
29 changes: 26 additions & 3 deletions internal/gitwt/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,11 +202,34 @@ func (x *Repository) mainWorktreeBranch() (string, error) {

func (x *Repository) remoteHeadBranch() (string, error) {
result, err := x.git("symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD")
if err != nil {
return "", fmt.Errorf("resolve origin/HEAD: %w", err)
if err == nil {
return result.stdout, nil
}

fallback, fallbackErr := x.firstExistingRemoteBranch("master", "main")
if fallbackErr != nil {
return "", fallbackErr
}
if fallback != "" {
return fallback, nil
}

return "", fmt.Errorf("resolve origin/HEAD: %w", err)
}

func (x *Repository) firstExistingRemoteBranch(branchNames ...string) (string, error) {
for _, branchName := range branchNames {
remoteBranch := remoteName + "/" + branchName
exists, err := x.branchStillExists(referenceName(remoteRefPrefix + remoteBranch))
if err != nil {
return "", err
}
if exists {
return remoteBranch, nil
}
}

return result.stdout, nil
return "", nil
}

func (x *Repository) upstreamReference(branchName string) (referenceName, error) {
Expand Down