diff --git a/README.md b/README.md index 2c950a7..71c4ab0 100644 --- a/README.md +++ b/README.md @@ -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`. diff --git a/internal/gitwt/gitwt_test.go b/internal/gitwt/gitwt_test.go index b0b916b..017ae67 100644 --- a/internal/gitwt/gitwt_test.go +++ b/internal/gitwt/gitwt_test.go @@ -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) diff --git a/internal/gitwt/repository.go b/internal/gitwt/repository.go index b38f34e..7cbf6cb 100644 --- a/internal/gitwt/repository.go +++ b/internal/gitwt/repository.go @@ -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) {