mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-15 02:01:43 +03:00
Merge pull request #240 from MichaelMure/repo-config
repository config interface and implementation rework
This commit is contained in:
commit
f5193cc76d
@ -134,7 +134,7 @@ func DefaultBridge(repo *cache.RepoCache) (*Bridge, error) {
|
||||
// ConfiguredBridges return the list of bridge that are configured for the given
|
||||
// repo
|
||||
func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) {
|
||||
configs, err := repo.ReadConfigs(bridgeConfigKeyPrefix + ".")
|
||||
configs, err := repo.LocalConfig().ReadAll(bridgeConfigKeyPrefix + ".")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't read configured bridges")
|
||||
}
|
||||
@ -171,7 +171,7 @@ func ConfiguredBridges(repo repository.RepoCommon) ([]string, error) {
|
||||
func BridgeExist(repo repository.RepoCommon, name string) bool {
|
||||
keyPrefix := fmt.Sprintf("git-bug.bridge.%s.", name)
|
||||
|
||||
conf, err := repo.ReadConfigs(keyPrefix)
|
||||
conf, err := repo.LocalConfig().ReadAll(keyPrefix)
|
||||
|
||||
return err == nil && len(conf) > 0
|
||||
}
|
||||
@ -188,7 +188,7 @@ func RemoveBridge(repo repository.RepoCommon, name string) error {
|
||||
}
|
||||
|
||||
keyPrefix := fmt.Sprintf("git-bug.bridge.%s", name)
|
||||
return repo.RmConfigs(keyPrefix)
|
||||
return repo.LocalConfig().RemoveAll(keyPrefix)
|
||||
}
|
||||
|
||||
// Configure run the target specific configuration process
|
||||
@ -211,7 +211,7 @@ func (b *Bridge) storeConfig(conf Configuration) error {
|
||||
for key, val := range conf {
|
||||
storeKey := fmt.Sprintf("git-bug.bridge.%s.%s", b.Name, key)
|
||||
|
||||
err := b.repo.StoreConfig(storeKey, val)
|
||||
err := b.repo.LocalConfig().StoreString(storeKey, val)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error while storing bridge configuration")
|
||||
}
|
||||
@ -235,7 +235,7 @@ func (b *Bridge) ensureConfig() error {
|
||||
func loadConfig(repo repository.RepoCommon, name string) (Configuration, error) {
|
||||
keyPrefix := fmt.Sprintf("git-bug.bridge.%s.", name)
|
||||
|
||||
pairs, err := repo.ReadConfigs(keyPrefix)
|
||||
pairs, err := repo.LocalConfig().ReadAll(keyPrefix)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error while reading bridge configuration")
|
||||
}
|
||||
|
35
cache/repo_cache.go
vendored
35
cache/repo_cache.go
vendored
@ -99,6 +99,16 @@ func NewRepoCache(r repository.ClockedRepo) (*RepoCache, error) {
|
||||
return c, c.write()
|
||||
}
|
||||
|
||||
// LocalConfig give access to the repository scoped configuration
|
||||
func (c *RepoCache) LocalConfig() repository.Config {
|
||||
return c.repo.LocalConfig()
|
||||
}
|
||||
|
||||
// GlobalConfig give access to the git global configuration
|
||||
func (c *RepoCache) GlobalConfig() repository.Config {
|
||||
return c.repo.GlobalConfig()
|
||||
}
|
||||
|
||||
// GetPath returns the path to the repo.
|
||||
func (c *RepoCache) GetPath() string {
|
||||
return c.repo.GetPath()
|
||||
@ -124,31 +134,6 @@ func (c *RepoCache) GetUserEmail() (string, error) {
|
||||
return c.repo.GetUserEmail()
|
||||
}
|
||||
|
||||
// StoreConfig store a single key/value pair in the config of the repo
|
||||
func (c *RepoCache) StoreConfig(key string, value string) error {
|
||||
return c.repo.StoreConfig(key, value)
|
||||
}
|
||||
|
||||
// ReadConfigs read all key/value pair matching the key prefix
|
||||
func (c *RepoCache) ReadConfigs(keyPrefix string) (map[string]string, error) {
|
||||
return c.repo.ReadConfigs(keyPrefix)
|
||||
}
|
||||
|
||||
// ReadConfigBool read a single boolean value from the config
|
||||
func (c *RepoCache) ReadConfigBool(key string) (bool, error) {
|
||||
return c.repo.ReadConfigBool(key)
|
||||
}
|
||||
|
||||
// ReadConfigBool read a single string value from the config
|
||||
func (c *RepoCache) ReadConfigString(key string) (string, error) {
|
||||
return c.repo.ReadConfigString(key)
|
||||
}
|
||||
|
||||
// RmConfigs remove all key/value pair matching the key prefix
|
||||
func (c *RepoCache) RmConfigs(keyPrefix string) error {
|
||||
return c.repo.RmConfigs(keyPrefix)
|
||||
}
|
||||
|
||||
func (c *RepoCache) lock() error {
|
||||
lockPath := repoLockFilePath(c.repo)
|
||||
|
||||
|
@ -100,7 +100,7 @@ func runWebUI(cmd *cobra.Command, args []string) error {
|
||||
fmt.Printf("Graphql Playground: http://%s/playground\n", addr)
|
||||
fmt.Println("Press Ctrl+c to quit")
|
||||
|
||||
configOpen, err := repo.ReadConfigBool(webUIOpenConfigKey)
|
||||
configOpen, err := repo.LocalConfig().ReadBool(webUIOpenConfigKey)
|
||||
if err == repository.ErrNoConfigEntry {
|
||||
// default to true
|
||||
configOpen = true
|
||||
|
@ -220,7 +220,7 @@ func NewFromGitUser(repo repository.Repo) (*Identity, error) {
|
||||
|
||||
// IsUserIdentitySet tell if the user identity is correctly set.
|
||||
func IsUserIdentitySet(repo repository.RepoCommon) (bool, error) {
|
||||
configs, err := repo.ReadConfigs(identityConfigKey)
|
||||
configs, err := repo.LocalConfig().ReadAll(identityConfigKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -234,12 +234,12 @@ func IsUserIdentitySet(repo repository.RepoCommon) (bool, error) {
|
||||
|
||||
// SetUserIdentity store the user identity's id in the git config
|
||||
func SetUserIdentity(repo repository.RepoCommon, identity *Identity) error {
|
||||
return repo.StoreConfig(identityConfigKey, identity.Id().String())
|
||||
return repo.LocalConfig().StoreString(identityConfigKey, identity.Id().String())
|
||||
}
|
||||
|
||||
// GetUserIdentity read the current user identity, set with a git config entry
|
||||
func GetUserIdentity(repo repository.Repo) (*Identity, error) {
|
||||
configs, err := repo.ReadConfigs(identityConfigKey)
|
||||
configs, err := repo.LocalConfig().ReadAll(identityConfigKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -263,7 +263,7 @@ func GetUserIdentity(repo repository.Repo) (*Identity, error) {
|
||||
|
||||
i, err := ReadLocal(repo, id)
|
||||
if err == ErrIdentityNotExist {
|
||||
innerErr := repo.RmConfigs(identityConfigKey)
|
||||
innerErr := repo.LocalConfig().RemoveAll(identityConfigKey)
|
||||
if innerErr != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, errors.Wrap(innerErr, "can't clear user identity").Error())
|
||||
}
|
||||
|
49
repository/config.go
Normal file
49
repository/config.go
Normal file
@ -0,0 +1,49 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
// ReadAll reads all key/value pair matching the key prefix
|
||||
ReadAll(keyPrefix string) (map[string]string, error)
|
||||
|
||||
// ReadBool read a single boolean value from the config
|
||||
// Return ErrNoConfigEntry or ErrMultipleConfigEntry if
|
||||
// there is zero or more than one entry for this key
|
||||
ReadBool(key string) (bool, error)
|
||||
|
||||
// ReadBool read a single string value from the config
|
||||
// Return ErrNoConfigEntry or ErrMultipleConfigEntry if
|
||||
// there is zero or more than one entry for this key
|
||||
ReadString(key string) (string, error)
|
||||
|
||||
// ReadTimestamp read a single timestamp value from the config
|
||||
// Return ErrNoConfigEntry or ErrMultipleConfigEntry if
|
||||
// there is zero or more than one entry for this key
|
||||
ReadTimestamp(key string) (*time.Time, error)
|
||||
|
||||
// RemoveAll removes all key/value pair matching the key prefix
|
||||
RemoveAll(keyPrefix string) error
|
||||
}
|
||||
|
||||
func parseTimestamp(s string) (*time.Time, error) {
|
||||
timestamp, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := time.Unix(int64(timestamp), 0)
|
||||
return &t, nil
|
||||
}
|
225
repository/config_git.go
Normal file
225
repository/config_git.go
Normal file
@ -0,0 +1,225 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var _ Config = &gitConfig{}
|
||||
|
||||
type gitConfig struct {
|
||||
repo *GitRepo
|
||||
localityFlag string
|
||||
}
|
||||
|
||||
func newGitConfig(repo *GitRepo, global bool) *gitConfig {
|
||||
localityFlag := "--local"
|
||||
if global {
|
||||
localityFlag = "--global"
|
||||
}
|
||||
return &gitConfig{
|
||||
repo: repo,
|
||||
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)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gc *gitConfig) StoreBool(key string, value bool) error {
|
||||
return gc.StoreString(key, strconv.FormatBool(value))
|
||||
}
|
||||
|
||||
func (gc *gitConfig) StoreTimestamp(key string, value time.Time) error {
|
||||
return gc.StoreString(key, strconv.Itoa(int(value.Unix())))
|
||||
}
|
||||
|
||||
// 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, "--get-regexp", keyPrefix)
|
||||
|
||||
// / \
|
||||
// / ! \
|
||||
// -------
|
||||
//
|
||||
// There can be a legitimate error here, but I see no portable way to
|
||||
// distinguish them from the git error that say "no matching value exist"
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
result := make(map[string]string, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("bad git config: %s", line)
|
||||
}
|
||||
|
||||
result[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (gc *gitConfig) ReadString(key string) (string, error) {
|
||||
stdout, err := gc.repo.runGitCommand("config", gc.localityFlag, "--get-all", key)
|
||||
|
||||
// / \
|
||||
// / ! \
|
||||
// -------
|
||||
//
|
||||
// There can be a legitimate error here, but I see no portable way to
|
||||
// distinguish them from the git error that say "no matching value exist"
|
||||
if err != nil {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
if len(lines) == 0 {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
if len(lines) > 1 {
|
||||
return "", ErrMultipleConfigEntry
|
||||
}
|
||||
|
||||
return lines[0], nil
|
||||
}
|
||||
|
||||
func (gc *gitConfig) ReadBool(key string) (bool, error) {
|
||||
val, err := gc.ReadString(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return strconv.ParseBool(val)
|
||||
}
|
||||
|
||||
func (gc *gitConfig) ReadTimestamp(key string) (*time.Time, error) {
|
||||
value, err := gc.ReadString(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseTimestamp(value)
|
||||
}
|
||||
|
||||
func (gc *gitConfig) rmSection(keyPrefix string) error {
|
||||
_, err := gc.repo.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)
|
||||
return err
|
||||
}
|
||||
|
||||
// return keyPrefix section
|
||||
// example: sectionFromKey(a.b.c.d) return a.b.c
|
||||
func sectionFromKey(keyPrefix string) string {
|
||||
s := strings.Split(keyPrefix, ".")
|
||||
if len(s) == 1 {
|
||||
return keyPrefix
|
||||
}
|
||||
|
||||
return strings.Join(s[:len(s)-1], ".")
|
||||
}
|
||||
|
||||
// rmConfigs with git version lesser than 2.18
|
||||
func (gc *gitConfig) rmConfigsGitVersionLT218(keyPrefix string) error {
|
||||
// try to remove key/value pair by key
|
||||
err := gc.unsetAll(keyPrefix)
|
||||
if err != nil {
|
||||
return gc.rmSection(keyPrefix)
|
||||
}
|
||||
|
||||
m, err := gc.ReadAll(sectionFromKey(keyPrefix))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if section doesn't have any left key/value remove the section
|
||||
if len(m) == 0 {
|
||||
return gc.rmSection(sectionFromKey(keyPrefix))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RmConfigs remove all key/value pair matching the key prefix
|
||||
func (gc *gitConfig) RemoveAll(keyPrefix string) error {
|
||||
// starting from git 2.18.0 sections are automatically deleted when the last existing
|
||||
// key/value is removed. Before 2.18.0 we should remove the section
|
||||
// see https://github.com/git/git/blob/master/Documentation/RelNotes/2.18.0.txt#L379
|
||||
lt218, err := gc.gitVersionLT218()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting git version")
|
||||
}
|
||||
|
||||
if lt218 {
|
||||
return gc.rmConfigsGitVersionLT218(keyPrefix)
|
||||
}
|
||||
|
||||
err = gc.unsetAll(keyPrefix)
|
||||
if err != nil {
|
||||
return gc.rmSection(keyPrefix)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc *gitConfig) gitVersion() (*semver.Version, error) {
|
||||
versionOut, err := gc.repo.runGitCommand("version")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseGitVersion(versionOut)
|
||||
}
|
||||
|
||||
func parseGitVersion(versionOut string) (*semver.Version, error) {
|
||||
// extract the version and truncate potential bad parts
|
||||
// ex: 2.23.0.rc1 instead of 2.23.0-rc1
|
||||
r := regexp.MustCompile(`(\d+\.){1,2}\d+`)
|
||||
|
||||
extracted := r.FindString(versionOut)
|
||||
if extracted == "" {
|
||||
return nil, fmt.Errorf("unreadable git version %s", versionOut)
|
||||
}
|
||||
|
||||
version, err := semver.Make(extracted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &version, nil
|
||||
}
|
||||
|
||||
func (gc *gitConfig) gitVersionLT218() (bool, error) {
|
||||
version, err := gc.gitVersion()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
version218string := "2.18.0"
|
||||
gitVersion218, err := semver.Make(version218string)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return version.LT(gitVersion218), nil
|
||||
}
|
84
repository/config_mem.go
Normal file
84
repository/config_mem.go
Normal file
@ -0,0 +1,84 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var _ Config = &memConfig{}
|
||||
|
||||
type memConfig struct {
|
||||
config map[string]string
|
||||
}
|
||||
|
||||
func newMemConfig(config map[string]string) *memConfig {
|
||||
return &memConfig{config: config}
|
||||
}
|
||||
|
||||
func (mc *memConfig) StoreString(key, value string) error {
|
||||
mc.config[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mc *memConfig) StoreBool(key string, value bool) error {
|
||||
return mc.StoreString(key, strconv.FormatBool(value))
|
||||
}
|
||||
|
||||
func (mc *memConfig) StoreTimestamp(key string, value time.Time) error {
|
||||
return mc.StoreString(key, strconv.Itoa(int(value.Unix())))
|
||||
}
|
||||
|
||||
func (mc *memConfig) ReadAll(keyPrefix string) (map[string]string, error) {
|
||||
result := make(map[string]string)
|
||||
for key, val := range mc.config {
|
||||
if strings.HasPrefix(key, keyPrefix) {
|
||||
result[key] = val
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (mc *memConfig) ReadString(key string) (string, error) {
|
||||
// unlike git, the mock can only store one value for the same key
|
||||
val, ok := mc.config[key]
|
||||
if !ok {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (mc *memConfig) ReadBool(key string) (bool, error) {
|
||||
// unlike git, the mock can only store one value for the same key
|
||||
val, ok := mc.config[key]
|
||||
if !ok {
|
||||
return false, ErrNoConfigEntry
|
||||
}
|
||||
|
||||
return strconv.ParseBool(val)
|
||||
}
|
||||
|
||||
func (mc *memConfig) ReadTimestamp(key string) (*time.Time, error) {
|
||||
value, err := mc.ReadString(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timestamp, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := time.Unix(int64(timestamp), 0)
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// RmConfigs remove all key/value pair matching the key prefix
|
||||
func (mc *memConfig) RemoveAll(keyPrefix string) error {
|
||||
for key := range mc.config {
|
||||
if strings.HasPrefix(key, keyPrefix) {
|
||||
delete(mc.config, key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -7,11 +7,8 @@ import (
|
||||
"io"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/MichaelMure/git-bug/util/git"
|
||||
@ -33,16 +30,26 @@ type GitRepo struct {
|
||||
editClock *lamport.Persisted
|
||||
}
|
||||
|
||||
// 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 {
|
||||
repopath:=repo.Path
|
||||
if repopath==".git" {
|
||||
repopath := repo.Path
|
||||
if repopath == ".git" {
|
||||
// seeduvax> trangely the git command sometimes fail for very unknown
|
||||
// reason wihtout this replacement.
|
||||
// observed with rev-list command when git-bug is called from git
|
||||
// hook script, even the same command with same args runs perfectly
|
||||
// when called directly from the same hook script.
|
||||
repopath=""
|
||||
// when called directly from the same hook script.
|
||||
repopath = ""
|
||||
}
|
||||
// fmt.Printf("[%s] Running git %s\n", repopath, strings.Join(args, " "))
|
||||
|
||||
@ -125,7 +132,7 @@ func NewGitRepo(path string, witnesser Witnesser) (*GitRepo, error) {
|
||||
|
||||
// InitGitRepo create a new empty git repo at the given path
|
||||
func InitGitRepo(path string) (*GitRepo, error) {
|
||||
repo := &GitRepo{Path: path+"/.git"}
|
||||
repo := &GitRepo{Path: path + "/.git"}
|
||||
err := repo.createClocks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -197,174 +204,6 @@ func (repo *GitRepo) GetRemotes() (map[string]string, error) {
|
||||
return remotes, nil
|
||||
}
|
||||
|
||||
// StoreConfig store a single key/value pair in the config of the repo
|
||||
func (repo *GitRepo) StoreConfig(key string, value string) error {
|
||||
_, err := repo.runGitCommand("config", "--replace-all", key, value)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadConfigs read all key/value pair matching the key prefix
|
||||
func (repo *GitRepo) ReadConfigs(keyPrefix string) (map[string]string, error) {
|
||||
stdout, err := repo.runGitCommand("config", "--get-regexp", keyPrefix)
|
||||
|
||||
// / \
|
||||
// / ! \
|
||||
// -------
|
||||
//
|
||||
// There can be a legitimate error here, but I see no portable way to
|
||||
// distinguish them from the git error that say "no matching value exist"
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
result := make(map[string]string, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
parts := strings.Fields(line)
|
||||
if len(parts) != 2 {
|
||||
return nil, fmt.Errorf("bad git config: %s", line)
|
||||
}
|
||||
|
||||
result[parts[0]] = parts[1]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (repo *GitRepo) ReadConfigBool(key string) (bool, error) {
|
||||
val, err := repo.ReadConfigString(key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return strconv.ParseBool(val)
|
||||
}
|
||||
|
||||
func (repo *GitRepo) ReadConfigString(key string) (string, error) {
|
||||
stdout, err := repo.runGitCommand("config", "--get-all", key)
|
||||
|
||||
// / \
|
||||
// / ! \
|
||||
// -------
|
||||
//
|
||||
// There can be a legitimate error here, but I see no portable way to
|
||||
// distinguish them from the git error that say "no matching value exist"
|
||||
if err != nil {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
|
||||
lines := strings.Split(stdout, "\n")
|
||||
|
||||
if len(lines) == 0 {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
if len(lines) > 1 {
|
||||
return "", ErrMultipleConfigEntry
|
||||
}
|
||||
|
||||
return lines[0], nil
|
||||
}
|
||||
|
||||
func (repo *GitRepo) rmSection(keyPrefix string) error {
|
||||
_, err := repo.runGitCommand("config", "--remove-section", keyPrefix)
|
||||
return err
|
||||
}
|
||||
|
||||
func (repo *GitRepo) unsetAll(keyPrefix string) error {
|
||||
_, err := repo.runGitCommand("config", "--unset-all", keyPrefix)
|
||||
return err
|
||||
}
|
||||
|
||||
// return keyPrefix section
|
||||
// example: sectionFromKey(a.b.c.d) return a.b.c
|
||||
func sectionFromKey(keyPrefix string) string {
|
||||
s := strings.Split(keyPrefix, ".")
|
||||
if len(s) == 1 {
|
||||
return keyPrefix
|
||||
}
|
||||
|
||||
return strings.Join(s[:len(s)-1], ".")
|
||||
}
|
||||
|
||||
// rmConfigs with git version lesser than 2.18
|
||||
func (repo *GitRepo) rmConfigsGitVersionLT218(keyPrefix string) error {
|
||||
// try to remove key/value pair by key
|
||||
err := repo.unsetAll(keyPrefix)
|
||||
if err != nil {
|
||||
return repo.rmSection(keyPrefix)
|
||||
}
|
||||
|
||||
m, err := repo.ReadConfigs(sectionFromKey(keyPrefix))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if section doesn't have any left key/value remove the section
|
||||
if len(m) == 0 {
|
||||
return repo.rmSection(sectionFromKey(keyPrefix))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RmConfigs remove all key/value pair matching the key prefix
|
||||
func (repo *GitRepo) RmConfigs(keyPrefix string) error {
|
||||
// starting from git 2.18.0 sections are automatically deleted when the last existing
|
||||
// key/value is removed. Before 2.18.0 we should remove the section
|
||||
// see https://github.com/git/git/blob/master/Documentation/RelNotes/2.18.0.txt#L379
|
||||
lt218, err := repo.gitVersionLT218()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "getting git version")
|
||||
}
|
||||
|
||||
if lt218 {
|
||||
return repo.rmConfigsGitVersionLT218(keyPrefix)
|
||||
}
|
||||
|
||||
err = repo.unsetAll(keyPrefix)
|
||||
if err != nil {
|
||||
return repo.rmSection(keyPrefix)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *GitRepo) gitVersionLT218() (bool, error) {
|
||||
versionOut, err := repo.runGitCommand("version")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// extract the version and truncate potential bad parts
|
||||
// ex: 2.23.0.rc1 instead of 2.23.0-rc1
|
||||
r := regexp.MustCompile(`(\d+\.){1,2}\d+`)
|
||||
|
||||
extracted := r.FindString(versionOut)
|
||||
if extracted == "" {
|
||||
return false, fmt.Errorf("unreadable git version %s", versionOut)
|
||||
}
|
||||
|
||||
version, err := semver.Make(extracted)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
version218string := "2.18.0"
|
||||
gitVersion218, err := semver.Make(version218string)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return version.LT(gitVersion218), nil
|
||||
}
|
||||
|
||||
// FetchRefs fetch git refs from a remote
|
||||
func (repo *GitRepo) FetchRefs(remote, refSpec string) (string, error) {
|
||||
stdout, err := repo.runGitCommand("fetch", remote, refSpec)
|
||||
|
@ -11,56 +11,57 @@ func TestConfig(t *testing.T) {
|
||||
repo := CreateTestRepo(false)
|
||||
defer CleanupTestRepos(t, repo)
|
||||
|
||||
err := repo.StoreConfig("section.key", "value")
|
||||
err := repo.LocalConfig().StoreString("section.key", "value")
|
||||
assert.NoError(t, err)
|
||||
|
||||
val, err := repo.ReadConfigString("section.key")
|
||||
val, err := repo.LocalConfig().ReadString("section.key")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "value", val)
|
||||
|
||||
err = repo.StoreConfig("section.true", "true")
|
||||
err = repo.LocalConfig().StoreString("section.true", "true")
|
||||
assert.NoError(t, err)
|
||||
|
||||
val2, err := repo.ReadConfigBool("section.true")
|
||||
val2, err := repo.LocalConfig().ReadBool("section.true")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, true, val2)
|
||||
|
||||
configs, err := repo.ReadConfigs("section")
|
||||
configs, err := repo.LocalConfig().ReadAll("section")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, configs, map[string]string{
|
||||
"section.key": "value",
|
||||
"section.true": "true",
|
||||
})
|
||||
|
||||
err = repo.RmConfigs("section.true")
|
||||
err = repo.LocalConfig().RemoveAll("section.true")
|
||||
assert.NoError(t, err)
|
||||
|
||||
configs, err = repo.ReadConfigs("section")
|
||||
configs, err = repo.LocalConfig().ReadAll("section")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, configs, map[string]string{
|
||||
"section.key": "value",
|
||||
})
|
||||
|
||||
_, err = repo.ReadConfigBool("section.true")
|
||||
_, err = repo.LocalConfig().ReadBool("section.true")
|
||||
assert.Equal(t, ErrNoConfigEntry, err)
|
||||
|
||||
err = repo.RmConfigs("section.nonexistingkey")
|
||||
err = repo.LocalConfig().RemoveAll("section.nonexistingkey")
|
||||
assert.Error(t, err)
|
||||
|
||||
err = repo.RmConfigs("section.key")
|
||||
err = repo.LocalConfig().RemoveAll("section.key")
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = repo.ReadConfigString("section.key")
|
||||
_, err = repo.LocalConfig().ReadString("section.key")
|
||||
assert.Equal(t, ErrNoConfigEntry, err)
|
||||
|
||||
err = repo.RmConfigs("nonexistingsection")
|
||||
err = repo.LocalConfig().RemoveAll("nonexistingsection")
|
||||
assert.Error(t, err)
|
||||
|
||||
err = repo.RmConfigs("section")
|
||||
err = repo.LocalConfig().RemoveAll("section")
|
||||
assert.Error(t, err)
|
||||
|
||||
_, err = repo.ReadConfigString("section.key")
|
||||
_, err = repo.LocalConfig().ReadString("section.key")
|
||||
assert.Error(t, err)
|
||||
|
||||
err = repo.RmConfigs("section.key")
|
||||
err = repo.LocalConfig().RemoveAll("section.key")
|
||||
assert.Error(t, err)
|
||||
|
||||
}
|
||||
|
@ -31,10 +31,11 @@ func CreateTestRepo(bare bool) *GitRepo {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := repo.StoreConfig("user.name", "testuser"); err != nil {
|
||||
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 := repo.StoreConfig("user.email", "testuser@example.com"); err != nil {
|
||||
if err := config.StoreString("user.email", "testuser@example.com"); err != nil {
|
||||
log.Fatal("failed to set user.email for test repository: ", err)
|
||||
}
|
||||
|
||||
|
@ -3,8 +3,6 @@ package repository
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/MichaelMure/git-bug/util/git"
|
||||
"github.com/MichaelMure/git-bug/util/lamport"
|
||||
@ -14,13 +12,14 @@ var _ ClockedRepo = &mockRepoForTest{}
|
||||
|
||||
// mockRepoForTest defines an instance of Repo that can be used for testing.
|
||||
type mockRepoForTest struct {
|
||||
config map[string]string
|
||||
blobs map[git.Hash][]byte
|
||||
trees map[git.Hash]string
|
||||
commits map[git.Hash]commit
|
||||
refs map[string]git.Hash
|
||||
createClock lamport.Clock
|
||||
editClock lamport.Clock
|
||||
config map[string]string
|
||||
globalConfig map[string]string
|
||||
blobs map[git.Hash][]byte
|
||||
trees map[git.Hash]string
|
||||
commits map[git.Hash]commit
|
||||
refs map[string]git.Hash
|
||||
createClock lamport.Clock
|
||||
editClock lamport.Clock
|
||||
}
|
||||
|
||||
type commit struct {
|
||||
@ -40,6 +39,16 @@ func NewMockRepoForTest() *mockRepoForTest {
|
||||
}
|
||||
}
|
||||
|
||||
// LocalConfig give access to the repository scoped configuration
|
||||
func (r *mockRepoForTest) LocalConfig() Config {
|
||||
return newMemConfig(r.config)
|
||||
}
|
||||
|
||||
// GlobalConfig give access to the git global configuration
|
||||
func (r *mockRepoForTest) GlobalConfig() Config {
|
||||
return newMemConfig(r.globalConfig)
|
||||
}
|
||||
|
||||
// GetPath returns the path to the repo.
|
||||
func (r *mockRepoForTest) GetPath() string {
|
||||
return "~/mockRepo/"
|
||||
@ -66,53 +75,6 @@ func (r *mockRepoForTest) GetRemotes() (map[string]string, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) StoreConfig(key string, value string) error {
|
||||
r.config[key] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) ReadConfigs(keyPrefix string) (map[string]string, error) {
|
||||
result := make(map[string]string)
|
||||
|
||||
for key, val := range r.config {
|
||||
if strings.HasPrefix(key, keyPrefix) {
|
||||
result[key] = val
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) ReadConfigBool(key string) (bool, error) {
|
||||
// unlike git, the mock can only store one value for the same key
|
||||
val, ok := r.config[key]
|
||||
if !ok {
|
||||
return false, ErrNoConfigEntry
|
||||
}
|
||||
|
||||
return strconv.ParseBool(val)
|
||||
}
|
||||
|
||||
func (r *mockRepoForTest) ReadConfigString(key string) (string, error) {
|
||||
// unlike git, the mock can only store one value for the same key
|
||||
val, ok := r.config[key]
|
||||
if !ok {
|
||||
return "", ErrNoConfigEntry
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// RmConfigs remove all key/value pair matching the key prefix
|
||||
func (r *mockRepoForTest) RmConfigs(keyPrefix string) error {
|
||||
for key := range r.config {
|
||||
if strings.HasPrefix(key, keyPrefix) {
|
||||
delete(r.config, key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushRefs push git refs to a remote
|
||||
func (r *mockRepoForTest) PushRefs(remote string, refSpec string) (string, error) {
|
||||
return "", nil
|
||||
|
@ -30,24 +30,11 @@ type RepoCommon interface {
|
||||
// GetRemotes returns the configured remotes repositories.
|
||||
GetRemotes() (map[string]string, error)
|
||||
|
||||
// StoreConfig store a single key/value pair in the config of the repo
|
||||
StoreConfig(key string, value string) error
|
||||
// LocalConfig give access to the repository scoped configuration
|
||||
LocalConfig() Config
|
||||
|
||||
// ReadConfigs read all key/value pair matching the key prefix
|
||||
ReadConfigs(keyPrefix string) (map[string]string, error)
|
||||
|
||||
// ReadConfigBool read a single boolean value from the config
|
||||
// Return ErrNoConfigEntry or ErrMultipleConfigEntry if there is zero or more than one entry
|
||||
// for this key
|
||||
ReadConfigBool(key string) (bool, error)
|
||||
|
||||
// ReadConfigBool read a single string value from the config
|
||||
// Return ErrNoConfigEntry or ErrMultipleConfigEntry if there is zero or more than one entry
|
||||
// for this key
|
||||
ReadConfigString(key string) (string, error)
|
||||
|
||||
// RmConfigs remove all key/value pair matching the key prefix
|
||||
RmConfigs(keyPrefix string) error
|
||||
// GlobalConfig give access to the git global configuration
|
||||
GlobalConfig() Config
|
||||
}
|
||||
|
||||
// Repo represents a source code repository.
|
||||
|
Loading…
Reference in New Issue
Block a user