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 {
20 repo string // owner/name, "" when not inferable 20 repo string // owner/name, "" when not inferable
21} 21}
22 22
23// resolveTarget picks the instance and repo. Inside a git repo whose origin 23// resolveTarget picks the instance and repo. An origin remote matching a
24// remote points at a configured (or any ssh) forge host, that wins; 24// CONFIGURED instance wins and carries repo inference; otherwise the
25// otherwise the configured default instance. 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.
26func resolveTarget() (target, error) { 29func resolveTarget() (target, error) {
27 cfg, err := cliconfig.Load() 30 cfg, err := cliconfig.Load()
28 if err != nil { 31 if err != nil {
29 return target{}, err 32 return target{}, err
30 } 33 }
31 34
32 if url := originURL(); url != "" { 35 parsed, repo, originOK := cliconfig.ParseRemoteURL(originURL())
33 if parsed, repo, ok := cliconfig.ParseRemoteURL(url); ok { 36 if originOK {
34 // Prefer a configured instance for the same host+port: it may 37 norm := func(p int) int {
35 // carry ssh_options the bare URL cannot express. 38 if p == 0 {
36 norm := func(p int) int { 39 return 22
37 if p == 0 {
38 return 22
39 }
40 return p
41 } 40 }
42 for _, inst := range cfg.Instances { 41 return p
43 if inst.Host == parsed.Host && norm(inst.Port) == norm(parsed.Port) { 42 }
44 return target{inst: inst, repo: repo}, nil 43 for _, inst := range cfg.Instances {
45 } 44 if inst.Host == parsed.Host && norm(inst.Port) == norm(parsed.Port) {
45 return target{inst: inst, repo: repo}, nil
46 } 46 }
47 return target{inst: parsed, repo: repo}, nil
48 } 47 }
49 } 48 }
50 49
51 inst, _, err := cfg.DefaultInstance() 50 if inst, _, err := cfg.DefaultInstance(); err == nil {
52 if err != nil { 51 return target{inst: inst}, nil
53 return target{}, err 52 }
53 if originOK {
54 return target{inst: parsed, repo: repo}, nil
54 } 55 }
55 return target{inst: inst}, nil 56 return target{}, fmt.Errorf("no gitbay instance configured; run: gitbay remote add <name> <host>")
56} 57}
57 58
58func originURL() string { 59func originURL() string {
e2e/cli_test.go +16
@@ -193,6 +193,22 @@ func TestCLI(t *testing.T) {
193 t.Fatalf("init-created repo: %s", out) 193 t.Fatalf("init-created repo: %s", out)
194 } 194 }
195 195
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
196 // Man pages and completions generate. 212 // Man pages and completions generate.
197 manDir := t.TempDir() 213 manDir := t.TempDir()
198 c.must(t, "", "", "man", "--dir", manDir) 214 c.must(t, "", "", "man", "--dir", manDir)