| // Package config contains the abstraction of multiple config files |
| package config |
| |
| import ( |
| "bytes" |
| "errors" |
| "fmt" |
| "sort" |
| "strconv" |
| |
| "gopkg.in/src-d/go-git.v4/internal/url" |
| format "gopkg.in/src-d/go-git.v4/plumbing/format/config" |
| ) |
| |
| const ( |
| // DefaultFetchRefSpec is the default refspec used for fetch. |
| DefaultFetchRefSpec = "+refs/heads/*:refs/remotes/%s/*" |
| // DefaultPushRefSpec is the default refspec used for push. |
| DefaultPushRefSpec = "refs/heads/*:refs/heads/*" |
| ) |
| |
| // ConfigStorer generic storage of Config object |
| type ConfigStorer interface { |
| Config() (*Config, error) |
| SetConfig(*Config) error |
| } |
| |
| var ( |
| 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") |
| ) |
| |
| // Config contains the repository configuration |
| // ftp://www.kernel.org/pub/software/scm/git/docs/git-config.html#FILES |
| type Config struct { |
| Core struct { |
| // IsBare if true this repository is assumed to be bare and has no |
| // working directory associated with it. |
| 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 { |
| // Window controls the size of the sliding window for delta |
| // compression. The default is 10. A value of 0 turns off |
| // delta compression entirely. |
| Window uint |
| } |
| |
| // Remotes list of repository remotes, the key of the map is the name |
| // of the remote, should equal to RemoteConfig.Name. |
| Remotes map[string]*RemoteConfig |
| // 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. |
| Raw *format.Config |
| } |
| |
| // NewConfig returns a new empty Config. |
| func NewConfig() *Config { |
| config := &Config{ |
| Remotes: make(map[string]*RemoteConfig), |
| Submodules: make(map[string]*Submodule), |
| Branches: make(map[string]*Branch), |
| Raw: format.New(), |
| } |
| |
| config.Pack.Window = DefaultPackWindow |
| |
| return config |
| } |
| |
| // Validate validates the fields and sets the default values. |
| func (c *Config) Validate() error { |
| for name, r := range c.Remotes { |
| if r.Name != name { |
| return ErrInvalid |
| } |
| |
| if err := r.Validate(); err != nil { |
| return err |
| } |
| } |
| |
| 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. |
| DefaultPackWindow = uint(10) |
| ) |
| |
| // Unmarshal parses a git-config file and stores it. |
| func (c *Config) Unmarshal(b []byte) error { |
| r := bytes.NewBuffer(b) |
| d := format.NewDecoder(r) |
| |
| c.Raw = format.New() |
| if err := d.Decode(c.Raw); err != nil { |
| return err |
| } |
| |
| c.unmarshalCore() |
| if err := c.unmarshalPack(); err != nil { |
| return err |
| } |
| unmarshalSubmodules(c.Raw, c.Submodules) |
| |
| if err := c.unmarshalBranches(); err != nil { |
| return err |
| } |
| |
| return c.unmarshalRemotes() |
| } |
| |
| func (c *Config) unmarshalCore() { |
| s := c.Raw.Section(coreSection) |
| if s.Options.Get(bareKey) == "true" { |
| c.Core.IsBare = true |
| } |
| |
| c.Core.Worktree = s.Options.Get(worktreeKey) |
| c.Core.CommentChar = s.Options.Get(commentCharKey) |
| } |
| |
| func (c *Config) unmarshalPack() error { |
| s := c.Raw.Section(packSection) |
| window := s.Options.Get(windowKey) |
| if window == "" { |
| c.Pack.Window = DefaultPackWindow |
| } else { |
| winUint, err := strconv.ParseUint(window, 10, 32) |
| if err != nil { |
| return err |
| } |
| c.Pack.Window = uint(winUint) |
| } |
| return nil |
| } |
| |
| func (c *Config) unmarshalRemotes() error { |
| s := c.Raw.Section(remoteSection) |
| for _, sub := range s.Subsections { |
| r := &RemoteConfig{} |
| if err := r.unmarshal(sub); err != nil { |
| return err |
| } |
| |
| c.Remotes[r.Name] = r |
| } |
| |
| return nil |
| } |
| |
| func unmarshalSubmodules(fc *format.Config, submodules map[string]*Submodule) { |
| s := fc.Section(submoduleSection) |
| for _, sub := range s.Subsections { |
| m := &Submodule{} |
| m.unmarshal(sub) |
| |
| 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 { |
| return nil, err |
| } |
| |
| return buf.Bytes(), nil |
| } |
| |
| func (c *Config) marshalCore() { |
| s := c.Raw.Section(coreSection) |
| s.SetOption(bareKey, fmt.Sprintf("%t", c.Core.IsBare)) |
| |
| if c.Core.Worktree != "" { |
| s.SetOption(worktreeKey, c.Core.Worktree) |
| } |
| } |
| |
| func (c *Config) marshalPack() { |
| s := c.Raw.Section(packSection) |
| if c.Pack.Window != DefaultPackWindow { |
| s.SetOption(windowKey, fmt.Sprintf("%d", c.Pack.Window)) |
| } |
| } |
| |
| func (c *Config) marshalRemotes() { |
| s := c.Raw.Section(remoteSection) |
| newSubsections := make(format.Subsections, 0, len(c.Remotes)) |
| added := make(map[string]bool) |
| for _, subsection := range s.Subsections { |
| if remote, ok := c.Remotes[subsection.Name]; ok { |
| newSubsections = append(newSubsections, remote.marshal()) |
| added[subsection.Name] = true |
| } |
| } |
| |
| remoteNames := make([]string, 0, len(c.Remotes)) |
| for name := range c.Remotes { |
| remoteNames = append(remoteNames, name) |
| } |
| |
| sort.Strings(remoteNames) |
| |
| for _, name := range remoteNames { |
| if !added[name] { |
| newSubsections = append(newSubsections, c.Remotes[name].marshal()) |
| } |
| } |
| |
| s.Subsections = newSubsections |
| } |
| |
| func (c *Config) marshalSubmodules() { |
| s := c.Raw.Section(submoduleSection) |
| s.Subsections = make(format.Subsections, len(c.Submodules)) |
| |
| var i int |
| for _, r := range c.Submodules { |
| section := r.marshal() |
| // the submodule section at config is a subset of the .gitmodule file |
| // we should remove the non-valid options for the config file. |
| section.RemoveOption(pathKey) |
| s.Subsections[i] = section |
| i++ |
| } |
| } |
| |
| 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 |
| Name string |
| // URLs the URLs of a remote repository. It must be non-empty. Fetch will |
| // always use the first URL, while push will use all of them. |
| URLs []string |
| // Fetch the default set of "refspec" for fetch operation |
| Fetch []RefSpec |
| |
| // raw representation of the subsection, filled by marshal or unmarshal are |
| // called |
| raw *format.Subsection |
| } |
| |
| // Validate validates the fields and sets the default values. |
| func (c *RemoteConfig) Validate() error { |
| if c.Name == "" { |
| return ErrRemoteConfigEmptyName |
| } |
| |
| if len(c.URLs) == 0 { |
| return ErrRemoteConfigEmptyURL |
| } |
| |
| for _, r := range c.Fetch { |
| if err := r.Validate(); err != nil { |
| return err |
| } |
| } |
| |
| if len(c.Fetch) == 0 { |
| c.Fetch = []RefSpec{RefSpec(fmt.Sprintf(DefaultFetchRefSpec, c.Name))} |
| } |
| |
| return nil |
| } |
| |
| func (c *RemoteConfig) unmarshal(s *format.Subsection) error { |
| c.raw = s |
| |
| fetch := []RefSpec{} |
| for _, f := range c.raw.Options.GetAll(fetchKey) { |
| rs := RefSpec(f) |
| if err := rs.Validate(); err != nil { |
| return err |
| } |
| |
| fetch = append(fetch, rs) |
| } |
| |
| c.Name = c.raw.Name |
| c.URLs = append([]string(nil), c.raw.Options.GetAll(urlKey)...) |
| c.Fetch = fetch |
| |
| return nil |
| } |
| |
| func (c *RemoteConfig) marshal() *format.Subsection { |
| if c.raw == nil { |
| c.raw = &format.Subsection{} |
| } |
| |
| c.raw.Name = c.Name |
| if len(c.URLs) == 0 { |
| c.raw.RemoveOption(urlKey) |
| } else { |
| c.raw.SetOption(urlKey, c.URLs...) |
| } |
| |
| if len(c.Fetch) == 0 { |
| c.raw.RemoveOption(fetchKey) |
| } else { |
| var values []string |
| for _, rs := range c.Fetch { |
| values = append(values, rs.String()) |
| } |
| |
| c.raw.SetOption(fetchKey, values...) |
| } |
| |
| return c.raw |
| } |
| |
| func (c *RemoteConfig) IsFirstURLLocal() bool { |
| return url.IsLocalEndpoint(c.URLs[0]) |
| } |