fork of go-git with some jj specific features
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4.4.0 392 lines 9.1 kB view raw
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}