1package git
2
3import (
4 "bufio"
5 "fmt"
6 "io"
7 "strings"
8
9 "tangled.sh/tangled.sh/core/api/tangled"
10
11 "github.com/go-git/go-git/v5/plumbing"
12)
13
14type PostReceiveLine struct {
15 OldSha plumbing.Hash // old sha of reference being updated
16 NewSha plumbing.Hash // new sha of reference being updated
17 Ref string // the reference being updated
18}
19
20func ParsePostReceive(buf io.Reader) ([]PostReceiveLine, error) {
21 scanner := bufio.NewScanner(buf)
22 var lines []PostReceiveLine
23 for scanner.Scan() {
24 line := scanner.Text()
25 parts := strings.SplitN(line, " ", 3)
26 if len(parts) != 3 {
27 continue
28 }
29
30 oldSha := parts[0]
31 newSha := parts[1]
32 ref := parts[2]
33
34 lines = append(lines, PostReceiveLine{
35 OldSha: plumbing.NewHash(oldSha),
36 NewSha: plumbing.NewHash(newSha),
37 Ref: ref,
38 })
39 }
40
41 if err := scanner.Err(); err != nil {
42 return nil, err
43 }
44
45 return lines, nil
46}
47
48type RefUpdateMeta struct {
49 CommitCount CommitCount
50 IsDefaultRef bool
51}
52
53type CommitCount struct {
54 ByEmail map[string]int
55}
56
57func (g *GitRepo) RefUpdateMeta(line PostReceiveLine) RefUpdateMeta {
58 commitCount, err := g.newCommitCount(line)
59 if err != nil {
60 // TODO: non-fatal, log this
61 }
62
63 isDefaultRef, err := g.isDefaultBranch(line)
64 if err != nil {
65 // TODO: non-fatal, log this
66 }
67
68 return RefUpdateMeta{
69 CommitCount: commitCount,
70 IsDefaultRef: isDefaultRef,
71 }
72}
73
74func (g *GitRepo) newCommitCount(line PostReceiveLine) (CommitCount, error) {
75 byEmail := make(map[string]int)
76 commitCount := CommitCount{
77 ByEmail: byEmail,
78 }
79
80 if !line.NewSha.IsZero() {
81 output, err := g.revList(
82 fmt.Sprintf("--max-count=%d", 100),
83 fmt.Sprintf("%s..%s", line.OldSha.String(), line.NewSha.String()),
84 )
85 if err != nil {
86 return commitCount, fmt.Errorf("failed to run rev-list: %w", err)
87 }
88
89 lines := strings.Split(strings.TrimSpace(string(output)), "\n")
90 if len(lines) == 1 && lines[0] == "" {
91 return commitCount, nil
92 }
93
94 for _, item := range lines {
95 obj, err := g.r.CommitObject(plumbing.NewHash(item))
96 if err != nil {
97 continue
98 }
99 commitCount.ByEmail[obj.Author.Email] += 1
100 }
101 }
102
103 return commitCount, nil
104}
105
106func (g *GitRepo) isDefaultBranch(line PostReceiveLine) (bool, error) {
107 defaultBranch, err := g.FindMainBranch()
108 if err != nil {
109 return false, err
110 }
111
112 refName := plumbing.ReferenceName(line.Ref)
113 if refName.IsBranch() {
114 return defaultBranch == refName.Short(), nil
115 }
116
117 return false, err
118}
119
120func (m RefUpdateMeta) AsRecord() tangled.GitRefUpdate_Meta {
121 var byEmail []*tangled.GitRefUpdate_Meta_CommitCount_ByEmail_Elem
122 for e, v := range m.CommitCount.ByEmail {
123 byEmail = append(byEmail, &tangled.GitRefUpdate_Meta_CommitCount_ByEmail_Elem{
124 Email: e,
125 Count: int64(v),
126 })
127 }
128
129 return tangled.GitRefUpdate_Meta{
130 CommitCount: &tangled.GitRefUpdate_Meta_CommitCount{
131 ByEmail: byEmail,
132 },
133 IsDefaultRef: m.IsDefaultRef,
134 }
135}