Skip to content

Commit

Permalink
Merge pull request src-d#803 from TheHipbot/branch-tracking-on-clone
Browse files Browse the repository at this point in the history
config: adds branches to config for tracking branches against remotes…
  • Loading branch information
mcuadros authored Apr 11, 2018
2 parents 4099191 + 02335b1 commit 0db54e8
Show file tree
Hide file tree
Showing 6 changed files with 507 additions and 7 deletions.
71 changes: 71 additions & 0 deletions config/branch.go
Original file line number Diff line number Diff line change
@@ -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()
}
76 changes: 76 additions & 0 deletions config/branch_test.go
Original file line number Diff line number Diff line change
@@ -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"))
}
66 changes: 64 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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.
Expand All @@ -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(),
}

Expand All @@ -87,19 +90,31 @@ 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"
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.
Expand All @@ -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()
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 0db54e8

Please sign in to comment.