Skip to content

Commit 29a36d3

Browse files
Fix repository recognition for SSH mapped configurations (#554)
* Bump go-gh to v2.16.2 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Test repo resolution through SSH host aliases Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent fc2b03d commit 29a36d3

3 files changed

Lines changed: 120 additions & 3 deletions

File tree

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ require (
1010
github.com/charmbracelet/glamour v0.10.0
1111
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834
1212
github.com/cli/cli/v2 v2.93.0
13-
github.com/cli/go-gh/v2 v2.16.1
13+
github.com/cli/go-gh/v2 v2.16.2
1414
github.com/cli/shurcooL-graphql v0.0.4
1515
github.com/muesli/termenv v0.16.0
1616
github.com/spf13/cobra v1.10.2

‎go.sum‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ github.com/cli/browser v1.3.0 h1:LejqCrpWr+1pRqmEPDGnTZOjsMe7sehifLynZJuqJpo=
4444
github.com/cli/browser v1.3.0/go.mod h1:HH8s+fOAxjhQoBUAsKuPCbqUuxZDhQ2/aD+SzsEfBTk=
4545
github.com/cli/cli/v2 v2.93.0 h1:B2ZvEAbFaATbeH+vjZdXBL0wxCk0sedaprvrKGh/VZo=
4646
github.com/cli/cli/v2 v2.93.0/go.mod h1:I9qEbkL3Zq1j8soXKh7yjJasP2HdpwzI531bjJXoXBI=
47-
github.com/cli/go-gh/v2 v2.16.1 h1:t8s29LToBmYjXeDXUnK7Wdm7NkYSyzGZK3t4Doa6GX0=
48-
github.com/cli/go-gh/v2 v2.16.1/go.mod h1:OaJTFtHJapQq670h/3L0vqm4NwZGoJmSAVctWiY+3pQ=
47+
github.com/cli/go-gh/v2 v2.16.2 h1:Qsb8GsS4QQWjupxcMutrj5tl2cJs6HubyDM0xQ4qsII=
48+
github.com/cli/go-gh/v2 v2.16.2/go.mod h1:OaJTFtHJapQq670h/3L0vqm4NwZGoJmSAVctWiY+3pQ=
4949
github.com/cli/safeexec v1.0.1 h1:e/C79PbXF4yYTN/wauC4tviMxEV13BwljGj0N9j+N00=
5050
github.com/cli/safeexec v1.0.1/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
5151
github.com/cli/shurcooL-graphql v0.0.4 h1:6MogPnQJLjKkaXPyGqPRXOI2qCsQdqNfUY1QSJu2GuY=

‎internal/config/repo_test.go‎

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package config
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"os"
8+
"os/exec"
9+
"path/filepath"
10+
"runtime"
11+
"strings"
12+
"testing"
13+
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestMain(m *testing.M) {
19+
runFakeSSHIfRequested()
20+
os.Exit(m.Run())
21+
}
22+
23+
func TestRepoResolvesRemoteUsingSSHHostAlias(t *testing.T) {
24+
// Given ssh maps github.com-work to github.com
25+
stubSSH(t, map[string]string{"github.com-work": "github.com"})
26+
// And github.com is a known host
27+
t.Setenv("GH_CONFIG_DIR", t.TempDir())
28+
t.Setenv("GH_HOST", "github.com")
29+
t.Setenv("GH_REPO", "")
30+
// And origin uses the alias
31+
dir := t.TempDir()
32+
runGit(t, dir, "init", "-q")
33+
runGit(t, dir, "remote", "add", "origin", "git@github.com-work:org/repo.git")
34+
t.Chdir(dir)
35+
36+
// When the repository is resolved
37+
config := &Config{}
38+
repo, err := config.Repo()
39+
40+
// Then it is the GitHub repository behind the alias
41+
require.NoError(t, err)
42+
assert.Equal(t, "github.com", repo.Host)
43+
assert.Equal(t, "org", repo.Owner)
44+
assert.Equal(t, "repo", repo.Name)
45+
}
46+
47+
func runGit(t *testing.T, dir string, args ...string) {
48+
t.Helper()
49+
out, err := exec.Command("git", append([]string{"-C", dir}, args...)...).CombinedOutput()
50+
require.NoError(t, err, "git %v: %s", args, out)
51+
}
52+
53+
// The fake ssh below is adapted from github.com/cli/go-gh/v2 internal/testutils/ssh_stub.go
54+
// Maybe go-gh can export a similar ssh stub for testing purposes, but for now we include our own.
55+
56+
const fakeSSHHostnamesEnv = "GH_STACK_TEST_FAKE_SSH_HOSTNAMES"
57+
58+
// stubSSH puts a fake ssh first on PATH. Its `ssh -G HOST` reports hostnames[HOST], or HOST itself
59+
// when unmapped, like ssh without a matching config. The fake is the running test binary, so
60+
// TestMain must call runFakeSSHIfRequested.
61+
func stubSSH(t *testing.T, hostnames map[string]string) {
62+
t.Helper()
63+
encoded, err := json.Marshal(hostnames)
64+
require.NoError(t, err, "encoding fake ssh hostnames")
65+
dir := t.TempDir()
66+
name := "ssh"
67+
if runtime.GOOS == "windows" {
68+
name += ".exe"
69+
}
70+
copyTestBinary(t, filepath.Join(dir, name))
71+
t.Setenv(fakeSSHHostnamesEnv, string(encoded))
72+
t.Setenv("PATH", dir+string(os.PathListSeparator)+os.Getenv("PATH"))
73+
}
74+
75+
// runFakeSSHIfRequested acts as `ssh -G` and exits when this test binary was started as the fake
76+
// ssh installed by stubSSH. Otherwise it returns.
77+
func runFakeSSHIfRequested() {
78+
if strings.TrimSuffix(filepath.Base(os.Args[0]), ".exe") != "ssh" {
79+
return
80+
}
81+
encoded, ok := os.LookupEnv(fakeSSHHostnamesEnv)
82+
if !ok {
83+
return
84+
}
85+
var hostnames map[string]string
86+
if err := json.Unmarshal([]byte(encoded), &hostnames); err != nil {
87+
fmt.Fprintf(os.Stderr, "fake ssh: decoding hostnames: %v\n", err)
88+
os.Exit(1)
89+
}
90+
args := os.Args[1:]
91+
if len(args) != 2 || args[0] != "-G" {
92+
fmt.Fprintf(os.Stderr, "fake ssh: unexpected arguments %q\n", args)
93+
os.Exit(1)
94+
}
95+
host := args[1]
96+
hostname, mapped := hostnames[host]
97+
if !mapped {
98+
hostname = host
99+
}
100+
fmt.Printf("hostname %s\n", hostname)
101+
os.Exit(0)
102+
}
103+
104+
func copyTestBinary(t *testing.T, dst string) {
105+
t.Helper()
106+
src, err := os.Executable()
107+
require.NoError(t, err, "locating test binary")
108+
in, err := os.Open(src)
109+
require.NoError(t, err, "opening test binary")
110+
defer in.Close()
111+
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY, 0o755)
112+
require.NoError(t, err, "creating fake ssh")
113+
_, err = io.Copy(out, in)
114+
closeErr := out.Close()
115+
require.NoError(t, err, "copying test binary")
116+
require.NoError(t, closeErr, "closing fake ssh")
117+
}

0 commit comments

Comments
 (0)