mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-14 17:51:44 +03:00
Merge pull request #412 from MichaelMure/gogit-repo
repository: go-git backed Repo
This commit is contained in:
commit
1204b66e0c
@ -1,11 +1,11 @@
|
||||
matrix:
|
||||
include:
|
||||
- language: go
|
||||
go: 1.12.x
|
||||
- language: go
|
||||
go: 1.13.x
|
||||
- language: go
|
||||
go: 1.14.x
|
||||
- language: go
|
||||
go: 1.15.x
|
||||
- language: node_js
|
||||
node_js: node
|
||||
before_install:
|
||||
@ -41,5 +41,5 @@ deploy:
|
||||
file: dist/**/*
|
||||
on:
|
||||
repo: MichaelMure/git-bug
|
||||
go: 1.13.x
|
||||
go: 1.14.x
|
||||
tags: true
|
||||
|
@ -1,11 +1,11 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -14,12 +14,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
configKeyPrefix = "git-bug.auth"
|
||||
configKeyKind = "kind"
|
||||
configKeyTarget = "target"
|
||||
configKeyCreateTime = "createtime"
|
||||
configKeySalt = "salt"
|
||||
configKeyPrefixMeta = "meta."
|
||||
keyringKeyPrefix = "auth-"
|
||||
keyringKeyKind = "kind"
|
||||
keyringKeyTarget = "target"
|
||||
keyringKeyCreateTime = "createtime"
|
||||
keyringKeySalt = "salt"
|
||||
keyringKeyPrefixMeta = "meta."
|
||||
|
||||
MetaKeyLogin = "login"
|
||||
MetaKeyBaseURL = "base-url"
|
||||
@ -57,22 +57,23 @@ type Credential interface {
|
||||
}
|
||||
|
||||
// Load loads a credential from the repo config
|
||||
func LoadWithId(repo repository.RepoConfig, id entity.Id) (Credential, error) {
|
||||
keyPrefix := fmt.Sprintf("%s.%s.", configKeyPrefix, id)
|
||||
func LoadWithId(repo repository.RepoKeyring, id entity.Id) (Credential, error) {
|
||||
key := fmt.Sprintf("%s%s", keyringKeyPrefix, id)
|
||||
|
||||
// read token config pairs
|
||||
rawconfigs, err := repo.GlobalConfig().ReadAll(keyPrefix)
|
||||
if err != nil {
|
||||
// Not exactly right due to the limitation of ReadAll()
|
||||
item, err := repo.Keyring().Get(key)
|
||||
if err == repository.ErrKeyringKeyNotFound {
|
||||
return nil, ErrCredentialNotExist
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return loadFromConfig(rawconfigs, id)
|
||||
return decode(item)
|
||||
}
|
||||
|
||||
// LoadWithPrefix load a credential from the repo config with a prefix
|
||||
func LoadWithPrefix(repo repository.RepoConfig, prefix string) (Credential, error) {
|
||||
creds, err := List(repo)
|
||||
func LoadWithPrefix(repo repository.RepoKeyring, prefix string) (Credential, error) {
|
||||
keys, err := repo.Keyring().Keys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -80,10 +81,22 @@ func LoadWithPrefix(repo repository.RepoConfig, prefix string) (Credential, erro
|
||||
// preallocate but empty
|
||||
matching := make([]Credential, 0, 5)
|
||||
|
||||
for _, cred := range creds {
|
||||
if cred.ID().HasPrefix(prefix) {
|
||||
matching = append(matching, cred)
|
||||
for _, key := range keys {
|
||||
if !strings.HasPrefix(key, keyringKeyPrefix+prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
item, err := repo.Keyring().Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cred, err := decode(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matching = append(matching, cred)
|
||||
}
|
||||
|
||||
if len(matching) > 1 {
|
||||
@ -101,29 +114,25 @@ func LoadWithPrefix(repo repository.RepoConfig, prefix string) (Credential, erro
|
||||
return matching[0], nil
|
||||
}
|
||||
|
||||
// loadFromConfig is a helper to construct a Credential from the set of git configs
|
||||
func loadFromConfig(rawConfigs map[string]string, id entity.Id) (Credential, error) {
|
||||
keyPrefix := fmt.Sprintf("%s.%s.", configKeyPrefix, id)
|
||||
// decode is a helper to construct a Credential from the keyring Item
|
||||
func decode(item repository.Item) (Credential, error) {
|
||||
data := make(map[string]string)
|
||||
|
||||
// trim key prefix
|
||||
configs := make(map[string]string)
|
||||
for key, value := range rawConfigs {
|
||||
newKey := strings.TrimPrefix(key, keyPrefix)
|
||||
configs[newKey] = value
|
||||
err := json.Unmarshal(item.Data, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cred Credential
|
||||
var err error
|
||||
|
||||
switch CredentialKind(configs[configKeyKind]) {
|
||||
switch CredentialKind(data[keyringKeyKind]) {
|
||||
case KindToken:
|
||||
cred, err = NewTokenFromConfig(configs)
|
||||
cred, err = NewTokenFromConfig(data)
|
||||
case KindLogin:
|
||||
cred, err = NewLoginFromConfig(configs)
|
||||
cred, err = NewLoginFromConfig(data)
|
||||
case KindLoginPassword:
|
||||
cred, err = NewLoginPasswordFromConfig(configs)
|
||||
cred, err = NewLoginPasswordFromConfig(data)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown credential type \"%s\"", configs[configKeyKind])
|
||||
return nil, fmt.Errorf("unknown credential type \"%s\"", data[keyringKeyKind])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@ -133,64 +142,27 @@ func loadFromConfig(rawConfigs map[string]string, id entity.Id) (Credential, err
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
func metaFromConfig(configs map[string]string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for key, val := range configs {
|
||||
if strings.HasPrefix(key, configKeyPrefixMeta) {
|
||||
key = strings.TrimPrefix(key, configKeyPrefixMeta)
|
||||
result[key] = val
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func makeSalt() []byte {
|
||||
result := make([]byte, 16)
|
||||
_, err := rand.Read(result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func saltFromConfig(configs map[string]string) ([]byte, error) {
|
||||
val, ok := configs[configKeySalt]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no credential salt found")
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(val)
|
||||
}
|
||||
|
||||
// List load all existing credentials
|
||||
func List(repo repository.RepoConfig, opts ...Option) ([]Credential, error) {
|
||||
rawConfigs, err := repo.GlobalConfig().ReadAll(configKeyPrefix + ".")
|
||||
func List(repo repository.RepoKeyring, opts ...ListOption) ([]Credential, error) {
|
||||
keys, err := repo.Keyring().Keys()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
re := regexp.MustCompile(`^` + configKeyPrefix + `\.([^.]+)\.([^.]+(?:\.[^.]+)*)$`)
|
||||
|
||||
mapped := make(map[string]map[string]string)
|
||||
|
||||
for key, val := range rawConfigs {
|
||||
res := re.FindStringSubmatch(key)
|
||||
if res == nil {
|
||||
continue
|
||||
}
|
||||
if mapped[res[1]] == nil {
|
||||
mapped[res[1]] = make(map[string]string)
|
||||
}
|
||||
mapped[res[1]][res[2]] = val
|
||||
}
|
||||
|
||||
matcher := matcher(opts)
|
||||
|
||||
var credentials []Credential
|
||||
for id, kvs := range mapped {
|
||||
cred, err := loadFromConfig(kvs, entity.Id(id))
|
||||
for _, key := range keys {
|
||||
if !strings.HasPrefix(key, keyringKeyPrefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
item, err := repo.Keyring().Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cred, err := decode(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -203,74 +175,48 @@ func List(repo repository.RepoConfig, opts ...Option) ([]Credential, error) {
|
||||
}
|
||||
|
||||
// IdExist return whether a credential id exist or not
|
||||
func IdExist(repo repository.RepoConfig, id entity.Id) bool {
|
||||
func IdExist(repo repository.RepoKeyring, id entity.Id) bool {
|
||||
_, err := LoadWithId(repo, id)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// PrefixExist return whether a credential id prefix exist or not
|
||||
func PrefixExist(repo repository.RepoConfig, prefix string) bool {
|
||||
func PrefixExist(repo repository.RepoKeyring, prefix string) bool {
|
||||
_, err := LoadWithPrefix(repo, prefix)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Store stores a credential in the global git config
|
||||
func Store(repo repository.RepoConfig, cred Credential) error {
|
||||
confs := cred.toConfig()
|
||||
|
||||
prefix := fmt.Sprintf("%s.%s.", configKeyPrefix, cred.ID())
|
||||
|
||||
// Kind
|
||||
err := repo.GlobalConfig().StoreString(prefix+configKeyKind, string(cred.Kind()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Target
|
||||
err = repo.GlobalConfig().StoreString(prefix+configKeyTarget, cred.Target())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateTime
|
||||
err = repo.GlobalConfig().StoreTimestamp(prefix+configKeyCreateTime, cred.CreateTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Salt
|
||||
func Store(repo repository.RepoKeyring, cred Credential) error {
|
||||
if len(cred.Salt()) != 16 {
|
||||
panic("credentials need to be salted")
|
||||
}
|
||||
encoded := base64.StdEncoding.EncodeToString(cred.Salt())
|
||||
err = repo.GlobalConfig().StoreString(prefix+configKeySalt, encoded)
|
||||
|
||||
confs := cred.toConfig()
|
||||
|
||||
confs[keyringKeyKind] = string(cred.Kind())
|
||||
confs[keyringKeyTarget] = cred.Target()
|
||||
confs[keyringKeyCreateTime] = strconv.Itoa(int(cred.CreateTime().Unix()))
|
||||
confs[keyringKeySalt] = base64.StdEncoding.EncodeToString(cred.Salt())
|
||||
|
||||
for key, val := range cred.Metadata() {
|
||||
confs[keyringKeyPrefixMeta+key] = val
|
||||
}
|
||||
|
||||
data, err := json.Marshal(confs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Metadata
|
||||
for key, val := range cred.Metadata() {
|
||||
err := repo.GlobalConfig().StoreString(prefix+configKeyPrefixMeta+key, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Custom
|
||||
for key, val := range confs {
|
||||
err := repo.GlobalConfig().StoreString(prefix+key, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return repo.Keyring().Set(repository.Item{
|
||||
Key: keyringKeyPrefix + cred.ID().String(),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// Remove removes a credential from the global git config
|
||||
func Remove(repo repository.RepoConfig, id entity.Id) error {
|
||||
keyPrefix := fmt.Sprintf("%s.%s", configKeyPrefix, id)
|
||||
return repo.GlobalConfig().RemoveAll(keyPrefix)
|
||||
func Remove(repo repository.RepoKeyring, id entity.Id) error {
|
||||
return repo.Keyring().Remove(keyringKeyPrefix + id.String())
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1,7 +1,10 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/MichaelMure/git-bug/bridge/core"
|
||||
@ -23,13 +26,22 @@ func newCredentialBase(target string) *credentialBase {
|
||||
}
|
||||
}
|
||||
|
||||
func newCredentialBaseFromConfig(conf map[string]string) (*credentialBase, error) {
|
||||
func makeSalt() []byte {
|
||||
result := make([]byte, 16)
|
||||
_, err := rand.Read(result)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func newCredentialBaseFromData(data map[string]string) (*credentialBase, error) {
|
||||
base := &credentialBase{
|
||||
target: conf[configKeyTarget],
|
||||
meta: metaFromConfig(conf),
|
||||
target: data[keyringKeyTarget],
|
||||
meta: metaFromData(data),
|
||||
}
|
||||
|
||||
if createTime, ok := conf[configKeyCreateTime]; ok {
|
||||
if createTime, ok := data[keyringKeyCreateTime]; ok {
|
||||
t, err := repository.ParseTimestamp(createTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -39,7 +51,7 @@ func newCredentialBaseFromConfig(conf map[string]string) (*credentialBase, error
|
||||
return nil, fmt.Errorf("missing create time")
|
||||
}
|
||||
|
||||
salt, err := saltFromConfig(conf)
|
||||
salt, err := saltFromData(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -48,6 +60,28 @@ func newCredentialBaseFromConfig(conf map[string]string) (*credentialBase, error
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func metaFromData(data map[string]string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for key, val := range data {
|
||||
if strings.HasPrefix(key, keyringKeyPrefixMeta) {
|
||||
key = strings.TrimPrefix(key, keyringKeyPrefixMeta)
|
||||
result[key] = val
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func saltFromData(data map[string]string) ([]byte, error) {
|
||||
val, ok := data[keyringKeySalt]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no credential salt found")
|
||||
}
|
||||
return base64.StdEncoding.DecodeString(val)
|
||||
}
|
||||
|
||||
func (cb *credentialBase) Target() string {
|
||||
return cb.target
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
configKeyLoginLogin = "login"
|
||||
keyringKeyLoginLogin = "login"
|
||||
)
|
||||
|
||||
var _ Credential = &Login{}
|
||||
@ -26,14 +26,14 @@ func NewLogin(target, login string) *Login {
|
||||
}
|
||||
|
||||
func NewLoginFromConfig(conf map[string]string) (*Login, error) {
|
||||
base, err := newCredentialBaseFromConfig(conf)
|
||||
base, err := newCredentialBaseFromData(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Login{
|
||||
credentialBase: base,
|
||||
Login: conf[configKeyLoginLogin],
|
||||
Login: conf[keyringKeyLoginLogin],
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -62,6 +62,6 @@ func (lp *Login) Validate() error {
|
||||
|
||||
func (lp *Login) toConfig() map[string]string {
|
||||
return map[string]string{
|
||||
configKeyLoginLogin: lp.Login,
|
||||
keyringKeyLoginLogin: lp.Login,
|
||||
}
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
configKeyLoginPasswordLogin = "login"
|
||||
configKeyLoginPasswordPassword = "password"
|
||||
keyringKeyLoginPasswordLogin = "login"
|
||||
keyringKeyLoginPasswordPassword = "password"
|
||||
)
|
||||
|
||||
var _ Credential = &LoginPassword{}
|
||||
@ -29,15 +29,15 @@ func NewLoginPassword(target, login, password string) *LoginPassword {
|
||||
}
|
||||
|
||||
func NewLoginPasswordFromConfig(conf map[string]string) (*LoginPassword, error) {
|
||||
base, err := newCredentialBaseFromConfig(conf)
|
||||
base, err := newCredentialBaseFromData(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &LoginPassword{
|
||||
credentialBase: base,
|
||||
Login: conf[configKeyLoginPasswordLogin],
|
||||
Password: conf[configKeyLoginPasswordPassword],
|
||||
Login: conf[keyringKeyLoginPasswordLogin],
|
||||
Password: conf[keyringKeyLoginPasswordPassword],
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ func (lp *LoginPassword) Validate() error {
|
||||
|
||||
func (lp *LoginPassword) toConfig() map[string]string {
|
||||
return map[string]string{
|
||||
configKeyLoginPasswordLogin: lp.Login,
|
||||
configKeyLoginPasswordPassword: lp.Password,
|
||||
keyringKeyLoginPasswordLogin: lp.Login,
|
||||
keyringKeyLoginPasswordPassword: lp.Password,
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
package auth
|
||||
|
||||
type options struct {
|
||||
type listOptions struct {
|
||||
target string
|
||||
kind map[CredentialKind]interface{}
|
||||
meta map[string]string
|
||||
}
|
||||
|
||||
type Option func(opts *options)
|
||||
type ListOption func(opts *listOptions)
|
||||
|
||||
func matcher(opts []Option) *options {
|
||||
result := &options{}
|
||||
func matcher(opts []ListOption) *listOptions {
|
||||
result := &listOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(result)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (opts *options) Match(cred Credential) bool {
|
||||
func (opts *listOptions) Match(cred Credential) bool {
|
||||
if opts.target != "" && cred.Target() != opts.target {
|
||||
return false
|
||||
}
|
||||
@ -35,15 +35,15 @@ func (opts *options) Match(cred Credential) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func WithTarget(target string) Option {
|
||||
return func(opts *options) {
|
||||
func WithTarget(target string) ListOption {
|
||||
return func(opts *listOptions) {
|
||||
opts.target = target
|
||||
}
|
||||
}
|
||||
|
||||
// WithKind match credentials with the given kind. Can be specified multiple times.
|
||||
func WithKind(kind CredentialKind) Option {
|
||||
return func(opts *options) {
|
||||
func WithKind(kind CredentialKind) ListOption {
|
||||
return func(opts *listOptions) {
|
||||
if opts.kind == nil {
|
||||
opts.kind = make(map[CredentialKind]interface{})
|
||||
}
|
||||
@ -51,8 +51,8 @@ func WithKind(kind CredentialKind) Option {
|
||||
}
|
||||
}
|
||||
|
||||
func WithMeta(key string, val string) Option {
|
||||
return func(opts *options) {
|
||||
func WithMeta(key string, val string) ListOption {
|
||||
return func(opts *listOptions) {
|
||||
if opts.meta == nil {
|
||||
opts.meta = make(map[string]string)
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
configKeyTokenValue = "value"
|
||||
keyringKeyTokenValue = "value"
|
||||
)
|
||||
|
||||
var _ Credential = &Token{}
|
||||
@ -28,14 +28,14 @@ func NewToken(target, value string) *Token {
|
||||
}
|
||||
|
||||
func NewTokenFromConfig(conf map[string]string) (*Token, error) {
|
||||
base, err := newCredentialBaseFromConfig(conf)
|
||||
base, err := newCredentialBaseFromData(conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Token{
|
||||
credentialBase: base,
|
||||
Value: conf[configKeyTokenValue],
|
||||
Value: conf[keyringKeyTokenValue],
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -65,6 +65,6 @@ func (t *Token) Validate() error {
|
||||
|
||||
func (t *Token) toConfig() map[string]string {
|
||||
return map[string]string{
|
||||
configKeyTokenValue: t.Value,
|
||||
keyringKeyTokenValue: t.Value,
|
||||
}
|
||||
}
|
||||
|
@ -239,7 +239,7 @@ func randomFingerprint() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func promptTokenOptions(repo repository.RepoConfig, login, owner, project string) (auth.Credential, error) {
|
||||
func promptTokenOptions(repo repository.RepoKeyring, login, owner, project string) (auth.Credential, error) {
|
||||
creds, err := auth.List(repo,
|
||||
auth.WithTarget(target),
|
||||
auth.WithKind(auth.KindToken),
|
||||
|
@ -156,7 +156,7 @@ func (g *Gitlab) ValidateConfig(conf core.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func promptTokenOptions(repo repository.RepoConfig, login, baseUrl string) (auth.Credential, error) {
|
||||
func promptTokenOptions(repo repository.RepoKeyring, login, baseUrl string) (auth.Credential, error) {
|
||||
creds, err := auth.List(repo,
|
||||
auth.WithTarget(target),
|
||||
auth.WithKind(auth.KindToken),
|
||||
|
@ -163,7 +163,7 @@ func (*Jira) ValidateConfig(conf core.Configuration) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func promptCredOptions(repo repository.RepoConfig, login, baseUrl string) (auth.Credential, error) {
|
||||
func promptCredOptions(repo repository.RepoKeyring, login, baseUrl string) (auth.Credential, error) {
|
||||
creds, err := auth.List(repo,
|
||||
auth.WithTarget(target),
|
||||
auth.WithKind(auth.KindToken),
|
||||
|
2
cache/repo_cache.go
vendored
2
cache/repo_cache.go
vendored
@ -25,6 +25,8 @@ const formatVersion = 2
|
||||
const defaultMaxLoadedBugs = 1000
|
||||
|
||||
var _ repository.RepoCommon = &RepoCache{}
|
||||
var _ repository.RepoConfig = &RepoCache{}
|
||||
var _ repository.RepoKeyring = &RepoCache{}
|
||||
|
||||
// RepoCache is a cache for a Repository. This cache has multiple functions:
|
||||
//
|
||||
|
11
cache/repo_cache_common.go
vendored
11
cache/repo_cache_common.go
vendored
@ -20,11 +20,20 @@ func (c *RepoCache) LocalConfig() repository.Config {
|
||||
return c.repo.LocalConfig()
|
||||
}
|
||||
|
||||
// GlobalConfig give access to the git global configuration
|
||||
// GlobalConfig give access to the global scoped configuration
|
||||
func (c *RepoCache) GlobalConfig() repository.Config {
|
||||
return c.repo.GlobalConfig()
|
||||
}
|
||||
|
||||
// AnyConfig give access to a merged local/global configuration
|
||||
func (c *RepoCache) AnyConfig() repository.ConfigRead {
|
||||
return c.repo.AnyConfig()
|
||||
}
|
||||
|
||||
func (c *RepoCache) Keyring() repository.Keyring {
|
||||
return c.repo.Keyring()
|
||||
}
|
||||
|
||||
// GetPath returns the path to the repo.
|
||||
func (c *RepoCache) GetPath() string {
|
||||
return c.repo.GetPath()
|
||||
|
@ -54,7 +54,7 @@ func loadRepo(env *Env) func(*cobra.Command, []string) error {
|
||||
return fmt.Errorf("unable to get the current working directory: %q", err)
|
||||
}
|
||||
|
||||
env.repo, err = repository.NewGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader})
|
||||
env.repo, err = repository.NewGoGitRepo(cwd, []repository.ClockLoader{bug.ClockLoader})
|
||||
if err == repository.ErrNotARepo {
|
||||
return fmt.Errorf("%s must be run from within a git repo", rootCommandName)
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func runWebUI(env *Env, opts webUIOptions, args []string) error {
|
||||
env.out.Printf("Graphql Playground: http://%s/playground\n", addr)
|
||||
env.out.Println("Press Ctrl+c to quit")
|
||||
|
||||
configOpen, err := env.repo.LocalConfig().ReadBool(webUIOpenConfigKey)
|
||||
configOpen, err := env.repo.AnyConfig().ReadBool(webUIOpenConfigKey)
|
||||
if err == repository.ErrNoConfigEntry {
|
||||
// default to true
|
||||
configOpen = true
|
||||
|
10
go.mod
10
go.mod
@ -1,9 +1,10 @@
|
||||
module github.com/MichaelMure/git-bug
|
||||
|
||||
go 1.12
|
||||
go 1.13
|
||||
|
||||
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
|
||||
@ -12,6 +13,7 @@ require (
|
||||
github.com/corpix/uarand v0.1.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/go-git/go-git/v5 v5.1.0
|
||||
github.com/gorilla/mux v1.8.0
|
||||
github.com/hashicorp/golang-lru v0.5.4
|
||||
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428
|
||||
@ -25,8 +27,12 @@ require (
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/vektah/gqlparser v1.3.1
|
||||
github.com/xanzy/go-gitlab v0.33.0
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
golang.org/x/text v0.3.3
|
||||
)
|
||||
|
||||
// Use a forked go-git for now until https://github.com/go-git/go-git/pull/112 is merged
|
||||
// and released.
|
||||
replace github.com/go-git/go-git/v5 => github.com/MichaelMure/go-git/v5 v5.1.1-0.20200827115354-b40ca794fe33
|
||||
|
127
go.sum
127
go.sum
@ -1,12 +1,14 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/99designs/gqlgen v0.10.3-0.20200208093655-ab8d62b67dd0 h1:ADy3XJwhOYg6Pb90XeXazWvO+9gpOsgLuaM1buZUZOY=
|
||||
github.com/99designs/gqlgen v0.10.3-0.20200208093655-ab8d62b67dd0/go.mod h1:dfBhwZKMcSYiYRMTs8qWF+Oha6782e1xPfgRmVal9I8=
|
||||
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=
|
||||
github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA=
|
||||
github.com/MichaelMure/go-git/v5 v5.1.1-0.20200827115354-b40ca794fe33 h1:QFzkZPUMm0HRZ0dZ+GgDKHPUrgUrH3CbcyuzQlhBeww=
|
||||
github.com/MichaelMure/go-git/v5 v5.1.1-0.20200827115354-b40ca794fe33/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
|
||||
github.com/MichaelMure/go-term-text v0.2.6 h1:dSmJSzk2iI5xWymSMrMbdVM1bxYWu3DjDFhdcJvAuqA=
|
||||
github.com/MichaelMure/go-term-text v0.2.6/go.mod h1:o2Z5T3b28F4kwAojGvvNdbzjHf9t18vbQ7E2pmTe2Ww=
|
||||
github.com/MichaelMure/go-term-text v0.2.7 h1:nSYvYGwXxJoiQu6kdGSErpxZ6ah/4WlJyp/niqQor6g=
|
||||
@ -18,15 +20,21 @@ github.com/MichaelMure/go-term-text v0.2.9/go.mod h1:2QSU/Nn2u41Tqoar+90RlYuhjng
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195 h1:c4mLfegoDw6OhSJXTd2jUEQgZUQuJWtocudb97Qn9EM=
|
||||
github.com/araddon/dateparse v0.0.0-20190622164848-0fb0a474d195/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986 h1:QvIfX96O11qjX1Zr3hKkG0dI12JBRBGABWffyZ1GI60=
|
||||
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986/go.mod h1:1QikxFaPhe2frKeKvEwZEIGia3haiOxOUXKinrv17mA=
|
||||
github.com/awesome-gocui/termbox-go v0.0.0-20190427202837-c0aef3d18bcc h1:wGNpKcHU8Aadr9yOzsT3GEsFLS7HQu8HxQIomnekqf0=
|
||||
@ -45,7 +53,6 @@ github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
@ -53,33 +60,48 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbp
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U=
|
||||
github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
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=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
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=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-errors/errors v1.0.2 h1:xMxH9j2fNg/L4hLn/4y3M0IUsn0M6Wbu/Uh9QlOfBh4=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.1.1 h1:ljK/pL5ltg3qoN+OtN6yCv9HWSfMwxSx90GJCZQxYNg=
|
||||
github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
|
||||
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
||||
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
|
||||
github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
|
||||
github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
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=
|
||||
@ -92,14 +114,12 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
|
||||
@ -109,6 +129,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=
|
||||
@ -122,12 +144,19 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428 h1:Mo9W14pwbO9VfRe+ygqZ8dFbPpoIK1HFrG/zjTuQ+nc=
|
||||
github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo=
|
||||
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jondot/goweight v1.0.5 h1:aRpnyj1G8BLLNhem8xezuuV0GlFz4G11e3/UtBU/FlQ=
|
||||
github.com/jondot/goweight v1.0.5/go.mod h1:3PRcpOwkyspe1t4+KCNgauas+aNDTSSCwZ6AQ4kDD/A=
|
||||
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=
|
||||
@ -137,6 +166,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
@ -148,22 +179,21 @@ github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOA
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53 h1:tGfIHhDghvEnneeRhODvGYOt305TPwingKt6p90F4MU=
|
||||
github.com/mattn/go-zglob v0.0.0-20180803001819-2ea3427bfa53/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
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=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
@ -192,12 +222,12 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7 h1:Vk3RiBQpF0Ja+OqbFG7lYTk79+l8Cm2QESLXB0x6u6U=
|
||||
github.com/shurcooL/githubv4 v0.0.0-20190601194912-068505affed7/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo=
|
||||
github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk=
|
||||
@ -215,24 +245,17 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
|
||||
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v0.0.7 h1:FfTH+vuMXOas8jmfb5/M7dzEYx7LpcLb7a0LPe34uOU=
|
||||
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
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=
|
||||
@ -242,35 +265,18 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/theckman/goconstraint v1.11.0 h1:oBUwN5wpE4dwyPhRGraEgJsFTr+JtLWiDnaJZJeeXI0=
|
||||
github.com/theckman/goconstraint v1.11.0/go.mod h1:zkCR/f2kOULTk/h1ujgyB9BlCNLaqlQ6GN2Zl4mg81g=
|
||||
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713 h1:knaxjm6QMbUMNvuaSnJZmw0gRX4V/79JVUQiziJGM84=
|
||||
github.com/thoas/go-funk v0.0.0-20180716193722-1060394a7713/go.mod h1:mlR+dHGb+4YgXkf13rkQTuzrneeHANxOm6+ZnEV9HsA=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/gqlparser v1.2.1 h1:C+L7Go/eUbN0w6Y0kaiq2W6p2wN5j8wU82EdDXxDivc=
|
||||
github.com/vektah/gqlparser v1.2.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
|
||||
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
|
||||
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
|
||||
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
|
||||
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
|
||||
github.com/xanzy/go-gitlab v0.22.1 h1:TVxgHmoa35jQL+9FCkG0nwPDxU9dQZXknBTDtGaSFno=
|
||||
github.com/xanzy/go-gitlab v0.22.1/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
|
||||
github.com/xanzy/go-gitlab v0.24.0 h1:zP1zC4K76Gha0coN5GhygOLhsHTCvUjrnqGL3kHXkVU=
|
||||
github.com/xanzy/go-gitlab v0.24.0/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
|
||||
github.com/xanzy/go-gitlab v0.25.0 h1:G5aTZeqZd66Q6qMVieBfmHBsPpF0jY92zCLAMpULe3I=
|
||||
github.com/xanzy/go-gitlab v0.25.0/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
|
||||
github.com/xanzy/go-gitlab v0.26.0 h1:eAnJRBUC+GDJSy8OoGCZBqBMpXsGOOT235TFm/F8C0Q=
|
||||
github.com/xanzy/go-gitlab v0.26.0/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
|
||||
github.com/xanzy/go-gitlab v0.27.0 h1:zy7xBB8+PID6izH07ZArtkEisJ192dtQajRaeo4+glg=
|
||||
github.com/xanzy/go-gitlab v0.27.0/go.mod h1:t4Bmvnxj7k37S4Y17lfLx+nLqkf/oQwT2HagfWKv5Og=
|
||||
github.com/xanzy/go-gitlab v0.29.0 h1:9tMvAkG746eIlzcdpnRgpcKPA1woUDmldMIjR/E5OWM=
|
||||
github.com/xanzy/go-gitlab v0.29.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
|
||||
github.com/xanzy/go-gitlab v0.33.0 h1:MUJZknbLhVXSFzBA5eqGGhQ2yHSu8tPbGBPeB3sN4B0=
|
||||
github.com/xanzy/go-gitlab v0.33.0/go.mod h1:sPLojNBn68fMUWSxIJtdVVIP8uSBYqesTfDUseX11Ug=
|
||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
@ -278,11 +284,14 @@ go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
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=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
@ -297,6 +306,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
|
||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288 h1:JIqe8uIcRBHXDQVvZtHwp80ai3Lw3IJAeJEs55Dc1W0=
|
||||
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@ -309,16 +320,19 @@ golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
@ -348,11 +362,18 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -4,9 +4,10 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/MichaelMure/git-bug/entity"
|
||||
"github.com/MichaelMure/git-bug/repository"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Fetch retrieve updates from a remote
|
||||
|
@ -35,23 +35,18 @@ func GetUserIdentity(repo repository.Repo) (*Identity, error) {
|
||||
}
|
||||
|
||||
func GetUserIdentityId(repo repository.Repo) (entity.Id, error) {
|
||||
configs, err := repo.LocalConfig().ReadAll(identityConfigKey)
|
||||
val, err := repo.LocalConfig().ReadString(identityConfigKey)
|
||||
if err == repository.ErrNoConfigEntry {
|
||||
return entity.UnsetId, ErrNoIdentitySet
|
||||
}
|
||||
if err == repository.ErrMultipleConfigEntry {
|
||||
return entity.UnsetId, ErrMultipleIdentitiesSet
|
||||
}
|
||||
if err != nil {
|
||||
return entity.UnsetId, err
|
||||
}
|
||||
|
||||
if len(configs) == 0 {
|
||||
return entity.UnsetId, ErrNoIdentitySet
|
||||
}
|
||||
|
||||
if len(configs) > 1 {
|
||||
return entity.UnsetId, ErrMultipleIdentitiesSet
|
||||
}
|
||||
|
||||
var id entity.Id
|
||||
for _, val := range configs {
|
||||
id = entity.Id(val)
|
||||
}
|
||||
var id = entity.Id(val)
|
||||
|
||||
if err := id.Validate(); err != nil {
|
||||
return entity.UnsetId, err
|
||||
@ -62,10 +57,12 @@ func GetUserIdentityId(repo repository.Repo) (entity.Id, error) {
|
||||
|
||||
// IsUserIdentitySet say if the user has set his identity
|
||||
func IsUserIdentitySet(repo repository.Repo) (bool, error) {
|
||||
configs, err := repo.LocalConfig().ReadAll(identityConfigKey)
|
||||
_, err := repo.LocalConfig().ReadString(identityConfigKey)
|
||||
if err == repository.ErrNoConfigEntry {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return len(configs) == 1, nil
|
||||
return true, nil
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ func main() {
|
||||
bug.ClockLoader,
|
||||
}
|
||||
|
||||
repo, err := repository.NewGitRepo(dir, loaders)
|
||||
repo, err := repository.NewGoGitRepo(dir, loaders)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -1,21 +1,23 @@
|
||||
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
|
||||
StoreString(key, value string) error
|
||||
|
||||
// Store writes a key and timestamp value to the config
|
||||
StoreTimestamp(key string, value time.Time) error
|
||||
|
||||
// Store writes a key and boolean value to the config
|
||||
StoreBool(key string, value bool) error
|
||||
ConfigRead
|
||||
ConfigWrite
|
||||
}
|
||||
|
||||
type ConfigRead interface {
|
||||
// ReadAll reads all key/value pair matching the key prefix
|
||||
ReadAll(keyPrefix string) (map[string]string, error)
|
||||
|
||||
@ -33,6 +35,17 @@ type Config interface {
|
||||
// Return ErrNoConfigEntry or ErrMultipleConfigEntry if
|
||||
// there is zero or more than one entry for this key
|
||||
ReadTimestamp(key string) (time.Time, error)
|
||||
}
|
||||
|
||||
type ConfigWrite interface {
|
||||
// Store writes a single key/value pair in the config
|
||||
StoreString(key, value string) error
|
||||
|
||||
// Store writes a key and timestamp value to the config
|
||||
StoreTimestamp(key string, value time.Time) error
|
||||
|
||||
// Store writes a key and boolean value to the config
|
||||
StoreBool(key string, value bool) error
|
||||
|
||||
// RemoveAll removes all key/value pair matching the key prefix
|
||||
RemoveAll(keyPrefix string) error
|
||||
@ -46,3 +59,87 @@ func ParseTimestamp(s string) (time.Time, error) {
|
||||
|
||||
return time.Unix(int64(timestamp), 0), nil
|
||||
}
|
||||
|
||||
// mergeConfig is a helper to easily support RepoConfig.AnyConfig()
|
||||
// from two separate local and global Config
|
||||
func mergeConfig(local ConfigRead, global ConfigRead) *mergedConfig {
|
||||
return &mergedConfig{
|
||||
local: local,
|
||||
global: global,
|
||||
}
|
||||
}
|
||||
|
||||
var _ ConfigRead = &mergedConfig{}
|
||||
|
||||
type mergedConfig struct {
|
||||
local ConfigRead
|
||||
global ConfigRead
|
||||
}
|
||||
|
||||
func (m *mergedConfig) ReadAll(keyPrefix string) (map[string]string, error) {
|
||||
values, err := m.global.ReadAll(keyPrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
locals, err := m.local.ReadAll(keyPrefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for k, val := range locals {
|
||||
values[k] = val
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (m *mergedConfig) ReadBool(key string) (bool, error) {
|
||||
v, err := m.local.ReadBool(key)
|
||||
if err == nil {
|
||||
return v, nil
|
||||
}
|
||||
if err != ErrNoConfigEntry && err != ErrMultipleConfigEntry {
|
||||
return false, err
|
||||
}
|
||||
return m.global.ReadBool(key)
|
||||
}
|
||||
|
||||
func (m *mergedConfig) ReadString(key string) (string, error) {
|
||||
val, err := m.local.ReadString(key)
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
if err != ErrNoConfigEntry && err != ErrMultipleConfigEntry {
|
||||
return "", err
|
||||
}
|
||||
return m.global.ReadString(key)
|
||||
}
|
||||
|
||||
func (m *mergedConfig) ReadTimestamp(key string) (time.Time, error) {
|
||||
val, err := m.local.ReadTimestamp(key)
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
if err != ErrNoConfigEntry && err != ErrMultipleConfigEntry {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return m.global.ReadTimestamp(key)
|
||||
}
|
||||
|
||||
var _ ConfigWrite = &configPanicWriter{}
|
||||
|
||||
type configPanicWriter struct{}
|
||||
|
||||
func (c configPanicWriter) StoreString(key, value string) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c configPanicWriter) StoreTimestamp(key string, value time.Time) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c configPanicWriter) StoreBool(key string, value bool) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (c configPanicWriter) RemoveAll(keyPrefix string) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
54
repository/config_test.go
Normal file
54
repository/config_test.go
Normal file
@ -0,0 +1,54 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMergedConfig(t *testing.T) {
|
||||
local := NewMemConfig()
|
||||
global := NewMemConfig()
|
||||
merged := mergeConfig(local, global)
|
||||
|
||||
require.NoError(t, global.StoreBool("bool", true))
|
||||
require.NoError(t, global.StoreString("string", "foo"))
|
||||
require.NoError(t, global.StoreTimestamp("timestamp", time.Unix(1234, 0)))
|
||||
|
||||
val1, err := merged.ReadBool("bool")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val1, true)
|
||||
|
||||
val2, err := merged.ReadString("string")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val2, "foo")
|
||||
|
||||
val3, err := merged.ReadTimestamp("timestamp")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val3, time.Unix(1234, 0))
|
||||
|
||||
require.NoError(t, local.StoreBool("bool", false))
|
||||
require.NoError(t, local.StoreString("string", "bar"))
|
||||
require.NoError(t, local.StoreTimestamp("timestamp", time.Unix(5678, 0)))
|
||||
|
||||
val1, err = merged.ReadBool("bool")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val1, false)
|
||||
|
||||
val2, err = merged.ReadString("string")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val2, "bar")
|
||||
|
||||
val3, err = merged.ReadTimestamp("timestamp")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, val3, time.Unix(5678, 0))
|
||||
|
||||
all, err := merged.ReadAll("")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, all, map[string]string{
|
||||
"bool": "false",
|
||||
"string": "bar",
|
||||
"timestamp": "5678",
|
||||
})
|
||||
}
|
@ -2,62 +2,115 @@ package repository
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func testConfig(t *testing.T, config Config) {
|
||||
// string
|
||||
err := config.StoreString("section.key", "value")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
val, err := config.ReadString("section.key")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "value", val)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "value", val)
|
||||
|
||||
err = config.StoreString("section.true", "true")
|
||||
assert.NoError(t, err)
|
||||
// bool
|
||||
err = config.StoreBool("section.true", true)
|
||||
require.NoError(t, err)
|
||||
|
||||
val2, err := config.ReadBool("section.true")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, val2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, true, val2)
|
||||
|
||||
// timestamp
|
||||
err = config.StoreTimestamp("section.time", time.Unix(1234, 0))
|
||||
require.NoError(t, err)
|
||||
|
||||
val3, err := config.ReadTimestamp("section.time")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, time.Unix(1234, 0), val3)
|
||||
|
||||
// ReadAll
|
||||
configs, err := config.ReadAll("section")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]string{
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]string{
|
||||
"section.key": "value",
|
||||
"section.true": "true",
|
||||
"section.time": "1234",
|
||||
}, configs)
|
||||
|
||||
// RemoveAll
|
||||
err = config.RemoveAll("section.true")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
configs, err = config.ReadAll("section")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, map[string]string{
|
||||
"section.key": "value",
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]string{
|
||||
"section.key": "value",
|
||||
"section.time": "1234",
|
||||
}, configs)
|
||||
|
||||
_, err = config.ReadBool("section.true")
|
||||
assert.Equal(t, ErrNoConfigEntry, err)
|
||||
require.Equal(t, ErrNoConfigEntry, err)
|
||||
|
||||
err = config.RemoveAll("section.nonexistingkey")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
err = config.RemoveAll("section.key")
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = config.ReadString("section.key")
|
||||
assert.Equal(t, ErrNoConfigEntry, err)
|
||||
require.Equal(t, ErrNoConfigEntry, err)
|
||||
|
||||
err = config.RemoveAll("nonexistingsection")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
err = config.RemoveAll("section.time")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = config.RemoveAll("section")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = config.ReadString("section.key")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
err = config.RemoveAll("section.key")
|
||||
assert.Error(t, err)
|
||||
require.Error(t, err)
|
||||
|
||||
// section + subsections
|
||||
require.NoError(t, config.StoreString("section.opt1", "foo"))
|
||||
require.NoError(t, config.StoreString("section.opt2", "foo2"))
|
||||
require.NoError(t, config.StoreString("section.subsection.opt1", "foo3"))
|
||||
require.NoError(t, config.StoreString("section.subsection.opt2", "foo4"))
|
||||
require.NoError(t, config.StoreString("section.subsection.subsection.opt1", "foo5"))
|
||||
require.NoError(t, config.StoreString("section.subsection.subsection.opt2", "foo6"))
|
||||
|
||||
all, err := config.ReadAll("section")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]string{
|
||||
"section.opt1": "foo",
|
||||
"section.opt2": "foo2",
|
||||
"section.subsection.opt1": "foo3",
|
||||
"section.subsection.opt2": "foo4",
|
||||
"section.subsection.subsection.opt1": "foo5",
|
||||
"section.subsection.subsection.opt2": "foo6",
|
||||
}, all)
|
||||
|
||||
all, err = config.ReadAll("section.subsection")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]string{
|
||||
"section.subsection.opt1": "foo3",
|
||||
"section.subsection.opt2": "foo4",
|
||||
"section.subsection.subsection.opt1": "foo5",
|
||||
"section.subsection.subsection.opt2": "foo6",
|
||||
}, all)
|
||||
|
||||
all, err = config.ReadAll("section.subsection.subsection")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]string{
|
||||
"section.subsection.subsection.opt1": "foo5",
|
||||
"section.subsection.subsection.opt2": "foo6",
|
||||
}, all)
|
||||
}
|
||||
|
@ -4,8 +4,6 @@ package repository
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -22,70 +20,28 @@ var _ TestedRepo = &GitRepo{}
|
||||
|
||||
// GitRepo represents an instance of a (local) git repository.
|
||||
type GitRepo struct {
|
||||
gitCli
|
||||
path string
|
||||
|
||||
clocksMutex sync.Mutex
|
||||
clocks map[string]lamport.Clock
|
||||
}
|
||||
|
||||
// LocalConfig give access to the repository scoped configuration
|
||||
func (repo *GitRepo) LocalConfig() Config {
|
||||
return newGitConfig(repo, false)
|
||||
}
|
||||
|
||||
// GlobalConfig give access to the git global configuration
|
||||
func (repo *GitRepo) GlobalConfig() Config {
|
||||
return newGitConfig(repo, true)
|
||||
}
|
||||
|
||||
// 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
|
||||
// always exist, in particular when running "git init".
|
||||
path := strings.TrimSuffix(repo.path, ".git")
|
||||
|
||||
// fmt.Printf("[%s] Running git %s\n", path, strings.Join(args, " "))
|
||||
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = path
|
||||
cmd.Stdin = stdin
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// Run the given git command and return its stdout, or an error if the command fails.
|
||||
func (repo *GitRepo) runGitCommandRaw(stdin io.Reader, args ...string) (string, string, error) {
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
err := repo.runGitCommandWithIO(stdin, &stdout, &stderr, args...)
|
||||
return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err
|
||||
}
|
||||
|
||||
// Run the given git command and return its stdout, or an error if the command fails.
|
||||
func (repo *GitRepo) runGitCommandWithStdin(stdin io.Reader, args ...string) (string, error) {
|
||||
stdout, stderr, err := repo.runGitCommandRaw(stdin, args...)
|
||||
if err != nil {
|
||||
if stderr == "" {
|
||||
stderr = "Error running git command: " + strings.Join(args, " ")
|
||||
}
|
||||
err = fmt.Errorf(stderr)
|
||||
}
|
||||
return stdout, err
|
||||
}
|
||||
|
||||
// Run the given git command and return its stdout, or an error if the command fails.
|
||||
func (repo *GitRepo) runGitCommand(args ...string) (string, error) {
|
||||
return repo.runGitCommandWithStdin(nil, args...)
|
||||
keyring Keyring
|
||||
}
|
||||
|
||||
// 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),
|
||||
gitCli: gitCli{path: path},
|
||||
path: path,
|
||||
clocks: make(map[string]lamport.Clock),
|
||||
keyring: k,
|
||||
}
|
||||
|
||||
// Check the repo and retrieve the root path
|
||||
@ -100,6 +56,7 @@ func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) {
|
||||
|
||||
// Fix the path to be sure we are at the root
|
||||
repo.path = stdout
|
||||
repo.gitCli.path = stdout
|
||||
|
||||
for _, loader := range clockLoaders {
|
||||
allExist := true
|
||||
@ -123,6 +80,7 @@ func NewGitRepo(path string, clockLoaders []ClockLoader) (*GitRepo, error) {
|
||||
// InitGitRepo create a new empty git repo at the given path
|
||||
func InitGitRepo(path string) (*GitRepo, error) {
|
||||
repo := &GitRepo{
|
||||
gitCli: gitCli{path: path},
|
||||
path: path + "/.git",
|
||||
clocks: make(map[string]lamport.Clock),
|
||||
}
|
||||
@ -138,6 +96,7 @@ func InitGitRepo(path string) (*GitRepo, error) {
|
||||
// InitBareGitRepo create a new --bare empty git repo at the given path
|
||||
func InitBareGitRepo(path string) (*GitRepo, error) {
|
||||
repo := &GitRepo{
|
||||
gitCli: gitCli{path: path},
|
||||
path: path,
|
||||
clocks: make(map[string]lamport.Clock),
|
||||
}
|
||||
@ -150,6 +109,26 @@ func InitBareGitRepo(path string) (*GitRepo, error) {
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// LocalConfig give access to the repository scoped configuration
|
||||
func (repo *GitRepo) LocalConfig() Config {
|
||||
return newGitConfig(repo.gitCli, false)
|
||||
}
|
||||
|
||||
// GlobalConfig give access to the global scoped configuration
|
||||
func (repo *GitRepo) GlobalConfig() Config {
|
||||
return newGitConfig(repo.gitCli, true)
|
||||
}
|
||||
|
||||
// AnyConfig give access to a merged local/global configuration
|
||||
func (repo *GitRepo) AnyConfig() ConfigRead {
|
||||
return mergeConfig(repo.LocalConfig(), repo.GlobalConfig())
|
||||
}
|
||||
|
||||
// Keyring give access to a user-wide storage for secrets
|
||||
func (repo *GitRepo) Keyring() Keyring {
|
||||
return repo.keyring
|
||||
}
|
||||
|
||||
// GetPath returns the path to the repo.
|
||||
func (repo *GitRepo) GetPath() string {
|
||||
return repo.path
|
||||
@ -290,8 +269,8 @@ func (repo *GitRepo) RemoveRef(ref string) error {
|
||||
}
|
||||
|
||||
// ListRefs will return a list of Git ref matching the given refspec
|
||||
func (repo *GitRepo) ListRefs(refspec string) ([]string, error) {
|
||||
stdout, err := repo.runGitCommand("for-each-ref", "--format=%(refname)", refspec)
|
||||
func (repo *GitRepo) ListRefs(refPrefix string) ([]string, error) {
|
||||
stdout, err := repo.runGitCommand("for-each-ref", "--format=%(refname)", refPrefix)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
56
repository/git_cli.go
Normal file
56
repository/git_cli.go
Normal file
@ -0,0 +1,56 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// gitCli is a helper to launch CLI git commands
|
||||
type gitCli struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// Run the given git command with the given I/O reader/writers, returning an error if it fails.
|
||||
func (cli gitCli) runGitCommandWithIO(stdin io.Reader, stdout, stderr io.Writer, args ...string) error {
|
||||
// make sure that the working directory for the command
|
||||
// always exist, in particular when running "git init".
|
||||
path := strings.TrimSuffix(cli.path, ".git")
|
||||
|
||||
// fmt.Printf("[%s] Running git %s\n", path, strings.Join(args, " "))
|
||||
|
||||
cmd := exec.Command("git", args...)
|
||||
cmd.Dir = path
|
||||
cmd.Stdin = stdin
|
||||
cmd.Stdout = stdout
|
||||
cmd.Stderr = stderr
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// Run the given git command and return its stdout, or an error if the command fails.
|
||||
func (cli gitCli) runGitCommandRaw(stdin io.Reader, args ...string) (string, string, error) {
|
||||
var stdout bytes.Buffer
|
||||
var stderr bytes.Buffer
|
||||
err := cli.runGitCommandWithIO(stdin, &stdout, &stderr, args...)
|
||||
return strings.TrimSpace(stdout.String()), strings.TrimSpace(stderr.String()), err
|
||||
}
|
||||
|
||||
// Run the given git command and return its stdout, or an error if the command fails.
|
||||
func (cli gitCli) runGitCommandWithStdin(stdin io.Reader, args ...string) (string, error) {
|
||||
stdout, stderr, err := cli.runGitCommandRaw(stdin, args...)
|
||||
if err != nil {
|
||||
if stderr == "" {
|
||||
stderr = "Error running git command: " + strings.Join(args, " ")
|
||||
}
|
||||
err = fmt.Errorf(stderr)
|
||||
}
|
||||
return stdout, err
|
||||
}
|
||||
|
||||
// Run the given git command and return its stdout, or an error if the command fails.
|
||||
func (cli gitCli) runGitCommand(args ...string) (string, error) {
|
||||
return cli.runGitCommandWithStdin(nil, args...)
|
||||
}
|
@ -14,24 +14,24 @@ import (
|
||||
var _ Config = &gitConfig{}
|
||||
|
||||
type gitConfig struct {
|
||||
repo *GitRepo
|
||||
cli gitCli
|
||||
localityFlag string
|
||||
}
|
||||
|
||||
func newGitConfig(repo *GitRepo, global bool) *gitConfig {
|
||||
func newGitConfig(cli gitCli, global bool) *gitConfig {
|
||||
localityFlag := "--local"
|
||||
if global {
|
||||
localityFlag = "--global"
|
||||
}
|
||||
return &gitConfig{
|
||||
repo: repo,
|
||||
cli: cli,
|
||||
localityFlag: localityFlag,
|
||||
}
|
||||
}
|
||||
|
||||
// StoreString store a single key/value pair in the config of the repo
|
||||
func (gc *gitConfig) StoreString(key string, value string) error {
|
||||
_, err := gc.repo.runGitCommand("config", gc.localityFlag, "--replace-all", key, value)
|
||||
_, err := gc.cli.runGitCommand("config", gc.localityFlag, "--replace-all", key, value)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ func (gc *gitConfig) StoreTimestamp(key string, value time.Time) error {
|
||||
|
||||
// ReadAll read all key/value pair matching the key prefix
|
||||
func (gc *gitConfig) ReadAll(keyPrefix string) (map[string]string, error) {
|
||||
stdout, err := gc.repo.runGitCommand("config", gc.localityFlag, "--includes", "--get-regexp", keyPrefix)
|
||||
stdout, err := gc.cli.runGitCommand("config", gc.localityFlag, "--includes", "--get-regexp", keyPrefix)
|
||||
|
||||
// / \
|
||||
// / ! \
|
||||
@ -74,7 +74,7 @@ func (gc *gitConfig) ReadAll(keyPrefix string) (map[string]string, error) {
|
||||
}
|
||||
|
||||
func (gc *gitConfig) ReadString(key string) (string, error) {
|
||||
stdout, err := gc.repo.runGitCommand("config", gc.localityFlag, "--includes", "--get-all", key)
|
||||
stdout, err := gc.cli.runGitCommand("config", gc.localityFlag, "--includes", "--get-all", key)
|
||||
|
||||
// / \
|
||||
// / ! \
|
||||
@ -116,12 +116,12 @@ func (gc *gitConfig) ReadTimestamp(key string) (time.Time, error) {
|
||||
}
|
||||
|
||||
func (gc *gitConfig) rmSection(keyPrefix string) error {
|
||||
_, err := gc.repo.runGitCommand("config", gc.localityFlag, "--remove-section", keyPrefix)
|
||||
_, err := gc.cli.runGitCommand("config", gc.localityFlag, "--remove-section", keyPrefix)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gc *gitConfig) unsetAll(keyPrefix string) error {
|
||||
_, err := gc.repo.runGitCommand("config", gc.localityFlag, "--unset-all", keyPrefix)
|
||||
_, err := gc.cli.runGitCommand("config", gc.localityFlag, "--unset-all", keyPrefix)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ func (gc *gitConfig) RemoveAll(keyPrefix string) error {
|
||||
}
|
||||
|
||||
func (gc *gitConfig) gitVersion() (*semver.Version, error) {
|
||||
versionOut, err := gc.repo.runGitCommand("version")
|
||||
versionOut, err := gc.cli.runGitCommand("version")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ package repository
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/99designs/keyring"
|
||||
)
|
||||
|
||||
// This is intended for testing only
|
||||
@ -34,7 +36,11 @@ func CreateTestRepo(bare bool) TestedRepo {
|
||||
log.Fatal("failed to set user.email for test repository: ", err)
|
||||
}
|
||||
|
||||
return repo
|
||||
// make sure we use a mock keyring for testing to not interact with the global system
|
||||
return &replaceKeyring{
|
||||
TestedRepo: repo,
|
||||
keyring: keyring.NewArrayKeyring(nil),
|
||||
}
|
||||
}
|
||||
|
||||
func SetupReposAndRemote() (repoA, repoB, remote TestedRepo) {
|
||||
@ -56,3 +62,13 @@ func SetupReposAndRemote() (repoA, repoB, remote TestedRepo) {
|
||||
|
||||
return repoA, repoB, remote
|
||||
}
|
||||
|
||||
// replaceKeyring allow to replace the Keyring of the underlying repo
|
||||
type replaceKeyring struct {
|
||||
TestedRepo
|
||||
keyring Keyring
|
||||
}
|
||||
|
||||
func (rk replaceKeyring) Keyring() Keyring {
|
||||
return rk.keyring
|
||||
}
|
||||
|
615
repository/gogit.go
Normal file
615
repository/gogit.go
Normal file
@ -0,0 +1,615 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
stdpath "path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
"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/MichaelMure/git-bug/util/lamport"
|
||||
)
|
||||
|
||||
var _ ClockedRepo = &GoGitRepo{}
|
||||
|
||||
type GoGitRepo struct {
|
||||
r *gogit.Repository
|
||||
path string
|
||||
|
||||
clocksMutex sync.Mutex
|
||||
clocks map[string]lamport.Clock
|
||||
|
||||
keyring Keyring
|
||||
}
|
||||
|
||||
func NewGoGitRepo(path string, clockLoaders []ClockLoader) (*GoGitRepo, error) {
|
||||
path, err := detectGitPath(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := gogit.PlainOpen(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k, err := defaultKeyring()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo := &GoGitRepo{
|
||||
r: r,
|
||||
path: path,
|
||||
clocks: make(map[string]lamport.Clock),
|
||||
keyring: k,
|
||||
}
|
||||
|
||||
for _, loader := range clockLoaders {
|
||||
allExist := true
|
||||
for _, name := range loader.Clocks {
|
||||
if _, err := repo.getClock(name); err != nil {
|
||||
allExist = false
|
||||
}
|
||||
}
|
||||
|
||||
if !allExist {
|
||||
err = loader.Witnesser(repo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
func detectGitPath(path string) (string, error) {
|
||||
// normalize the path
|
||||
path, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for {
|
||||
fi, err := os.Stat(stdpath.Join(path, ".git"))
|
||||
if err == nil {
|
||||
if !fi.IsDir() {
|
||||
return "", fmt.Errorf(".git exist but is not a directory")
|
||||
}
|
||||
return stdpath.Join(path, ".git"), nil
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
// unknown error
|
||||
return "", err
|
||||
}
|
||||
|
||||
// detect bare repo
|
||||
ok, err := isGitDir(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if ok {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
if parent := filepath.Dir(path); parent == path {
|
||||
return "", fmt.Errorf(".git not found")
|
||||
} else {
|
||||
path = parent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isGitDir(path string) (bool, error) {
|
||||
markers := []string{"HEAD", "objects", "refs"}
|
||||
|
||||
for _, marker := range markers {
|
||||
_, err := os.Stat(stdpath.Join(path, marker))
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
// unknown error
|
||||
return false, err
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// InitGoGitRepo create a new empty git repo at the given path
|
||||
func InitGoGitRepo(path string) (*GoGitRepo, error) {
|
||||
r, err := gogit.PlainInit(path, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GoGitRepo{
|
||||
r: r,
|
||||
path: path + "/.git",
|
||||
clocks: make(map[string]lamport.Clock),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InitBareGoGitRepo create a new --bare empty git repo at the given path
|
||||
func InitBareGoGitRepo(path string) (*GoGitRepo, error) {
|
||||
r, err := gogit.PlainInit(path, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GoGitRepo{
|
||||
r: r,
|
||||
path: path,
|
||||
clocks: make(map[string]lamport.Clock),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LocalConfig give access to the repository scoped configuration
|
||||
func (repo *GoGitRepo) LocalConfig() Config {
|
||||
return newGoGitLocalConfig(repo.r)
|
||||
}
|
||||
|
||||
// GlobalConfig give access to the global scoped configuration
|
||||
func (repo *GoGitRepo) GlobalConfig() Config {
|
||||
// TODO: replace that with go-git native implementation once it's supported
|
||||
// see: https://github.com/go-git/go-git
|
||||
// see: https://github.com/src-d/go-git/issues/760
|
||||
return newGoGitGlobalConfig(repo.r)
|
||||
}
|
||||
|
||||
// AnyConfig give access to a merged local/global configuration
|
||||
func (repo *GoGitRepo) AnyConfig() ConfigRead {
|
||||
return mergeConfig(repo.LocalConfig(), repo.GlobalConfig())
|
||||
}
|
||||
|
||||
// Keyring give access to a user-wide storage for secrets
|
||||
func (repo *GoGitRepo) Keyring() Keyring {
|
||||
return repo.keyring
|
||||
}
|
||||
|
||||
// GetPath returns the path to the repo.
|
||||
func (repo *GoGitRepo) GetPath() string {
|
||||
return repo.path
|
||||
}
|
||||
|
||||
// GetUserName returns the name the the user has used to configure git
|
||||
func (repo *GoGitRepo) GetUserName() (string, error) {
|
||||
cfg, err := repo.r.Config()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cfg.User.Name, nil
|
||||
}
|
||||
|
||||
// GetUserEmail returns the email address that the user has used to configure git.
|
||||
func (repo *GoGitRepo) GetUserEmail() (string, error) {
|
||||
cfg, err := repo.r.Config()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cfg.User.Email, nil
|
||||
}
|
||||
|
||||
// GetCoreEditor returns the name of the editor that the user has used to configure git.
|
||||
func (repo *GoGitRepo) GetCoreEditor() (string, error) {
|
||||
// See https://git-scm.com/docs/git-var
|
||||
// The order of preference is the $GIT_EDITOR environment variable, then core.editor configuration, then $VISUAL, then $EDITOR, and then the default chosen at compile time, which is usually vi.
|
||||
|
||||
if val, ok := os.LookupEnv("GIT_EDITOR"); ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
val, err := repo.AnyConfig().ReadString("core.editor")
|
||||
if err == nil && val != "" {
|
||||
return val, nil
|
||||
}
|
||||
if err != nil && err != ErrNoConfigEntry {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if val, ok := os.LookupEnv("VISUAL"); ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if val, ok := os.LookupEnv("EDITOR"); ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
return "vi", nil
|
||||
}
|
||||
|
||||
// GetRemotes returns the configured remotes repositories.
|
||||
func (repo *GoGitRepo) GetRemotes() (map[string]string, error) {
|
||||
cfg, err := repo.r.Config()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]string, len(cfg.Remotes))
|
||||
for name, remote := range cfg.Remotes {
|
||||
if len(remote.URLs) > 0 {
|
||||
result[name] = remote.URLs[0]
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FetchRefs fetch git refs from a remote
|
||||
func (repo *GoGitRepo) FetchRefs(remote string, refSpec string) (string, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
err := repo.r.Fetch(&gogit.FetchOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(refSpec)},
|
||||
Progress: buf,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// PushRefs push git refs to a remote
|
||||
func (repo *GoGitRepo) PushRefs(remote string, refSpec string) (string, error) {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
err := repo.r.Push(&gogit.PushOptions{
|
||||
RemoteName: remote,
|
||||
RefSpecs: []config.RefSpec{config.RefSpec(refSpec)},
|
||||
Progress: buf,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// StoreData will store arbitrary data and return the corresponding hash
|
||||
func (repo *GoGitRepo) StoreData(data []byte) (Hash, error) {
|
||||
obj := repo.r.Storer.NewEncodedObject()
|
||||
obj.SetType(plumbing.BlobObject)
|
||||
|
||||
w, err := obj.Writer()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = w.Write(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
h, err := repo.r.Storer.SetEncodedObject(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return Hash(h.String()), nil
|
||||
}
|
||||
|
||||
// ReadData will attempt to read arbitrary data from the given hash
|
||||
func (repo *GoGitRepo) ReadData(hash Hash) ([]byte, error) {
|
||||
obj, err := repo.r.BlobObject(plumbing.NewHash(hash.String()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := obj.Reader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ioutil.ReadAll(r)
|
||||
}
|
||||
|
||||
// StoreTree will store a mapping key-->Hash as a Git tree
|
||||
func (repo *GoGitRepo) StoreTree(mapping []TreeEntry) (Hash, error) {
|
||||
var tree object.Tree
|
||||
|
||||
for _, entry := range mapping {
|
||||
mode := filemode.Regular
|
||||
if entry.ObjectType == Tree {
|
||||
mode = filemode.Dir
|
||||
}
|
||||
|
||||
tree.Entries = append(tree.Entries, object.TreeEntry{
|
||||
Name: entry.Name,
|
||||
Mode: mode,
|
||||
Hash: plumbing.NewHash(entry.Hash.String()),
|
||||
})
|
||||
}
|
||||
|
||||
obj := repo.r.Storer.NewEncodedObject()
|
||||
obj.SetType(plumbing.TreeObject)
|
||||
err := tree.Encode(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hash, err := repo.r.Storer.SetEncodedObject(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return Hash(hash.String()), nil
|
||||
}
|
||||
|
||||
// ReadTree will return the list of entries in a Git tree
|
||||
func (repo *GoGitRepo) ReadTree(hash Hash) ([]TreeEntry, error) {
|
||||
h := plumbing.NewHash(hash.String())
|
||||
|
||||
// the given hash could be a tree or a commit
|
||||
obj, err := repo.r.Storer.EncodedObject(plumbing.AnyObject, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tree *object.Tree
|
||||
switch obj.Type() {
|
||||
case plumbing.TreeObject:
|
||||
tree, err = object.DecodeTree(repo.r.Storer, obj)
|
||||
case plumbing.CommitObject:
|
||||
var commit *object.Commit
|
||||
commit, err = object.DecodeCommit(repo.r.Storer, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tree, err = commit.Tree()
|
||||
default:
|
||||
return nil, fmt.Errorf("given hash is not a tree")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
treeEntries := make([]TreeEntry, len(tree.Entries))
|
||||
for i, entry := range tree.Entries {
|
||||
objType := Blob
|
||||
if entry.Mode == filemode.Dir {
|
||||
objType = Tree
|
||||
}
|
||||
|
||||
treeEntries[i] = TreeEntry{
|
||||
ObjectType: objType,
|
||||
Hash: Hash(entry.Hash.String()),
|
||||
Name: entry.Name,
|
||||
}
|
||||
}
|
||||
|
||||
return treeEntries, nil
|
||||
}
|
||||
|
||||
// StoreCommit will store a Git commit with the given Git tree
|
||||
func (repo *GoGitRepo) StoreCommit(treeHash Hash) (Hash, error) {
|
||||
return repo.StoreCommitWithParent(treeHash, "")
|
||||
}
|
||||
|
||||
// StoreCommit will store a Git commit with the given Git tree
|
||||
func (repo *GoGitRepo) StoreCommitWithParent(treeHash Hash, parent Hash) (Hash, error) {
|
||||
cfg, err := repo.r.Config()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
commit := object.Commit{
|
||||
Author: object.Signature{
|
||||
cfg.Author.Name,
|
||||
cfg.Author.Email,
|
||||
time.Now(),
|
||||
},
|
||||
Committer: object.Signature{
|
||||
cfg.Committer.Name,
|
||||
cfg.Committer.Email,
|
||||
time.Now(),
|
||||
},
|
||||
Message: "",
|
||||
TreeHash: plumbing.NewHash(treeHash.String()),
|
||||
}
|
||||
|
||||
if parent != "" {
|
||||
commit.ParentHashes = []plumbing.Hash{plumbing.NewHash(parent.String())}
|
||||
}
|
||||
|
||||
obj := repo.r.Storer.NewEncodedObject()
|
||||
obj.SetType(plumbing.CommitObject)
|
||||
err = commit.Encode(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hash, err := repo.r.Storer.SetEncodedObject(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return Hash(hash.String()), nil
|
||||
}
|
||||
|
||||
// GetTreeHash return the git tree hash referenced in a commit
|
||||
func (repo *GoGitRepo) GetTreeHash(commit Hash) (Hash, error) {
|
||||
obj, err := repo.r.CommitObject(plumbing.NewHash(commit.String()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return Hash(obj.TreeHash.String()), nil
|
||||
}
|
||||
|
||||
// FindCommonAncestor will return the last common ancestor of two chain of commit
|
||||
func (repo *GoGitRepo) FindCommonAncestor(commit1 Hash, commit2 Hash) (Hash, error) {
|
||||
obj1, err := repo.r.CommitObject(plumbing.NewHash(commit1.String()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
obj2, err := repo.r.CommitObject(plumbing.NewHash(commit2.String()))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
commits, err := obj1.MergeBase(obj2)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return Hash(commits[0].Hash.String()), nil
|
||||
}
|
||||
|
||||
// UpdateRef will create or update a Git reference
|
||||
func (repo *GoGitRepo) UpdateRef(ref string, hash Hash) error {
|
||||
return repo.r.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(ref), plumbing.NewHash(hash.String())))
|
||||
}
|
||||
|
||||
// RemoveRef will remove a Git reference
|
||||
func (repo *GoGitRepo) RemoveRef(ref string) error {
|
||||
return repo.r.Storer.RemoveReference(plumbing.ReferenceName(ref))
|
||||
}
|
||||
|
||||
// ListRefs will return a list of Git ref matching the given refspec
|
||||
func (repo *GoGitRepo) ListRefs(refPrefix string) ([]string, error) {
|
||||
refIter, err := repo.r.References()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refs := make([]string, 0)
|
||||
|
||||
err = refIter.ForEach(func(ref *plumbing.Reference) error {
|
||||
if strings.HasPrefix(ref.Name().String(), refPrefix) {
|
||||
refs = append(refs, ref.Name().String())
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
// RefExist will check if a reference exist in Git
|
||||
func (repo *GoGitRepo) RefExist(ref string) (bool, error) {
|
||||
_, err := repo.r.Reference(plumbing.ReferenceName(ref), false)
|
||||
if err == nil {
|
||||
return true, nil
|
||||
} else if err == plumbing.ErrReferenceNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
// CopyRef will create a new reference with the same value as another one
|
||||
func (repo *GoGitRepo) CopyRef(source string, dest string) error {
|
||||
r, err := repo.r.Reference(plumbing.ReferenceName(source), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return repo.r.Storer.SetReference(plumbing.NewHashReference(plumbing.ReferenceName(dest), r.Hash()))
|
||||
}
|
||||
|
||||
// ListCommits will return the list of tree hashes of a ref, in chronological order
|
||||
func (repo *GoGitRepo) ListCommits(ref string) ([]Hash, error) {
|
||||
r, err := repo.r.Reference(plumbing.ReferenceName(ref), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commit, err := repo.r.CommitObject(r.Hash())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashes := []Hash{Hash(commit.Hash.String())}
|
||||
|
||||
for {
|
||||
commit, err = commit.Parent(0)
|
||||
if err == object.ErrParentNotFound {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if commit.NumParents() > 1 {
|
||||
return nil, fmt.Errorf("multiple parents")
|
||||
}
|
||||
|
||||
hashes = append([]Hash{Hash(commit.Hash.String())}, hashes...)
|
||||
}
|
||||
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// GetOrCreateClock return a Lamport clock stored in the Repo.
|
||||
// If the clock doesn't exist, it's created.
|
||||
func (repo *GoGitRepo) GetOrCreateClock(name string) (lamport.Clock, error) {
|
||||
c, err := repo.getClock(name)
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
if err != ErrClockNotExist {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo.clocksMutex.Lock()
|
||||
defer repo.clocksMutex.Unlock()
|
||||
|
||||
p := stdpath.Join(repo.path, clockPath, name+"-clock")
|
||||
|
||||
c, err = lamport.NewPersistedClock(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo.clocks[name] = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (repo *GoGitRepo) getClock(name string) (lamport.Clock, error) {
|
||||
repo.clocksMutex.Lock()
|
||||
defer repo.clocksMutex.Unlock()
|
||||
|
||||
if c, ok := repo.clocks[name]; ok {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
p := stdpath.Join(repo.path, clockPath, name+"-clock")
|
||||
|
||||
c, err := lamport.LoadPersistedClock(p)
|
||||
if err == nil {
|
||||
repo.clocks[name] = c
|
||||
return c, nil
|
||||
}
|
||||
if err == lamport.ErrClockNotExist {
|
||||
return nil, ErrClockNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// AddRemote add a new remote to the repository
|
||||
// Not in the interface because it's only used for testing
|
||||
func (repo *GoGitRepo) AddRemote(name string, url string) error {
|
||||
_, err := repo.r.CreateRemote(&config.RemoteConfig{
|
||||
Name: name,
|
||||
URLs: []string{url},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
235
repository/gogit_config.go
Normal file
235
repository/gogit_config.go
Normal file
@ -0,0 +1,235 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gogit "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/config"
|
||||
)
|
||||
|
||||
var _ Config = &goGitConfig{}
|
||||
|
||||
type goGitConfig struct {
|
||||
ConfigRead
|
||||
ConfigWrite
|
||||
}
|
||||
|
||||
func newGoGitLocalConfig(repo *gogit.Repository) *goGitConfig {
|
||||
return &goGitConfig{
|
||||
ConfigRead: &goGitConfigReader{getConfig: repo.Config},
|
||||
ConfigWrite: &goGitConfigWriter{repo: repo},
|
||||
}
|
||||
}
|
||||
|
||||
func newGoGitGlobalConfig(repo *gogit.Repository) *goGitConfig {
|
||||
return &goGitConfig{
|
||||
ConfigRead: &goGitConfigReader{getConfig: func() (*config.Config, error) {
|
||||
return config.LoadConfig(config.GlobalScope)
|
||||
}},
|
||||
ConfigWrite: &configPanicWriter{},
|
||||
}
|
||||
}
|
||||
|
||||
var _ ConfigRead = &goGitConfigReader{}
|
||||
|
||||
type goGitConfigReader struct {
|
||||
getConfig func() (*config.Config, error)
|
||||
}
|
||||
|
||||
func (cr *goGitConfigReader) ReadAll(keyPrefix string) (map[string]string, error) {
|
||||
cfg, err := cr.getConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
split := strings.Split(keyPrefix, ".")
|
||||
result := make(map[string]string)
|
||||
|
||||
switch {
|
||||
case keyPrefix == "":
|
||||
for _, section := range cfg.Raw.Sections {
|
||||
for _, option := range section.Options {
|
||||
result[fmt.Sprintf("%s.%s", section.Name, option.Key)] = option.Value
|
||||
}
|
||||
for _, subsection := range section.Subsections {
|
||||
for _, option := range subsection.Options {
|
||||
result[fmt.Sprintf("%s.%s.%s", section.Name, subsection.Name, option.Key)] = option.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
case len(split) == 1:
|
||||
if !cfg.Raw.HasSection(split[0]) {
|
||||
return nil, nil
|
||||
}
|
||||
section := cfg.Raw.Section(split[0])
|
||||
for _, option := range section.Options {
|
||||
result[fmt.Sprintf("%s.%s", section.Name, option.Key)] = option.Value
|
||||
}
|
||||
for _, subsection := range section.Subsections {
|
||||
for _, option := range subsection.Options {
|
||||
result[fmt.Sprintf("%s.%s.%s", section.Name, subsection.Name, option.Key)] = option.Value
|
||||
}
|
||||
}
|
||||
default:
|
||||
if !cfg.Raw.HasSection(split[0]) {
|
||||
return nil, nil
|
||||
}
|
||||
section := cfg.Raw.Section(split[0])
|
||||
rest := strings.Join(split[1:], ".")
|
||||
for _, subsection := range section.Subsections {
|
||||
if strings.HasPrefix(subsection.Name, rest) {
|
||||
for _, option := range subsection.Options {
|
||||
result[fmt.Sprintf("%s.%s.%s", section.Name, subsection.Name, option.Key)] = option.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (cr *goGitConfigReader) ReadBool(key string) (bool, error) {
|
||||
val, err := cr.ReadString(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return strconv.ParseBool(val)
|
||||
}
|
||||
|
||||
func (cr *goGitConfigReader) ReadString(key string) (string, error) {
|
||||
cfg, err := cr.getConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
split := strings.Split(key, ".")
|
||||
|
||||
if len(split) <= 1 {
|
||||
return "", fmt.Errorf("invalid key")
|
||||
}
|
||||
|
||||
sectionName := split[0]
|
||||
if !cfg.Raw.HasSection(sectionName) {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
section := cfg.Raw.Section(sectionName)
|
||||
|
||||
switch {
|
||||
case len(split) == 2:
|
||||
optionName := split[1]
|
||||
if !section.HasOption(optionName) {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
if len(section.OptionAll(optionName)) > 1 {
|
||||
return "", ErrMultipleConfigEntry
|
||||
}
|
||||
return section.Option(optionName), nil
|
||||
default:
|
||||
subsectionName := strings.Join(split[1:len(split)-2], ".")
|
||||
optionName := split[len(split)-1]
|
||||
if !section.HasSubsection(subsectionName) {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
subsection := section.Subsection(subsectionName)
|
||||
if !subsection.HasOption(optionName) {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
if len(subsection.OptionAll(optionName)) > 1 {
|
||||
return "", ErrMultipleConfigEntry
|
||||
}
|
||||
return subsection.Option(optionName), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (cr *goGitConfigReader) ReadTimestamp(key string) (time.Time, error) {
|
||||
value, err := cr.ReadString(key)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return ParseTimestamp(value)
|
||||
}
|
||||
|
||||
var _ ConfigWrite = &goGitConfigWriter{}
|
||||
|
||||
// Only works for the local config as go-git only support that
|
||||
type goGitConfigWriter struct {
|
||||
repo *gogit.Repository
|
||||
}
|
||||
|
||||
func (cw *goGitConfigWriter) StoreString(key, value string) error {
|
||||
cfg, err := cw.repo.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
split := strings.Split(key, ".")
|
||||
|
||||
switch {
|
||||
case len(split) <= 1:
|
||||
return fmt.Errorf("invalid key")
|
||||
case len(split) == 2:
|
||||
cfg.Raw.Section(split[0]).SetOption(split[1], value)
|
||||
default:
|
||||
section := split[0]
|
||||
subsection := strings.Join(split[1:len(split)-1], ".")
|
||||
option := split[len(split)-1]
|
||||
cfg.Raw.Section(section).Subsection(subsection).SetOption(option, value)
|
||||
}
|
||||
|
||||
return cw.repo.SetConfig(cfg)
|
||||
}
|
||||
|
||||
func (cw *goGitConfigWriter) StoreTimestamp(key string, value time.Time) error {
|
||||
return cw.StoreString(key, strconv.Itoa(int(value.Unix())))
|
||||
}
|
||||
|
||||
func (cw *goGitConfigWriter) StoreBool(key string, value bool) error {
|
||||
return cw.StoreString(key, strconv.FormatBool(value))
|
||||
}
|
||||
|
||||
func (cw *goGitConfigWriter) RemoveAll(keyPrefix string) error {
|
||||
cfg, err := cw.repo.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
split := strings.Split(keyPrefix, ".")
|
||||
|
||||
switch {
|
||||
case keyPrefix == "":
|
||||
cfg.Raw.Sections = nil
|
||||
// warning: this does not actually remove everything as go-git config hold
|
||||
// some entries in multiple places (cfg.User ...)
|
||||
case len(split) == 1:
|
||||
if cfg.Raw.HasSection(split[0]) {
|
||||
cfg.Raw.RemoveSection(split[0])
|
||||
} else {
|
||||
return fmt.Errorf("invalid key prefix")
|
||||
}
|
||||
default:
|
||||
if !cfg.Raw.HasSection(split[0]) {
|
||||
return fmt.Errorf("invalid key prefix")
|
||||
}
|
||||
section := cfg.Raw.Section(split[0])
|
||||
rest := strings.Join(split[1:], ".")
|
||||
|
||||
ok := false
|
||||
if section.HasSubsection(rest) {
|
||||
section.RemoveSubsection(rest)
|
||||
ok = true
|
||||
}
|
||||
if section.HasOption(rest) {
|
||||
section.RemoveOption(rest)
|
||||
ok = true
|
||||
}
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid key prefix")
|
||||
}
|
||||
}
|
||||
|
||||
return cw.repo.SetConfig(cfg)
|
||||
}
|
68
repository/gogit_test.go
Normal file
68
repository/gogit_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewGoGitRepo(t *testing.T) {
|
||||
// Plain
|
||||
plainRoot, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(plainRoot)
|
||||
|
||||
_, err = InitGoGitRepo(plainRoot)
|
||||
require.NoError(t, err)
|
||||
plainGitDir := path.Join(plainRoot, ".git")
|
||||
|
||||
// Bare
|
||||
bareRoot, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(bareRoot)
|
||||
|
||||
_, err = InitBareGoGitRepo(bareRoot)
|
||||
require.NoError(t, err)
|
||||
bareGitDir := bareRoot
|
||||
|
||||
tests := []struct {
|
||||
inPath string
|
||||
outPath string
|
||||
err bool
|
||||
}{
|
||||
// errors
|
||||
{"/", "", true},
|
||||
// parent dir of a repo
|
||||
{filepath.Dir(plainRoot), "", true},
|
||||
|
||||
// Plain repo
|
||||
{plainRoot, plainGitDir, false},
|
||||
{plainGitDir, plainGitDir, false},
|
||||
{path.Join(plainGitDir, "objects"), plainGitDir, false},
|
||||
|
||||
// Bare repo
|
||||
{bareRoot, bareGitDir, false},
|
||||
{bareGitDir, bareGitDir, false},
|
||||
{path.Join(bareGitDir, "objects"), bareGitDir, false},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
r, err := NewGoGitRepo(tc.inPath, nil)
|
||||
|
||||
if tc.err {
|
||||
require.Error(t, err, i)
|
||||
} else {
|
||||
require.NoError(t, err, i)
|
||||
assert.Equal(t, filepath.ToSlash(tc.outPath), filepath.ToSlash(r.GetPath()), i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoGitRepo(t *testing.T) {
|
||||
RepoTest(t, CreateGoGitTestRepo, CleanupTestRepos)
|
||||
}
|
58
repository/gogit_testing.go
Normal file
58
repository/gogit_testing.go
Normal file
@ -0,0 +1,58 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
)
|
||||
|
||||
// This is intended for testing only
|
||||
|
||||
func CreateGoGitTestRepo(bare bool) TestedRepo {
|
||||
dir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
var creator func(string) (*GoGitRepo, error)
|
||||
|
||||
if bare {
|
||||
creator = InitBareGoGitRepo
|
||||
} else {
|
||||
creator = InitGoGitRepo
|
||||
}
|
||||
|
||||
repo, err := creator(dir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
config := repo.LocalConfig()
|
||||
if err := config.StoreString("user.name", "testuser"); err != nil {
|
||||
log.Fatal("failed to set user.name for test repository: ", err)
|
||||
}
|
||||
if err := config.StoreString("user.email", "testuser@example.com"); err != nil {
|
||||
log.Fatal("failed to set user.email for test repository: ", err)
|
||||
}
|
||||
|
||||
return repo
|
||||
}
|
||||
|
||||
func SetupGoGitReposAndRemote() (repoA, repoB, remote TestedRepo) {
|
||||
repoA = CreateGoGitTestRepo(false)
|
||||
repoB = CreateGoGitTestRepo(false)
|
||||
remote = CreateGoGitTestRepo(true)
|
||||
|
||||
remoteAddr := "file://" + remote.GetPath()
|
||||
|
||||
err := repoA.AddRemote("origin", remoteAddr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = repoB.AddRemote("origin", remoteAddr)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return repoA, repoB, remote
|
||||
}
|
50
repository/keyring.go
Normal file
50
repository/keyring.go
Normal file
@ -0,0 +1,50 @@
|
||||
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
|
||||
}
|
||||
|
||||
return keyring.Open(keyring.Config{
|
||||
// only use the file backend until https://github.com/99designs/keyring/issues/74 is resolved
|
||||
AllowedBackends: []keyring.BackendType{
|
||||
keyring.FileBackend,
|
||||
},
|
||||
|
||||
ServiceName: "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
|
||||
},
|
||||
})
|
||||
}
|
@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/99designs/keyring"
|
||||
|
||||
"github.com/MichaelMure/git-bug/util/lamport"
|
||||
)
|
||||
|
||||
@ -13,85 +15,143 @@ var _ TestedRepo = &mockRepoForTest{}
|
||||
|
||||
// mockRepoForTest defines an instance of Repo that can be used for testing.
|
||||
type mockRepoForTest struct {
|
||||
config *MemConfig
|
||||
globalConfig *MemConfig
|
||||
blobs map[Hash][]byte
|
||||
trees map[Hash]string
|
||||
commits map[Hash]commit
|
||||
refs map[string]Hash
|
||||
clocks map[string]lamport.Clock
|
||||
*mockRepoConfig
|
||||
*mockRepoKeyring
|
||||
*mockRepoCommon
|
||||
*mockRepoData
|
||||
*mockRepoClock
|
||||
}
|
||||
|
||||
func NewMockRepoForTest() *mockRepoForTest {
|
||||
return &mockRepoForTest{
|
||||
mockRepoConfig: NewMockRepoConfig(),
|
||||
mockRepoKeyring: NewMockRepoKeyring(),
|
||||
mockRepoCommon: NewMockRepoCommon(),
|
||||
mockRepoData: NewMockRepoData(),
|
||||
mockRepoClock: NewMockRepoClock(),
|
||||
}
|
||||
}
|
||||
|
||||
var _ RepoConfig = &mockRepoConfig{}
|
||||
|
||||
type mockRepoConfig struct {
|
||||
localConfig *MemConfig
|
||||
globalConfig *MemConfig
|
||||
}
|
||||
|
||||
func NewMockRepoConfig() *mockRepoConfig {
|
||||
return &mockRepoConfig{
|
||||
localConfig: NewMemConfig(),
|
||||
globalConfig: NewMemConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
// LocalConfig give access to the repository scoped configuration
|
||||
func (r *mockRepoConfig) LocalConfig() Config {
|
||||
return r.localConfig
|
||||
}
|
||||
|
||||
// GlobalConfig give access to the git global configuration
|
||||
func (r *mockRepoConfig) GlobalConfig() Config {
|
||||
return r.globalConfig
|
||||
}
|
||||
|
||||
// AnyConfig give access to a merged local/global configuration
|
||||
func (r *mockRepoConfig) AnyConfig() ConfigRead {
|
||||
return mergeConfig(r.localConfig, r.globalConfig)
|
||||
}
|
||||
|
||||
var _ RepoKeyring = &mockRepoKeyring{}
|
||||
|
||||
type mockRepoKeyring struct {
|
||||
keyring *keyring.ArrayKeyring
|
||||
}
|
||||
|
||||
func NewMockRepoKeyring() *mockRepoKeyring {
|
||||
return &mockRepoKeyring{
|
||||
keyring: keyring.NewArrayKeyring(nil),
|
||||
}
|
||||
}
|
||||
|
||||
// Keyring give access to a user-wide storage for secrets
|
||||
func (r *mockRepoKeyring) Keyring() Keyring {
|
||||
return r.keyring
|
||||
}
|
||||
|
||||
var _ RepoCommon = &mockRepoCommon{}
|
||||
|
||||
type mockRepoCommon struct{}
|
||||
|
||||
func NewMockRepoCommon() *mockRepoCommon {
|
||||
return &mockRepoCommon{}
|
||||
}
|
||||
|
||||
// GetPath returns the path to the repo.
|
||||
func (r *mockRepoCommon) GetPath() string {
|
||||
return "~/mockRepo/"
|
||||
}
|
||||
|
||||
func (r *mockRepoCommon) GetUserName() (string, error) {
|
||||
return "René Descartes", nil
|
||||
}
|
||||
|
||||
// GetUserEmail returns the email address that the user has used to configure git.
|
||||
func (r *mockRepoCommon) GetUserEmail() (string, error) {
|
||||
return "user@example.com", nil
|
||||
}
|
||||
|
||||
// GetCoreEditor returns the name of the editor that the user has used to configure git.
|
||||
func (r *mockRepoCommon) GetCoreEditor() (string, error) {
|
||||
return "vi", nil
|
||||
}
|
||||
|
||||
// GetRemotes returns the configured remotes repositories.
|
||||
func (r *mockRepoCommon) GetRemotes() (map[string]string, error) {
|
||||
return map[string]string{
|
||||
"origin": "git://github.com/MichaelMure/git-bug",
|
||||
}, nil
|
||||
}
|
||||
|
||||
var _ RepoData = &mockRepoData{}
|
||||
|
||||
type commit struct {
|
||||
treeHash Hash
|
||||
parent Hash
|
||||
}
|
||||
|
||||
func NewMockRepoForTest() *mockRepoForTest {
|
||||
return &mockRepoForTest{
|
||||
config: NewMemConfig(),
|
||||
globalConfig: NewMemConfig(),
|
||||
blobs: make(map[Hash][]byte),
|
||||
trees: make(map[Hash]string),
|
||||
commits: make(map[Hash]commit),
|
||||
refs: make(map[string]Hash),
|
||||
clocks: make(map[string]lamport.Clock),
|
||||
type mockRepoData struct {
|
||||
blobs map[Hash][]byte
|
||||
trees map[Hash]string
|
||||
commits map[Hash]commit
|
||||
refs map[string]Hash
|
||||
}
|
||||
|
||||
func NewMockRepoData() *mockRepoData {
|
||||
return &mockRepoData{
|
||||
blobs: make(map[Hash][]byte),
|
||||
trees: make(map[Hash]string),
|
||||
commits: make(map[Hash]commit),
|
||||
refs: make(map[string]Hash),
|
||||
}
|
||||
}
|
||||
|
||||
// LocalConfig give access to the repository scoped configuration
|
||||
func (r *mockRepoForTest) LocalConfig() Config {
|
||||
return r.config
|
||||
}
|
||||
|
||||
// GlobalConfig give access to the git global configuration
|
||||
func (r *mockRepoForTest) GlobalConfig() Config {
|
||||
return r.globalConfig
|
||||
}
|
||||
|
||||
// GetPath returns the path to the repo.
|
||||
func (r *mockRepoForTest) GetPath() string {
|
||||
return "~/mockRepo/"
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) GetUserName() (string, error) {
|
||||
return "René Descartes", nil
|
||||
}
|
||||
|
||||
// GetUserEmail returns the email address that the user has used to configure git.
|
||||
func (r *mockRepoForTest) GetUserEmail() (string, error) {
|
||||
return "user@example.com", nil
|
||||
}
|
||||
|
||||
// GetCoreEditor returns the name of the editor that the user has used to configure git.
|
||||
func (r *mockRepoForTest) GetCoreEditor() (string, error) {
|
||||
return "vi", nil
|
||||
}
|
||||
|
||||
// GetRemotes returns the configured remotes repositories.
|
||||
func (r *mockRepoForTest) GetRemotes() (map[string]string, error) {
|
||||
return map[string]string{
|
||||
"origin": "git://github.com/MichaelMure/git-bug",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PushRefs push git refs to a remote
|
||||
func (r *mockRepoForTest) PushRefs(remote string, refSpec string) (string, error) {
|
||||
func (r *mockRepoData) PushRefs(remote string, refSpec string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) FetchRefs(remote string, refSpec string) (string, error) {
|
||||
func (r *mockRepoData) FetchRefs(remote string, refSpec string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) StoreData(data []byte) (Hash, error) {
|
||||
func (r *mockRepoData) StoreData(data []byte) (Hash, error) {
|
||||
rawHash := sha1.Sum(data)
|
||||
hash := Hash(fmt.Sprintf("%x", rawHash))
|
||||
r.blobs[hash] = data
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) ReadData(hash Hash) ([]byte, error) {
|
||||
func (r *mockRepoData) ReadData(hash Hash) ([]byte, error) {
|
||||
data, ok := r.blobs[hash]
|
||||
|
||||
if !ok {
|
||||
@ -101,7 +161,7 @@ func (r *mockRepoForTest) ReadData(hash Hash) ([]byte, error) {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) StoreTree(entries []TreeEntry) (Hash, error) {
|
||||
func (r *mockRepoData) StoreTree(entries []TreeEntry) (Hash, error) {
|
||||
buffer := prepareTreeEntries(entries)
|
||||
rawHash := sha1.Sum(buffer.Bytes())
|
||||
hash := Hash(fmt.Sprintf("%x", rawHash))
|
||||
@ -110,7 +170,7 @@ func (r *mockRepoForTest) StoreTree(entries []TreeEntry) (Hash, error) {
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) StoreCommit(treeHash Hash) (Hash, error) {
|
||||
func (r *mockRepoData) StoreCommit(treeHash Hash) (Hash, error) {
|
||||
rawHash := sha1.Sum([]byte(treeHash))
|
||||
hash := Hash(fmt.Sprintf("%x", rawHash))
|
||||
r.commits[hash] = commit{
|
||||
@ -119,7 +179,7 @@ func (r *mockRepoForTest) StoreCommit(treeHash Hash) (Hash, error) {
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) StoreCommitWithParent(treeHash Hash, parent Hash) (Hash, error) {
|
||||
func (r *mockRepoData) StoreCommitWithParent(treeHash Hash, parent Hash) (Hash, error) {
|
||||
rawHash := sha1.Sum([]byte(treeHash + parent))
|
||||
hash := Hash(fmt.Sprintf("%x", rawHash))
|
||||
r.commits[hash] = commit{
|
||||
@ -129,22 +189,22 @@ func (r *mockRepoForTest) StoreCommitWithParent(treeHash Hash, parent Hash) (Has
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) UpdateRef(ref string, hash Hash) error {
|
||||
func (r *mockRepoData) UpdateRef(ref string, hash Hash) error {
|
||||
r.refs[ref] = hash
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) RemoveRef(ref string) error {
|
||||
func (r *mockRepoData) RemoveRef(ref string) error {
|
||||
delete(r.refs, ref)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) RefExist(ref string) (bool, error) {
|
||||
func (r *mockRepoData) RefExist(ref string) (bool, error) {
|
||||
_, exist := r.refs[ref]
|
||||
return exist, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) CopyRef(source string, dest string) error {
|
||||
func (r *mockRepoData) CopyRef(source string, dest string) error {
|
||||
hash, exist := r.refs[source]
|
||||
|
||||
if !exist {
|
||||
@ -155,11 +215,11 @@ func (r *mockRepoForTest) CopyRef(source string, dest string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) ListRefs(refspec string) ([]string, error) {
|
||||
func (r *mockRepoData) ListRefs(refPrefix string) ([]string, error) {
|
||||
var keys []string
|
||||
|
||||
for k := range r.refs {
|
||||
if strings.HasPrefix(k, refspec) {
|
||||
if strings.HasPrefix(k, refPrefix) {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
}
|
||||
@ -167,7 +227,7 @@ func (r *mockRepoForTest) ListRefs(refspec string) ([]string, error) {
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) ListCommits(ref string) ([]Hash, error) {
|
||||
func (r *mockRepoData) ListCommits(ref string) ([]Hash, error) {
|
||||
var hashes []Hash
|
||||
|
||||
hash := r.refs[ref]
|
||||
@ -186,7 +246,7 @@ func (r *mockRepoForTest) ListCommits(ref string) ([]Hash, error) {
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) ReadTree(hash Hash) ([]TreeEntry, error) {
|
||||
func (r *mockRepoData) ReadTree(hash Hash) ([]TreeEntry, error) {
|
||||
var data string
|
||||
|
||||
data, ok := r.trees[hash]
|
||||
@ -209,7 +269,7 @@ func (r *mockRepoForTest) ReadTree(hash Hash) ([]TreeEntry, error) {
|
||||
return readTreeEntries(data)
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) FindCommonAncestor(hash1 Hash, hash2 Hash) (Hash, error) {
|
||||
func (r *mockRepoData) FindCommonAncestor(hash1 Hash, hash2 Hash) (Hash, error) {
|
||||
ancestor1 := []Hash{hash1}
|
||||
|
||||
for hash1 != "" {
|
||||
@ -241,7 +301,7 @@ func (r *mockRepoForTest) FindCommonAncestor(hash1 Hash, hash2 Hash) (Hash, erro
|
||||
}
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) GetTreeHash(commit Hash) (Hash, error) {
|
||||
func (r *mockRepoData) GetTreeHash(commit Hash) (Hash, error) {
|
||||
c, ok := r.commits[commit]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unknown commit")
|
||||
@ -250,7 +310,21 @@ func (r *mockRepoForTest) GetTreeHash(commit Hash) (Hash, error) {
|
||||
return c.treeHash, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) GetOrCreateClock(name string) (lamport.Clock, error) {
|
||||
func (r *mockRepoData) AddRemote(name string, url string) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
type mockRepoClock struct {
|
||||
clocks map[string]lamport.Clock
|
||||
}
|
||||
|
||||
func NewMockRepoClock() *mockRepoClock {
|
||||
return &mockRepoClock{
|
||||
clocks: make(map[string]lamport.Clock),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *mockRepoClock) GetOrCreateClock(name string) (lamport.Clock, error) {
|
||||
if c, ok := r.clocks[name]; ok {
|
||||
return c, nil
|
||||
}
|
||||
@ -259,7 +333,3 @@ func (r *mockRepoForTest) GetOrCreateClock(name string) (lamport.Clock, error) {
|
||||
r.clocks[name] = c
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) AddRemote(name string, url string) error {
|
||||
panic("implement me")
|
||||
}
|
||||
|
@ -2,29 +2,48 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/MichaelMure/git-bug/util/lamport"
|
||||
)
|
||||
|
||||
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
|
||||
ErrClockNotExist = errors.New("clock doesn't exist")
|
||||
)
|
||||
|
||||
// Repo represents a source code repository.
|
||||
type Repo interface {
|
||||
RepoConfig
|
||||
RepoKeyring
|
||||
RepoCommon
|
||||
RepoData
|
||||
}
|
||||
|
||||
// ClockedRepo is a Repo that also has Lamport clocks
|
||||
type ClockedRepo interface {
|
||||
Repo
|
||||
RepoClock
|
||||
}
|
||||
|
||||
// RepoConfig access the configuration of a repository
|
||||
type RepoConfig interface {
|
||||
// LocalConfig give access to the repository scoped configuration
|
||||
LocalConfig() Config
|
||||
|
||||
// GlobalConfig give access to the git global configuration
|
||||
// GlobalConfig give access to the global scoped configuration
|
||||
GlobalConfig() Config
|
||||
|
||||
// AnyConfig give access to a merged local/global configuration
|
||||
AnyConfig() ConfigRead
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -45,11 +64,8 @@ type RepoCommon interface {
|
||||
GetRemotes() (map[string]string, error)
|
||||
}
|
||||
|
||||
// Repo represents a source code repository.
|
||||
type Repo interface {
|
||||
RepoConfig
|
||||
RepoCommon
|
||||
|
||||
// RepoData give access to the git data storage
|
||||
type RepoData interface {
|
||||
// FetchRefs fetch git refs from a remote
|
||||
FetchRefs(remote string, refSpec string) (string, error)
|
||||
|
||||
@ -66,6 +82,7 @@ type Repo interface {
|
||||
StoreTree(mapping []TreeEntry) (Hash, error)
|
||||
|
||||
// ReadTree will return the list of entries in a Git tree
|
||||
// The given hash could be from either a commit or a tree
|
||||
ReadTree(hash Hash) ([]TreeEntry, error)
|
||||
|
||||
// StoreCommit will store a Git commit with the given Git tree
|
||||
@ -87,7 +104,7 @@ type Repo interface {
|
||||
RemoveRef(ref string) error
|
||||
|
||||
// ListRefs will return a list of Git ref matching the given refspec
|
||||
ListRefs(refspec string) ([]string, error)
|
||||
ListRefs(refPrefix string) ([]string, error)
|
||||
|
||||
// RefExist will check if a reference exist in Git
|
||||
RefExist(ref string) (bool, error)
|
||||
@ -99,10 +116,8 @@ type Repo interface {
|
||||
ListCommits(ref string) ([]Hash, error)
|
||||
}
|
||||
|
||||
// ClockedRepo is a Repo that also has Lamport clocks
|
||||
type ClockedRepo interface {
|
||||
Repo
|
||||
|
||||
// RepoClock give access to Lamport clocks
|
||||
type RepoClock interface {
|
||||
// GetOrCreateClock return a Lamport clock stored in the Repo.
|
||||
// If the clock doesn't exist, it's created.
|
||||
GetOrCreateClock(name string) (lamport.Clock, error)
|
||||
@ -120,41 +135,14 @@ type ClockLoader struct {
|
||||
Witnesser func(repo ClockedRepo) error
|
||||
}
|
||||
|
||||
func prepareTreeEntries(entries []TreeEntry) bytes.Buffer {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for _, entry := range entries {
|
||||
buffer.WriteString(entry.Format())
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
func readTreeEntries(s string) ([]TreeEntry, error) {
|
||||
split := strings.Split(strings.TrimSpace(s), "\n")
|
||||
|
||||
casted := make([]TreeEntry, len(split))
|
||||
for i, line := range split {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
entry, err := ParseTreeEntry(line)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
casted[i] = entry
|
||||
}
|
||||
|
||||
return casted, nil
|
||||
}
|
||||
|
||||
// TestedRepo is an extended ClockedRepo with function for testing only
|
||||
type TestedRepo interface {
|
||||
ClockedRepo
|
||||
repoTest
|
||||
}
|
||||
|
||||
// repoTest give access to test only functions
|
||||
type repoTest interface {
|
||||
// AddRemote add a new remote to the repository
|
||||
AddRemote(name string, url string) error
|
||||
}
|
||||
|
@ -7,8 +7,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/MichaelMure/git-bug/util/lamport"
|
||||
)
|
||||
|
||||
func CleanupTestRepos(repos ...Repo) {
|
||||
@ -47,136 +48,179 @@ type RepoCleaner func(repos ...Repo)
|
||||
|
||||
// Test suite for a Repo implementation
|
||||
func RepoTest(t *testing.T, creator RepoCreator, cleaner RepoCleaner) {
|
||||
t.Run("Blob-Tree-Commit-Ref", func(t *testing.T) {
|
||||
repo := creator(false)
|
||||
defer cleaner(repo)
|
||||
for bare, name := range map[bool]string{
|
||||
false: "Plain",
|
||||
true: "Bare",
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
repo := creator(bare)
|
||||
defer cleaner(repo)
|
||||
|
||||
// Blob
|
||||
t.Run("Data", func(t *testing.T) {
|
||||
RepoDataTest(t, repo)
|
||||
})
|
||||
|
||||
data := randomData()
|
||||
t.Run("Config", func(t *testing.T) {
|
||||
RepoConfigTest(t, repo)
|
||||
})
|
||||
|
||||
blobHash1, err := repo.StoreData(data)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, blobHash1.IsValid())
|
||||
t.Run("Clocks", func(t *testing.T) {
|
||||
RepoClockTest(t, repo)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
blob1Read, err := repo.ReadData(blobHash1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, data, blob1Read)
|
||||
// helper to test a RepoConfig
|
||||
func RepoConfigTest(t *testing.T, repo RepoConfig) {
|
||||
testConfig(t, repo.LocalConfig())
|
||||
}
|
||||
|
||||
// Tree
|
||||
// helper to test a RepoData
|
||||
func RepoDataTest(t *testing.T, repo RepoData) {
|
||||
// Blob
|
||||
|
||||
blobHash2, err := repo.StoreData(randomData())
|
||||
require.NoError(t, err)
|
||||
blobHash3, err := repo.StoreData(randomData())
|
||||
require.NoError(t, err)
|
||||
data := randomData()
|
||||
|
||||
tree1 := []TreeEntry{
|
||||
{
|
||||
ObjectType: Blob,
|
||||
Hash: blobHash1,
|
||||
Name: "blob1",
|
||||
},
|
||||
{
|
||||
ObjectType: Blob,
|
||||
Hash: blobHash2,
|
||||
Name: "blob2",
|
||||
},
|
||||
}
|
||||
blobHash1, err := repo.StoreData(data)
|
||||
require.NoError(t, err)
|
||||
require.True(t, blobHash1.IsValid())
|
||||
|
||||
treeHash1, err := repo.StoreTree(tree1)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, treeHash1.IsValid())
|
||||
blob1Read, err := repo.ReadData(blobHash1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, data, blob1Read)
|
||||
|
||||
tree1Read, err := repo.ReadTree(treeHash1)
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, tree1, tree1Read)
|
||||
// Tree
|
||||
|
||||
tree2 := []TreeEntry{
|
||||
{
|
||||
ObjectType: Tree,
|
||||
Hash: treeHash1,
|
||||
Name: "tree1",
|
||||
},
|
||||
{
|
||||
ObjectType: Blob,
|
||||
Hash: blobHash3,
|
||||
Name: "blob3",
|
||||
},
|
||||
}
|
||||
blobHash2, err := repo.StoreData(randomData())
|
||||
require.NoError(t, err)
|
||||
blobHash3, err := repo.StoreData(randomData())
|
||||
require.NoError(t, err)
|
||||
|
||||
treeHash2, err := repo.StoreTree(tree2)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, treeHash2.IsValid())
|
||||
tree1 := []TreeEntry{
|
||||
{
|
||||
ObjectType: Blob,
|
||||
Hash: blobHash1,
|
||||
Name: "blob1",
|
||||
},
|
||||
{
|
||||
ObjectType: Blob,
|
||||
Hash: blobHash2,
|
||||
Name: "blob2",
|
||||
},
|
||||
}
|
||||
|
||||
tree2Read, err := repo.ReadTree(treeHash2)
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, tree2, tree2Read)
|
||||
treeHash1, err := repo.StoreTree(tree1)
|
||||
require.NoError(t, err)
|
||||
require.True(t, treeHash1.IsValid())
|
||||
|
||||
// Commit
|
||||
tree1Read, err := repo.ReadTree(treeHash1)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, tree1, tree1Read)
|
||||
|
||||
commit1, err := repo.StoreCommit(treeHash1)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, commit1.IsValid())
|
||||
tree2 := []TreeEntry{
|
||||
{
|
||||
ObjectType: Tree,
|
||||
Hash: treeHash1,
|
||||
Name: "tree1",
|
||||
},
|
||||
{
|
||||
ObjectType: Blob,
|
||||
Hash: blobHash3,
|
||||
Name: "blob3",
|
||||
},
|
||||
}
|
||||
|
||||
treeHash1Read, err := repo.GetTreeHash(commit1)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, treeHash1, treeHash1Read)
|
||||
treeHash2, err := repo.StoreTree(tree2)
|
||||
require.NoError(t, err)
|
||||
require.True(t, treeHash2.IsValid())
|
||||
|
||||
commit2, err := repo.StoreCommitWithParent(treeHash2, commit1)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, commit2.IsValid())
|
||||
tree2Read, err := repo.ReadTree(treeHash2)
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, tree2, tree2Read)
|
||||
|
||||
treeHash2Read, err := repo.GetTreeHash(commit2)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, treeHash2, treeHash2Read)
|
||||
// Commit
|
||||
|
||||
// Ref
|
||||
commit1, err := repo.StoreCommit(treeHash1)
|
||||
require.NoError(t, err)
|
||||
require.True(t, commit1.IsValid())
|
||||
|
||||
exist1, err := repo.RefExist("refs/bugs/ref1")
|
||||
require.NoError(t, err)
|
||||
assert.False(t, exist1)
|
||||
treeHash1Read, err := repo.GetTreeHash(commit1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, treeHash1, treeHash1Read)
|
||||
|
||||
err = repo.UpdateRef("refs/bugs/ref1", commit2)
|
||||
require.NoError(t, err)
|
||||
commit2, err := repo.StoreCommitWithParent(treeHash2, commit1)
|
||||
require.NoError(t, err)
|
||||
require.True(t, commit2.IsValid())
|
||||
|
||||
exist1, err = repo.RefExist("refs/bugs/ref1")
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exist1)
|
||||
treeHash2Read, err := repo.GetTreeHash(commit2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, treeHash2, treeHash2Read)
|
||||
|
||||
ls, err := repo.ListRefs("refs/bugs")
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, []string{"refs/bugs/ref1"}, ls)
|
||||
// ReadTree should accept tree and commit hashes
|
||||
tree1read, err := repo.ReadTree(commit1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tree1read, tree1)
|
||||
|
||||
err = repo.CopyRef("refs/bugs/ref1", "refs/bugs/ref2")
|
||||
require.NoError(t, err)
|
||||
// Ref
|
||||
|
||||
ls, err = repo.ListRefs("refs/bugs")
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, []string{"refs/bugs/ref1", "refs/bugs/ref2"}, ls)
|
||||
exist1, err := repo.RefExist("refs/bugs/ref1")
|
||||
require.NoError(t, err)
|
||||
require.False(t, exist1)
|
||||
|
||||
commits, err := repo.ListCommits("refs/bugs/ref2")
|
||||
require.NoError(t, err)
|
||||
assert.ElementsMatch(t, []Hash{commit1, commit2}, commits)
|
||||
err = repo.UpdateRef("refs/bugs/ref1", commit2)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Graph
|
||||
exist1, err = repo.RefExist("refs/bugs/ref1")
|
||||
require.NoError(t, err)
|
||||
require.True(t, exist1)
|
||||
|
||||
commit3, err := repo.StoreCommitWithParent(treeHash1, commit1)
|
||||
require.NoError(t, err)
|
||||
ls, err := repo.ListRefs("refs/bugs")
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, []string{"refs/bugs/ref1"}, ls)
|
||||
|
||||
ancestorHash, err := repo.FindCommonAncestor(commit2, commit3)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, commit1, ancestorHash)
|
||||
err = repo.CopyRef("refs/bugs/ref1", "refs/bugs/ref2")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.RemoveRef("refs/bugs/ref1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
ls, err = repo.ListRefs("refs/bugs")
|
||||
require.NoError(t, err)
|
||||
require.ElementsMatch(t, []string{"refs/bugs/ref1", "refs/bugs/ref2"}, ls)
|
||||
|
||||
t.Run("Local config", func(t *testing.T) {
|
||||
repo := creator(false)
|
||||
defer cleaner(repo)
|
||||
commits, err := repo.ListCommits("refs/bugs/ref2")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []Hash{commit1, commit2}, commits)
|
||||
|
||||
testConfig(t, repo.LocalConfig())
|
||||
})
|
||||
// Graph
|
||||
|
||||
commit3, err := repo.StoreCommitWithParent(treeHash1, commit1)
|
||||
require.NoError(t, err)
|
||||
|
||||
ancestorHash, err := repo.FindCommonAncestor(commit2, commit3)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, commit1, ancestorHash)
|
||||
|
||||
err = repo.RemoveRef("refs/bugs/ref1")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// helper to test a RepoClock
|
||||
func RepoClockTest(t *testing.T, repo RepoClock) {
|
||||
clock, err := repo.GetOrCreateClock("foo")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, lamport.Time(1), clock.Time())
|
||||
|
||||
time, err := clock.Increment()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, lamport.Time(1), time)
|
||||
require.Equal(t, lamport.Time(2), clock.Time())
|
||||
|
||||
clock2, err := repo.GetOrCreateClock("foo")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, lamport.Time(2), clock2.Time())
|
||||
|
||||
clock3, err := repo.GetOrCreateClock("bar")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, lamport.Time(1), clock3.Time())
|
||||
}
|
||||
|
||||
func randomData() []byte {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
@ -68,3 +69,34 @@ func ParseObjectType(mode, objType string) (ObjectType, error) {
|
||||
return Unknown, fmt.Errorf("Unknown git object type %s %s", mode, objType)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareTreeEntries(entries []TreeEntry) bytes.Buffer {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for _, entry := range entries {
|
||||
buffer.WriteString(entry.Format())
|
||||
}
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
func readTreeEntries(s string) ([]TreeEntry, error) {
|
||||
split := strings.Split(strings.TrimSpace(s), "\n")
|
||||
|
||||
casted := make([]TreeEntry, len(split))
|
||||
for i, line := range split {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
entry, err := ParseTreeEntry(line)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
casted[i] = entry
|
||||
}
|
||||
|
||||
return casted, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user