Skip to content

Commit

Permalink
fix: Add worktree scope to git config cache (#91)
Browse files Browse the repository at this point in the history
- Any non-parsable scope leads to fallback of not using the config cache.
  • Loading branch information
gabyx authored May 21, 2022
1 parent c2609ed commit 8025718
Show file tree
Hide file tree
Showing 11 changed files with 135 additions and 66 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/gabyx/githooks)](https://goreportcard.com/report/github.com/gabyx/githooks)
[![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/nlohmann/json/master/LICENSE.MIT)
[![GitHub Releases](https://img.shields.io/github/release/gabyx/githooks.svg)](https://github.com/gabyx/githooks/releases)
![Git Version](https://img.shields.io/badge/Git-%E2%89%A5v.2.28.0-blue)
![Git Version](https://img.shields.io/badge/Git-%E2%89%A5v2.28.0,%20latest%20tests%20v2.36.1-blue)
![Go Version](https://img.shields.io/badge/Go-1.17-blue)
![OS](https://img.shields.io/badge/OS-linux,%20macOs,%20Windows-blue)

Expand Down
11 changes: 6 additions & 5 deletions githooks/apps/runner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func setupSettings(repoPath string) (HookSettings, UISettings) {

isTrusted, hasTrustFile, trustAllSet := hooks.IsRepoTrusted(gitx, repoPath)
if !isTrusted && hasTrustFile && !trustAllSet && !nonInteractive && !isGithooksDisabled {
isTrusted = showTrustRepoPrompt(gitx, promptx)
isTrusted = showTrustRepoPrompt(gitx, promptx, repoPath)
}

s := HookSettings{
Expand Down Expand Up @@ -251,10 +251,11 @@ func assertRegistered(gitx *git.Context, installDir string) {
}
}

func showTrustRepoPrompt(gitx *git.Context, promptx prompt.IContext) (isTrusted bool) {
question := "This repository wants you to trust all current and\n" +
"future hooks without prompting.\n" +
"Do you want to allow running every current and future hooks?"
func showTrustRepoPrompt(gitx *git.Context, promptx prompt.IContext, repoPath string) (isTrusted bool) {
question := strs.Fmt(
`This repository '%s'
wants you to trust all current and future hooks without prompting.
Do you want to allow running every current and future hooks?`, repoPath)

var answer string
answer, err := promptx.ShowOptions(question, "(yes, no)", "y/n", "Yes", "No")
Expand Down
26 changes: 9 additions & 17 deletions githooks/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,8 @@ import (
strs "github.com/gabyx/githooks/githooks/strings"
)

// ConfigScope Defines the scope of a config file, such as local, global or system.
type ConfigScope string

// Available ConfigScope's.
const (
LocalScope ConfigScope = "--local"
GlobalScope ConfigScope = "--global"
SystemScope ConfigScope = "--system"
Traverse ConfigScope = ""
HEAD string = "HEAD"
HEAD string = "HEAD"
)

// Context defines the context to execute it commands.
Expand Down Expand Up @@ -77,7 +69,7 @@ func (c *Context) GetConfig(key string, scope ConfigScope) (val string) {
}

if scope != Traverse {
val, err = c.Get("config", "--includes", string(scope), key)
val, err = c.Get("config", "--includes", toConfigArg(scope), key)
} else {
val, err = c.Get("config", "--includes", key)
}
Expand All @@ -99,7 +91,7 @@ func (c *Context) LookupConfig(key string, scope ConfigScope) (val string, exist
}

if scope != Traverse {
val, err = c.Get("config", "--includes", string(scope), key)
val, err = c.Get("config", "--includes", toConfigArg(scope), key)
} else {
val, err = c.Get("config", "--includes", key)
}
Expand All @@ -117,7 +109,7 @@ func (c *Context) getConfigWithArgs(key string, scope ConfigScope, args ...strin
var err error

if scope != Traverse {
out, err = c.Get(append(append([]string{"config", "--includes"}, args...), string(scope), key)...)
out, err = c.Get(append(append([]string{"config", "--includes"}, args...), toConfigArg(scope), key)...)
} else {
out, err = c.Get(append(append([]string{"config", "--includes"}, args...), key)...)
}
Expand Down Expand Up @@ -159,7 +151,7 @@ func (c *Context) GetConfigRegex(regex string, scope ConfigScope) (res []KeyValu
return c.cache.GetAllRegex(regexp.MustCompile(regex), scope)
}

configs, err := c.Get("config", "--includes", string(scope), "--get-regexp", regex)
configs, err := c.Get("config", "--includes", toConfigArg(scope), "--get-regexp", regex)

if err != nil {
return
Expand Down Expand Up @@ -197,7 +189,7 @@ func (c *Context) SetConfig(key string, value interface{}, scope ConfigScope) er
c.cache.Set(key, s, scope)
}

return c.Check("config", string(scope), key, s)
return c.Check("config", toConfigArg(scope), key, s)
}

// AddConfig adds a Git configuration values with key `key`.
Expand All @@ -209,7 +201,7 @@ func (c *Context) AddConfig(key string, value interface{}, scope ConfigScope) er
c.cache.Add(key, s, scope)
}

return c.Check("config", "--add", string(scope), key, s)
return c.Check("config", "--add", toConfigArg(scope), key, s)
}

// UnsetConfig unsets all Git configuration values with key `key`.
Expand All @@ -221,7 +213,7 @@ func (c *Context) UnsetConfig(key string, scope ConfigScope) (err error) {
var exitC int

if scope != Traverse {
exitC, err = c.GetExitCode("config", "--unset-all", string(scope), key)
exitC, err = c.GetExitCode("config", "--unset-all", toConfigArg(scope), key)
} else {
exitC, err = c.GetExitCode("config", "--unset-all", key)
}
Expand All @@ -242,7 +234,7 @@ func (c *Context) IsConfigSet(key string, scope ConfigScope) bool {

var err error
if scope != Traverse {
err = c.Check("config", string(scope), key)
err = c.Check("config", toConfigArg(scope), key)
} else {
err = c.Check("config", key)
}
Expand Down
127 changes: 90 additions & 37 deletions githooks/git/gitconfig-cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,91 @@ import (
strs "github.com/gabyx/githooks/githooks/strings"
)

var commandScope = ConfigScope("--command")
// ConfigScope Defines the scope of a config file, such as local, global or system.
type ConfigScope int

// Available ConfigScope's.
const (
commandScope ConfigScope = 0
worktreeScope ConfigScope = 1

LocalScope ConfigScope = 2
GlobalScope ConfigScope = 3
SystemScope ConfigScope = 4

Traverse ConfigScope = -1
)

type ConfigEntry struct {
name string
values []string
changed bool
}

// ConfigMap holds all configs Git reads.
type ConfigMap map[string]*ConfigEntry

// GitConfigCache for faster read access.
type ConfigCache struct {
scopes [4]ConfigMap
scopes [5]ConfigMap
}

func (c *ConfigCache) getScopeMap(scope ConfigScope) ConfigMap {
cm.DebugAssertF(int(scope) < len(c.scopes), "Wrong scope '%s'", scope)

return c.scopes[int(scope)]
}

func toMapIdx(scope string) ConfigScope {

switch scope {
case "system":
return SystemScope
case "global":
return GlobalScope
case "local":
return LocalScope
case "worktree":
return worktreeScope
case "command":
return commandScope
default:
return -1
}
}

func toConfigArg(scope ConfigScope) string {
if scope == Traverse {
return ""
}

return "--" + ToConfigName(scope)
}

func ToConfigName(scope ConfigScope) string {

switch scope {
case SystemScope:
return c.scopes[3]
return "system"
case GlobalScope:
return c.scopes[2]
return "global"
case LocalScope:
return c.scopes[1]
return "local"
case worktreeScope:
return "worktree"
case commandScope:
return c.scopes[0]
return "command"
default:
cm.PanicF("Wrong scope '%s'", scope)
cm.PanicF("Wrong scope '%v'", scope)

return nil
return ""
}
}

func parseConfig(s string, filterFunc func(string) bool) (c ConfigCache, err error) {

c.scopes = [4]ConfigMap{
c.scopes = [5]ConfigMap{
make(ConfigMap),
make(ConfigMap),
make(ConfigMap),
make(ConfigMap),
Expand Down Expand Up @@ -81,18 +130,32 @@ func parseConfig(s string, filterFunc func(string) bool) (c ConfigCache, err err
scanner.Split(onNullTerminator)

// Scan.
i := -1
var txt string
var scope string
i := 0
for scanner.Scan() {
i++

txt = scanner.Text()

if i%2 == 0 {
scope = scanner.Text()
if strs.IsNotEmpty(s) {
scope = "--" + scope
}
scope = txt
} else {
addEntry(ConfigScope(scope), strings.SplitN(scanner.Text(), "\n", 2)) // nolint: gomnd
if strs.IsEmpty(scope) { // Can happen, but shouldn't...
continue
}

idx := toMapIdx(scope)
if idx < 0 {
err = cm.Error("Wrong Git config scope '%v' for value '%s'", scope, txt)

return
}

addEntry(
idx,
strings.SplitN(txt, "\n", 2)) // nolint: gomnd
}
i++
}

return
Expand All @@ -114,16 +177,11 @@ func (c *ConfigCache) SyncChangedValues() {}
func (c *ConfigCache) getAll(key string, scope ConfigScope) (val []string, exists bool) {

if scope == Traverse {
val, exists = c.GetAll(key, SystemScope) // This order is how Git reports it.
if !exists {
val, exists = c.GetAll(key, GlobalScope)
if !exists {
val, exists = c.GetAll(key, LocalScope)
if !exists {
val, exists = c.GetAll(key, commandScope)
}
}
for i := len(c.scopes) - 1; i >= 0; i-- {
res, _ := c.getAll(key, ConfigScope(i)) // This order is how Git config reports it.
val = append(val, res...)
}
exists = len(val) != 0

return
}
Expand All @@ -146,10 +204,9 @@ func (c *ConfigCache) GetAll(key string, scope ConfigScope) (val []string, exist
// Get all config values for regex key `key` in the cache.
func (c *ConfigCache) GetAllRegex(key *regexp.Regexp, scope ConfigScope) (vals []KeyValue) {
if scope == Traverse {
vals = append(vals, c.GetAllRegex(key, SystemScope)...)
vals = append(vals, c.GetAllRegex(key, GlobalScope)...)
vals = append(vals, c.GetAllRegex(key, LocalScope)...)
vals = append(vals, c.GetAllRegex(key, commandScope)...)
for i := len(c.scopes) - 1; i >= 0; i-- {
vals = append(vals, c.GetAllRegex(key, ConfigScope(i))...)
}

return
}
Expand All @@ -169,14 +226,10 @@ func (c *ConfigCache) GetAllRegex(key *regexp.Regexp, scope ConfigScope) (vals [
// Get a config value for key `key` in the cache.
func (c *ConfigCache) get(key string, scope ConfigScope) (val string, exists bool) {
if scope == Traverse {
val, exists = c.Get(key, commandScope)
if !exists {
val, exists = c.Get(key, LocalScope)
if !exists {
val, exists = c.Get(key, GlobalScope)
if !exists {
val, exists = c.Get(key, SystemScope)
}
for i := 0; i < len(c.scopes); i++ {
val, exists = c.get(key, ConfigScope(i)) // This order is how Git config takes precedence over others.
if exists {
break
}
}

Expand Down
22 changes: 19 additions & 3 deletions githooks/git/gitconfig-cache_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package git

import (
"regexp"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -17,20 +18,23 @@ func TestGitConfigCache(t *testing.T) {
"\x00local\x00a.b\na3.1\na3.2\na3.3" +
"\x00local\x00a.c\nc1\x00\x00\x00\x00" +
"\x00global\x00a.a\na3" +
"\x00worktree\x00t.t\na2" +
"\x00command\x00t.t\na3"

c, err := parseConfig(s, func(string) bool { return true })

command := c.scopes[0]
local := c.scopes[1]
global := c.scopes[2]
system := c.scopes[3]
worktree := c.scopes[1]
local := c.scopes[2]
global := c.scopes[3]
system := c.scopes[4]

assert.Nil(t, err)
assert.Equal(t, 1, len(command))
assert.Equal(t, 1, len(system))
assert.Equal(t, 2, len(global))
assert.Equal(t, 3, len(local))
assert.Equal(t, 1, len(worktree))

assert.Equal(t, "bla", system["a.a"].values[0])

Expand Down Expand Up @@ -78,4 +82,16 @@ func TestGitConfigCache(t *testing.T) {
assert.True(t, c.Unset("a.aa", GlobalScope))
assert.False(t, c.IsSet("a.aa", GlobalScope))
assert.True(t, c.IsSet("a.aa", Traverse))

v, exists = c.GetAll("t.t", Traverse)
assert.True(t, exists)
assert.Equal(t, 2, len(v))
assert.Equal(t, []string{"a2", "a3"}, v)

kv := c.GetAllRegex(regexp.MustCompile("t.*"), Traverse)
assert.True(t, exists)
assert.Equal(t, 2, len(v))
assert.Equal(t, []KeyValue{
{Key: "t.t", Value: "a2"},
{Key: "t.t", Value: "a3"}}, kv)
}
4 changes: 3 additions & 1 deletion githooks/hooks/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ func getGitConfig(key string, scope git.ConfigScope) (string, bool) {
return "", false
}

s := string(scope)
s := ""
if scope == git.Traverse {
s = "--traverse"
} else {
s = "--" + git.ToConfigName(scope)
}

return key + s, true
Expand Down
2 changes: 1 addition & 1 deletion githooks/hooks/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func saveConfigSharedHooks(gitx *git.Context, scope git.ConfigScope, config *sha
for _, url := range config.Urls {
if e := gitx.AddConfig(GitCKShared, url, scope); e != nil {
return cm.CombineErrors(e,
cm.ErrorF("Could not add back all %s shared repository urls: '%q'", scope, config.Urls))
cm.ErrorF("Could not add back all %s shared repository urls: '%q'", git.ToConfigName(scope), config.Urls))
}
}

Expand Down
Loading

0 comments on commit 8025718

Please sign in to comment.