repo: fix various config issues around case insentivity

Michael Muré created

Change summary

bridge/core/auth/credential.go |  6 +++-
bug/bug_test.go                |  5 ---
repository/config_mem.go       | 19 ++++++++++++++--
repository/config_testing.go   | 39 ++++++++++++++++++++++++++++++++++++
repository/gogit_config.go     |  2 
5 files changed, 61 insertions(+), 10 deletions(-)

Detailed changes

bridge/core/auth/credential.go 🔗

@@ -3,12 +3,13 @@ package auth
 import (
 	"encoding/base64"
 	"encoding/json"
-	"errors"
 	"fmt"
 	"strconv"
 	"strings"
 	"time"
 
+	"github.com/pkg/errors"
+
 	"github.com/MichaelMure/git-bug/entity"
 	"github.com/MichaelMure/git-bug/repository"
 )
@@ -159,7 +160,8 @@ func List(repo repository.RepoKeyring, opts ...ListOption) ([]Credential, error)
 
 		item, err := repo.Keyring().Get(key)
 		if err != nil {
-			return nil, err
+			// skip unreadable items, nothing much we can do for them anyway
+			continue
 		}
 
 		cred, err := decode(item)

bug/bug_test.go 🔗

@@ -25,10 +25,7 @@ func TestBugId(t *testing.T) {
 	bug1.Append(createOp)
 
 	err = bug1.Commit(mockRepo)
-
-	if err != nil {
-		t.Fatal(err)
-	}
+	require.NoError(t, err)
 
 	bug1.Id()
 }

repository/config_mem.go 🔗

@@ -20,6 +20,7 @@ func NewMemConfig() *MemConfig {
 }
 
 func (mc *MemConfig) StoreString(key, value string) error {
+	key = normalizeKey(key)
 	mc.config[key] = value
 	return nil
 }
@@ -33,6 +34,7 @@ func (mc *MemConfig) StoreTimestamp(key string, value time.Time) error {
 }
 
 func (mc *MemConfig) ReadAll(keyPrefix string) (map[string]string, error) {
+	keyPrefix = normalizeKey(keyPrefix)
 	result := make(map[string]string)
 	for key, val := range mc.config {
 		if strings.HasPrefix(key, keyPrefix) {
@@ -44,6 +46,7 @@ func (mc *MemConfig) ReadAll(keyPrefix string) (map[string]string, error) {
 
 func (mc *MemConfig) ReadString(key string) (string, error) {
 	// unlike git, the mock can only store one value for the same key
+	key = normalizeKey(key)
 	val, ok := mc.config[key]
 	if !ok {
 		return "", ErrNoConfigEntry
@@ -54,9 +57,9 @@ func (mc *MemConfig) ReadString(key string) (string, error) {
 
 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
+	val, err := mc.ReadString(key)
+	if err != nil {
+		return false, err
 	}
 
 	return strconv.ParseBool(val)
@@ -78,6 +81,7 @@ func (mc *MemConfig) ReadTimestamp(key string) (time.Time, error) {
 
 // RmConfigs remove all key/value pair matching the key prefix
 func (mc *MemConfig) RemoveAll(keyPrefix string) error {
+	keyPrefix = normalizeKey(keyPrefix)
 	found := false
 	for key := range mc.config {
 		if strings.HasPrefix(key, keyPrefix) {
@@ -92,3 +96,12 @@ func (mc *MemConfig) RemoveAll(keyPrefix string) error {
 
 	return nil
 }
+
+func normalizeKey(key string) string {
+	// this feels so wrong, but that's apparently how git behave.
+	// only section and final segment are case insensitive, subsection in between are not.
+	s := strings.Split(key, ".")
+	s[0] = strings.ToLower(s[0])
+	s[len(s)-1] = strings.ToLower(s[len(s)-1])
+	return strings.Join(s, ".")
+}

repository/config_testing.go 🔗

@@ -113,4 +113,43 @@ func testConfig(t *testing.T, config Config) {
 		"section.subsection.subsection.opt1": "foo5",
 		"section.subsection.subsection.opt2": "foo6",
 	}, all)
+
+	// missing section + case insensitive
+	val, err = config.ReadString("section2.opt1")
+	require.Error(t, err)
+
+	val, err = config.ReadString("section.opt1")
+	require.NoError(t, err)
+	require.Equal(t, "foo", val)
+
+	val, err = config.ReadString("SECTION.OPT1")
+	require.NoError(t, err)
+	require.Equal(t, "foo", val)
+
+	_, err = config.ReadString("SECTION2.OPT3")
+	require.Error(t, err)
+
+	// missing subsection + case insensitive
+	val, err = config.ReadString("section.subsection.opt1")
+	require.NoError(t, err)
+	require.Equal(t, "foo3", val)
+
+	// for some weird reason, subsection ARE case sensitive
+	_, err = config.ReadString("SECTION.SUBSECTION.OPT1")
+	require.Error(t, err)
+
+	_, err = config.ReadString("SECTION.SUBSECTION1.OPT1")
+	require.Error(t, err)
+
+	// missing sub-subsection + case insensitive
+	val, err = config.ReadString("section.subsection.subsection.opt1")
+	require.NoError(t, err)
+	require.Equal(t, "foo5", val)
+
+	// for some weird reason, subsection ARE case sensitive
+	_, err = config.ReadString("SECTION.SUBSECTION.SUBSECTION.OPT1")
+	require.Error(t, err)
+
+	_, err = config.ReadString("SECTION.SUBSECTION.SUBSECTION1.OPT1")
+	require.Error(t, err)
 }

repository/gogit_config.go 🔗

@@ -134,7 +134,7 @@ func (cr *goGitConfigReader) ReadString(key string) (string, error) {
 		}
 		return section.Option(optionName), nil
 	default:
-		subsectionName := strings.Join(split[1:len(split)-2], ".")
+		subsectionName := strings.Join(split[1:len(split)-1], ".")
 		optionName := split[len(split)-1]
 		if !section.HasSubsection(subsectionName) {
 			return "", ErrNoConfigEntry