diff options
Diffstat (limited to 'repository/git_config.go')
-rw-r--r-- | repository/git_config.go | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/repository/git_config.go b/repository/git_config.go new file mode 100644 index 00000000..987cf195 --- /dev/null +++ b/repository/git_config.go @@ -0,0 +1,221 @@ +package repository + +import ( + "fmt" + "regexp" + "strconv" + "strings" + "time" + + "github.com/blang/semver" + "github.com/pkg/errors" +) + +var _ Config = &gitConfig{} + +type gitConfig struct { + repo *GitRepo + localityFlag string +} + +func newGitConfig(repo *GitRepo, global bool) *gitConfig { + localityFlag := "--local" + if global { + localityFlag = "--global" + } + return &gitConfig{ + repo: repo, + localityFlag: localityFlag, + } +} + +// StoreString store a single key/value pair in the config of the repo +func (gc *gitConfig) StoreString(key string, value string) error { + _, err := gc.repo.runGitCommand("config", gc.localityFlag, "--replace-all", key, value) + return err +} + +func (gc *gitConfig) StoreBool(key string, value bool) error { + return gc.StoreString(key, strconv.FormatBool(value)) +} + +func (gc *gitConfig) StoreTimestamp(key string, value time.Time) error { + return gc.StoreString(key, strconv.Itoa(int(value.Unix()))) +} + +// ReadAll read all key/value pair matching the key prefix +func (gc *gitConfig) ReadAll(keyPrefix string) (map[string]string, error) { + stdout, err := gc.repo.runGitCommand("config", gc.localityFlag, "--includes", "--get-regexp", keyPrefix) + + // / \ + // / ! \ + // ------- + // + // 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 nil, nil + } + + lines := strings.Split(stdout, "\n") + + result := make(map[string]string, len(lines)) + + for _, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + + parts := strings.SplitN(line, " ", 2) + result[parts[0]] = parts[1] + } + + return result, nil +} + +func (gc *gitConfig) ReadString(key string) (string, error) { + stdout, err := gc.repo.runGitCommand("config", gc.localityFlag, "--includes", "--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 +} + +func (gc *gitConfig) ReadBool(key string) (bool, error) { + val, err := gc.ReadString(key) + if err != nil { + return false, err + } + + return strconv.ParseBool(val) +} + +func (gc *gitConfig) ReadTimestamp(key string) (time.Time, error) { + value, err := gc.ReadString(key) + if err != nil { + return time.Time{}, err + } + return ParseTimestamp(value) +} + +func (gc *gitConfig) rmSection(keyPrefix string) error { + _, err := gc.repo.runGitCommand("config", gc.localityFlag, "--remove-section", keyPrefix) + return err +} + +func (gc *gitConfig) unsetAll(keyPrefix string) error { + _, err := gc.repo.runGitCommand("config", gc.localityFlag, "--unset-all", keyPrefix) + return err +} + +// return keyPrefix section +// example: sectionFromKey(a.b.c.d) return a.b.c +func sectionFromKey(keyPrefix string) string { + s := strings.Split(keyPrefix, ".") + if len(s) == 1 { + return keyPrefix + } + + return strings.Join(s[:len(s)-1], ".") +} + +// rmConfigs with git version lesser than 2.18 +func (gc *gitConfig) rmConfigsGitVersionLT218(keyPrefix string) error { + // try to remove key/value pair by key + err := gc.unsetAll(keyPrefix) + if err != nil { + return gc.rmSection(keyPrefix) + } + + m, err := gc.ReadAll(sectionFromKey(keyPrefix)) + if err != nil { + return err + } + + // if section doesn't have any left key/value remove the section + if len(m) == 0 { + return gc.rmSection(sectionFromKey(keyPrefix)) + } + + return nil +} + +// RmConfigs remove all key/value pair matching the key prefix +func (gc *gitConfig) RemoveAll(keyPrefix string) error { + // starting from git 2.18.0 sections are automatically deleted when the last existing + // key/value is removed. Before 2.18.0 we should remove the section + // see https://github.com/git/git/blob/master/Documentation/RelNotes/2.18.0.txt#L379 + lt218, err := gc.gitVersionLT218() + if err != nil { + return errors.Wrap(err, "getting git version") + } + + if lt218 { + return gc.rmConfigsGitVersionLT218(keyPrefix) + } + + err = gc.unsetAll(keyPrefix) + if err != nil { + return gc.rmSection(keyPrefix) + } + + return nil +} + +func (gc *gitConfig) gitVersion() (*semver.Version, error) { + versionOut, err := gc.repo.runGitCommand("version") + if err != nil { + return nil, err + } + return parseGitVersion(versionOut) +} + +func parseGitVersion(versionOut string) (*semver.Version, error) { + // extract the version and truncate potential bad parts + // ex: 2.23.0.rc1 instead of 2.23.0-rc1 + r := regexp.MustCompile(`(\d+\.){1,2}\d+`) + + extracted := r.FindString(versionOut) + if extracted == "" { + return nil, fmt.Errorf("unreadable git version %s", versionOut) + } + + version, err := semver.Make(extracted) + if err != nil { + return nil, err + } + + return &version, nil +} + +func (gc *gitConfig) gitVersionLT218() (bool, error) { + version, err := gc.gitVersion() + if err != nil { + return false, err + } + + version218string := "2.18.0" + gitVersion218, err := semver.Make(version218string) + if err != nil { + return false, err + } + + return version.LT(gitVersion218), nil +} |