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 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. // Package config contains the abstraction of multiple config files
  2. package config
  3. import (
  4. "bytes"
  5. "errors"
  6. "fmt"
  7. "io"
  8. "io/ioutil"
  9. "os"
  10. "path/filepath"
  11. "sort"
  12. "strconv"
  13. "github.com/go-git/go-git/v5/internal/url"
  14. format "github.com/go-git/go-git/v5/plumbing/format/config"
  15. "github.com/mitchellh/go-homedir"
  16. )
  17. const (
  18. // DefaultFetchRefSpec is the default refspec used for fetch.
  19. DefaultFetchRefSpec = "+refs/heads/*:refs/remotes/%s/*"
  20. // DefaultPushRefSpec is the default refspec used for push.
  21. DefaultPushRefSpec = "refs/heads/*:refs/heads/*"
  22. )
  23. // ConfigStorer generic storage of Config object
  24. type ConfigStorer interface {
  25. Config() (*Config, error)
  26. SetConfig(*Config) error
  27. }
  28. var (
  29. ErrInvalid = errors.New("config invalid key in remote or branch")
  30. ErrRemoteConfigNotFound = errors.New("remote config not found")
  31. ErrRemoteConfigEmptyURL = errors.New("remote config: empty URL")
  32. ErrRemoteConfigEmptyName = errors.New("remote config: empty name")
  33. )
  34. // Scope defines the scope of a config file, such as local, global or system.
  35. type Scope int
  36. // Available ConfigScope's
  37. const (
  38. LocalScope Scope = iota
  39. GlobalScope
  40. SystemScope
  41. )
  42. // Config contains the repository configuration
  43. // https://www.kernel.org/pub/software/scm/git/docs/git-config.html#FILES
  44. type Config struct {
  45. Core struct {
  46. // IsBare if true this repository is assumed to be bare and has no
  47. // working directory associated with it.
  48. IsBare bool
  49. // Worktree is the path to the root of the working tree.
  50. Worktree string
  51. // CommentChar is the character indicating the start of a
  52. // comment for commands like commit and tag
  53. CommentChar string
  54. }
  55. User struct {
  56. // Name is the personal name of the author and the commiter of a commit.
  57. Name string
  58. // Email is the email of the author and the commiter of a commit.
  59. Email string
  60. }
  61. Author struct {
  62. // Name is the personal name of the author of a commit.
  63. Name string
  64. // Email is the email of the author of a commit.
  65. Email string
  66. }
  67. Committer struct {
  68. // Name is the personal name of the commiter of a commit.
  69. Name string
  70. // Email is the email of the the commiter of a commit.
  71. Email string
  72. }
  73. Pack struct {
  74. // Window controls the size of the sliding window for delta
  75. // compression. The default is 10. A value of 0 turns off
  76. // delta compression entirely.
  77. Window uint
  78. }
  79. // Remotes list of repository remotes, the key of the map is the name
  80. // of the remote, should equal to RemoteConfig.Name.
  81. Remotes map[string]*RemoteConfig
  82. // Submodules list of repository submodules, the key of the map is the name
  83. // of the submodule, should equal to Submodule.Name.
  84. Submodules map[string]*Submodule
  85. // Branches list of branches, the key is the branch name and should
  86. // equal Branch.Name
  87. Branches map[string]*Branch
  88. // Raw contains the raw information of a config file. The main goal is
  89. // preserve the parsed information from the original format, to avoid
  90. // dropping unsupported fields.
  91. Raw *format.Config
  92. }
  93. // NewConfig returns a new empty Config.
  94. func NewConfig() *Config {
  95. config := &Config{
  96. Remotes: make(map[string]*RemoteConfig),
  97. Submodules: make(map[string]*Submodule),
  98. Branches: make(map[string]*Branch),
  99. Raw: format.New(),
  100. }
  101. config.Pack.Window = DefaultPackWindow
  102. return config
  103. }
  104. // ReadConfig reads a config file from a io.Reader.
  105. func ReadConfig(r io.Reader) (*Config, error) {
  106. b, err := ioutil.ReadAll(r)
  107. if err != nil {
  108. return nil, err
  109. }
  110. cfg := NewConfig()
  111. if err = cfg.Unmarshal(b); err != nil {
  112. return nil, err
  113. }
  114. return cfg, nil
  115. }
  116. // LoadConfig loads a config file from a given scope. The returned Config,
  117. // contains exclusively information fom the given scope. If couldn't find a
  118. // config file to the given scope, a empty one is returned.
  119. func LoadConfig(scope Scope) (*Config, error) {
  120. if scope == LocalScope {
  121. return nil, fmt.Errorf("LocalScope should be read from the a ConfigStorer.")
  122. }
  123. files, err := Paths(scope)
  124. if err != nil {
  125. return nil, err
  126. }
  127. for _, file := range files {
  128. f, err := os.Open(file)
  129. if err != nil {
  130. if os.IsNotExist(err) {
  131. continue
  132. }
  133. return nil, err
  134. }
  135. defer f.Close()
  136. return ReadConfig(f)
  137. }
  138. return NewConfig(), nil
  139. }
  140. // Paths returns the config file location for a given scope.
  141. func Paths(scope Scope) ([]string, error) {
  142. var files []string
  143. switch scope {
  144. case GlobalScope:
  145. xdg := os.Getenv("XDG_CONFIG_HOME")
  146. if xdg != "" {
  147. files = append(files, filepath.Join(xdg, "git/config"))
  148. }
  149. home, err := homedir.Dir()
  150. if err != nil {
  151. return nil, err
  152. }
  153. files = append(files,
  154. filepath.Join(home, ".gitconfig"),
  155. filepath.Join(home, ".config/git/config"),
  156. )
  157. case SystemScope:
  158. files = append(files, "/etc/gitconfig")
  159. }
  160. return files, nil
  161. }
  162. // Validate validates the fields and sets the default values.
  163. func (c *Config) Validate() error {
  164. for name, r := range c.Remotes {
  165. if r.Name != name {
  166. return ErrInvalid
  167. }
  168. if err := r.Validate(); err != nil {
  169. return err
  170. }
  171. }
  172. for name, b := range c.Branches {
  173. if b.Name != name {
  174. return ErrInvalid
  175. }
  176. if err := b.Validate(); err != nil {
  177. return err
  178. }
  179. }
  180. return nil
  181. }
  182. const (
  183. remoteSection = "remote"
  184. submoduleSection = "submodule"
  185. branchSection = "branch"
  186. coreSection = "core"
  187. packSection = "pack"
  188. userSection = "user"
  189. authorSection = "author"
  190. committerSection = "committer"
  191. fetchKey = "fetch"
  192. urlKey = "url"
  193. bareKey = "bare"
  194. worktreeKey = "worktree"
  195. commentCharKey = "commentChar"
  196. windowKey = "window"
  197. mergeKey = "merge"
  198. rebaseKey = "rebase"
  199. nameKey = "name"
  200. emailKey = "email"
  201. // DefaultPackWindow holds the number of previous objects used to
  202. // generate deltas. The value 10 is the same used by git command.
  203. DefaultPackWindow = uint(10)
  204. )
  205. // Unmarshal parses a git-config file and stores it.
  206. func (c *Config) Unmarshal(b []byte) error {
  207. r := bytes.NewBuffer(b)
  208. d := format.NewDecoder(r)
  209. c.Raw = format.New()
  210. if err := d.Decode(c.Raw); err != nil {
  211. return err
  212. }
  213. c.unmarshalCore()
  214. c.unmarshalUser()
  215. if err := c.unmarshalPack(); err != nil {
  216. return err
  217. }
  218. unmarshalSubmodules(c.Raw, c.Submodules)
  219. if err := c.unmarshalBranches(); err != nil {
  220. return err
  221. }
  222. return c.unmarshalRemotes()
  223. }
  224. func (c *Config) unmarshalCore() {
  225. s := c.Raw.Section(coreSection)
  226. if s.Options.Get(bareKey) == "true" {
  227. c.Core.IsBare = true
  228. }
  229. c.Core.Worktree = s.Options.Get(worktreeKey)
  230. c.Core.CommentChar = s.Options.Get(commentCharKey)
  231. }
  232. func (c *Config) unmarshalUser() {
  233. s := c.Raw.Section(userSection)
  234. c.User.Name = s.Options.Get(nameKey)
  235. c.User.Email = s.Options.Get(emailKey)
  236. s = c.Raw.Section(authorSection)
  237. c.Author.Name = s.Options.Get(nameKey)
  238. c.Author.Email = s.Options.Get(emailKey)
  239. s = c.Raw.Section(committerSection)
  240. c.Committer.Name = s.Options.Get(nameKey)
  241. c.Committer.Email = s.Options.Get(emailKey)
  242. }
  243. func (c *Config) unmarshalPack() error {
  244. s := c.Raw.Section(packSection)
  245. window := s.Options.Get(windowKey)
  246. if window == "" {
  247. c.Pack.Window = DefaultPackWindow
  248. } else {
  249. winUint, err := strconv.ParseUint(window, 10, 32)
  250. if err != nil {
  251. return err
  252. }
  253. c.Pack.Window = uint(winUint)
  254. }
  255. return nil
  256. }
  257. func (c *Config) unmarshalRemotes() error {
  258. s := c.Raw.Section(remoteSection)
  259. for _, sub := range s.Subsections {
  260. r := &RemoteConfig{}
  261. if err := r.unmarshal(sub); err != nil {
  262. return err
  263. }
  264. c.Remotes[r.Name] = r
  265. }
  266. return nil
  267. }
  268. func unmarshalSubmodules(fc *format.Config, submodules map[string]*Submodule) {
  269. s := fc.Section(submoduleSection)
  270. for _, sub := range s.Subsections {
  271. m := &Submodule{}
  272. m.unmarshal(sub)
  273. if m.Validate() == ErrModuleBadPath {
  274. continue
  275. }
  276. submodules[m.Name] = m
  277. }
  278. }
  279. func (c *Config) unmarshalBranches() error {
  280. bs := c.Raw.Section(branchSection)
  281. for _, sub := range bs.Subsections {
  282. b := &Branch{}
  283. if err := b.unmarshal(sub); err != nil {
  284. return err
  285. }
  286. c.Branches[b.Name] = b
  287. }
  288. return nil
  289. }
  290. // Marshal returns Config encoded as a git-config file.
  291. func (c *Config) Marshal() ([]byte, error) {
  292. c.marshalCore()
  293. c.marshalUser()
  294. c.marshalPack()
  295. c.marshalRemotes()
  296. c.marshalSubmodules()
  297. c.marshalBranches()
  298. buf := bytes.NewBuffer(nil)
  299. if err := format.NewEncoder(buf).Encode(c.Raw); err != nil {
  300. return nil, err
  301. }
  302. return buf.Bytes(), nil
  303. }
  304. func (c *Config) marshalCore() {
  305. s := c.Raw.Section(coreSection)
  306. s.SetOption(bareKey, fmt.Sprintf("%t", c.Core.IsBare))
  307. if c.Core.Worktree != "" {
  308. s.SetOption(worktreeKey, c.Core.Worktree)
  309. }
  310. }
  311. func (c *Config) marshalUser() {
  312. s := c.Raw.Section(userSection)
  313. if c.User.Name != "" {
  314. s.SetOption(nameKey, c.User.Name)
  315. }
  316. if c.User.Email != "" {
  317. s.SetOption(emailKey, c.User.Email)
  318. }
  319. s = c.Raw.Section(authorSection)
  320. if c.Author.Name != "" {
  321. s.SetOption(nameKey, c.Author.Name)
  322. }
  323. if c.Author.Email != "" {
  324. s.SetOption(emailKey, c.Author.Email)
  325. }
  326. s = c.Raw.Section(committerSection)
  327. if c.Committer.Name != "" {
  328. s.SetOption(nameKey, c.Committer.Name)
  329. }
  330. if c.Committer.Email != "" {
  331. s.SetOption(emailKey, c.Committer.Email)
  332. }
  333. }
  334. func (c *Config) marshalPack() {
  335. s := c.Raw.Section(packSection)
  336. if c.Pack.Window != DefaultPackWindow {
  337. s.SetOption(windowKey, fmt.Sprintf("%d", c.Pack.Window))
  338. }
  339. }
  340. func (c *Config) marshalRemotes() {
  341. s := c.Raw.Section(remoteSection)
  342. newSubsections := make(format.Subsections, 0, len(c.Remotes))
  343. added := make(map[string]bool)
  344. for _, subsection := range s.Subsections {
  345. if remote, ok := c.Remotes[subsection.Name]; ok {
  346. newSubsections = append(newSubsections, remote.marshal())
  347. added[subsection.Name] = true
  348. }
  349. }
  350. remoteNames := make([]string, 0, len(c.Remotes))
  351. for name := range c.Remotes {
  352. remoteNames = append(remoteNames, name)
  353. }
  354. sort.Strings(remoteNames)
  355. for _, name := range remoteNames {
  356. if !added[name] {
  357. newSubsections = append(newSubsections, c.Remotes[name].marshal())
  358. }
  359. }
  360. s.Subsections = newSubsections
  361. }
  362. func (c *Config) marshalSubmodules() {
  363. s := c.Raw.Section(submoduleSection)
  364. s.Subsections = make(format.Subsections, len(c.Submodules))
  365. var i int
  366. for _, r := range c.Submodules {
  367. section := r.marshal()
  368. // the submodule section at config is a subset of the .gitmodule file
  369. // we should remove the non-valid options for the config file.
  370. section.RemoveOption(pathKey)
  371. s.Subsections[i] = section
  372. i++
  373. }
  374. }
  375. func (c *Config) marshalBranches() {
  376. s := c.Raw.Section(branchSection)
  377. newSubsections := make(format.Subsections, 0, len(c.Branches))
  378. added := make(map[string]bool)
  379. for _, subsection := range s.Subsections {
  380. if branch, ok := c.Branches[subsection.Name]; ok {
  381. newSubsections = append(newSubsections, branch.marshal())
  382. added[subsection.Name] = true
  383. }
  384. }
  385. branchNames := make([]string, 0, len(c.Branches))
  386. for name := range c.Branches {
  387. branchNames = append(branchNames, name)
  388. }
  389. sort.Strings(branchNames)
  390. for _, name := range branchNames {
  391. if !added[name] {
  392. newSubsections = append(newSubsections, c.Branches[name].marshal())
  393. }
  394. }
  395. s.Subsections = newSubsections
  396. }
  397. // RemoteConfig contains the configuration for a given remote repository.
  398. type RemoteConfig struct {
  399. // Name of the remote
  400. Name string
  401. // URLs the URLs of a remote repository. It must be non-empty. Fetch will
  402. // always use the first URL, while push will use all of them.
  403. URLs []string
  404. // Fetch the default set of "refspec" for fetch operation
  405. Fetch []RefSpec
  406. // raw representation of the subsection, filled by marshal or unmarshal are
  407. // called
  408. raw *format.Subsection
  409. }
  410. // Validate validates the fields and sets the default values.
  411. func (c *RemoteConfig) Validate() error {
  412. if c.Name == "" {
  413. return ErrRemoteConfigEmptyName
  414. }
  415. if len(c.URLs) == 0 {
  416. return ErrRemoteConfigEmptyURL
  417. }
  418. for _, r := range c.Fetch {
  419. if err := r.Validate(); err != nil {
  420. return err
  421. }
  422. }
  423. if len(c.Fetch) == 0 {
  424. c.Fetch = []RefSpec{RefSpec(fmt.Sprintf(DefaultFetchRefSpec, c.Name))}
  425. }
  426. return nil
  427. }
  428. func (c *RemoteConfig) unmarshal(s *format.Subsection) error {
  429. c.raw = s
  430. fetch := []RefSpec{}
  431. for _, f := range c.raw.Options.GetAll(fetchKey) {
  432. rs := RefSpec(f)
  433. if err := rs.Validate(); err != nil {
  434. return err
  435. }
  436. fetch = append(fetch, rs)
  437. }
  438. c.Name = c.raw.Name
  439. c.URLs = append([]string(nil), c.raw.Options.GetAll(urlKey)...)
  440. c.Fetch = fetch
  441. return nil
  442. }
  443. func (c *RemoteConfig) marshal() *format.Subsection {
  444. if c.raw == nil {
  445. c.raw = &format.Subsection{}
  446. }
  447. c.raw.Name = c.Name
  448. if len(c.URLs) == 0 {
  449. c.raw.RemoveOption(urlKey)
  450. } else {
  451. c.raw.SetOption(urlKey, c.URLs...)
  452. }
  453. if len(c.Fetch) == 0 {
  454. c.raw.RemoveOption(fetchKey)
  455. } else {
  456. var values []string
  457. for _, rs := range c.Fetch {
  458. values = append(values, rs.String())
  459. }
  460. c.raw.SetOption(fetchKey, values...)
  461. }
  462. return c.raw
  463. }
  464. func (c *RemoteConfig) IsFirstURLLocal() bool {
  465. return url.IsLocalEndpoint(c.URLs[0])
  466. }