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

config: adds branches to config for tracking branches against remotes… #803

Merged
merged 1 commit into from
Apr 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not covered by tests.

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