repository: add access to the system keyring, with fallback on a file

Michael Muré created

Change summary

go.mod                  |  1 
go.sum                  | 17 ++++++++++
repository/config.go    |  6 +++
repository/git.go       | 16 ++++++++-
repository/gogit.go     | 18 ++++++++-
repository/keyring.go   | 73 +++++++++++++++++++++++++++++++++++++++++++
repository/mock_repo.go |  9 +++++
repository/repo.go      | 11 +++++-
8 files changed, 144 insertions(+), 7 deletions(-)

Detailed changes

go.mod 🔗

@@ -4,6 +4,7 @@ go 1.12
 
 require (
 	github.com/99designs/gqlgen v0.10.3-0.20200209012558-b7a58a1c0e4b
+	github.com/99designs/keyring v1.1.5
 	github.com/MichaelMure/go-term-text v0.2.9
 	github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195
 	github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986

go.sum 🔗

@@ -1,6 +1,8 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/99designs/gqlgen v0.10.3-0.20200209012558-b7a58a1c0e4b h1:510xa84qGbDemwTHNio4cLWkdKFxxJgVtsIOH+Ku8bo=
 github.com/99designs/gqlgen v0.10.3-0.20200209012558-b7a58a1c0e4b/go.mod h1:dfBhwZKMcSYiYRMTs8qWF+Oha6782e1xPfgRmVal9I8=
+github.com/99designs/keyring v1.1.5 h1:wLv7QyzYpFIyMSwOADq1CLTF9KbjbBfcnfmOGJ64aO4=
+github.com/99designs/keyring v1.1.5/go.mod h1:7hsVvt2qXgtadGevGJ4ujg+u8m6SpJ5TpHqTozIPqf0=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic=
 github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
@@ -53,6 +55,8 @@ github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ
 github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU=
+github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -62,6 +66,8 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
 github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
 github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a h1:mq+R6XEM6lJX5VlLyZIrUSP8tSuJp82xTK89hvBwJbU=
+github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
 github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
 github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
@@ -85,6 +91,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
+github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
 github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
@@ -112,6 +120,8 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
+github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
 github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
 github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
 github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
@@ -136,6 +146,8 @@ github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
 github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM=
+github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -167,6 +179,8 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk
 github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
+github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
@@ -232,6 +246,7 @@ github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
 github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -263,6 +278,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
 golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM=
@@ -302,6 +318,7 @@ golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpbl
 golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
 golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

repository/config.go 🔗

@@ -1,10 +1,16 @@
 package repository
 
 import (
+	"errors"
 	"strconv"
 	"time"
 )
 
+var (
+	ErrNoConfigEntry       = errors.New("no config entry for the given key")
+	ErrMultipleConfigEntry = errors.New("multiple config entry for the given key")
+)
+
 // Config represent the common function interacting with the repository config storage
 type Config interface {
 	// Store writes a single key/value pair in the config

repository/git.go 🔗

@@ -26,6 +26,8 @@ type GitRepo struct {
 
 	clocksMutex sync.Mutex
 	clocks      map[string]lamport.Clock
+
+	keyring Keyring
 }
 
 // LocalConfig give access to the repository scoped configuration
@@ -38,6 +40,10 @@ func (repo *GitRepo) GlobalConfig() Config {
 	return newGitConfig(repo, true)
 }
 
+func (repo *GitRepo) Keyring() Keyring {
+	return repo.keyring
+}
+
 // Run the given git command with the given I/O reader/writers, returning an error if it fails.
 func (repo *GitRepo) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error {
 	// make sure that the working directory for the command
@@ -83,9 +89,15 @@ func (repo *GitRepo) runGitCommand(args ...string) (string, error) {
 // NewGitRepo determines if the given working directory is inside of a git repository,
 // and returns the corresponding GitRepo instance if it is.
 func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) {
+	k, err := defaultKeyring()
+	if err != nil {
+		return nil, err
+	}
+
 	repo := &GitRepo{
-		path:   path,
-		clocks: make(map[string]lamport.Clock),
+		path:    path,
+		clocks:  make(map[string]lamport.Clock),
+		keyring: k,
 	}
 
 	// Check the repo and retrieve the root path

repository/gogit.go 🔗

@@ -24,6 +24,8 @@ type GoGitRepo struct {
 
 	clocksMutex sync.Mutex
 	clocks      map[string]lamport.Clock
+
+	keyring Keyring
 }
 
 func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) {
@@ -37,10 +39,16 @@ func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) {
 		return nil, err
 	}
 
+	k, err := defaultKeyring()
+	if err != nil {
+		return nil, err
+	}
+
 	repo := &GoGitRepo{
-		r:      r,
-		path:   path,
-		clocks: make(map[string]lamport.Clock),
+		r:       r,
+		path:    path,
+		clocks:  make(map[string]lamport.Clock),
+		keyring: k,
 	}
 
 	for _, loader := range clockLoaders {
@@ -154,6 +162,10 @@ func (repo *GoGitRepo) GlobalConfig() Config {
 	panic("go-git doesn't support writing global config")
 }
 
+func (repo *GoGitRepo) Keyring() Keyring {
+	return repo.keyring
+}
+
 // GetPath returns the path to the repo.
 func (repo *GoGitRepo) GetPath() string {
 	return repo.path

repository/keyring.go 🔗

@@ -0,0 +1,73 @@
+package repository
+
+import (
+	"os"
+	"path"
+
+	"github.com/99designs/keyring"
+)
+
+type Item = keyring.Item
+
+var ErrKeyringKeyNotFound = keyring.ErrKeyNotFound
+
+// Keyring provides the uniform interface over the underlying backends
+type Keyring interface {
+	// Returns an Item matching the key or ErrKeyringKeyNotFound
+	Get(key string) (Item, error)
+	// Stores an Item on the keyring
+	Set(item Item) error
+	// Removes the item with matching key
+	Remove(key string) error
+	// Provides a slice of all keys stored on the keyring
+	Keys() ([]string, error)
+}
+
+func defaultKeyring() (Keyring, error) {
+	ucd, err := os.UserConfigDir()
+	if err != nil {
+		return nil, err
+	}
+
+	backends := []keyring.BackendType{
+		keyring.WinCredBackend,
+		keyring.KeychainBackend,
+		keyring.PassBackend,
+		keyring.FileBackend,
+	}
+
+	return keyring.Open(keyring.Config{
+		// TODO: ideally this would not be there, it disable the freedesktop backend on linux
+		// due to https://github.com/99designs/keyring/issues/44
+		AllowedBackends: backends,
+
+		ServiceName: "git-bug",
+
+		// MacOS keychain
+		KeychainName:             "git-bug",
+		KeychainTrustApplication: true,
+
+		// KDE Wallet
+		KWalletAppID:  "git-bug",
+		KWalletFolder: "git-bug",
+
+		// Windows
+		WinCredPrefix: "git-bug",
+
+		// freedesktop.org's Secret Service
+		LibSecretCollectionName: "git-bug",
+
+		// Pass (https://www.passwordstore.org/)
+		PassPrefix: "git-bug",
+
+		// Fallback encrypted file
+		FileDir: path.Join(ucd, "git-bug", "keyring"),
+		// As we write the file in the user's config directory, this file should already be protected by the OS against
+		// other user's access. We actually don't terribly need to protect it further and a password prompt across all
+		// UI's would be a pain. Therefore we use here a constant password so the file will be unreadable by generic file
+		// scanners if the user's machine get compromised.
+		FilePasswordFunc: func(string) (string, error) {
+			return "git-bug", nil
+		},
+	})
+}

repository/mock_repo.go 🔗

@@ -5,6 +5,8 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/99designs/keyring"
+
 	"github.com/MichaelMure/git-bug/util/lamport"
 )
 
@@ -15,6 +17,7 @@ var _ TestedRepo = &mockRepoForTest{}
 type mockRepoForTest struct {
 	config       *MemConfig
 	globalConfig *MemConfig
+	keyring      *keyring.ArrayKeyring
 	blobs        map[Hash][]byte
 	trees        map[Hash]string
 	commits      map[Hash]commit
@@ -31,6 +34,7 @@ func NewMockRepoForTest() *mockRepoForTest {
 	return &mockRepoForTest{
 		config:       NewMemConfig(),
 		globalConfig: NewMemConfig(),
+		keyring:      keyring.NewArrayKeyring(nil),
 		blobs:        make(map[Hash][]byte),
 		trees:        make(map[Hash]string),
 		commits:      make(map[Hash]commit),
@@ -49,6 +53,11 @@ func (r *mockRepoForTest) GlobalConfig() Config {
 	return r.globalConfig
 }
 
+// Keyring give access to a user-wide storage for secrets
+func (r *mockRepoForTest) Keyring() Keyring {
+	return r.keyring
+}
+
 // GetPath returns the path to the repo.
 func (r *mockRepoForTest) GetPath() string {
 	return "~/mockRepo/"

repository/repo.go 🔗

@@ -10,8 +10,6 @@ import (
 )
 
 var (
-	ErrNoConfigEntry       = errors.New("no config entry for the given key")
-	ErrMultipleConfigEntry = errors.New("multiple config entry for the given key")
 	// ErrNotARepo is the error returned when the git repo root wan't be found
 	ErrNotARepo = errors.New("not a git repository")
 	// ErrClockNotExist is the error returned when a clock can't be found
@@ -24,9 +22,17 @@ type RepoConfig interface {
 	LocalConfig() Config
 
 	// GlobalConfig give access to the git global configuration
+	// Deprecated: to remove in favor of Keyring()
+	// TODO: remove
 	GlobalConfig() Config
 }
 
+// RepoKeyring give access to a user-wide storage for secrets
+type RepoKeyring interface {
+	// Keyring give access to a user-wide storage for secrets
+	Keyring() Keyring
+}
+
 // RepoCommon represent the common function the we want all the repo to implement
 type RepoCommon interface {
 	// GetPath returns the path to the repo.
@@ -48,6 +54,7 @@ type RepoCommon interface {
 // Repo represents a source code repository.
 type Repo interface {
 	RepoConfig
+	RepoKeyring
 	RepoCommon
 
 	// FetchRefs fetch git refs from a remote