aboutsummaryrefslogtreecommitdiffstats
path: root/config
diff options
context:
space:
mode:
Diffstat (limited to 'config')
-rw-r--r--config/config.go206
-rw-r--r--config/config_test.go204
2 files changed, 207 insertions, 203 deletions
diff --git a/config/config.go b/config/config.go
index 2b427cb..7d6ab58 100644
--- a/config/config.go
+++ b/config/config.go
@@ -5,11 +5,16 @@ import (
"bytes"
"errors"
"fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
"sort"
"strconv"
"github.com/go-git/go-git/v5/internal/url"
format "github.com/go-git/go-git/v5/plumbing/format/config"
+ "github.com/mitchellh/go-homedir"
)
const (
@@ -32,6 +37,16 @@ var (
ErrRemoteConfigEmptyName = errors.New("remote config: empty name")
)
+// Scope defines the scope of a config file, such as local, global or system.
+type Scope int
+
+// Available ConfigScope's
+const (
+ LocalScope Scope = iota
+ GlobalScope
+ SystemScope
+)
+
// Config contains the repository configuration
// https://www.kernel.org/pub/software/scm/git/docs/git-config.html#FILES
type Config struct {
@@ -46,6 +61,27 @@ type Config struct {
CommentChar string
}
+ User struct {
+ // Name is the personal name of the author and the commiter of a commit.
+ Name string
+ // Email is the email of the author and the commiter of a commit.
+ Email string
+ }
+
+ Author struct {
+ // Name is the personal name of the author of a commit.
+ Name string
+ // Email is the email of the author of a commit.
+ Email string
+ }
+
+ Committer struct {
+ // Name is the personal name of the commiter of a commit.
+ Name string
+ // Email is the email of the the commiter of a commit.
+ Email string
+ }
+
Pack struct {
// Window controls the size of the sliding window for delta
// compression. The default is 10. A value of 0 turns off
@@ -66,9 +102,6 @@ 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.
@@ -77,16 +110,85 @@ func NewConfig() *Config {
Remotes: make(map[string]*RemoteConfig),
Submodules: make(map[string]*Submodule),
Branches: make(map[string]*Branch),
- Merged: format.NewMerged(),
+ Raw: format.New(),
}
- config.Raw = config.Merged.LocalConfig()
-
config.Pack.Window = DefaultPackWindow
return config
}
+// ReadConfig reads a config file from a io.Reader.
+func ReadConfig(r io.Reader) (*Config, error) {
+ b, err := ioutil.ReadAll(r)
+ if err != nil {
+ return nil, err
+ }
+
+ cfg := NewConfig()
+ if err = cfg.Unmarshal(b); err != nil {
+ return nil, err
+ }
+
+ return cfg, nil
+}
+
+// LoadConfig loads a config file from a given scope. The returned Config,
+// contains exclusively information fom the given scope. If couldn't find a
+// config file to the given scope, a empty one is returned.
+func LoadConfig(scope Scope) (*Config, error) {
+ if scope == LocalScope {
+ return nil, fmt.Errorf("LocalScope should be read from the a ConfigStorer.")
+ }
+
+ files, err := Paths(scope)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, file := range files {
+ f, err := os.Open(file)
+ if err != nil {
+ if os.IsNotExist(err) {
+ continue
+ }
+
+ return nil, err
+ }
+
+ defer f.Close()
+ return ReadConfig(f)
+ }
+
+ return NewConfig(), nil
+}
+
+// Paths returns the config file location for a given scope.
+func Paths(scope Scope) ([]string, error) {
+ var files []string
+ switch scope {
+ case GlobalScope:
+ xdg := os.Getenv("XDG_CONFIG_HOME")
+ if xdg != "" {
+ files = append(files, filepath.Join(xdg, "git/config"))
+ }
+
+ home, err := homedir.Dir()
+ if err != nil {
+ return nil, err
+ }
+
+ files = append(files,
+ filepath.Join(home, ".gitconfig"),
+ filepath.Join(home, ".config/git/config"),
+ )
+ case SystemScope:
+ files = append(files, "/etc/gitconfig")
+ }
+
+ return files, nil
+}
+
// Validate validates the fields and sets the default values.
func (c *Config) Validate() error {
for name, r := range c.Remotes {
@@ -118,6 +220,9 @@ const (
branchSection = "branch"
coreSection = "core"
packSection = "pack"
+ userSection = "user"
+ authorSection = "author"
+ committerSection = "committer"
fetchKey = "fetch"
urlKey = "url"
bareKey = "bare"
@@ -126,6 +231,8 @@ const (
windowKey = "window"
mergeKey = "merge"
rebaseKey = "rebase"
+ nameKey = "name"
+ emailKey = "email"
// DefaultPackWindow holds the number of previous objects used to
// generate deltas. The value 10 is the same used by git command.
@@ -134,38 +241,26 @@ 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.Merged.ResetScopedConfig(scope)
-
- if err := d.Decode(c.Merged.ScopedConfig(scope)); err != nil {
+ c.Raw = format.New()
+ if err := d.Decode(c.Raw); 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
- }
+ c.unmarshalCore()
+ c.unmarshalUser()
+ if err := c.unmarshalPack(); err != nil {
+ return err
+ }
+ unmarshalSubmodules(c.Raw, c.Submodules)
- if err := c.unmarshalRemotes(); err != nil {
- return err
- }
+ if err := c.unmarshalBranches(); err != nil {
+ return err
}
- return nil
+ return c.unmarshalRemotes()
}
func (c *Config) unmarshalCore() {
@@ -178,6 +273,20 @@ func (c *Config) unmarshalCore() {
c.Core.CommentChar = s.Options.Get(commentCharKey)
}
+func (c *Config) unmarshalUser() {
+ s := c.Raw.Section(userSection)
+ c.User.Name = s.Options.Get(nameKey)
+ c.User.Email = s.Options.Get(emailKey)
+
+ s = c.Raw.Section(authorSection)
+ c.Author.Name = s.Options.Get(nameKey)
+ c.Author.Email = s.Options.Get(emailKey)
+
+ s = c.Raw.Section(committerSection)
+ c.Committer.Name = s.Options.Get(nameKey)
+ c.Committer.Email = s.Options.Get(emailKey)
+}
+
func (c *Config) unmarshalPack() error {
s := c.Raw.Section(packSection)
window := s.Options.Get(windowKey)
@@ -236,26 +345,22 @@ func (c *Config) unmarshalBranches() error {
}
// Marshal returns Config encoded as a git-config file.
-func (c *Config) MarshalScope(scope format.Scope) ([]byte, error) {
+func (c *Config) Marshal() ([]byte, error) {
c.marshalCore()
+ c.marshalUser()
c.marshalPack()
c.marshalRemotes()
c.marshalSubmodules()
c.marshalBranches()
buf := bytes.NewBuffer(nil)
- cfg := c.Merged.ScopedConfig(scope)
- if err := format.NewEncoder(buf).Encode(cfg); err != nil {
+ if err := format.NewEncoder(buf).Encode(c.Raw); 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))
@@ -265,6 +370,35 @@ func (c *Config) marshalCore() {
}
}
+func (c *Config) marshalUser() {
+ s := c.Raw.Section(userSection)
+ if c.User.Name != "" {
+ s.SetOption(nameKey, c.User.Name)
+ }
+
+ if c.User.Email != "" {
+ s.SetOption(emailKey, c.User.Email)
+ }
+
+ s = c.Raw.Section(authorSection)
+ if c.Author.Name != "" {
+ s.SetOption(nameKey, c.Author.Name)
+ }
+
+ if c.Author.Email != "" {
+ s.SetOption(emailKey, c.Author.Email)
+ }
+
+ s = c.Raw.Section(committerSection)
+ if c.Committer.Name != "" {
+ s.SetOption(nameKey, c.Committer.Name)
+ }
+
+ if c.Committer.Email != "" {
+ s.SetOption(emailKey, c.Committer.Email)
+ }
+}
+
func (c *Config) marshalPack() {
s := c.Raw.Section(packSection)
if c.Pack.Window != DefaultPackWindow {
diff --git a/config/config_test.go b/config/config_test.go
index a2ece2a..e68626b 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -1,9 +1,8 @@
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"
+ . "gopkg.in/check.v1"
)
type ConfigSuite struct{}
@@ -12,65 +11,23 @@ var _ = Suite(&ConfigSuite{})
func (s *ConfigSuite) TestUnmarshal(c *C) {
input := []byte(`[core]
- bare = true
+ bare = true
worktree = foo
commentchar = bar
+[user]
+ name = John Doe
+ email = john@example.com
+[author]
+ name = Jane Roe
+ email = jane@example.com
+[committer]
+ name = Richard Roe
+ email = richard@example.com
[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
-`)
-
- cfg := NewConfig()
- err := cfg.Unmarshal(input)
- 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"))
-}
-
-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
@@ -79,37 +36,27 @@ func (s *ConfigSuite) TestMergedUnmarshal(c *C) {
[remote "win-local"]
url = X:\\Git\\
[submodule "qux"]
- path = qux
- url = https://github.com/foo/qux.git
+ 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
+ remote = origin
+ merge = refs/heads/master
`)
cfg := NewConfig()
-
- err := cfg.UnmarshalScoped(format.LocalScope, localInput)
- c.Assert(err, IsNil)
-
- err = cfg.UnmarshalScoped(format.GlobalScope, globalInput)
+ err := cfg.Unmarshal(input)
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.User.Name, Equals, "John Doe")
+ c.Assert(cfg.User.Email, Equals, "john@example.com")
+ c.Assert(cfg.Author.Name, Equals, "Jane Roe")
+ c.Assert(cfg.Author.Email, Equals, "jane@example.com")
+ c.Assert(cfg.Committer.Name, Equals, "Richard Roe")
+ c.Assert(cfg.Committer.Email, Equals, "richard@example.com")
c.Assert(cfg.Pack.Window, Equals, uint(20))
c.Assert(cfg.Remotes, HasLen, 3)
c.Assert(cfg.Remotes["origin"].Name, Equals, "origin")
@@ -126,9 +73,6 @@ func (s *ConfigSuite) TestMergedUnmarshal(c *C) {
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) {
@@ -190,100 +134,20 @@ 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
worktree = foo
custom = ignored
+[user]
+ name = John Doe
+ email = john@example.com
+[author]
+ name = Jane Roe
+ email = jane@example.com
+[committer]
+ name = Richard Roe
+ email = richard@example.co
[pack]
window = 20
[remote "origin"]
@@ -306,6 +170,12 @@ func (s *ConfigSuite) TestUnmarshalMarshal(c *C) {
c.Assert(string(output), DeepEquals, string(input))
}
+func (s *ConfigSuite) TestLoadConfig(c *C) {
+ cfg, err := LoadConfig(GlobalScope)
+ c.Assert(err, IsNil)
+ c.Assert(cfg.User.Email, Not(Equals), "")
+}
+
func (s *ConfigSuite) TestValidateConfig(c *C) {
config := &Config{
Remotes: map[string]*RemoteConfig{