Commit 0d422fbdaf

0d422fbdaf6144ce5f971d8210e4b8123f3bdad4

parent: 388142116c

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-24 02:31 UTC

cli: foreign origins must not hijack instance resolution

Resolution order is now configured-instance match (with repo
inference), then the default instance (no inference), then the raw
origin as an ad-hoc instance only when nothing is configured. Running
gitbay inside a github clone previously sshed to github and got
git-shell errors. Repo inference only applies when the origin matches
the chosen instance.

Layout: unified · split

cmd/gitbay/ssh.go +22 −21
@@ -20,39 +20,40 @@ type target struct {
2020 repo string // owner/name, "" when not inferable
2121}
2222
23// resolveTarget picks the instance and repo. Inside a git repo whose origin
24// remote points at a configured (or any ssh) forge host, that wins;
25// otherwise the configured default instance.
23// resolveTarget picks the instance and repo. An origin remote matching a
24// CONFIGURED instance wins and carries repo inference; otherwise the
25// default instance is used with no inference — a clone from some other
26// host (github, a different forge) must never hijack the command. The
27// raw origin serves as an ad-hoc instance only when nothing is configured
28// at all.
2629func resolveTarget() (target, error) {
2730 cfg, err := cliconfig.Load()
2831 if err != nil {
2932 return target{}, err
3033 }
3134
32 if url := originURL(); url != "" {
33 if parsed, repo, ok := cliconfig.ParseRemoteURL(url); ok {
34 // Prefer a configured instance for the same host+port: it may
35 // carry ssh_options the bare URL cannot express.
36 norm := func(p int) int {
37 if p == 0 {
38 return 22
39 }
40 return p
35 parsed, repo, originOK := cliconfig.ParseRemoteURL(originURL())
36 if originOK {
37 norm := func(p int) int {
38 if p == 0 {
39 return 22
4140 }
42 for _, inst := range cfg.Instances {
43 if inst.Host == parsed.Host && norm(inst.Port) == norm(parsed.Port) {
44 return target{inst: inst, repo: repo}, nil
45 }
41 return p
42 }
43 for _, inst := range cfg.Instances {
44 if inst.Host == parsed.Host && norm(inst.Port) == norm(parsed.Port) {
45 return target{inst: inst, repo: repo}, nil
4646 }
47 return target{inst: parsed, repo: repo}, nil
4847 }
4948 }
5049
51 inst, _, err := cfg.DefaultInstance()
52 if err != nil {
53 return target{}, err
50 if inst, _, err := cfg.DefaultInstance(); err == nil {
51 return target{inst: inst}, nil
52 }
53 if originOK {
54 return target{inst: parsed, repo: repo}, nil
5455 }
55 return target{inst: inst}, nil
56 return target{}, fmt.Errorf("no gitbay instance configured; run: gitbay remote add <name> <host>")
5657}
5758
5859func originURL() string {
e2e/cli_test.go +16
@@ -193,6 +193,22 @@ func TestCLI(t *testing.T) {
193193 t.Fatalf("init-created repo: %s", out)
194194 }
195195
196 // A clone whose origin points at a FOREIGN host must not hijack the
197 // command: the configured default instance is used, and repo inference
198 // is dropped rather than guessed across hosts.
199 foreign := filepath.Join(t.TempDir(), "f")
200 os.MkdirAll(foreign, 0o755)
201 mustGit(t, foreign, cliGitEnv, "init", "-q")
202 mustGit(t, foreign, cliGitEnv, "remote", "add", "origin", "git@github.example:someone/thing.git")
203 out = c.must(t, foreign, "", "auth", "whoami")
204 if strings.TrimSpace(out) != "alice" {
205 t.Fatalf("foreign-origin whoami hit the wrong host: %q", out)
206 }
207 _, errOut2, code2 := c.run(t, foreign, "", "issue", "list")
208 if code2 != 2 || !strings.Contains(errOut2, "none inferable") {
209 t.Fatalf("foreign-origin repo inference: exit %d, %s", code2, errOut2)
210 }
211
196212 // Man pages and completions generate.
197213 manDir := t.TempDir()
198214 c.must(t, "", "", "man", "--dir", manDir)