fix(git): prefer the upstream remote over a tracked origin when finding the repository (follow gh)

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-18 18:59:04 +02:00
1 parent 2a221d0a81
commit 517d164d8a
3 files changed
+16 -8

No files matched your search

+5 -3
View File
@@ -292,9 +292,11 @@ func helpTopics(globals *pflag.FlagSet) []*cobra.Command {
` + indent(flagUsages(flags)) + ` ` + indent(flagUsages(flags)) + `
tea selects the repository from --repo, TEA_REPO, git config tea.repository or a tea selects the repository from --repo, TEA_REPO, git config tea.repository (tea repo
Git remote (--remote, else the current branch's remote, upstream or origin, skipping set-default) or a Git remote (--remote, else the base remote pr checkout recorded, the
remotes on instances without an account, or not on --host or the GITEA_TOKEN instance), current branch's remote, upstream or origin, skipping remotes on instances without an
account, or not on --host or the GITEA_TOKEN instance). Like gh, a clone with upstream
and origin targets upstream: a branch tracking origin does not select the fork,
and the instance from the repository URL, --host, TEA_HOST or GITEA_INSTANCE_URL, and the instance from the repository URL, --host, TEA_HOST or GITEA_INSTANCE_URL,
default_host, or the only configured instance. default_host, or the only configured instance.
+9 -5
View File
@@ -16,7 +16,7 @@ var ErrAmbiguousRemote = errors.New("ambiguous repository: select --remote or --
// DiscoverRemote names the remote used for repository discovery: preferred when set, otherwise // DiscoverRemote names the remote used for repository discovery: preferred when set, otherwise
// the base remote pr checkout recorded for the current branch, the current branch's upstream // the base remote pr checkout recorded for the current branch, the current branch's upstream
// remote unless it is a pulls/<owner>/<repo> head remote, then upstream, origin, or the only remote. // remote unless it is a pulls/<owner>/<repo> head remote or origin beside an upstream remote (a fork clone), then upstream, origin, or the only remote.
// When repository is not nil, it names the repository of a remote tea can act on, or returns "" // When repository is not nil, it names the repository of a remote tea can act on, or returns ""
// for one on another forge. Those steps then skip such remotes, and the other remotes decide when // for one on another forge. Those steps then skip such remotes, and the other remotes decide when
// they name exactly one repository; with none of them usable the choice is the name-based one. // they name exactly one repository; with none of them usable the choice is the name-based one.
@@ -43,19 +43,23 @@ func DiscoverRemote(ctx context.Context, runner Runner, dir, preferred string, r
if strings.HasPrefix(tracking, "pulls/") { if strings.HasPrefix(tracking, "pulls/") {
tracking = "" tracking = ""
} }
names, namesErr := runner.Output(ctx, dir, false, "remote")
available := strings.Fields(names)
// In a fork clone, branches pushed to the fork track origin, but the base repository is upstream.
if tracking == "origin" && slices.Contains(available, "upstream") {
tracking = ""
}
for _, remote := range []string{pullBaseRemote(ctx, runner, dir, branch), tracking} { for _, remote := range []string{pullBaseRemote(ctx, runner, dir, branch), tracking} {
if try(remote) { if try(remote) {
return remote, nil return remote, nil
} }
} }
names, err := runner.Output(ctx, dir, false, "remote") if namesErr != nil {
if err != nil {
if fallback != "" { if fallback != "" {
return fallback, nil return fallback, nil
} }
return "", err return "", namesErr
} }
available := strings.Fields(names)
for _, name := range []string{"upstream", "origin"} { for _, name := range []string{"upstream", "origin"} {
if slices.Contains(available, name) && try(name) { if slices.Contains(available, name) && try(name) {
return name, nil return name, nil
+2
View File
@@ -38,6 +38,8 @@ func TestDiscoverRemote(t *testing.T) {
{name: "recorded pull base", runner: discoverRunner{head: "main", tracking: "fork", base: "upstream", "remote": "origin\nupstream\nfork"}, want: "upstream"}, {name: "recorded pull base", runner: discoverRunner{head: "main", tracking: "fork", base: "upstream", "remote": "origin\nupstream\nfork"}, want: "upstream"},
{name: "removed pull base", runner: discoverRunner{head: "main", tracking: "fork", base: "gone", "remote": "origin\nfork"}, want: "fork"}, {name: "removed pull base", runner: discoverRunner{head: "main", tracking: "fork", base: "gone", "remote": "origin\nfork"}, want: "fork"},
{name: "local branch upstream", runner: discoverRunner{head: "main", tracking: ".", "remote": "origin\nfork"}, want: "origin"}, {name: "local branch upstream", runner: discoverRunner{head: "main", tracking: ".", "remote": "origin\nfork"}, want: "origin"},
{name: "fork clone branch tracking origin", runner: discoverRunner{head: "main", tracking: "origin", "remote": "origin\nupstream"}, want: "upstream"},
{name: "branch tracking origin without upstream", runner: discoverRunner{head: "main", tracking: "origin", "remote": "origin\nfork"}, want: "origin"},
{name: "upstream remote", runner: discoverRunner{"remote": "origin\nupstream"}, want: "upstream"}, {name: "upstream remote", runner: discoverRunner{"remote": "origin\nupstream"}, want: "upstream"},
{name: "origin remote", runner: discoverRunner{"remote": "fork\norigin"}, want: "origin"}, {name: "origin remote", runner: discoverRunner{"remote": "fork\norigin"}, want: "origin"},
{name: "sole remote", runner: discoverRunner{"remote": "fork"}, want: "fork"}, {name: "sole remote", runner: discoverRunner{"remote": "fork"}, want: "fork"},