config_git.go

  1package repository
  2
  3import (
  4	"fmt"
  5	"strconv"
  6	"strings"
  7
  8	"github.com/blang/semver"
  9	"github.com/pkg/errors"
 10)
 11
 12type gitConfig struct {
 13	version *semver.Version
 14	execFn  func(args ...string) (string, error)
 15}
 16
 17func NewGitConfig(repo *GitRepo, global bool) *gitConfig {
 18	version, _ := repo.GitVersion()
 19
 20	if global {
 21		return &gitConfig{
 22			execFn: func(args ...string) (string, error) {
 23				args = append([]string{"config", "--global"}, args...)
 24				return repo.runGitCommand(args...)
 25			},
 26			version: version,
 27		}
 28	}
 29
 30	return &gitConfig{
 31		execFn: func(args ...string) (string, error) {
 32			args = append([]string{"config"}, args...)
 33			return repo.runGitCommand(args...)
 34		},
 35		version: version,
 36	}
 37}
 38
 39// StoreConfig store a single key/value pair in the config of the repo
 40func (gc *gitConfig) Store(key string, value string) error {
 41	_, err := gc.execFn("--replace-all", key, value)
 42
 43	return err
 44}
 45
 46// ReadConfigs read all key/value pair matching the key prefix
 47func (gc *gitConfig) ReadAll(keyPrefix string) (map[string]string, error) {
 48	stdout, err := gc.execFn("--get-regexp", keyPrefix)
 49
 50	//   / \
 51	//  / ! \
 52	// -------
 53	//
 54	// There can be a legitimate error here, but I see no portable way to
 55	// distinguish them from the git error that say "no matching value exist"
 56	if err != nil {
 57		return nil, nil
 58	}
 59
 60	lines := strings.Split(stdout, "\n")
 61
 62	result := make(map[string]string, len(lines))
 63
 64	for _, line := range lines {
 65		if strings.TrimSpace(line) == "" {
 66			continue
 67		}
 68
 69		parts := strings.Fields(line)
 70		if len(parts) != 2 {
 71			return nil, fmt.Errorf("bad git config: %s", line)
 72		}
 73
 74		result[parts[0]] = parts[1]
 75	}
 76
 77	return result, nil
 78}
 79
 80func (gc *gitConfig) ReadString(key string) (string, error) {
 81	stdout, err := gc.execFn("--get-all", key)
 82
 83	//   / \
 84	//  / ! \
 85	// -------
 86	//
 87	// There can be a legitimate error here, but I see no portable way to
 88	// distinguish them from the git error that say "no matching value exist"
 89	if err != nil {
 90		return "", ErrNoConfigEntry
 91	}
 92
 93	lines := strings.Split(stdout, "\n")
 94
 95	if len(lines) == 0 {
 96		return "", ErrNoConfigEntry
 97	}
 98	if len(lines) > 1 {
 99		return "", ErrMultipleConfigEntry
100	}
101
102	return lines[0], nil
103}
104
105func (gc *gitConfig) ReadBool(key string) (bool, error) {
106	val, err := gc.ReadString(key)
107	if err != nil {
108		return false, err
109	}
110
111	return strconv.ParseBool(val)
112}
113
114func (gc *gitConfig) rmSection(keyPrefix string) error {
115	_, err := gc.execFn("--remove-section", keyPrefix)
116	return err
117}
118
119func (gc *gitConfig) unsetAll(keyPrefix string) error {
120	_, err := gc.execFn("--unset-all", keyPrefix)
121	return err
122}
123
124// return keyPrefix section
125// example: sectionFromKey(a.b.c.d) return a.b.c
126func sectionFromKey(keyPrefix string) string {
127	s := strings.Split(keyPrefix, ".")
128	if len(s) == 1 {
129		return keyPrefix
130	}
131
132	return strings.Join(s[:len(s)-1], ".")
133}
134
135// rmConfigs with git version lesser than 2.18
136func (gc *gitConfig) rmConfigsGitVersionLT218(keyPrefix string) error {
137	// try to remove key/value pair by key
138	err := gc.unsetAll(keyPrefix)
139	if err != nil {
140		return gc.rmSection(keyPrefix)
141	}
142
143	m, err := gc.ReadAll(sectionFromKey(keyPrefix))
144	if err != nil {
145		return err
146	}
147
148	// if section doesn't have any left key/value remove the section
149	if len(m) == 0 {
150		return gc.rmSection(sectionFromKey(keyPrefix))
151	}
152
153	return nil
154}
155
156// RmConfigs remove all key/value pair matching the key prefix
157func (gc *gitConfig) RemoveAll(keyPrefix string) error {
158	// starting from git 2.18.0 sections are automatically deleted when the last existing
159	// key/value is removed. Before 2.18.0 we should remove the section
160	// see https://github.com/git/git/blob/master/Documentation/RelNotes/2.18.0.txt#L379
161	lt218, err := gc.gitVersionLT218()
162	if err != nil {
163		return errors.Wrap(err, "getting git version")
164	}
165
166	if lt218 {
167		return gc.rmConfigsGitVersionLT218(keyPrefix)
168	}
169
170	err = gc.unsetAll(keyPrefix)
171	if err != nil {
172		return gc.rmSection(keyPrefix)
173	}
174
175	return nil
176}
177
178func (gc *gitConfig) gitVersionLT218() (bool, error) {
179	gitVersion218, err := semver.Make("2.18.0")
180	if err != nil {
181		return false, err
182	}
183
184	return gc.version.LT(gitVersion218), nil
185}