diff options
Diffstat (limited to 'config')
-rw-r--r-- | config/branch.go | 71 | ||||
-rw-r--r-- | config/branch_test.go | 76 | ||||
-rw-r--r-- | config/config.go | 83 | ||||
-rw-r--r-- | config/config_test.go | 93 | ||||
-rw-r--r-- | config/modules.go | 20 | ||||
-rw-r--r-- | config/modules_test.go | 26 | ||||
-rw-r--r-- | config/refspec.go | 10 | ||||
-rw-r--r-- | config/refspec_test.go | 19 |
8 files changed, 378 insertions, 20 deletions
diff --git a/config/branch.go b/config/branch.go new file mode 100644 index 0000000..e18073c --- /dev/null +++ b/config/branch.go @@ -0,0 +1,71 @@ +package config + +import ( + "errors" + + "gopkg.in/src-d/go-git.v4/plumbing" + format "gopkg.in/src-d/go-git.v4/plumbing/format/config" +) + +var ( + errBranchEmptyName = errors.New("branch config: empty name") + errBranchInvalidMerge = errors.New("branch config: invalid merge") +) + +// Branch contains information on the +// local branches and which remote to track +type Branch struct { + // Name of branch + Name string + // Remote name of remote to track + Remote string + // Merge is the local refspec for the branch + Merge plumbing.ReferenceName + + raw *format.Subsection +} + +// Validate validates fields of branch +func (b *Branch) Validate() error { + if b.Name == "" { + return errBranchEmptyName + } + + if b.Merge != "" && !b.Merge.IsBranch() { + return errBranchInvalidMerge + } + + return nil +} + +func (b *Branch) marshal() *format.Subsection { + if b.raw == nil { + b.raw = &format.Subsection{} + } + + b.raw.Name = b.Name + + if b.Remote == "" { + b.raw.RemoveOption(remoteSection) + } else { + b.raw.SetOption(remoteSection, b.Remote) + } + + if b.Merge == "" { + b.raw.RemoveOption(mergeKey) + } else { + b.raw.SetOption(mergeKey, string(b.Merge)) + } + + return b.raw +} + +func (b *Branch) unmarshal(s *format.Subsection) error { + b.raw = s + + b.Name = b.raw.Name + b.Remote = b.raw.Options.Get(remoteSection) + b.Merge = plumbing.ReferenceName(b.raw.Options.Get(mergeKey)) + + return b.Validate() +} diff --git a/config/branch_test.go b/config/branch_test.go new file mode 100644 index 0000000..d74122e --- /dev/null +++ b/config/branch_test.go @@ -0,0 +1,76 @@ +package config + +import ( + . "gopkg.in/check.v1" + "gopkg.in/src-d/go-git.v4/plumbing" +) + +type BranchSuite struct{} + +var _ = Suite(&BranchSuite{}) + +func (b *BranchSuite) TestValidateName(c *C) { + goodBranch := Branch{ + Name: "master", + Remote: "some_remote", + Merge: "refs/heads/master", + } + badBranch := Branch{ + Remote: "some_remote", + Merge: "refs/heads/master", + } + c.Assert(goodBranch.Validate(), IsNil) + c.Assert(badBranch.Validate(), NotNil) +} + +func (b *BranchSuite) TestValidateMerge(c *C) { + goodBranch := Branch{ + Name: "master", + Remote: "some_remote", + Merge: "refs/heads/master", + } + badBranch := Branch{ + Name: "master", + Remote: "some_remote", + Merge: "blah", + } + c.Assert(goodBranch.Validate(), IsNil) + c.Assert(badBranch.Validate(), NotNil) +} + +func (b *BranchSuite) TestMarshall(c *C) { + expected := []byte(`[core] + bare = false +[branch "branch-tracking-on-clone"] + remote = fork + merge = refs/heads/branch-tracking-on-clone +`) + + cfg := NewConfig() + cfg.Branches["branch-tracking-on-clone"] = &Branch{ + Name: "branch-tracking-on-clone", + Remote: "fork", + Merge: plumbing.ReferenceName("refs/heads/branch-tracking-on-clone"), + } + + actual, err := cfg.Marshal() + c.Assert(err, IsNil) + c.Assert(string(actual), Equals, string(expected)) +} + +func (b *BranchSuite) TestUnmarshall(c *C) { + input := []byte(`[core] + bare = false +[branch "branch-tracking-on-clone"] + remote = fork + merge = refs/heads/branch-tracking-on-clone +`) + + cfg := NewConfig() + err := cfg.Unmarshal(input) + c.Assert(err, IsNil) + branch := cfg.Branches["branch-tracking-on-clone"] + c.Assert(branch.Name, Equals, "branch-tracking-on-clone") + c.Assert(branch.Remote, Equals, "fork") + c.Assert(branch.Merge, Equals, plumbing.ReferenceName("refs/heads/branch-tracking-on-clone")) +} diff --git a/config/config.go b/config/config.go index 87a847d..a637f6d 100644 --- a/config/config.go +++ b/config/config.go @@ -25,7 +25,7 @@ type ConfigStorer interface { } var ( - ErrInvalid = errors.New("config invalid remote") + ErrInvalid = errors.New("config invalid key in remote or branch") ErrRemoteConfigNotFound = errors.New("remote config not found") ErrRemoteConfigEmptyURL = errors.New("remote config: empty URL") ErrRemoteConfigEmptyName = errors.New("remote config: empty name") @@ -40,6 +40,9 @@ type Config struct { IsBare bool // Worktree is the path to the root of the working tree. Worktree string + // CommentChar is the character indicating the start of a + // comment for commands like commit and tag + CommentChar string } Pack struct { @@ -55,7 +58,9 @@ type Config struct { // Submodules list of repository submodules, the key of the map is the name // of the submodule, should equal to Submodule.Name. Submodules map[string]*Submodule - + // Branches list of branches, the key is the branch name and should + // equal Branch.Name + Branches map[string]*Branch // Raw contains the raw information of a config file. The main goal is // preserve the parsed information from the original format, to avoid // dropping unsupported fields. @@ -67,6 +72,7 @@ func NewConfig() *Config { config := &Config{ Remotes: make(map[string]*RemoteConfig), Submodules: make(map[string]*Submodule), + Branches: make(map[string]*Branch), Raw: format.New(), } @@ -87,19 +93,32 @@ func (c *Config) Validate() error { } } + for name, b := range c.Branches { + if b.Name != name { + return ErrInvalid + } + + if err := b.Validate(); err != nil { + return err + } + } + return nil } const ( remoteSection = "remote" submoduleSection = "submodule" + branchSection = "branch" coreSection = "core" packSection = "pack" fetchKey = "fetch" urlKey = "url" bareKey = "bare" worktreeKey = "worktree" + commentCharKey = "commentChar" windowKey = "window" + mergeKey = "merge" // DefaultPackWindow holds the number of previous objects used to // generate deltas. The value 10 is the same used by git command. @@ -120,7 +139,12 @@ func (c *Config) Unmarshal(b []byte) error { if err := c.unmarshalPack(); err != nil { return err } - c.unmarshalSubmodules() + unmarshalSubmodules(c.Raw, c.Submodules) + + if err := c.unmarshalBranches(); err != nil { + return err + } + return c.unmarshalRemotes() } @@ -131,6 +155,7 @@ func (c *Config) unmarshalCore() { } c.Core.Worktree = s.Options.Get(worktreeKey) + c.Core.CommentChar = s.Options.Get(commentCharKey) } func (c *Config) unmarshalPack() error { @@ -162,22 +187,41 @@ func (c *Config) unmarshalRemotes() error { return nil } -func (c *Config) unmarshalSubmodules() { - s := c.Raw.Section(submoduleSection) +func unmarshalSubmodules(fc *format.Config, submodules map[string]*Submodule) { + s := fc.Section(submoduleSection) for _, sub := range s.Subsections { m := &Submodule{} m.unmarshal(sub) - c.Submodules[m.Name] = m + if m.Validate() == ErrModuleBadPath { + continue + } + + submodules[m.Name] = m } } +func (c *Config) unmarshalBranches() error { + bs := c.Raw.Section(branchSection) + for _, sub := range bs.Subsections { + b := &Branch{} + + if err := b.unmarshal(sub); err != nil { + return err + } + + c.Branches[b.Name] = b + } + return nil +} + // Marshal returns Config encoded as a git-config file. func (c *Config) Marshal() ([]byte, error) { c.marshalCore() c.marshalPack() c.marshalRemotes() c.marshalSubmodules() + c.marshalBranches() buf := bytes.NewBuffer(nil) if err := format.NewEncoder(buf).Encode(c.Raw); err != nil { @@ -245,6 +289,33 @@ func (c *Config) marshalSubmodules() { } } +func (c *Config) marshalBranches() { + s := c.Raw.Section(branchSection) + newSubsections := make(format.Subsections, 0, len(c.Branches)) + added := make(map[string]bool) + for _, subsection := range s.Subsections { + if branch, ok := c.Branches[subsection.Name]; ok { + newSubsections = append(newSubsections, branch.marshal()) + added[subsection.Name] = true + } + } + + branchNames := make([]string, 0, len(c.Branches)) + for name := range c.Branches { + branchNames = append(branchNames, name) + } + + sort.Strings(branchNames) + + for _, name := range branchNames { + if !added[name] { + newSubsections = append(newSubsections, c.Branches[name].marshal()) + } + } + + s.Subsections = newSubsections +} + // RemoteConfig contains the configuration for a given remote repository. type RemoteConfig struct { // Name of the remote diff --git a/config/config_test.go b/config/config_test.go index 1f120c0..db0932c 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,6 +1,9 @@ package config -import . "gopkg.in/check.v1" +import ( + . "gopkg.in/check.v1" + "gopkg.in/src-d/go-git.v4/plumbing" +) type ConfigSuite struct{} @@ -10,6 +13,7 @@ func (s *ConfigSuite) TestUnmarshall(c *C) { input := []byte(`[core] bare = true worktree = foo + commentchar = bar [pack] window = 20 [remote "origin"] @@ -20,6 +24,8 @@ func (s *ConfigSuite) TestUnmarshall(c *C) { 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 @@ -35,19 +41,23 @@ func (s *ConfigSuite) TestUnmarshall(c *C) { 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, 2) + 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) TestMarshall(c *C) { @@ -63,8 +73,13 @@ func (s *ConfigSuite) TestMarshall(c *C) { 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 `) cfg := NewConfig() @@ -82,11 +97,22 @@ func (s *ConfigSuite) TestMarshall(c *C) { 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", + } + b, err := cfg.Marshal() c.Assert(err, IsNil) @@ -104,6 +130,8 @@ func (s *ConfigSuite) TestUnmarshallMarshall(c *C) { url = git@github.com:mcuadros/go-git.git fetch = +refs/heads/*:refs/remotes/origin/* mirror = true +[remote "win-local"] + url = "X:\\Git\\" [branch "master"] remote = origin merge = refs/heads/master @@ -118,6 +146,29 @@ func (s *ConfigSuite) TestUnmarshallMarshall(c *C) { c.Assert(string(output), DeepEquals, string(input)) } +func (s *ConfigSuite) TestValidateConfig(c *C) { + config := &Config{ + Remotes: map[string]*RemoteConfig{ + "bar": { + Name: "bar", + URLs: []string{"http://foo/bar"}, + }, + }, + Branches: map[string]*Branch{ + "bar": { + Name: "bar", + }, + "foo": { + Name: "foo", + Remote: "origin", + Merge: plumbing.ReferenceName("refs/heads/foo"), + }, + }, + } + + c.Assert(config.Validate(), IsNil) +} + func (s *ConfigSuite) TestValidateInvalidRemote(c *C) { config := &Config{ Remotes: map[string]*RemoteConfig{ @@ -128,7 +179,7 @@ func (s *ConfigSuite) TestValidateInvalidRemote(c *C) { c.Assert(config.Validate(), Equals, ErrRemoteConfigEmptyURL) } -func (s *ConfigSuite) TestValidateInvalidKey(c *C) { +func (s *ConfigSuite) TestValidateInvalidRemoteKey(c *C) { config := &Config{ Remotes: map[string]*RemoteConfig{ "bar": {Name: "foo"}, @@ -157,10 +208,44 @@ func (s *ConfigSuite) TestRemoteConfigValidateDefault(c *C) { c.Assert(fetch[0].String(), Equals, "+refs/heads/*:refs/remotes/foo/*") } +func (s *ConfigSuite) TestValidateInvalidBranchKey(c *C) { + config := &Config{ + Branches: map[string]*Branch{ + "foo": { + Name: "bar", + Remote: "origin", + Merge: plumbing.ReferenceName("refs/heads/bar"), + }, + }, + } + + c.Assert(config.Validate(), Equals, ErrInvalid) +} + +func (s *ConfigSuite) TestValidateInvalidBranch(c *C) { + config := &Config{ + Branches: map[string]*Branch{ + "bar": { + Name: "bar", + Remote: "origin", + Merge: plumbing.ReferenceName("refs/heads/bar"), + }, + "foo": { + Name: "foo", + Remote: "origin", + Merge: plumbing.ReferenceName("baz"), + }, + }, + } + + c.Assert(config.Validate(), Equals, errBranchInvalidMerge) +} + func (s *ConfigSuite) TestRemoteConfigDefaultValues(c *C) { config := NewConfig() c.Assert(config.Remotes, HasLen, 0) + c.Assert(config.Branches, HasLen, 0) c.Assert(config.Submodules, HasLen, 0) c.Assert(config.Raw, NotNil) c.Assert(config.Pack.Window, Equals, DefaultPackWindow) diff --git a/config/modules.go b/config/modules.go index b208984..90758d9 100644 --- a/config/modules.go +++ b/config/modules.go @@ -3,6 +3,7 @@ package config import ( "bytes" "errors" + "regexp" format "gopkg.in/src-d/go-git.v4/plumbing/format/config" ) @@ -10,6 +11,12 @@ import ( var ( ErrModuleEmptyURL = errors.New("module config: empty URL") ErrModuleEmptyPath = errors.New("module config: empty path") + ErrModuleBadPath = errors.New("submodule has an invalid path") +) + +var ( + // Matches module paths with dotdot ".." components. + dotdotPath = regexp.MustCompile(`(^|[/\\])\.\.([/\\]|$)`) ) // Modules defines the submodules properties, represents a .gitmodules file @@ -44,14 +51,7 @@ func (m *Modules) Unmarshal(b []byte) error { return err } - s := m.raw.Section(submoduleSection) - for _, sub := range s.Subsections { - mod := &Submodule{} - mod.unmarshal(sub) - - m.Submodules[mod.Path] = mod - } - + unmarshalSubmodules(m.raw, m.Submodules) return nil } @@ -102,6 +102,10 @@ func (m *Submodule) Validate() error { return ErrModuleEmptyURL } + if dotdotPath.MatchString(m.Path) { + return ErrModuleBadPath + } + return nil } diff --git a/config/modules_test.go b/config/modules_test.go index 36cd93f..8e10d70 100644 --- a/config/modules_test.go +++ b/config/modules_test.go @@ -11,6 +11,29 @@ func (s *ModulesSuite) TestValidateMissingURL(c *C) { c.Assert(m.Validate(), Equals, ErrModuleEmptyURL) } +func (s *ModulesSuite) TestValidateBadPath(c *C) { + input := []string{ + `..`, + `../`, + `../bar`, + + `/..`, + `/../bar`, + + `foo/..`, + `foo/../`, + `foo/../bar`, + } + + for _, p := range input { + m := &Submodule{ + Path: p, + URL: "https://example.com/", + } + c.Assert(m.Validate(), Equals, ErrModuleBadPath) + } +} + func (s *ModulesSuite) TestValidateMissingName(c *C) { m := &Submodule{URL: "bar"} c.Assert(m.Validate(), Equals, ErrModuleEmptyPath) @@ -39,6 +62,9 @@ func (s *ModulesSuite) TestUnmarshall(c *C) { path = foo/bar url = https://github.com/foo/bar.git branch = dev +[submodule "suspicious"] + path = ../../foo/bar + url = https://github.com/foo/bar.git `) cfg := NewModules() diff --git a/config/refspec.go b/config/refspec.go index af7e732..391705c 100644 --- a/config/refspec.go +++ b/config/refspec.go @@ -15,7 +15,7 @@ const ( var ( ErrRefSpecMalformedSeparator = errors.New("malformed refspec, separators are wrong") - ErrRefSpecMalformedWildcard = errors.New("malformed refspec, missmatched number of wildcards") + ErrRefSpecMalformedWildcard = errors.New("malformed refspec, mismatched number of wildcards") ) // RefSpec is a mapping from local branches to remote references @@ -62,7 +62,13 @@ func (s RefSpec) IsDelete() bool { // Src return the src side. func (s RefSpec) Src() string { spec := string(s) - start := strings.Index(spec, refSpecForce) + 1 + + var start int + if s.IsForceUpdate() { + start = 1 + } else { + start = 0 + } end := strings.Index(spec, refSpecSeparator) return spec[start:end] diff --git a/config/refspec_test.go b/config/refspec_test.go index 5ee6108..675e075 100644 --- a/config/refspec_test.go +++ b/config/refspec_test.go @@ -62,8 +62,17 @@ func (s *RefSpecSuite) TestRefSpecSrc(c *C) { spec := RefSpec("refs/heads/*:refs/remotes/origin/*") c.Assert(spec.Src(), Equals, "refs/heads/*") + spec = RefSpec("+refs/heads/*:refs/remotes/origin/*") + c.Assert(spec.Src(), Equals, "refs/heads/*") + spec = RefSpec(":refs/heads/master") c.Assert(spec.Src(), Equals, "") + + spec = RefSpec("refs/heads/love+hate:refs/heads/love+hate") + c.Assert(spec.Src(), Equals, "refs/heads/love+hate") + + spec = RefSpec("+refs/heads/love+hate:refs/heads/love+hate") + c.Assert(spec.Src(), Equals, "refs/heads/love+hate") } func (s *RefSpecSuite) TestRefSpecMatch(c *C) { @@ -71,9 +80,19 @@ func (s *RefSpecSuite) TestRefSpecMatch(c *C) { c.Assert(spec.Match(plumbing.ReferenceName("refs/heads/foo")), Equals, false) c.Assert(spec.Match(plumbing.ReferenceName("refs/heads/master")), Equals, true) + spec = RefSpec("+refs/heads/master:refs/remotes/origin/master") + c.Assert(spec.Match(plumbing.ReferenceName("refs/heads/foo")), Equals, false) + c.Assert(spec.Match(plumbing.ReferenceName("refs/heads/master")), Equals, true) + spec = RefSpec(":refs/heads/master") c.Assert(spec.Match(plumbing.ReferenceName("")), Equals, true) c.Assert(spec.Match(plumbing.ReferenceName("refs/heads/master")), Equals, false) + + spec = RefSpec("refs/heads/love+hate:heads/love+hate") + c.Assert(spec.Match(plumbing.ReferenceName("refs/heads/love+hate")), Equals, true) + + spec = RefSpec("+refs/heads/love+hate:heads/love+hate") + c.Assert(spec.Match(plumbing.ReferenceName("refs/heads/love+hate")), Equals, true) } func (s *RefSpecSuite) TestRefSpecMatchGlob(c *C) { |