main
1package repo
2
3import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "sort"
8)
9
10// Repo represents a bare git repository discovered under the root.
11type Repo struct {
12 FullPath string
13 RelPath string
14 Name string
15 Category string
16}
17
18// IsBare reports whether path looks like a bare git repo by checking
19// for a HEAD file.
20func IsBare(path string) (bool, error) {
21 info, err := os.Stat(filepath.Join(path, "HEAD"))
22 if os.IsNotExist(err) {
23 return false, nil
24 }
25 if err != nil {
26 return false, fmt.Errorf("checking HEAD in %s: %w", path, err)
27 }
28 return !info.IsDir(), nil
29}
30
31// Discover walks a two-level directory structure (category/repo) under root
32// and returns all bare git repos found, sorted by RelPath.
33func Discover(root string) ([]Repo, error) {
34 categories, err := os.ReadDir(root)
35 if err != nil {
36 return nil, fmt.Errorf("reading root dir (root=%s): %w", root, err)
37 }
38
39 var repos []Repo
40 for _, cat := range categories {
41 if !cat.IsDir() {
42 continue
43 }
44 catPath := filepath.Join(root, cat.Name())
45 entries, err := os.ReadDir(catPath)
46 if err != nil {
47 return nil, fmt.Errorf("reading category dir (category=%s): %w", cat.Name(), err)
48 }
49 for _, entry := range entries {
50 if !entry.IsDir() {
51 continue
52 }
53 repoPath := filepath.Join(catPath, entry.Name())
54 bare, err := IsBare(repoPath)
55 if err != nil {
56 return nil, err
57 }
58 if !bare {
59 continue
60 }
61 repos = append(repos, Repo{
62 FullPath: repoPath,
63 RelPath: filepath.Join(cat.Name(), entry.Name()),
64 Name: entry.Name(),
65 Category: cat.Name(),
66 })
67 }
68 }
69
70 sort.Slice(repos, func(i, j int) bool {
71 return repos[i].RelPath < repos[j].RelPath
72 })
73 return repos, nil
74}