You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

401 lines
9.2 KiB

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