repository: add ReadConfigBool and ReadConfigString functions

Michael Muré created

Change summary

cache/repo_cache.go     | 12 ++++++++++
repository/git.go       | 35 +++++++++++++++++++++++++++++
repository/git_test.go  | 51 ++++++++++++++++++++++++++++++++++++++++++
repository/mock_repo.go | 21 +++++++++++++++++
repository/repo.go      | 14 +++++++++++
5 files changed, 133 insertions(+)

Detailed changes

cache/repo_cache.go 🔗

@@ -37,6 +37,8 @@ func (e ErrInvalidCacheFormat) Error() string {
 	return e.message
 }
 
+var _ repository.RepoCommon = &RepoCache{}
+
 // RepoCache is a cache for a Repository. This cache has multiple functions:
 //
 // 1. After being loaded, a Bug is kept in memory in the cache, allowing for fast
@@ -127,6 +129,16 @@ 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)

repository/git.go 🔗

@@ -8,6 +8,7 @@ import (
 	"io"
 	"os/exec"
 	"path"
+	"strconv"
 	"strings"
 
 	"github.com/MichaelMure/git-bug/util/git"
@@ -202,6 +203,40 @@ func (repo *GitRepo) ReadConfigs(keyPrefix string) (map[string]string, error) {
 	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
+}
+
 // RmConfigs remove all key/value pair matching the key prefix
 func (repo *GitRepo) RmConfigs(keyPrefix string) error {
 	_, err := repo.runGitCommand("config", "--unset-all", keyPrefix)

repository/git_test.go 🔗

@@ -0,0 +1,51 @@
+// Package repository contains helper methods for working with the Git repo.
+package repository
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestConfig(t *testing.T) {
+	repo := CreateTestRepo(false)
+	defer CleanupTestRepos(t, repo)
+
+	err := repo.StoreConfig("section.key", "value")
+	assert.NoError(t, err)
+
+	val, err := repo.ReadConfigString("section.key")
+	assert.Equal(t, "value", val)
+
+	err = repo.StoreConfig("section.true", "true")
+	assert.NoError(t, err)
+
+	val2, err := repo.ReadConfigBool("section.true")
+	assert.Equal(t, true, val2)
+
+	configs, err := repo.ReadConfigs("section")
+	assert.NoError(t, err)
+	assert.Equal(t, configs, map[string]string{
+		"section.key":  "value",
+		"section.true": "true",
+	})
+
+	err = repo.RmConfigs("section.true")
+	assert.NoError(t, err)
+
+	configs, err = repo.ReadConfigs("section")
+	assert.NoError(t, err)
+
+	assert.Equal(t, configs, map[string]string{
+		"section.key": "value",
+	})
+
+	_, err = repo.ReadConfigBool("section.true")
+	assert.Equal(t, ErrNoConfigEntry, err)
+
+	err = repo.RmConfigs("section.key")
+	assert.NoError(t, err)
+
+	_, err = repo.ReadConfigString("section.key")
+	assert.Equal(t, ErrNoConfigEntry, err)
+}

repository/mock_repo.go 🔗

@@ -3,6 +3,7 @@ package repository
 import (
 	"crypto/sha1"
 	"fmt"
+	"strconv"
 	"strings"
 
 	"github.com/MichaelMure/git-bug/util/git"
@@ -75,6 +76,26 @@ func (r *mockRepoForTest) ReadConfigs(keyPrefix string) (map[string]string, erro
 	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
+}
+
 func (r *mockRepoForTest) RmConfigs(keyPrefix string) error {
 	for key := range r.config {
 		if strings.HasPrefix(key, keyPrefix) {

repository/repo.go 🔗

@@ -3,12 +3,16 @@ package repository
 
 import (
 	"bytes"
+	"errors"
 	"strings"
 
 	"github.com/MichaelMure/git-bug/util/git"
 	"github.com/MichaelMure/git-bug/util/lamport"
 )
 
+var ErrNoConfigEntry = errors.New("no config entry for the given key")
+var ErrMultipleConfigEntry = errors.New("multiple config entry for the given key")
+
 // RepoCommon represent the common function the we want all the repo to implement
 type RepoCommon interface {
 	// GetPath returns the path to the repo.
@@ -29,6 +33,16 @@ type RepoCommon interface {
 	// 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
 }