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 return commitCount, nil
82 }
83
84 args := []string{fmt.Sprintf("--max-count=%d", 100)}
85
86 if line.OldSha.IsZero() {
87 // just git rev-list <newsha>
88 args = append(args, line.NewSha.String())
89 } else {
90 // git rev-list <oldsha>..<newsha>
91 args = append(args, fmt.Sprintf("%s..%s", line.OldSha.String(), line.NewSha.String()))
92 }
93
94 output, err := g.revList(args...)
95 if err != nil {
96 return commitCount, fmt.Errorf("failed to run rev-list: %w", err)
97 }
98
99 lines := strings.Split(strings.TrimSpace(string(output)), "\n")
100 if len(lines) == 1 && lines[0] == "" {
101 return commitCount, nil
102 }
103
104 for _, item := range lines {
105 obj, err := g.r.CommitObject(plumbing.NewHash(item))
106 if err != nil {
107 continue
108 }
109 commitCount.ByEmail[obj.Author.Email] += 1
110 }
111
112 return commitCount, nil
113}
114
115func (g *GitRepo) isDefaultBranch(line PostReceiveLine) (bool, error) {
116 defaultBranch, err := g.FindMainBranch()
117 if err != nil {
118 return false, err
119 }
120
121 refName := plumbing.ReferenceName(line.Ref)
122 if refName.IsBranch() {
123 return defaultBranch == refName.Short(), nil
124 }
125
126 return false, err
127}
128
129func (m RefUpdateMeta) AsRecord() tangled.GitRefUpdate_Meta {
130 var byEmail []*tangled.GitRefUpdate_Meta_CommitCount_ByEmail_Elem
131 for e, v := range m.CommitCount.ByEmail {
132 byEmail = append(byEmail, &tangled.GitRefUpdate_Meta_CommitCount_ByEmail_Elem{
133 Email: e,
134 Count: int64(v),
135 })
136 }
137
138 return tangled.GitRefUpdate_Meta{
139 CommitCount: &tangled.GitRefUpdate_Meta_CommitCount{
140 ByEmail: byEmail,
141 },
142 IsDefaultRef: m.IsDefaultRef,
143 }
144}