aboutsummaryrefslogtreecommitdiffstats
path: root/config
diff options
context:
space:
mode:
authorJeremy Chambers <jeremy@thehipbot.com>2018-04-07 14:34:39 -0500
committerJeremy Chambers <jeremy@thehipbot.com>2018-04-10 19:43:38 -0500
commit02335b10dee417d0338bf6ea070feeead18e636b (patch)
tree073ed50a360ce4a91ad3895d949c6ffccf24d9bb /config
parentc4ace4d53535d00899503bfaedc6e9709e3aff0a (diff)
downloadgo-git-02335b10dee417d0338bf6ea070feeead18e636b.tar.gz
config: adds branches to config for tracking branches against remotes, updates clone to track when cloning a branch. Fixes #313
Signed-off-by: Jeremy Chambers <jeremy@thehipbot.com>
Diffstat (limited to 'config')
-rw-r--r--config/branch.go71
-rw-r--r--config/branch_test.go76
-rw-r--r--config/config.go66
-rw-r--r--config/config_test.go76
4 files changed, 284 insertions, 5 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..c730015 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")
@@ -55,7 +55,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 +69,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,12 +90,23 @@ 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"
@@ -100,6 +114,7 @@ const (
bareKey = "bare"
worktreeKey = "worktree"
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.
@@ -121,6 +136,11 @@ func (c *Config) Unmarshal(b []byte) error {
return err
}
c.unmarshalSubmodules()
+
+ if err := c.unmarshalBranches(); err != nil {
+ return err
+ }
+
return c.unmarshalRemotes()
}
@@ -172,12 +192,27 @@ func (c *Config) unmarshalSubmodules() {
}
}
+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 +280,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..5cd713e 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{}
@@ -47,7 +50,8 @@ func (s *ConfigSuite) TestUnmarshall(c *C) {
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) {
@@ -65,6 +69,9 @@ func (s *ConfigSuite) TestMarshall(c *C) {
url = git@github.com:mcuadros/go-git.git
[submodule "qux"]
url = https://github.com/foo/qux.git
+[branch "master"]
+ remote = origin
+ merge = refs/heads/master
`)
cfg := NewConfig()
@@ -87,6 +94,12 @@ func (s *ConfigSuite) TestMarshall(c *C) {
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)
@@ -118,6 +131,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 +164,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 +193,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)