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}