aboutsummaryrefslogtreecommitdiffstats
path: root/repository
diff options
context:
space:
mode:
Diffstat (limited to 'repository')
-rw-r--r--repository/gogit.go82
-rw-r--r--repository/gogit_test.go46
-rw-r--r--repository/mock_repo.go5
-rw-r--r--repository/repo.go4
4 files changed, 133 insertions, 4 deletions
diff --git a/repository/gogit.go b/repository/gogit.go
index 96d62665..a271ab8b 100644
--- a/repository/gogit.go
+++ b/repository/gogit.go
@@ -9,6 +9,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
+ "regexp"
"sort"
"strings"
"sync"
@@ -21,6 +22,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"
@@ -383,14 +385,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
}
@@ -436,14 +455,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
}
@@ -451,6 +487,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 6ea5c71e..a4095fec 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/MichaelMure/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 c39051d5..49f5a97b 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/MichaelMure/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)