Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit aabcd10

Browse files
committed
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 <[email protected]>
1 parent c4ace4d commit aabcd10

File tree

6 files changed

+442
-3
lines changed

6 files changed

+442
-3
lines changed

config/branch.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package config
2+
3+
import (
4+
"errors"
5+
"strings"
6+
7+
format "gopkg.in/src-d/go-git.v4/plumbing/format/config"
8+
)
9+
10+
var (
11+
errBranchEmptyName = errors.New("branch config: empty name")
12+
errBranchInvalidRemote = errors.New("branch config: invalid remote")
13+
)
14+
15+
// Branch contains information on the
16+
// local branches and which remote to track
17+
type Branch struct {
18+
// Name name of branch
19+
Name string
20+
// Remote name of remote to track
21+
Remote string
22+
// Merge is the local refspec for the branch
23+
Merge string
24+
25+
raw *format.Subsection
26+
}
27+
28+
// Validate validates fields of branch
29+
func (b *Branch) Validate() error {
30+
if b.Name == "" {
31+
return errBranchEmptyName
32+
}
33+
34+
if b.Merge != "" && !strings.HasPrefix(b.Merge, "refs/heads/") {
35+
return errBranchInvalidRemote
36+
}
37+
38+
return nil
39+
}
40+
41+
func (b *Branch) marshal() *format.Subsection {
42+
if b.raw == nil {
43+
b.raw = &format.Subsection{}
44+
}
45+
46+
b.raw.Name = b.Name
47+
48+
if b.Remote == "" {
49+
b.raw.RemoveOption(remoteSection)
50+
} else {
51+
b.raw.SetOption(remoteSection, b.Remote)
52+
}
53+
54+
if b.Merge == "" {
55+
b.raw.RemoveOption(mergeKey)
56+
} else {
57+
b.raw.SetOption(mergeKey, b.Merge)
58+
}
59+
60+
return b.raw
61+
}
62+
63+
func (b *Branch) unmarshal(s *format.Subsection) error {
64+
b.raw = s
65+
66+
b.Name = b.raw.Name
67+
b.Remote = b.raw.Options.Get(remoteSection)
68+
b.Merge = b.raw.Options.Get(mergeKey)
69+
70+
return b.Validate()
71+
}

config/branch_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package config
2+
3+
import . "gopkg.in/check.v1"
4+
5+
type BranchSuite struct{}
6+
7+
var _ = Suite(&BranchSuite{})
8+
9+
func (b *BranchSuite) TestValidateName(c *C) {
10+
goodBranch := Branch{
11+
Name: "master",
12+
Remote: "some_remote",
13+
Merge: "refs/heads/master",
14+
}
15+
badBranch := Branch{
16+
Remote: "some_remote",
17+
Merge: "refs/heads/master",
18+
}
19+
c.Assert(goodBranch.Validate(), IsNil)
20+
c.Assert(badBranch.Validate(), NotNil)
21+
}
22+
23+
func (b *BranchSuite) TestValidateMerge(c *C) {
24+
goodBranch := Branch{
25+
Name: "master",
26+
Remote: "some_remote",
27+
Merge: "refs/heads/master",
28+
}
29+
badBranch := Branch{
30+
Name: "master",
31+
Remote: "some_remote",
32+
Merge: "blah",
33+
}
34+
c.Assert(goodBranch.Validate(), IsNil)
35+
c.Assert(badBranch.Validate(), NotNil)
36+
}
37+
38+
func (b *BranchSuite) TestMarshall(c *C) {
39+
expected := []byte(`[core]
40+
bare = false
41+
[branch "branch-tracking-on-clone"]
42+
remote = fork
43+
merge = refs/heads/branch-tracking-on-clone
44+
`)
45+
46+
cfg := NewConfig()
47+
cfg.Branches["branch-tracking-on-clone"] = &Branch{
48+
Name: "branch-tracking-on-clone",
49+
Remote: "fork",
50+
Merge: "refs/heads/branch-tracking-on-clone",
51+
}
52+
53+
actual, err := cfg.Marshal()
54+
c.Assert(err, IsNil)
55+
c.Assert(string(actual), Equals, string(expected))
56+
}
57+
58+
func (b *BranchSuite) TestUnmarshall(c *C) {
59+
input := []byte(`[core]
60+
bare = false
61+
[branch "branch-tracking-on-clone"]
62+
remote = fork
63+
merge = refs/heads/branch-tracking-on-clone
64+
`)
65+
66+
cfg := NewConfig()
67+
err := cfg.Unmarshal(input)
68+
c.Assert(err, IsNil)
69+
branch := cfg.Branches["branch-tracking-on-clone"]
70+
c.Assert(branch.Name, Equals, "branch-tracking-on-clone")
71+
c.Assert(branch.Remote, Equals, "fork")
72+
c.Assert(branch.Merge, Equals, "refs/heads/branch-tracking-on-clone")
73+
}

config/config.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ type Config struct {
5656
// of the submodule, should equal to Submodule.Name.
5757
Submodules map[string]*Submodule
5858

59+
// Branches list of branches, the key is the branch name and should
60+
// equal Branch.Name
61+
Branches map[string]*Branch
62+
5963
// Raw contains the raw information of a config file. The main goal is
6064
// preserve the parsed information from the original format, to avoid
6165
// dropping unsupported fields.
@@ -67,6 +71,7 @@ func NewConfig() *Config {
6771
config := &Config{
6872
Remotes: make(map[string]*RemoteConfig),
6973
Submodules: make(map[string]*Submodule),
74+
Branches: make(map[string]*Branch),
7075
Raw: format.New(),
7176
}
7277

@@ -87,19 +92,31 @@ func (c *Config) Validate() error {
8792
}
8893
}
8994

95+
for name, b := range c.Branches {
96+
if b.Name != name {
97+
return ErrInvalid
98+
}
99+
100+
if err := b.Validate(); err != nil {
101+
return err
102+
}
103+
}
104+
90105
return nil
91106
}
92107

93108
const (
94109
remoteSection = "remote"
95110
submoduleSection = "submodule"
111+
branchSection = "branch"
96112
coreSection = "core"
97113
packSection = "pack"
98114
fetchKey = "fetch"
99115
urlKey = "url"
100116
bareKey = "bare"
101117
worktreeKey = "worktree"
102118
windowKey = "window"
119+
mergeKey = "merge"
103120

104121
// DefaultPackWindow holds the number of previous objects used to
105122
// generate deltas. The value 10 is the same used by git command.
@@ -121,6 +138,11 @@ func (c *Config) Unmarshal(b []byte) error {
121138
return err
122139
}
123140
c.unmarshalSubmodules()
141+
142+
if err := c.unmarshalBranches(); err != nil {
143+
return err
144+
}
145+
124146
return c.unmarshalRemotes()
125147
}
126148

@@ -172,12 +194,27 @@ func (c *Config) unmarshalSubmodules() {
172194
}
173195
}
174196

197+
func (c *Config) unmarshalBranches() error {
198+
bs := c.Raw.Section(branchSection)
199+
for _, sub := range bs.Subsections {
200+
b := &Branch{}
201+
202+
if err := b.unmarshal(sub); err != nil {
203+
return err
204+
}
205+
206+
c.Branches[b.Name] = b
207+
}
208+
return nil
209+
}
210+
175211
// Marshal returns Config encoded as a git-config file.
176212
func (c *Config) Marshal() ([]byte, error) {
177213
c.marshalCore()
178214
c.marshalPack()
179215
c.marshalRemotes()
180216
c.marshalSubmodules()
217+
c.marshalBranches()
181218

182219
buf := bytes.NewBuffer(nil)
183220
if err := format.NewEncoder(buf).Encode(c.Raw); err != nil {
@@ -245,6 +282,33 @@ func (c *Config) marshalSubmodules() {
245282
}
246283
}
247284

285+
func (c *Config) marshalBranches() {
286+
s := c.Raw.Section(branchSection)
287+
newSubsections := make(format.Subsections, 0, len(c.Branches))
288+
added := make(map[string]bool)
289+
for _, subsection := range s.Subsections {
290+
if branch, ok := c.Branches[subsection.Name]; ok {
291+
newSubsections = append(newSubsections, branch.marshal())
292+
added[subsection.Name] = true
293+
}
294+
}
295+
296+
branchNames := make([]string, 0, len(c.Branches))
297+
for name := range c.Branches {
298+
branchNames = append(branchNames, name)
299+
}
300+
301+
sort.Strings(branchNames)
302+
303+
for _, name := range branchNames {
304+
if !added[name] {
305+
newSubsections = append(newSubsections, c.Branches[name].marshal())
306+
}
307+
}
308+
309+
s.Subsections = newSubsections
310+
}
311+
248312
// RemoteConfig contains the configuration for a given remote repository.
249313
type RemoteConfig struct {
250314
// Name of the remote

config/config_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ func (s *ConfigSuite) TestUnmarshall(c *C) {
4747
c.Assert(cfg.Submodules["qux"].Name, Equals, "qux")
4848
c.Assert(cfg.Submodules["qux"].URL, Equals, "https://github.com/foo/qux.git")
4949
c.Assert(cfg.Submodules["qux"].Branch, Equals, "bar")
50-
50+
c.Assert(cfg.Branches["master"].Remote, Equals, "origin")
51+
c.Assert(cfg.Branches["master"].Merge, Equals, "refs/heads/master")
5152
}
5253

5354
func (s *ConfigSuite) TestMarshall(c *C) {
@@ -65,6 +66,9 @@ func (s *ConfigSuite) TestMarshall(c *C) {
6566
url = [email protected]:mcuadros/go-git.git
6667
[submodule "qux"]
6768
url = https://github.com/foo/qux.git
69+
[branch "master"]
70+
remote = origin
71+
merge = refs/heads/master
6872
`)
6973

7074
cfg := NewConfig()
@@ -87,6 +91,12 @@ func (s *ConfigSuite) TestMarshall(c *C) {
8791
URL: "https://github.com/foo/qux.git",
8892
}
8993

94+
cfg.Branches["master"] = &Branch{
95+
Name: "master",
96+
Remote: "origin",
97+
Merge: "refs/heads/master",
98+
}
99+
90100
b, err := cfg.Marshal()
91101
c.Assert(err, IsNil)
92102

0 commit comments

Comments
 (0)