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.

config.go 9.4KB

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