1package git
2
3import (
4 "fmt"
5 "slices"
6 "strconv"
7 "strings"
8 "time"
9
10 "github.com/go-git/go-git/v5/plumbing"
11 "github.com/go-git/go-git/v5/plumbing/object"
12 "tangled.sh/tangled.sh/core/types"
13)
14
15func (g *GitRepo) Branches() ([]types.Branch, error) {
16 fields := []string{
17 "refname:short",
18 "objectname",
19 "authorname",
20 "authoremail",
21 "authordate:unix",
22 "committername",
23 "committeremail",
24 "committerdate:unix",
25 "tree",
26 "parent",
27 "contents",
28 }
29
30 var outFormat strings.Builder
31 outFormat.WriteString("--format=")
32 for i, f := range fields {
33 if i != 0 {
34 outFormat.WriteString(fieldSeparator)
35 }
36 outFormat.WriteString(fmt.Sprintf("%%(%s)", f))
37 }
38 outFormat.WriteString("")
39 outFormat.WriteString(recordSeparator)
40
41 output, err := g.forEachRef(outFormat.String(), "refs/heads")
42 if err != nil {
43 return nil, fmt.Errorf("failed to get branches: %w", err)
44 }
45
46 records := strings.Split(strings.TrimSpace(string(output)), recordSeparator)
47 if len(records) == 1 && records[0] == "" {
48 return nil, nil
49 }
50
51 branches := make([]types.Branch, 0, len(records))
52
53 // ignore errors here
54 defaultBranch, _ := g.FindMainBranch()
55
56 for _, line := range records {
57 parts := strings.SplitN(strings.TrimSpace(line), fieldSeparator, len(fields))
58 if len(parts) < 6 {
59 continue
60 }
61
62 branchName := parts[0]
63 commitHash := plumbing.NewHash(parts[1])
64 authorName := parts[2]
65 authorEmail := strings.TrimSuffix(strings.TrimPrefix(parts[3], "<"), ">")
66 authorDate := parts[4]
67 committerName := parts[5]
68 committerEmail := strings.TrimSuffix(strings.TrimPrefix(parts[6], "<"), ">")
69 committerDate := parts[7]
70 treeHash := plumbing.NewHash(parts[8])
71 parentHash := plumbing.NewHash(parts[9])
72 message := parts[10]
73
74 // parse creation time
75 var authoredAt, committedAt time.Time
76 if unix, err := strconv.ParseInt(authorDate, 10, 64); err == nil {
77 authoredAt = time.Unix(unix, 0)
78 }
79 if unix, err := strconv.ParseInt(committerDate, 10, 64); err == nil {
80 committedAt = time.Unix(unix, 0)
81 }
82
83 branch := types.Branch{
84 IsDefault: branchName == defaultBranch,
85 Reference: types.Reference{
86 Name: branchName,
87 Hash: commitHash.String(),
88 },
89 Commit: &object.Commit{
90 Hash: commitHash,
91 Author: object.Signature{
92 Name: authorName,
93 Email: authorEmail,
94 When: authoredAt,
95 },
96 Committer: object.Signature{
97 Name: committerName,
98 Email: committerEmail,
99 When: committedAt,
100 },
101 TreeHash: treeHash,
102 ParentHashes: []plumbing.Hash{parentHash},
103 Message: message,
104 },
105 }
106
107 branches = append(branches, branch)
108 }
109
110 slices.Reverse(branches)
111 return branches, nil
112}