summaryrefslogtreecommitdiffstats
path: root/pkg/commands/git_config
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2021-10-23 09:52:19 +1100
committerJesse Duffield <jessedduffield@gmail.com>2021-10-23 10:26:47 +1100
commitb6a5e9d615f0fee7d76f5db33ff48bf32f0ef98b (patch)
tree32644c7c8810c2217cb26d750e5eafee7e07760f /pkg/commands/git_config
parent5011cac7ea2b1d8ce9d9976b59c17f579c270fd9 (diff)
use cached git config
Diffstat (limited to 'pkg/commands/git_config')
-rw-r--r--pkg/commands/git_config/cached_git_config.go59
-rw-r--r--pkg/commands/git_config/cached_git_config_test.go116
-rw-r--r--pkg/commands/git_config/fake_git_config.go22
-rw-r--r--pkg/commands/git_config/get_key.go56
4 files changed, 253 insertions, 0 deletions
diff --git a/pkg/commands/git_config/cached_git_config.go b/pkg/commands/git_config/cached_git_config.go
new file mode 100644
index 000000000..e2b83bad8
--- /dev/null
+++ b/pkg/commands/git_config/cached_git_config.go
@@ -0,0 +1,59 @@
+package git_config
+
+import (
+ "strings"
+
+ "github.com/sirupsen/logrus"
+)
+
+type IGitConfig interface {
+ Get(string) string
+ GetBool(string) bool
+}
+
+type CachedGitConfig struct {
+ cache map[string]string
+ getKey func(string) (string, error)
+ log *logrus.Entry
+}
+
+func NewStdCachedGitConfig(log *logrus.Entry) *CachedGitConfig {
+ return NewCachedGitConfig(getGitConfigValue, log)
+}
+
+func NewCachedGitConfig(getKey func(string) (string, error), log *logrus.Entry) *CachedGitConfig {
+ return &CachedGitConfig{
+ cache: make(map[string]string),
+ getKey: getKey,
+ log: log,
+ }
+}
+
+func (self *CachedGitConfig) Get(key string) string {
+ if value, ok := self.cache[key]; ok {
+ self.log.Debugf("using cache for key " + key)
+ return value
+ }
+
+ value := self.getAux(key)
+ self.cache[key] = value
+ return value
+}
+
+func (self *CachedGitConfig) getAux(key string) string {
+ value, err := self.getKey(key)
+ if err != nil {
+ self.log.Debugf("Error getting git config value for key: " + key + ". Error: " + err.Error())
+ return ""
+ }
+ return strings.TrimSpace(value)
+}
+
+func (self *CachedGitConfig) GetBool(key string) bool {
+ return isTruthy(self.Get(key))
+}
+
+func isTruthy(value string) bool {
+ lcValue := strings.ToLower(value)
+ return lcValue == "true" || lcValue == "1" || lcValue == "yes" || lcValue == "on"
+}
diff --git a/pkg/commands/git_config/cached_git_config_test.go b/pkg/commands/git_config/cached_git_config_test.go
new file mode 100644
index 000000000..f03535a6e
--- /dev/null
+++ b/pkg/commands/git_config/cached_git_config_test.go
@@ -0,0 +1,116 @@
+package git_config
+
+import (
+ "testing"
+
+ "github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestGetBool(t *testing.T) {
+ type scenario struct {
+ testName string
+ mockResponses map[string]string
+ expected bool
+ }
+
+ scenarios := []scenario{
+ {
+ "Option global and local config commit.gpgsign is not set",
+ map[string]string{},
+ false,
+ },
+ {
+ "Some other random key is set",
+ map[string]string{"blah": "blah"},
+ false,
+ },
+ {
+ "Option commit.gpgsign is true",
+ map[string]string{"commit.gpgsign": "True"},
+ true,
+ },
+ {
+ "Option commit.gpgsign is on",
+ map[string]string{"commit.gpgsign": "ON"},
+ true,
+ },
+ {
+ "Option commit.gpgsign is yes",
+ map[string]string{"commit.gpgsign": "YeS"},
+ true,
+ },
+ {
+ "Option commit.gpgsign is 1",
+ map[string]string{"commit.gpgsign": "1"},
+ true,
+ },
+ }
+
+ for _, s := range scenarios {
+ s := s
+ t.Run(s.testName, func(t *testing.T) {
+ fake := NewFakeGitConfig(s.mockResponses)
+ real := NewCachedGitConfig(
+ func(key string) (string, error) {
+ return fake.Get(key), nil
+ },
+ utils.NewDummyLog(),
+ )
+ result := real.GetBool("commit.gpgsign")
+ assert.Equal(t, s.expected, result)
+ })
+ }
+}
+
+func TestGet(t *testing.T) {
+ type scenario struct {
+ testName string
+ mockResponses map[string]string
+ expected string
+ }
+
+ scenarios := []scenario{
+ {
+ "not set",
+ map[string]string{},
+ "",
+ },
+ {
+ "is set",
+ map[string]string{"commit.gpgsign": "blah"},
+ "blah",
+ },
+ }
+
+ for _, s := range scenarios {
+ s := s
+ t.Run(s.testName, func(t *testing.T) {
+ fake := NewFakeGitConfig(s.mockResponses)
+ real := NewCachedGitConfig(
+ func(key string) (string, error) {
+ return fake.Get(key), nil
+ },
+ utils.NewDummyLog(),
+ )
+ result := real.Get("commit.gpgsign")
+ assert.Equal(t, s.expected, result)
+ })
+ }
+
+ // verifying that the cache is used
+ count := 0
+ real := NewCachedGitConfig(
+ func(key string) (string, error) {
+ count++
+ assert.Equal(t, "commit.gpgsign", key)
+ return "blah", nil
+ },
+ utils.NewDummyLog(),
+ )
+ result := real.Get("commit.gpgsign")
+ assert.Equal(t, "blah", result)
+ result = real.Get("commit.gpgsign")
+ assert.Equal(t, "blah", result)
+ assert.Equal(t, 1, count)
+}
diff --git a/pkg/commands/git_config/fake_git_config.go b/pkg/commands/git_config/fake_git_config.go
new file mode 100644
index 000000000..f010efd8c
--- /dev/null
+++ b/pkg/commands/git_config/fake_git_config.go
@@ -0,0 +1,22 @@
+package git_config
+
+type FakeGitConfig struct {
+ mockResponses map[string]string
+}
+
+func NewFakeGitConfig(mockResponses map[string]string) *FakeGitConfig {
+ return &FakeGitConfig{
+ mockResponses: mockResponses,
+ }
+}
+
+func (self *FakeGitConfig) Get(key string) string {
+ if self.mockResponses == nil {
+ return ""
+ }
+ return self.mockResponses[key]
+}
+
+func (self *FakeGitConfig) GetBool(key string) bool {
+ return isTruthy(self.Get(key))
+}
diff --git a/pkg/commands/git_config/get_key.go b/pkg/commands/git_config/get_key.go
new file mode 100644
index 000000000..85892ea2d
--- /dev/null
+++ b/pkg/commands/git_config/get_key.go
@@ -0,0 +1,56 @@
+package git_config
+
+import (
+ "bytes"
+ "fmt"
+ "io/ioutil"
+ "os/exec"
+ "strings"
+ "syscall"
+
+ "github.com/jesseduffield/lazygit/pkg/secureexec"
+)
+
+// including license from https://github.com/tcnksm/go-gitconfig because this file is an adaptation of that repo's code
+// Copyright (c) 2014 tcnksm
+
+// MIT License
+
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+func getGitConfigValue(key string) (string, error) {
+ gitArgs := []string{"config", "--get", "--null", key}
+ var stdout bytes.Buffer
+ cmd := secureexec.Command("git", gitArgs...)
+ cmd.Stdout = &stdout
+ cmd.Stderr = ioutil.Discard
+
+ err := cmd.Run()
+ if exitError, ok := err.(*exec.ExitError); ok {
+ if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
+ if waitStatus.ExitStatus() == 1 {
+ return "", fmt.Errorf("the key `%s` is not found", key)
+ }
+ }
+ return "", err
+ }
+
+ return strings.TrimRight(stdout.String(), "\000"), nil
+}