Skip to content

Commit ecb64c0

Browse files
committed
Add Merged config
...for reading and writing global (~/.git/config) and reading system (/etc/gitconfig) configs in addition to local repo config
1 parent 8fddd7a commit ecb64c0

File tree

6 files changed

+559
-32
lines changed

6 files changed

+559
-32
lines changed

Diff for: _examples/config/main.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package main
2+
3+
import (
4+
"github.com/go-git/go-git/v5"
5+
. "github.com/go-git/go-git/v5/_examples"
6+
format "github.com/go-git/go-git/v5/plumbing/format/config"
7+
8+
"github.com/go-git/go-git/v5/config"
9+
)
10+
11+
// Example of how to:
12+
// - Access basic local (i.e. ./.git/config) configuration params
13+
// - Set basic local config params
14+
// - Set custom local config params
15+
// - Access custom local config params
16+
// - Set global config params
17+
// - Access global & system config params
18+
19+
func main() {
20+
// Open this repository
21+
// Info("git init")
22+
// r, err := git.Init(memory.NewStorage(), nil)
23+
Info("open local git repo")
24+
r, err := git.PlainOpen(".")
25+
CheckIfError(err)
26+
27+
// Load the configuration
28+
cfg, err := r.Config()
29+
CheckIfError(err)
30+
31+
// Get core local config params
32+
if cfg.Core.IsBare {
33+
Info("repo is bare")
34+
} else {
35+
Info("repo is not bare")
36+
}
37+
38+
Info("worktree is %s", cfg.Core.Worktree)
39+
40+
// Set basic local config params
41+
cfg.Remotes["origin"] = &config.RemoteConfig{
42+
Name: "origin",
43+
URLs: []string{"[email protected]:mcuadros/go-git.git"},
44+
}
45+
46+
Info("origin remote: %+v", cfg.Remotes["origin"])
47+
48+
// Set local custom config param
49+
cfg.Merged.LocalConfig().AddOption("custom", format.NoSubsection, "name", "Local Name")
50+
51+
// Set global config param (~/.gitconfig)
52+
cfg.Merged.GlobalConfig().AddOption("custom", format.NoSubsection, "name", "Global Name")
53+
54+
// Get custom config param (merged in the same way git does: system -> global -> local)
55+
Info("custom.name is %s", cfg.Merged.Section("custom").Option("name"))
56+
57+
// Get system config params (/etc/gitconfig)
58+
systemSections := cfg.Merged.SystemConfig().Sections
59+
for _, ss := range systemSections {
60+
Info("System section: %s", ss.Name)
61+
for _, o := range ss.Options {
62+
Info("\tOption: %s = %s", o.Key, o.Value)
63+
}
64+
}
65+
}

Diff for: config/config.go

+36-13
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ type Config struct {
6666
// preserve the parsed information from the original format, to avoid
6767
// dropping unsupported fields.
6868
Raw *format.Config
69+
// Merged contains the raw form of how git views the system (/etc/gitconfig),
70+
// global (~/.gitconfig), and local (./.git/config) config params.
71+
Merged *format.Merged
6972
}
7073

7174
// NewConfig returns a new empty Config.
@@ -74,9 +77,11 @@ func NewConfig() *Config {
7477
Remotes: make(map[string]*RemoteConfig),
7578
Submodules: make(map[string]*Submodule),
7679
Branches: make(map[string]*Branch),
77-
Raw: format.New(),
80+
Merged: format.NewMerged(),
7881
}
7982

83+
config.Raw = config.Merged.LocalConfig()
84+
8085
config.Pack.Window = DefaultPackWindow
8186

8287
return config
@@ -129,25 +134,38 @@ const (
129134

130135
// Unmarshal parses a git-config file and stores it.
131136
func (c *Config) Unmarshal(b []byte) error {
137+
return c.UnmarshalScoped(format.LocalScope, b)
138+
}
139+
140+
func (c *Config) UnmarshalScoped(scope format.Scope, b []byte) error {
132141
r := bytes.NewBuffer(b)
133142
d := format.NewDecoder(r)
134143

135-
c.Raw = format.New()
136-
if err := d.Decode(c.Raw); err != nil {
137-
return err
138-
}
144+
c.Merged.ResetScopedConfig(scope)
139145

140-
c.unmarshalCore()
141-
if err := c.unmarshalPack(); err != nil {
146+
if err := d.Decode(c.Merged.ScopedConfig(scope)); err != nil {
142147
return err
143148
}
144-
unmarshalSubmodules(c.Raw, c.Submodules)
145149

146-
if err := c.unmarshalBranches(); err != nil {
147-
return err
150+
if scope == format.LocalScope {
151+
c.Raw = c.Merged.LocalConfig()
152+
153+
c.unmarshalCore()
154+
if err := c.unmarshalPack(); err != nil {
155+
return err
156+
}
157+
unmarshalSubmodules(c.Raw, c.Submodules)
158+
159+
if err := c.unmarshalBranches(); err != nil {
160+
return err
161+
}
162+
163+
if err := c.unmarshalRemotes(); err != nil {
164+
return err
165+
}
148166
}
149167

150-
return c.unmarshalRemotes()
168+
return nil
151169
}
152170

153171
func (c *Config) unmarshalCore() {
@@ -218,21 +236,26 @@ func (c *Config) unmarshalBranches() error {
218236
}
219237

220238
// Marshal returns Config encoded as a git-config file.
221-
func (c *Config) Marshal() ([]byte, error) {
239+
func (c *Config) MarshalScope(scope format.Scope) ([]byte, error) {
222240
c.marshalCore()
223241
c.marshalPack()
224242
c.marshalRemotes()
225243
c.marshalSubmodules()
226244
c.marshalBranches()
227245

228246
buf := bytes.NewBuffer(nil)
229-
if err := format.NewEncoder(buf).Encode(c.Raw); err != nil {
247+
cfg := c.Merged.ScopedConfig(scope)
248+
if err := format.NewEncoder(buf).Encode(cfg); err != nil {
230249
return nil, err
231250
}
232251

233252
return buf.Bytes(), nil
234253
}
235254

255+
func (c *Config) Marshal() ([]byte, error) {
256+
return c.MarshalScope(format.LocalScope)
257+
}
258+
236259
func (c *Config) marshalCore() {
237260
s := c.Raw.Section(coreSection)
238261
s.SetOption(bareKey, fmt.Sprintf("%t", c.Core.IsBare))

Diff for: config/config_test.go

+160
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package config
33
import (
44
. "gopkg.in/check.v1"
55
"github.com/go-git/go-git/v5/plumbing"
6+
format "github.com/go-git/go-git/v5/plumbing/format/config"
67
)
78

89
type ConfigSuite struct{}
@@ -60,6 +61,76 @@ func (s *ConfigSuite) TestUnmarshal(c *C) {
6061
c.Assert(cfg.Branches["master"].Merge, Equals, plumbing.ReferenceName("refs/heads/master"))
6162
}
6263

64+
func (s *ConfigSuite) TestMergedUnmarshal(c *C) {
65+
localInput := []byte(`[core]
66+
bare = true
67+
worktree = foo
68+
commentchar = bar
69+
[pack]
70+
window = 20
71+
[remote "origin"]
72+
url = [email protected]:mcuadros/go-git.git
73+
fetch = +refs/heads/*:refs/remotes/origin/*
74+
[remote "alt"]
75+
url = [email protected]:mcuadros/go-git.git
76+
url = [email protected]:src-d/go-git.git
77+
fetch = +refs/heads/*:refs/remotes/origin/*
78+
fetch = +refs/pull/*:refs/remotes/origin/pull/*
79+
[remote "win-local"]
80+
url = X:\\Git\\
81+
[submodule "qux"]
82+
path = qux
83+
url = https://github.com/foo/qux.git
84+
branch = bar
85+
[branch "master"]
86+
remote = origin
87+
merge = refs/heads/master
88+
[user]
89+
name = Override
90+
`)
91+
92+
globalInput := []byte(`
93+
[user]
94+
name = Soandso
95+
96+
[core]
97+
editor = nvim
98+
[push]
99+
default = simple
100+
`)
101+
102+
cfg := NewConfig()
103+
104+
err := cfg.UnmarshalScoped(format.LocalScope, localInput)
105+
c.Assert(err, IsNil)
106+
107+
err = cfg.UnmarshalScoped(format.GlobalScope, globalInput)
108+
c.Assert(err, IsNil)
109+
110+
c.Assert(cfg.Core.IsBare, Equals, true)
111+
c.Assert(cfg.Core.Worktree, Equals, "foo")
112+
c.Assert(cfg.Core.CommentChar, Equals, "bar")
113+
c.Assert(cfg.Pack.Window, Equals, uint(20))
114+
c.Assert(cfg.Remotes, HasLen, 3)
115+
c.Assert(cfg.Remotes["origin"].Name, Equals, "origin")
116+
c.Assert(cfg.Remotes["origin"].URLs, DeepEquals, []string{"[email protected]:mcuadros/go-git.git"})
117+
c.Assert(cfg.Remotes["origin"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*"})
118+
c.Assert(cfg.Remotes["alt"].Name, Equals, "alt")
119+
c.Assert(cfg.Remotes["alt"].URLs, DeepEquals, []string{"[email protected]:mcuadros/go-git.git", "[email protected]:src-d/go-git.git"})
120+
c.Assert(cfg.Remotes["alt"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"})
121+
c.Assert(cfg.Remotes["win-local"].Name, Equals, "win-local")
122+
c.Assert(cfg.Remotes["win-local"].URLs, DeepEquals, []string{"X:\\Git\\"})
123+
c.Assert(cfg.Submodules, HasLen, 1)
124+
c.Assert(cfg.Submodules["qux"].Name, Equals, "qux")
125+
c.Assert(cfg.Submodules["qux"].URL, Equals, "https://github.com/foo/qux.git")
126+
c.Assert(cfg.Submodules["qux"].Branch, Equals, "bar")
127+
c.Assert(cfg.Branches["master"].Remote, Equals, "origin")
128+
c.Assert(cfg.Branches["master"].Merge, Equals, plumbing.ReferenceName("refs/heads/master"))
129+
c.Assert(cfg.Merged.Section("user").Option("name"), Equals, "Override")
130+
c.Assert(cfg.Merged.Section("user").Option("email"), Equals, "[email protected]")
131+
c.Assert(cfg.Merged.Section("push").Option("default"), Equals, "simple")
132+
}
133+
63134
func (s *ConfigSuite) TestMarshal(c *C) {
64135
output := []byte(`[core]
65136
bare = true
@@ -119,6 +190,95 @@ func (s *ConfigSuite) TestMarshal(c *C) {
119190
c.Assert(string(b), Equals, string(output))
120191
}
121192

193+
func (s *ConfigSuite) TestMergedMarshal(c *C) {
194+
localOutput := []byte(`[user]
195+
name = Override
196+
[custom]
197+
key = value
198+
[core]
199+
bare = true
200+
worktree = bar
201+
[pack]
202+
window = 20
203+
[remote "alt"]
204+
url = [email protected]:mcuadros/go-git.git
205+
url = [email protected]:src-d/go-git.git
206+
fetch = +refs/heads/*:refs/remotes/origin/*
207+
fetch = +refs/pull/*:refs/remotes/origin/pull/*
208+
[remote "origin"]
209+
url = [email protected]:mcuadros/go-git.git
210+
[remote "win-local"]
211+
url = "X:\\Git\\"
212+
[submodule "qux"]
213+
url = https://github.com/foo/qux.git
214+
[branch "master"]
215+
remote = origin
216+
merge = refs/heads/master
217+
`)
218+
219+
globalOutput := []byte(`[user]
220+
name = Soandso
221+
222+
[core]
223+
editor = nvim
224+
[push]
225+
default = simple
226+
`)
227+
228+
cfg := NewConfig()
229+
230+
cfg.Core.IsBare = true
231+
cfg.Core.Worktree = "bar"
232+
cfg.Pack.Window = 20
233+
cfg.Remotes["origin"] = &RemoteConfig{
234+
Name: "origin",
235+
URLs: []string{"[email protected]:mcuadros/go-git.git"},
236+
}
237+
238+
cfg.Remotes["alt"] = &RemoteConfig{
239+
Name: "alt",
240+
URLs: []string{"[email protected]:mcuadros/go-git.git", "[email protected]:src-d/go-git.git"},
241+
Fetch: []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"},
242+
}
243+
244+
cfg.Remotes["win-local"] = &RemoteConfig{
245+
Name: "win-local",
246+
URLs: []string{"X:\\Git\\"},
247+
}
248+
249+
cfg.Submodules["qux"] = &Submodule{
250+
Name: "qux",
251+
URL: "https://github.com/foo/qux.git",
252+
}
253+
254+
cfg.Branches["master"] = &Branch{
255+
Name: "master",
256+
Remote: "origin",
257+
Merge: "refs/heads/master",
258+
}
259+
260+
cfg.Merged.GlobalConfig().Section("user").SetOption("name", "Soandso")
261+
cfg.Merged.LocalConfig().Section("user").SetOption("name", "Override")
262+
cfg.Merged.GlobalConfig().Section("user").SetOption("email", "[email protected]")
263+
cfg.Merged.GlobalConfig().Section("core").AddOption("editor", "nvim")
264+
cfg.Merged.LocalConfig().Section("custom").SetOption("key", "value")
265+
cfg.Merged.GlobalConfig().Section("push").AddOption("default", "simple")
266+
267+
c.Assert(cfg.Merged.Section("user").Option("name"), Equals, "Override")
268+
269+
localBytes, err := cfg.Marshal()
270+
c.Assert(err, IsNil)
271+
c.Assert(string(localBytes), Equals, string(localOutput))
272+
273+
globalBytes, err := cfg.MarshalScope(format.GlobalScope)
274+
c.Assert(err, IsNil)
275+
c.Assert(string(globalBytes), Equals, string(globalOutput))
276+
277+
systemBytes, err := cfg.MarshalScope(format.SystemScope)
278+
c.Assert(err, IsNil)
279+
c.Assert(string(systemBytes), Equals, "")
280+
}
281+
122282
func (s *ConfigSuite) TestUnmarshalMarshal(c *C) {
123283
input := []byte(`[core]
124284
bare = true

0 commit comments

Comments
 (0)