diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2021-10-23 09:52:19 +1100 |
---|---|---|
committer | Jesse Duffield <jessedduffield@gmail.com> | 2021-10-23 10:26:47 +1100 |
commit | b6a5e9d615f0fee7d76f5db33ff48bf32f0ef98b (patch) | |
tree | 32644c7c8810c2217cb26d750e5eafee7e07760f /pkg/commands/git_config | |
parent | 5011cac7ea2b1d8ce9d9976b59c17f579c270fd9 (diff) |
use cached git config
Diffstat (limited to 'pkg/commands/git_config')
-rw-r--r-- | pkg/commands/git_config/cached_git_config.go | 59 | ||||
-rw-r--r-- | pkg/commands/git_config/cached_git_config_test.go | 116 | ||||
-rw-r--r-- | pkg/commands/git_config/fake_git_config.go | 22 | ||||
-rw-r--r-- | pkg/commands/git_config/get_key.go | 56 |
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 +} |