diff options
Diffstat (limited to 'config')
-rw-r--r-- | config/config.go | 206 | ||||
-rw-r--r-- | config/config_test.go | 204 |
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{ |