aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMáximo Cuadros <mcuadros@gmail.com>2020-04-07 23:18:13 +0200
committerGitHub <noreply@github.com>2020-04-07 23:18:13 +0200
commit3127ad9a44a2ee935502816065dfe39f494f583d (patch)
treec5b7892e36ba31890e5c60571082deacf255bca7
parent73c52edaad2dae256be61bd1dbbab08e1092f58e (diff)
parent68bffab4d6fa84260cc4287f979a6feceaf92ff8 (diff)
downloadgo-git-3127ad9a44a2ee935502816065dfe39f494f583d.tar.gz
Merge pull request #20 from quorumcontrol/feature/other-configs
plumbing: format, config.Merged to allow access to local and global config
-rw-r--r--_examples/config/main.go69
-rw-r--r--config/config.go49
-rw-r--r--config/config_test.go160
-rw-r--r--plumbing/format/config/merged.go261
-rw-r--r--storage/filesystem/config.go52
-rw-r--r--storage/filesystem/dotgit/dotgit.go50
-rw-r--r--storage/filesystem/dotgit/dotgit_test.go6
7 files changed, 612 insertions, 35 deletions
diff --git a/_examples/config/main.go b/_examples/config/main.go
new file mode 100644
index 0000000..6ccc9f6
--- /dev/null
+++ b/_examples/config/main.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+ "github.com/go-git/go-git/v5"
+ . "github.com/go-git/go-git/v5/_examples"
+ format "github.com/go-git/go-git/v5/plumbing/format/config"
+
+ "github.com/go-git/go-git/v5/config"
+)
+
+// Example of how to:
+// - Access basic local (i.e. ./.git/config) configuration params
+// - Set basic local config params
+// - Set custom local config params
+// - Access custom local config params
+// - Set global config params
+// - Access global & system config params
+
+func main() {
+ // Open this repository
+ // Info("git init")
+ // r, err := git.Init(memory.NewStorage(), nil)
+ Info("open local git repo")
+ r, err := git.PlainOpen(".")
+ CheckIfError(err)
+
+ // Load the configuration
+ cfg, err := r.Config()
+ CheckIfError(err)
+
+ // Get core local config params
+ if cfg.Core.IsBare {
+ Info("repo is bare")
+ } else {
+ Info("repo is not bare")
+ }
+
+ Info("worktree is %s", cfg.Core.Worktree)
+
+ // Set basic local config params
+ cfg.Remotes["origin"] = &config.RemoteConfig{
+ Name: "origin",
+ URLs: []string{"git@github.com:mcuadros/go-git.git"},
+ }
+
+ Info("origin remote: %+v", cfg.Remotes["origin"])
+
+ // NOTE: The examples below show advanced usage of the config.Merged system, which should
+ // only be used as a last resort if the basic data defined on the Config struct don't
+ // suffice for what you're trying to do.
+
+ // Set local custom config param
+ cfg.Merged.AddOption(format.LocalScope, "custom", format.NoSubsection, "name", "Local Name")
+
+ // Set global config param (~/.gitconfig)
+ cfg.Merged.AddOption(format.GlobalScope, "custom", format.NoSubsection, "name", "Global Name")
+
+ // Get custom config param (merged in the same way git does: system -> global -> local)
+ Info("custom.name is %s", cfg.Merged.Section("custom").Option("name"))
+
+ // Get system config params (/etc/gitconfig)
+ systemSections := cfg.Merged.SystemConfig().Sections
+ for _, ss := range systemSections {
+ Info("System section: %s", ss.Name)
+ for _, o := range ss.Options {
+ Info("\tOption: %s = %s", o.Key, o.Value)
+ }
+ }
+}
diff --git a/config/config.go b/config/config.go
index bec35b0..2b427cb 100644
--- a/config/config.go
+++ b/config/config.go
@@ -66,6 +66,9 @@ type Config struct {
// preserve the parsed information from the original format, to avoid
// dropping unsupported fields.
Raw *format.Config
+ // Merged contains the raw form of how git views the system (/etc/gitconfig),
+ // global (~/.gitconfig), and local (./.git/config) config params.
+ Merged *format.Merged
}
// NewConfig returns a new empty Config.
@@ -74,9 +77,11 @@ func NewConfig() *Config {
Remotes: make(map[string]*RemoteConfig),
Submodules: make(map[string]*Submodule),
Branches: make(map[string]*Branch),
- Raw: format.New(),
+ Merged: format.NewMerged(),
}
+ config.Raw = config.Merged.LocalConfig()
+
config.Pack.Window = DefaultPackWindow
return config
@@ -129,25 +134,38 @@ const (
// Unmarshal parses a git-config file and stores it.
func (c *Config) Unmarshal(b []byte) error {
+ return c.UnmarshalScoped(format.LocalScope, b)
+}
+
+func (c *Config) UnmarshalScoped(scope format.Scope, b []byte) error {
r := bytes.NewBuffer(b)
d := format.NewDecoder(r)
- c.Raw = format.New()
- if err := d.Decode(c.Raw); err != nil {
- return err
- }
+ c.Merged.ResetScopedConfig(scope)
- c.unmarshalCore()
- if err := c.unmarshalPack(); err != nil {
+ if err := d.Decode(c.Merged.ScopedConfig(scope)); err != nil {
return err
}
- unmarshalSubmodules(c.Raw, c.Submodules)
- if err := c.unmarshalBranches(); err != nil {
- return err
+ if scope == format.LocalScope {
+ c.Raw = c.Merged.LocalConfig()
+
+ c.unmarshalCore()
+ if err := c.unmarshalPack(); err != nil {
+ return err
+ }
+ unmarshalSubmodules(c.Raw, c.Submodules)
+
+ if err := c.unmarshalBranches(); err != nil {
+ return err
+ }
+
+ if err := c.unmarshalRemotes(); err != nil {
+ return err
+ }
}
- return c.unmarshalRemotes()
+ return nil
}
func (c *Config) unmarshalCore() {
@@ -218,7 +236,7 @@ func (c *Config) unmarshalBranches() error {
}
// Marshal returns Config encoded as a git-config file.
-func (c *Config) Marshal() ([]byte, error) {
+func (c *Config) MarshalScope(scope format.Scope) ([]byte, error) {
c.marshalCore()
c.marshalPack()
c.marshalRemotes()
@@ -226,13 +244,18 @@ func (c *Config) Marshal() ([]byte, error) {
c.marshalBranches()
buf := bytes.NewBuffer(nil)
- if err := format.NewEncoder(buf).Encode(c.Raw); err != nil {
+ cfg := c.Merged.ScopedConfig(scope)
+ if err := format.NewEncoder(buf).Encode(cfg); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
+func (c *Config) Marshal() ([]byte, error) {
+ return c.MarshalScope(format.LocalScope)
+}
+
func (c *Config) marshalCore() {
s := c.Raw.Section(coreSection)
s.SetOption(bareKey, fmt.Sprintf("%t", c.Core.IsBare))
diff --git a/config/config_test.go b/config/config_test.go
index e5e3be5..a2ece2a 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -3,6 +3,7 @@ package config
import (
. "gopkg.in/check.v1"
"github.com/go-git/go-git/v5/plumbing"
+ format "github.com/go-git/go-git/v5/plumbing/format/config"
)
type ConfigSuite struct{}
@@ -60,6 +61,76 @@ func (s *ConfigSuite) TestUnmarshal(c *C) {
c.Assert(cfg.Branches["master"].Merge, Equals, plumbing.ReferenceName("refs/heads/master"))
}
+func (s *ConfigSuite) TestMergedUnmarshal(c *C) {
+ localInput := []byte(`[core]
+ bare = true
+ worktree = foo
+ commentchar = bar
+[pack]
+ window = 20
+[remote "origin"]
+ url = git@github.com:mcuadros/go-git.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+[remote "alt"]
+ url = git@github.com:mcuadros/go-git.git
+ url = git@github.com:src-d/go-git.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ fetch = +refs/pull/*:refs/remotes/origin/pull/*
+[remote "win-local"]
+ url = X:\\Git\\
+[submodule "qux"]
+ path = qux
+ url = https://github.com/foo/qux.git
+ branch = bar
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
+[user]
+ name = Override
+`)
+
+ globalInput := []byte(`
+[user]
+ name = Soandso
+ email = soandso@example.com
+[core]
+ editor = nvim
+[push]
+ default = simple
+`)
+
+ cfg := NewConfig()
+
+ err := cfg.UnmarshalScoped(format.LocalScope, localInput)
+ c.Assert(err, IsNil)
+
+ err = cfg.UnmarshalScoped(format.GlobalScope, globalInput)
+ c.Assert(err, IsNil)
+
+ c.Assert(cfg.Core.IsBare, Equals, true)
+ c.Assert(cfg.Core.Worktree, Equals, "foo")
+ c.Assert(cfg.Core.CommentChar, Equals, "bar")
+ c.Assert(cfg.Pack.Window, Equals, uint(20))
+ c.Assert(cfg.Remotes, HasLen, 3)
+ c.Assert(cfg.Remotes["origin"].Name, Equals, "origin")
+ c.Assert(cfg.Remotes["origin"].URLs, DeepEquals, []string{"git@github.com:mcuadros/go-git.git"})
+ c.Assert(cfg.Remotes["origin"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*"})
+ c.Assert(cfg.Remotes["alt"].Name, Equals, "alt")
+ c.Assert(cfg.Remotes["alt"].URLs, DeepEquals, []string{"git@github.com:mcuadros/go-git.git", "git@github.com:src-d/go-git.git"})
+ c.Assert(cfg.Remotes["alt"].Fetch, DeepEquals, []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"})
+ c.Assert(cfg.Remotes["win-local"].Name, Equals, "win-local")
+ c.Assert(cfg.Remotes["win-local"].URLs, DeepEquals, []string{"X:\\Git\\"})
+ c.Assert(cfg.Submodules, HasLen, 1)
+ c.Assert(cfg.Submodules["qux"].Name, Equals, "qux")
+ c.Assert(cfg.Submodules["qux"].URL, Equals, "https://github.com/foo/qux.git")
+ c.Assert(cfg.Submodules["qux"].Branch, Equals, "bar")
+ c.Assert(cfg.Branches["master"].Remote, Equals, "origin")
+ c.Assert(cfg.Branches["master"].Merge, Equals, plumbing.ReferenceName("refs/heads/master"))
+ c.Assert(cfg.Merged.Section("user").Option("name"), Equals, "Override")
+ c.Assert(cfg.Merged.Section("user").Option("email"), Equals, "soandso@example.com")
+ c.Assert(cfg.Merged.Section("push").Option("default"), Equals, "simple")
+}
+
func (s *ConfigSuite) TestMarshal(c *C) {
output := []byte(`[core]
bare = true
@@ -119,6 +190,95 @@ func (s *ConfigSuite) TestMarshal(c *C) {
c.Assert(string(b), Equals, string(output))
}
+func (s *ConfigSuite) TestMergedMarshal(c *C) {
+ localOutput := []byte(`[user]
+ name = Override
+[custom]
+ key = value
+[core]
+ bare = true
+ worktree = bar
+[pack]
+ window = 20
+[remote "alt"]
+ url = git@github.com:mcuadros/go-git.git
+ url = git@github.com:src-d/go-git.git
+ fetch = +refs/heads/*:refs/remotes/origin/*
+ fetch = +refs/pull/*:refs/remotes/origin/pull/*
+[remote "origin"]
+ url = git@github.com:mcuadros/go-git.git
+[remote "win-local"]
+ url = "X:\\Git\\"
+[submodule "qux"]
+ url = https://github.com/foo/qux.git
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
+`)
+
+ globalOutput := []byte(`[user]
+ name = Soandso
+ email = soandso@example.com
+[core]
+ editor = nvim
+[push]
+ default = simple
+`)
+
+ cfg := NewConfig()
+
+ cfg.Core.IsBare = true
+ cfg.Core.Worktree = "bar"
+ cfg.Pack.Window = 20
+ cfg.Remotes["origin"] = &RemoteConfig{
+ Name: "origin",
+ URLs: []string{"git@github.com:mcuadros/go-git.git"},
+ }
+
+ cfg.Remotes["alt"] = &RemoteConfig{
+ Name: "alt",
+ URLs: []string{"git@github.com:mcuadros/go-git.git", "git@github.com:src-d/go-git.git"},
+ Fetch: []RefSpec{"+refs/heads/*:refs/remotes/origin/*", "+refs/pull/*:refs/remotes/origin/pull/*"},
+ }
+
+ cfg.Remotes["win-local"] = &RemoteConfig{
+ Name: "win-local",
+ URLs: []string{"X:\\Git\\"},
+ }
+
+ cfg.Submodules["qux"] = &Submodule{
+ Name: "qux",
+ URL: "https://github.com/foo/qux.git",
+ }
+
+ cfg.Branches["master"] = &Branch{
+ Name: "master",
+ Remote: "origin",
+ Merge: "refs/heads/master",
+ }
+
+ cfg.Merged.GlobalConfig().Section("user").SetOption("name", "Soandso")
+ cfg.Merged.LocalConfig().Section("user").SetOption("name", "Override")
+ cfg.Merged.GlobalConfig().Section("user").SetOption("email", "soandso@example.com")
+ cfg.Merged.GlobalConfig().Section("core").AddOption("editor", "nvim")
+ cfg.Merged.LocalConfig().Section("custom").SetOption("key", "value")
+ cfg.Merged.GlobalConfig().Section("push").AddOption("default", "simple")
+
+ c.Assert(cfg.Merged.Section("user").Option("name"), Equals, "Override")
+
+ localBytes, err := cfg.Marshal()
+ c.Assert(err, IsNil)
+ c.Assert(string(localBytes), Equals, string(localOutput))
+
+ globalBytes, err := cfg.MarshalScope(format.GlobalScope)
+ c.Assert(err, IsNil)
+ c.Assert(string(globalBytes), Equals, string(globalOutput))
+
+ systemBytes, err := cfg.MarshalScope(format.SystemScope)
+ c.Assert(err, IsNil)
+ c.Assert(string(systemBytes), Equals, "")
+}
+
func (s *ConfigSuite) TestUnmarshalMarshal(c *C) {
input := []byte(`[core]
bare = true
diff --git a/plumbing/format/config/merged.go b/plumbing/format/config/merged.go
new file mode 100644
index 0000000..a44f3b8
--- /dev/null
+++ b/plumbing/format/config/merged.go
@@ -0,0 +1,261 @@
+package config
+
+// Scope defines which configuration we're reading / writing. Git uses
+// a local config file in each repo (./.git/config) which corresponds
+// with LocalScope here. It's values have the highest priority (i.e. they
+// override those in the other two scopes). Next is the global config which
+// could also be described as a user config since it lives in the
+// ~/.gitconfig file. Last and lowest priority is the system config which
+// resides in the /etc/gitconfig file.
+type Scope int
+const (
+ LocalScope Scope = iota
+ GlobalScope
+ SystemScope
+)
+
+type ScopedConfigs map[Scope]*Config
+
+// Merged defines a read-only view of the priority-merged config params
+// so that you can access the effective settings in the same way git does.
+// For example, if a user has defined a user.name value in ~/.gitconfig but
+// a different one in the current repo's ./.git/config file, this code:
+// config.Merged.Section("user").Option("name") will give you the value
+// from ./.git/config.
+type Merged struct {
+ scopedConfigs ScopedConfigs
+}
+
+// MergedSection is a read-only Section for merged config views.
+type MergedSection struct {
+ backingSection *Section
+}
+
+// MergedSubsection is a read-only Subsection for merged config views.
+type MergedSubsection struct {
+ backingSubsection *Subsection
+}
+
+type MergedSubsections []*MergedSubsection
+
+func NewMerged() *Merged {
+ cfg := &Merged{
+ scopedConfigs: make(ScopedConfigs),
+ }
+ for s := LocalScope; s <= SystemScope; s++ {
+ cfg.scopedConfigs[s] = New()
+ }
+
+ return cfg
+}
+
+func (m *Merged) ResetScopedConfig(scope Scope) {
+ m.scopedConfigs[scope] = New()
+}
+
+// ScopedConfig allows accessing specific backing *Config instances.
+func (m *Merged) ScopedConfig(scope Scope) *Config {
+ return m.scopedConfigs[scope]
+}
+
+// LocalConfig allows accessing the local (i.e. ./.git/config) backing
+// *Config instance.
+func (m *Merged) LocalConfig() *Config {
+ return m.ScopedConfig(LocalScope)
+}
+
+// GlobalConfig allows accessing the global (i.e. ~/.gitconfig) backing
+// *Config instance.
+func (m *Merged) GlobalConfig() *Config {
+ return m.ScopedConfig(GlobalScope)
+}
+
+// SystemConfig allows accessing the system (i.e. /etc/gitconfig) backing
+// *Config instance.
+func (m *Merged) SystemConfig() *Config {
+ return m.ScopedConfig(SystemScope)
+}
+
+// SetLocalConfig allows updating the local (i.e. ./.git/config) config. If you
+// call config.SetConfig(...) on the containing top-level config instance after
+// this, your new local config params will be written to ./.git/config.
+func (m *Merged) SetLocalConfig(c *Config) {
+ m.scopedConfigs[LocalScope] = c
+}
+
+// SetGlobalConfig allows updating the global (i.e. ~/.gitconfig) config. If you
+// call config.SetConfig(...) on the containing top-level config instance after
+// this, your new global config params will be written to ~/.gitconfig.
+func (m *Merged) SetGlobalConfig(c *Config) {
+ m.scopedConfigs[GlobalScope] = c
+}
+
+// Config.Section creates the section if it doesn't exist, which is not
+// what we want in here.
+func (c *Config) hasSection(name string) bool {
+ sections := c.Sections
+ var found bool
+
+ for _, s := range sections {
+ if s.IsName(name) {
+ found = true
+ break
+ }
+ }
+
+ return found
+}
+
+// Section returns a read-only *MergedSection view of the config in which
+// params are merged in the same order as git itself:
+// Local overrides global overrides system.
+func (m *Merged) Section(name string) *MergedSection {
+ var mergedSection *MergedSection
+
+ for s := SystemScope; s >= LocalScope; s-- {
+ if m.scopedConfigs[s].hasSection(name) {
+ sec := m.scopedConfigs[s].Section(name)
+ if mergedSection == nil {
+ mergedSection = NewMergedSection(sec)
+ }
+
+ if mergedSection.Options() == nil {
+ mergedSection.backingSection.Options = sec.Options
+ } else {
+ for _, o := range sec.Options {
+ mergedSection.backingSection.SetOption(o.Key, o.Value)
+ }
+ }
+
+ if mergedSection.Subsections() == nil {
+ mergedSection.backingSection.Subsections = sec.Subsections
+ } else {
+ for _, ss := range sec.Subsections {
+ if mergedSection.HasSubsection(ss.Name) {
+ for _, o := range ss.Options {
+ mergedSection.backingSection.Subsection(ss.Name).SetOption(o.Key, o.Value)
+ }
+ } else {
+ mergedSection.backingSection.Subsections = append(mergedSection.backingSection.Subsections, ss)
+ }
+ }
+ }
+ }
+ }
+
+ if mergedSection != nil {
+ mergedSection.backingSection.Name = name
+ }
+
+ return mergedSection
+}
+
+// AddOption works just like config.AddOption except that it takes a Scope as its first argument.
+// This defines which config scope (local, global, or system) this option should be added to.
+func (m *Merged) AddOption(scope Scope, section string, subsection string, key string, value string) *Config {
+ return m.ScopedConfig(scope).AddOption(section, subsection, key, value)
+}
+
+// SetOption works just like config.SetOption except that it takes a Scope as its first argument.
+// This defines which config scope (local, global, or system) this option should be set in.
+func (m *Merged) SetOption(scope Scope, section string, subsection string, key string, value string) *Config {
+ return m.ScopedConfig(scope).SetOption(section, subsection, key, value)
+}
+
+// RemoveSection works just like config.RemoveSection except that it takes a Scope as its first argument.
+// This defines which config scope (local, global, or system) the section should be removed from.
+func (m *Merged) RemoveSection(scope Scope, name string) *Config {
+ return m.ScopedConfig(scope).RemoveSection(name)
+}
+
+// RemoveSubsection works just like config.RemoveSubsection except that it takes a Scope as its first argument.
+// This defines which config scope (local, global, or system) the subsection should be removed from.
+func (m *Merged) RemoveSubsection(scope Scope, section string, subsection string) *Config {
+ return m.ScopedConfig(scope).RemoveSubsection(section, subsection)
+}
+
+func copyOptions(os Options) Options {
+ copiedOptions := make(Options, 0)
+
+ for _, o := range os {
+ copiedOptions = append(copiedOptions, o)
+ }
+
+ return copiedOptions
+}
+
+func copySubsections(ss Subsections) Subsections {
+ copiedSubsections := make(Subsections, 0)
+
+ for _, ss := range ss {
+ copiedSubsections = append(copiedSubsections, &Subsection{
+ Name: ss.Name,
+ Options: copyOptions(ss.Options),
+ })
+ }
+
+ return copiedSubsections
+}
+
+func NewMergedSection(backing *Section) *MergedSection {
+ return &MergedSection{
+ backingSection: &Section{
+ Name: backing.Name,
+ Options: copyOptions(backing.Options),
+ Subsections: copySubsections(backing.Subsections),
+ },
+ }
+}
+
+func (ms *MergedSection) Name() string {
+ return ms.backingSection.Name
+}
+
+func (ms *MergedSection) IsName(name string) bool {
+ return ms.backingSection.IsName(name)
+}
+
+func (ms *MergedSection) Options() []*Option {
+ return ms.backingSection.Options
+}
+
+func (ms *MergedSection) Option(key string) string {
+ return ms.backingSection.Option(key)
+}
+
+func (ms *MergedSection) Subsections() MergedSubsections {
+ mss := make(MergedSubsections, 0)
+ for _, ss := range ms.backingSection.Subsections {
+ mss = append(mss, NewMergedSubsection(ss))
+ }
+ return mss
+}
+
+func (ms *MergedSection) Subsection(name string) *MergedSubsection {
+ return NewMergedSubsection(ms.backingSection.Subsection(name))
+}
+
+func (ms *MergedSection) HasSubsection(name string) bool {
+ return ms.backingSection.HasSubsection(name)
+}
+
+func NewMergedSubsection(backing *Subsection) *MergedSubsection {
+ return &MergedSubsection{backingSubsection: backing}
+}
+
+func (mss *MergedSubsection) Name() string {
+ return mss.backingSubsection.Name
+}
+
+func (mss *MergedSubsection) IsName(name string) bool {
+ return mss.backingSubsection.IsName(name)
+}
+
+func (mss *MergedSubsection) Options() []*Option {
+ return mss.backingSubsection.Options
+}
+
+func (mss *MergedSubsection) Option(key string) string {
+ return mss.backingSubsection.Option(key)
+}
+
diff --git a/storage/filesystem/config.go b/storage/filesystem/config.go
index 01b35b4..dbdce54 100644
--- a/storage/filesystem/config.go
+++ b/storage/filesystem/config.go
@@ -5,6 +5,7 @@ import (
"os"
"github.com/go-git/go-git/v5/config"
+ format "github.com/go-git/go-git/v5/plumbing/format/config"
"github.com/go-git/go-git/v5/storage/filesystem/dotgit"
"github.com/go-git/go-git/v5/utils/ioutil"
)
@@ -13,10 +14,11 @@ type ConfigStorage struct {
dir *dotgit.DotGit
}
-func (c *ConfigStorage) Config() (conf *config.Config, err error) {
+func (c *ConfigStorage) Config() (*config.Config, error) {
cfg := config.NewConfig()
- f, err := c.dir.Config()
+ // local config (./.git/config)
+ f, err := c.dir.LocalConfig()
if err != nil {
if os.IsNotExist(err) {
return cfg, nil
@@ -29,13 +31,55 @@ func (c *ConfigStorage) Config() (conf *config.Config, err error) {
b, err := stdioutil.ReadAll(f)
if err != nil {
+ return cfg, err
+ }
+
+ if err = cfg.UnmarshalScoped(format.LocalScope, b); err != nil {
+ return cfg, err
+ }
+
+ // global config (~/.gitconfig)
+ f, err = c.dir.GlobalConfig()
+ if err != nil {
+ if os.IsNotExist(err) {
+ return cfg, nil
+ }
+
return nil, err
}
- if err = cfg.Unmarshal(b); err != nil {
+ defer ioutil.CheckClose(f, &err)
+
+ b, err = stdioutil.ReadAll(f)
+ if err != nil {
+ return cfg, err
+ }
+
+ if err = cfg.UnmarshalScoped(format.GlobalScope, b); err != nil {
+ return cfg, err
+ }
+
+ // system config (/etc/gitconfig)
+ f, err = c.dir.SystemConfig()
+ if err != nil {
+ if os.IsNotExist(err) {
+ return cfg, nil
+ }
+
return nil, err
}
+ defer ioutil.CheckClose(f, &err)
+
+ b, err = stdioutil.ReadAll(f)
+ if err != nil {
+ return cfg, err
+ }
+
+ if err = cfg.UnmarshalScoped(format.SystemScope, b); err != nil {
+ return cfg, err
+ }
+
return cfg, err
}
@@ -44,7 +88,7 @@ func (c *ConfigStorage) SetConfig(cfg *config.Config) (err error) {
return err
}
- f, err := c.dir.ConfigWriter()
+ f, err := c.dir.LocalConfigWriter()
if err != nil {
return err
}
diff --git a/storage/filesystem/dotgit/dotgit.go b/storage/filesystem/dotgit/dotgit.go
index 3ce9dae..503ce18 100644
--- a/storage/filesystem/dotgit/dotgit.go
+++ b/storage/filesystem/dotgit/dotgit.go
@@ -21,15 +21,17 @@ import (
)
const (
- suffix = ".git"
- packedRefsPath = "packed-refs"
- configPath = "config"
- indexPath = "index"
- shallowPath = "shallow"
- modulePath = "modules"
- objectsPath = "objects"
- packPath = "pack"
- refsPath = "refs"
+ suffix = ".git"
+ packedRefsPath = "packed-refs"
+ localConfigPath = "config"
+ globalConfigPath = ".gitconfig"
+ systemConfigPath = "/etc/gitconfig"
+ indexPath = "index"
+ shallowPath = "shallow"
+ modulePath = "modules"
+ objectsPath = "objects"
+ packPath = "pack"
+ refsPath = "refs"
tmpPackedRefsPrefix = "._packed-refs"
@@ -152,14 +154,32 @@ func (d *DotGit) Close() error {
return nil
}
-// ConfigWriter returns a file pointer for write to the config file
-func (d *DotGit) ConfigWriter() (billy.File, error) {
- return d.fs.Create(configPath)
+// LocalConfigWriter returns a file pointer for write to the local config file
+func (d *DotGit) LocalConfigWriter() (billy.File, error) {
+ return d.fs.Create(localConfigPath)
}
-// Config returns a file pointer for read to the config file
-func (d *DotGit) Config() (billy.File, error) {
- return d.fs.Open(configPath)
+// LocalConfig returns a file pointer for read to the local config file
+func (d *DotGit) LocalConfig() (billy.File, error) {
+ return d.fs.Open(localConfigPath)
+}
+
+// GlobalConfigWriter returns a file pointer for write to the global config file
+func (d *DotGit) GlobalConfigWriter() (billy.File, error) {
+ return osfs.New(os.Getenv("HOME")).Create(globalConfigPath)
+}
+
+// GlobalConfig returns a file pointer for read to the global config file
+func (d *DotGit) GlobalConfig() (billy.File, error) {
+ return osfs.New(os.Getenv("HOME")).Open(globalConfigPath)
+}
+
+// SystemConfigWriter doesn't exist because we typically do not have permission
+// to write to files in /etc.
+
+// SystemConfig returns a file pointer for read to the system config file
+func (d *DotGit) SystemConfig() (billy.File, error) {
+ return osfs.New("/").Open(systemConfigPath)
}
// IndexWriter returns a file pointer for write to the index file
diff --git a/storage/filesystem/dotgit/dotgit_test.go b/storage/filesystem/dotgit/dotgit_test.go
index cf81c23..d57ff15 100644
--- a/storage/filesystem/dotgit/dotgit_test.go
+++ b/storage/filesystem/dotgit/dotgit_test.go
@@ -332,7 +332,7 @@ func (s *SuiteDotGit) TestConfig(c *C) {
fs := fixtures.Basic().ByTag(".git").One().DotGit()
dir := New(fs)
- file, err := dir.Config()
+ file, err := dir.LocalConfig()
c.Assert(err, IsNil)
c.Assert(filepath.Base(file.Name()), Equals, "config")
}
@@ -345,13 +345,13 @@ func (s *SuiteDotGit) TestConfigWriteAndConfig(c *C) {
fs := osfs.New(tmp)
dir := New(fs)
- f, err := dir.ConfigWriter()
+ f, err := dir.LocalConfigWriter()
c.Assert(err, IsNil)
_, err = f.Write([]byte("foo"))
c.Assert(err, IsNil)
- f, err = dir.Config()
+ f, err = dir.LocalConfig()
c.Assert(err, IsNil)
cnt, err := ioutil.ReadAll(f)