master
1package main
2
3import (
4 "fmt"
5 "os"
6 "os/exec"
7 "path/filepath"
8 "strings"
9
10 "github.com/BurntSushi/toml"
11)
12
13type SingleRepoConfig struct {
14 Name string `toml:"name"`
15 Path string `toml:"path"`
16 Description string `toml:"description"`
17 DefaultBranch string `toml:"default_branch"`
18}
19
20type RepoEntry struct {
21 Name string `toml:"name"`
22 Slug string `toml:"slug"`
23 Path string `toml:"path"`
24 Description string `toml:"description"`
25 DefaultBranch string `toml:"default_branch"`
26}
27
28type Config struct {
29 SiteName string `toml:"site_name"`
30 Repo *SingleRepoConfig `toml:"repo"`
31 Repos []RepoEntry `toml:"repos"`
32}
33
34func parseConfig(path string) (*Config, error) {
35 absPath, err := filepath.Abs(path)
36 if err != nil {
37 return nil, fmt.Errorf("resolve config path: %w", err)
38 }
39
40 data, err := os.ReadFile(absPath)
41 if err != nil {
42 return nil, fmt.Errorf("read config file: %w", err)
43 }
44
45 var cfg Config
46 if err := toml.Unmarshal(data, &cfg); err != nil {
47 return nil, fmt.Errorf("parse config: %w", err)
48 }
49
50 if cfg.SiteName == "" {
51 cfg.SiteName = "Git repositories"
52 }
53
54 if hasRepo := cfg.Repo != nil; hasRepo {
55 if len(cfg.Repos) > 0 {
56 return nil, fmt.Errorf("config: cannot use both [repo] and [[repos]]")
57 }
58 if cfg.Repo.Name == "" {
59 return nil, fmt.Errorf("config: [repo].name is required")
60 }
61 if cfg.Repo.Path == "" {
62 return nil, fmt.Errorf("config: [repo].path is required")
63 }
64 if err := normalizeRepoPath(&cfg.Repo.Path); err != nil {
65 return nil, fmt.Errorf("config: [repo].path: %w", err)
66 }
67 if cfg.Repo.DefaultBranch == "" {
68 cfg.Repo.DefaultBranch = autoDefaultBranchName(cfg.Repo.Path)
69 }
70 }
71
72 if len(cfg.Repos) > 0 {
73 for i := range cfg.Repos {
74 repo := &cfg.Repos[i]
75 if repo.Name == "" {
76 return nil, fmt.Errorf("config: repos[%d].name is required", i)
77 }
78 if repo.Slug == "" {
79 return nil, fmt.Errorf("config: repos[%d].slug is required", i)
80 }
81 if repo.Path == "" {
82 return nil, fmt.Errorf("config: repos[%d].path is required", i)
83 }
84 if err := normalizeRepoPath(&repo.Path); err != nil {
85 return nil, fmt.Errorf("config: repos[%d].path: %w", i, err)
86 }
87 if repo.DefaultBranch == "" {
88 repo.DefaultBranch = autoDefaultBranchName(repo.Path)
89 }
90 }
91 }
92
93 return &cfg, nil
94}
95
96func normalizeRepoPath(path *string) error {
97 abs, err := filepath.Abs(*path)
98 if err != nil {
99 return err
100 }
101 *path = abs
102 return nil
103}
104
105func autoDefaultBranchName(repoPath string) string {
106 cmd := exec.Command("git", "symbolic-ref", "--short", "HEAD")
107 cmd.Dir = repoPath
108 out, err := cmd.Output()
109 if err != nil {
110 return ""
111 }
112 return strings.TrimSpace(string(out))
113}