aboutsummaryrefslogtreecommitdiffstats
path: root/config
diff options
context:
space:
mode:
Diffstat (limited to 'config')
-rw-r--r--config/branch.go71
-rw-r--r--config/branch_test.go76
-rw-r--r--config/config.go83
-rw-r--r--config/config_test.go93
-rw-r--r--config/modules.go20
-rw-r--r--config/modules_test.go26
-rw-r--r--config/refspec.go10
-rw-r--r--config/refspec_test.go19
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) {