fork of go-git with some jj specific features
1// Package config contains the abstraction of multiple config files
2package config
3
4import (
5 "bytes"
6 "errors"
7 "fmt"
8 "sort"
9 "strconv"
10
11 format "gopkg.in/src-d/go-git.v4/plumbing/format/config"
12)
13
14const (
15 // DefaultFetchRefSpec is the default refspec used for fetch.
16 DefaultFetchRefSpec = "+refs/heads/*:refs/remotes/%s/*"
17 // DefaultPushRefSpec is the default refspec used for push.
18 DefaultPushRefSpec = "refs/heads/*:refs/heads/*"
19)
20
21// ConfigStorer generic storage of Config object
22type ConfigStorer interface {
23 Config() (*Config, error)
24 SetConfig(*Config) error
25}
26
27var (
28 ErrInvalid = errors.New("config invalid key in remote or branch")
29 ErrRemoteConfigNotFound = errors.New("remote config not found")
30 ErrRemoteConfigEmptyURL = errors.New("remote config: empty URL")
31 ErrRemoteConfigEmptyName = errors.New("remote config: empty name")
32)
33
34// Config contains the repository configuration
35// ftp://www.kernel.org/pub/software/scm/git/docs/git-config.html#FILES
36type Config struct {
37 Core struct {
38 // IsBare if true this repository is assumed to be bare and has no
39 // working directory associated with it.
40 IsBare bool
41 // Worktree is the path to the root of the working tree.
42 Worktree string
43 }
44
45 Pack struct {
46 // Window controls the size of the sliding window for delta
47 // compression. The default is 10. A value of 0 turns off
48 // delta compression entirely.
49 Window uint
50 }
51
52 // Remotes list of repository remotes, the key of the map is the name
53 // of the remote, should equal to RemoteConfig.Name.
54 Remotes map[string]*RemoteConfig
55 // Submodules list of repository submodules, the key of the map is the name
56 // of the submodule, should equal to Submodule.Name.
57 Submodules map[string]*Submodule
58 // Branches list of branches, the key is the branch name and should
59 // equal Branch.Name
60 Branches map[string]*Branch
61 // Raw contains the raw information of a config file. The main goal is
62 // preserve the parsed information from the original format, to avoid
63 // dropping unsupported fields.
64 Raw *format.Config
65}
66
67// NewConfig returns a new empty Config.
68func NewConfig() *Config {
69 config := &Config{
70 Remotes: make(map[string]*RemoteConfig),
71 Submodules: make(map[string]*Submodule),
72 Branches: make(map[string]*Branch),
73 Raw: format.New(),
74 }
75
76 config.Pack.Window = DefaultPackWindow
77
78 return config
79}
80
81// Validate validates the fields and sets the default values.
82func (c *Config) Validate() error {
83 for name, r := range c.Remotes {
84 if r.Name != name {
85 return ErrInvalid
86 }
87
88 if err := r.Validate(); err != nil {
89 return err
90 }
91 }
92
93 for name, b := range c.Branches {
94 if b.Name != name {
95 return ErrInvalid
96 }
97
98 if err := b.Validate(); err != nil {
99 return err
100 }
101 }
102
103 return nil
104}
105
106const (
107 remoteSection = "remote"
108 submoduleSection = "submodule"
109 branchSection = "branch"
110 coreSection = "core"
111 packSection = "pack"
112 fetchKey = "fetch"
113 urlKey = "url"
114 bareKey = "bare"
115 worktreeKey = "worktree"
116 windowKey = "window"
117 mergeKey = "merge"
118
119 // DefaultPackWindow holds the number of previous objects used to
120 // generate deltas. The value 10 is the same used by git command.
121 DefaultPackWindow = uint(10)
122)
123
124// Unmarshal parses a git-config file and stores it.
125func (c *Config) Unmarshal(b []byte) error {
126 r := bytes.NewBuffer(b)
127 d := format.NewDecoder(r)
128
129 c.Raw = format.New()
130 if err := d.Decode(c.Raw); err != nil {
131 return err
132 }
133
134 c.unmarshalCore()
135 if err := c.unmarshalPack(); err != nil {
136 return err
137 }
138 c.unmarshalSubmodules()
139
140 if err := c.unmarshalBranches(); err != nil {
141 return err
142 }
143
144 return c.unmarshalRemotes()
145}
146
147func (c *Config) unmarshalCore() {
148 s := c.Raw.Section(coreSection)
149 if s.Options.Get(bareKey) == "true" {
150 c.Core.IsBare = true
151 }
152
153 c.Core.Worktree = s.Options.Get(worktreeKey)
154}
155
156func (c *Config) unmarshalPack() error {
157 s := c.Raw.Section(packSection)
158 window := s.Options.Get(windowKey)
159 if window == "" {
160 c.Pack.Window = DefaultPackWindow
161 } else {
162 winUint, err := strconv.ParseUint(window, 10, 32)
163 if err != nil {
164 return err
165 }
166 c.Pack.Window = uint(winUint)
167 }
168 return nil
169}
170
171func (c *Config) unmarshalRemotes() error {
172 s := c.Raw.Section(remoteSection)
173 for _, sub := range s.Subsections {
174 r := &RemoteConfig{}
175 if err := r.unmarshal(sub); err != nil {
176 return err
177 }
178
179 c.Remotes[r.Name] = r
180 }
181
182 return nil
183}
184
185func (c *Config) unmarshalSubmodules() {
186 s := c.Raw.Section(submoduleSection)
187 for _, sub := range s.Subsections {
188 m := &Submodule{}
189 m.unmarshal(sub)
190
191 c.Submodules[m.Name] = m
192 }
193}
194
195func (c *Config) unmarshalBranches() error {
196 bs := c.Raw.Section(branchSection)
197 for _, sub := range bs.Subsections {
198 b := &Branch{}
199
200 if err := b.unmarshal(sub); err != nil {
201 return err
202 }
203
204 c.Branches[b.Name] = b
205 }
206 return nil
207}
208
209// Marshal returns Config encoded as a git-config file.
210func (c *Config) Marshal() ([]byte, error) {
211 c.marshalCore()
212 c.marshalPack()
213 c.marshalRemotes()
214 c.marshalSubmodules()
215 c.marshalBranches()
216
217 buf := bytes.NewBuffer(nil)
218 if err := format.NewEncoder(buf).Encode(c.Raw); err != nil {
219 return nil, err
220 }
221
222 return buf.Bytes(), nil
223}
224
225func (c *Config) marshalCore() {
226 s := c.Raw.Section(coreSection)
227 s.SetOption(bareKey, fmt.Sprintf("%t", c.Core.IsBare))
228
229 if c.Core.Worktree != "" {
230 s.SetOption(worktreeKey, c.Core.Worktree)
231 }
232}
233
234func (c *Config) marshalPack() {
235 s := c.Raw.Section(packSection)
236 if c.Pack.Window != DefaultPackWindow {
237 s.SetOption(windowKey, fmt.Sprintf("%d", c.Pack.Window))
238 }
239}
240
241func (c *Config) marshalRemotes() {
242 s := c.Raw.Section(remoteSection)
243 newSubsections := make(format.Subsections, 0, len(c.Remotes))
244 added := make(map[string]bool)
245 for _, subsection := range s.Subsections {
246 if remote, ok := c.Remotes[subsection.Name]; ok {
247 newSubsections = append(newSubsections, remote.marshal())
248 added[subsection.Name] = true
249 }
250 }
251
252 remoteNames := make([]string, 0, len(c.Remotes))
253 for name := range c.Remotes {
254 remoteNames = append(remoteNames, name)
255 }
256
257 sort.Strings(remoteNames)
258
259 for _, name := range remoteNames {
260 if !added[name] {
261 newSubsections = append(newSubsections, c.Remotes[name].marshal())
262 }
263 }
264
265 s.Subsections = newSubsections
266}
267
268func (c *Config) marshalSubmodules() {
269 s := c.Raw.Section(submoduleSection)
270 s.Subsections = make(format.Subsections, len(c.Submodules))
271
272 var i int
273 for _, r := range c.Submodules {
274 section := r.marshal()
275 // the submodule section at config is a subset of the .gitmodule file
276 // we should remove the non-valid options for the config file.
277 section.RemoveOption(pathKey)
278 s.Subsections[i] = section
279 i++
280 }
281}
282
283func (c *Config) marshalBranches() {
284 s := c.Raw.Section(branchSection)
285 newSubsections := make(format.Subsections, 0, len(c.Branches))
286 added := make(map[string]bool)
287 for _, subsection := range s.Subsections {
288 if branch, ok := c.Branches[subsection.Name]; ok {
289 newSubsections = append(newSubsections, branch.marshal())
290 added[subsection.Name] = true
291 }
292 }
293
294 branchNames := make([]string, 0, len(c.Branches))
295 for name := range c.Branches {
296 branchNames = append(branchNames, name)
297 }
298
299 sort.Strings(branchNames)
300
301 for _, name := range branchNames {
302 if !added[name] {
303 newSubsections = append(newSubsections, c.Branches[name].marshal())
304 }
305 }
306
307 s.Subsections = newSubsections
308}
309
310// RemoteConfig contains the configuration for a given remote repository.
311type RemoteConfig struct {
312 // Name of the remote
313 Name string
314 // URLs the URLs of a remote repository. It must be non-empty. Fetch will
315 // always use the first URL, while push will use all of them.
316 URLs []string
317 // Fetch the default set of "refspec" for fetch operation
318 Fetch []RefSpec
319
320 // raw representation of the subsection, filled by marshal or unmarshal are
321 // called
322 raw *format.Subsection
323}
324
325// Validate validates the fields and sets the default values.
326func (c *RemoteConfig) Validate() error {
327 if c.Name == "" {
328 return ErrRemoteConfigEmptyName
329 }
330
331 if len(c.URLs) == 0 {
332 return ErrRemoteConfigEmptyURL
333 }
334
335 for _, r := range c.Fetch {
336 if err := r.Validate(); err != nil {
337 return err
338 }
339 }
340
341 if len(c.Fetch) == 0 {
342 c.Fetch = []RefSpec{RefSpec(fmt.Sprintf(DefaultFetchRefSpec, c.Name))}
343 }
344
345 return nil
346}
347
348func (c *RemoteConfig) unmarshal(s *format.Subsection) error {
349 c.raw = s
350
351 fetch := []RefSpec{}
352 for _, f := range c.raw.Options.GetAll(fetchKey) {
353 rs := RefSpec(f)
354 if err := rs.Validate(); err != nil {
355 return err
356 }
357
358 fetch = append(fetch, rs)
359 }
360
361 c.Name = c.raw.Name
362 c.URLs = append([]string(nil), c.raw.Options.GetAll(urlKey)...)
363 c.Fetch = fetch
364
365 return nil
366}
367
368func (c *RemoteConfig) marshal() *format.Subsection {
369 if c.raw == nil {
370 c.raw = &format.Subsection{}
371 }
372
373 c.raw.Name = c.Name
374 if len(c.URLs) == 0 {
375 c.raw.RemoveOption(urlKey)
376 } else {
377 c.raw.SetOption(urlKey, c.URLs...)
378 }
379
380 if len(c.Fetch) == 0 {
381 c.raw.RemoveOption(fetchKey)
382 } else {
383 var values []string
384 for _, rs := range c.Fetch {
385 values = append(values, rs.String())
386 }
387
388 c.raw.SetOption(fetchKey, values...)
389 }
390
391 return c.raw
392}