config_git.go

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