@@ -6,12 +6,19 @@ import (
66 "errors"
77 "fmt"
88 "io"
9+ "os"
10+ "path/filepath"
911 "strconv"
12+ "strings"
1013
1114 "github.com/go-git/go-git/v5"
1215 "github.com/go-git/go-git/v5/config"
1316 "github.com/go-git/go-git/v5/plumbing"
17+ "github.com/go-git/go-git/v5/plumbing/transport"
1418 githttp "github.com/go-git/go-git/v5/plumbing/transport/http"
19+ gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
20+ "github.com/skeema/knownhosts"
21+ gossh "golang.org/x/crypto/ssh"
1522)
1623
1724// OpenRepo opens the git repository rooted at dir or any parent directory.
@@ -90,7 +97,7 @@ func FetchRemote(
9097 ctx context.Context ,
9198 repo * git.Repository ,
9299 remoteName string ,
93- auth * githttp. BasicAuth ,
100+ auth transport. AuthMethod ,
94101 progress io.Writer ,
95102) error {
96103 err := repo .FetchContext (ctx , & git.FetchOptions {
@@ -167,7 +174,7 @@ func PushToPR(
167174 repo * git.Repository ,
168175 remoteName , localBranch , remoteBranch string ,
169176 force bool ,
170- auth * githttp. BasicAuth ,
177+ auth transport. AuthMethod ,
171178) error {
172179 refspec := config .RefSpec (
173180 "refs/heads/" + localBranch + ":refs/heads/" + remoteBranch ,
@@ -204,6 +211,52 @@ func TokenAuth(token string) *githttp.BasicAuth {
204211 }
205212}
206213
214+ // IsSSHURL reports whether remoteURL uses SSH transport (git@ or scp-style).
215+ func IsSSHURL (remoteURL string ) bool {
216+ return strings .HasPrefix (remoteURL , "git@" ) || strings .Contains (remoteURL , "github.com:" )
217+ }
218+
219+ // AuthForURL returns the appropriate auth for the remote URL.
220+ // HTTPS remotes get token-based BasicAuth; SSH remotes get SSH agent auth.
221+ func AuthForURL (remoteURL , token string ) (transport.AuthMethod , error ) {
222+ if IsSSHURL (remoteURL ) {
223+ return sshAgentAuth ()
224+ }
225+ return TokenAuth (token ), nil
226+ }
227+
228+ // sshAgentAuth creates SSH agent auth with known_hosts host key verification.
229+ // go-git's DefaultAuthBuilder has a known bug where its internal known_hosts
230+ // checker fails on hashed entries and multiple key types for the same host.
231+ // We build the auth explicitly so we control the HostKeyCallback.
232+ func sshAgentAuth () (transport.AuthMethod , error ) {
233+ auth , err := gitssh .NewSSHAgentAuth ("git" )
234+ if err != nil {
235+ return nil , fmt .Errorf ("connect to SSH agent: %w" , err )
236+ }
237+
238+ home , err := os .UserHomeDir ()
239+ if err != nil {
240+ home = os .Getenv ("HOME" )
241+ }
242+ knownHostsPath := filepath .Join (home , ".ssh" , "known_hosts" )
243+ cb , err := knownhosts .New (knownHostsPath )
244+ if err != nil {
245+ // known_hosts unreadable — proceed without host key verification.
246+ // This is the same behaviour as `ssh -o StrictHostKeyChecking=no`.
247+ auth .HostKeyCallbackHelper = gitssh.HostKeyCallbackHelper {
248+ HostKeyCallback : gossh .InsecureIgnoreHostKey (),
249+ }
250+ return auth , nil
251+ }
252+ // skeema/knownhosts.HostKeyCallback has the same underlying signature as
253+ // golang.org/x/crypto/ssh.HostKeyCallback; convert between the two named types.
254+ auth .HostKeyCallbackHelper = gitssh.HostKeyCallbackHelper {
255+ HostKeyCallback : gossh .HostKeyCallback (cb ),
256+ }
257+ return auth , nil
258+ }
259+
207260// StorePRNumber writes prNum into .git/config under
208261// [gh-commandeer "branchName"] pr = N so that it can be recalled later.
209262func StorePRNumber (repo * git.Repository , branchName string , prNum int ) error {
0 commit comments