diff options
author | jan <git@wiegelmann.net> | 2024-04-13 15:01:34 +0200 |
---|---|---|
committer | Matěj Cepl <mcepl@cepl.eu> | 2024-11-03 00:42:08 +0100 |
commit | 46ea695556342708e8d647d3fb07ec9a1518ee20 (patch) | |
tree | 09bb0f5875e19806a96429fd84f3260f8ed695b8 | |
parent | 10cdf7860f8bcbf94b73611ebd95b4a1a123bb9f (diff) | |
download | git-bug-46ea695556342708e8d647d3fb07ec9a1518ee20.tar.gz |
-rw-r--r-- | repository/gogit.go | 82 | ||||
-rw-r--r-- | repository/gogit_test.go | 46 | ||||
-rw-r--r-- | repository/mock_repo.go | 5 | ||||
-rw-r--r-- | repository/repo.go | 4 |
4 files changed, 133 insertions, 4 deletions
diff --git a/repository/gogit.go b/repository/gogit.go index 2862eb7d..eca3986f 100644 --- a/repository/gogit.go +++ b/repository/gogit.go @@ -8,6 +8,7 @@ import ( "io" "os" "path/filepath" + "regexp" "sort" "strings" "sync" @@ -20,6 +21,7 @@ import ( "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/filemode" "github.com/go-git/go-git/v5/plumbing/object" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" "golang.org/x/sync/errgroup" "golang.org/x/sys/execabs" @@ -382,14 +384,31 @@ func (repo *GoGitRepo) FetchRefs(remote string, prefixes ...string) (string, err buf := bytes.NewBuffer(nil) - err := repo.r.Fetch(&gogit.FetchOptions{ + fetchOptions := &gogit.FetchOptions{ RemoteName: remote, RefSpecs: refSpecs, Progress: buf, - }) + } + + publicKeys, err := repo.SSHAuth(remote) + if err != nil { + return "", err + } + + err = repo.r.Fetch(fetchOptions) if err == gogit.NoErrAlreadyUpToDate { return "already up-to-date", nil } + // ssh-agent is required if ssh or scp-like url is configured in repository config + // we can not fetch if the ssh-agent has invalid keys or ssh-agent is not running + // retry to fetch again if we can retreive public keys from the users home directory + if err != nil && publicKeys != nil { + fetchOptions.Auth = publicKeys + err = repo.r.Fetch(fetchOptions) + if err == gogit.NoErrAlreadyUpToDate { + return "already up-to-date", nil + } + } if err != nil { return "", err } @@ -435,14 +454,31 @@ func (repo *GoGitRepo) PushRefs(remote string, prefixes ...string) (string, erro buf := bytes.NewBuffer(nil) - err = remo.Push(&gogit.PushOptions{ + pushOptions := &gogit.PushOptions{ RemoteName: remote, RefSpecs: refSpecs, Progress: buf, - }) + } + + publicKeys, err := repo.SSHAuth(remote) + if err != nil { + return "", err + } + + err = repo.r.Push(pushOptions) if err == gogit.NoErrAlreadyUpToDate { return "already up-to-date", nil } + // ssh-agent is required if ssh or scp-like url is configured in repository config + // we can not push if the ssh-agent has invalid keys or ssh-agent is not running + // retry to push again if we can retreive public keys from the users home directory + if err != nil && publicKeys != nil { + pushOptions.Auth = publicKeys + err = repo.r.Push(pushOptions) + if err == gogit.NoErrAlreadyUpToDate { + return "already up-to-date", nil + } + } if err != nil { return "", err } @@ -450,6 +486,44 @@ func (repo *GoGitRepo) PushRefs(remote string, prefixes ...string) (string, erro return buf.String(), nil } +// SSHAuth will attempt to read public keys for SSH auth +// if the repository remote contains a ssh or scp-like url +func (repo *GoGitRepo) SSHAuth(remote string) (*ssh.PublicKeys, error) { + // get the repository config + config, err := repo.r.Config() + if err != nil { + return nil, err + } + + // check if the repository config has at least one remote url + remotes, found := config.Remotes[remote] + if !found || len(remotes.URLs) < 1 { + return nil, fmt.Errorf("remote %s url not found in repository config", remote) + } + + schemeRegexp := regexp.MustCompile(`^[^:]+://`) + scpLikeRegexp := regexp.MustCompile(`^(?:(?P<user>[^@]+)@)?(?P<host>)`) + + // try to load public keys from the users home directory + // if the repository remote contains a ssh or scp-like url + if strings.HasPrefix(remotes.URLs[0], "ssh://") || (scpLikeRegexp.MatchString(remotes.URLs[0]) && !schemeRegexp.MatchString(remotes.URLs[0])) { + home, err := os.UserHomeDir() + if err != nil { + return nil, err + } + + // try to find and load valid public keys from the users home directory + attemptKeys := []string{"id_rsa", "id_ecdsa", "id_ecdsa_sk", "id_ed25519", "id_ed25519_sk", "id_xmss", "id_dsa"} + for _, key := range attemptKeys { + authMethod, err := ssh.NewPublicKeysFromFile("git", filepath.Join(home, ".ssh", key), "") + if err == nil { + return authMethod, nil + } + } + } + return nil, nil +} + // StoreData will store arbitrary data and return the corresponding hash func (repo *GoGitRepo) StoreData(data []byte) (Hash, error) { obj := repo.r.Storer.NewEncodedObject() diff --git a/repository/gogit_test.go b/repository/gogit_test.go index 21acd5df..dff94605 100644 --- a/repository/gogit_test.go +++ b/repository/gogit_test.go @@ -2,6 +2,7 @@ package repository import ( "fmt" + "log" "os" "path" "path/filepath" @@ -96,3 +97,48 @@ func TestGoGit_DetectsSubmodules(t *testing.T) { assert.Empty(t, err) assert.Equal(t, expected, result) } + +func TestGoGitRepoSSH(t *testing.T) { + repo := CreateGoGitTestRepo(t, false) + + err := repo.AddRemote("ssh", "ssh://git@github.com:MichaelMure/git-bug.git") + if err != nil { + log.Fatal(err) + } + keys, err := repo.SSHAuth("ssh") + require.NotNil(t, keys) + require.Empty(t, err) + + err = repo.AddRemote("http", "http://github.com/MichaelMure/git-bug.git") + if err != nil { + log.Fatal(err) + } + keys, err = repo.SSHAuth("http") + require.Nil(t, keys) + require.Empty(t, err) + + err = repo.AddRemote("https", "https://github.com/MichaelMure/git-bug.git") + if err != nil { + log.Fatal(err) + } + keys, err = repo.SSHAuth("https") + require.Nil(t, keys) + require.Empty(t, err) + + err = repo.AddRemote("git", "git://github.com/MichaelMure/git-bug.git") + if err != nil { + log.Fatal(err) + } + keys, err = repo.SSHAuth("git") + require.Nil(t, keys) + require.Empty(t, err) + + err = repo.AddRemote("scp-like", "git@github.com:MichaelMure/git-bug.git") + if err != nil { + log.Fatal(err) + } + keys, err = repo.SSHAuth("scp-like") + require.NotNil(t, keys) + require.Empty(t, err) + +} diff --git a/repository/mock_repo.go b/repository/mock_repo.go index b9cbe138..3f8555ee 100644 --- a/repository/mock_repo.go +++ b/repository/mock_repo.go @@ -10,6 +10,7 @@ import ( "github.com/99designs/keyring" "github.com/ProtonMail/go-crypto/openpgp" "github.com/go-git/go-billy/v5/memfs" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" "github.com/git-bug/git-bug/util/lamport" ) @@ -252,6 +253,10 @@ func (r *mockRepoData) PushRefs(remote string, prefixes ...string) (string, erro panic("implement me") } +func (r *mockRepoData) SSHAuth(remote string) (*ssh.PublicKeys, error) { + panic("implement me") +} + func (r *mockRepoData) StoreData(data []byte) (Hash, error) { rawHash := sha1.Sum(data) hash := Hash(fmt.Sprintf("%x", rawHash)) diff --git a/repository/repo.go b/repository/repo.go index 23474277..3876a42f 100644 --- a/repository/repo.go +++ b/repository/repo.go @@ -7,6 +7,7 @@ import ( "github.com/ProtonMail/go-crypto/openpgp" "github.com/go-git/go-billy/v5" + "github.com/go-git/go-git/v5/plumbing/transport/ssh" "github.com/git-bug/git-bug/util/lamport" ) @@ -142,6 +143,9 @@ type RepoData interface { // the remote state. PushRefs(remote string, prefixes ...string) (string, error) + // SSHAuth will attempt to read public keys for SSH auth + SSHAuth(remote string) (*ssh.PublicKeys, error) + // StoreData will store arbitrary data and return the corresponding hash StoreData(data []byte) (Hash, error) |