@@ -16,6 +16,7 @@ package gitlabrepo
16
16
17
17
import (
18
18
"fmt"
19
+ "strconv"
19
20
"strings"
20
21
"sync"
21
22
@@ -25,11 +26,11 @@ import (
25
26
)
26
27
27
28
type commitsHandler struct {
28
- glClient * gitlab.Client
29
- once * sync.Once
30
- errSetup error
31
- repourl * repoURL
32
- commits []clients .Commit
29
+ glClient * gitlab.Client
30
+ once * sync.Once
31
+ errSetup error
32
+ repourl * repoURL
33
+ commitsRaw [] * gitlab .Commit
33
34
}
34
35
35
36
func (handler * commitsHandler ) init (repourl * repoURL ) {
@@ -38,123 +39,118 @@ func (handler *commitsHandler) init(repourl *repoURL) {
38
39
handler .once = new (sync.Once )
39
40
}
40
41
41
- // nolint: gocognit
42
42
func (handler * commitsHandler ) setup () error {
43
43
handler .once .Do (func () {
44
- commits , _ , err := handler .glClient .Commits .ListCommits (handler .repourl .project , & gitlab.ListCommitsOptions {})
44
+ commits , _ , err := handler .glClient .Commits .ListCommits (handler .repourl .projectID , & gitlab.ListCommitsOptions {})
45
45
if err != nil {
46
46
handler .errSetup = fmt .Errorf ("request for commits failed with %w" , err )
47
47
return
48
48
}
49
+ handler .commitsRaw = commits
50
+ })
49
51
50
- // To limit the number of user requests we are going to map every committer email
51
- // to a user.
52
- userToEmail := make (map [string ]* gitlab.User )
53
- for _ , commit := range commits {
54
- user , ok := userToEmail [commit .AuthorEmail ]
55
- if ! ok {
56
- users , _ , err := handler .glClient .Search .Users (commit .CommitterName , & gitlab.SearchOptions {})
57
- if err != nil {
58
- // Possibility this shouldn't be an issue as individuals can leave organizations
59
- // (possibly taking their account with them)
60
- handler .errSetup = fmt .Errorf ("unable to find user associated with commit: %w" , err )
61
- return
62
- }
63
-
64
- // For some reason some users have unknown names, so below we are going to parse their email into pieces.
65
- // i.e. ([email protected] ) -> "firstname lastname".
66
- if len (users ) == 0 {
67
- users , _ , err = handler .glClient .Search .Users (parseEmailToName (commit .CommitterEmail ), & gitlab.SearchOptions {})
68
- if err != nil {
69
- handler .errSetup = fmt .Errorf ("unable to find user associated with commit: %w" , err )
70
- return
71
- }
72
- }
73
- userToEmail [commit .AuthorEmail ] = users [0 ]
74
- user = users [0 ]
75
- }
52
+ return handler .errSetup
53
+ }
76
54
77
- // Commits are able to be a part of multiple merge requests, but the only one that will be important
78
- // here is the earliest one.
79
- mergeRequests , _ , err := handler .glClient .Commits .ListMergeRequestsByCommit (handler .repourl .project , commit .ID )
80
- if err != nil {
81
- handler .errSetup = fmt .Errorf ("unable to find merge requests associated with commit: %w" , err )
82
- return
83
- }
84
- var mergeRequest * gitlab.MergeRequest
85
- if len (mergeRequests ) > 0 {
86
- mergeRequest = mergeRequests [0 ]
87
- for i := range mergeRequests {
88
- if mergeRequests [i ] == nil || mergeRequests [i ].MergedAt == nil {
89
- continue
90
- }
91
- if mergeRequests [i ].CreatedAt .Before (* mergeRequest .CreatedAt ) {
92
- mergeRequest = mergeRequests [i ]
93
- }
94
- }
95
- } else {
96
- handler .commits = append (handler .commits , clients.Commit {
97
- CommittedDate : * commit .CommittedDate ,
98
- Message : commit .Message ,
99
- SHA : commit .ID ,
100
- })
101
- continue
102
- }
55
+ func (handler * commitsHandler ) listRawCommits () ([]* gitlab.Commit , error ) {
56
+ if err := handler .setup (); err != nil {
57
+ return nil , fmt .Errorf ("error during commitsHandler.setup: %w" , err )
58
+ }
59
+
60
+ return handler .commitsRaw , nil
61
+ }
103
62
104
- if mergeRequest == nil || mergeRequest .MergedAt == nil {
105
- handler .commits = append (handler .commits , clients.Commit {
106
- CommittedDate : * commit .CommittedDate ,
107
- Message : commit .Message ,
108
- SHA : commit .ID ,
109
- })
63
+ // zip combines Commit and MergeRequest information from the GitLab REST API with
64
+ // information from the GitLab GraphQL API. The REST API doesn't provide any way to
65
+ // get from Commits -> MRs that they were part of or vice-versa (MRs -> commits they
66
+ // contain), except through a separate API call. Instead of calling the REST API
67
+ // len(commits) times to get the associated MR, we make 3 calls (2 REST, 1 GraphQL).
68
+ func (handler * commitsHandler ) zip (commitsRaw []* gitlab.Commit , data graphqlData ) []clients.Commit {
69
+ commitToMRIID := make (map [string ]string ) // which mr does a commit belong to?
70
+ for i := range data .Project .MergeRequests .Nodes {
71
+ mr := data .Project .MergeRequests .Nodes [i ]
72
+ for _ , commit := range mr .Commits .Nodes {
73
+ commitToMRIID [commit .SHA ] = mr .IID
74
+ }
75
+ commitToMRIID [mr .MergeCommitSHA ] = mr .IID
76
+ }
77
+
78
+ iidToMr := make (map [string ]clients.PullRequest )
79
+ for i := range data .Project .MergeRequests .Nodes {
80
+ mr := data .Project .MergeRequests .Nodes [i ]
81
+ // Two GitLab APIs for reviews (reviews vs. approvals)
82
+ // Use a map to consolidate results from both APIs by the user ID who performed review
83
+ reviews := make (map [string ]clients.Review )
84
+ for _ , reviewer := range mr .Reviewers .Nodes {
85
+ reviews [reviewer .Username ] = clients.Review {
86
+ Author : & clients.User {Login : reviewer .Username },
87
+ State : "COMMENTED" ,
110
88
}
89
+ }
111
90
112
- // Casting the Reviewers into clients.Review.
113
- var reviews []clients.Review
114
- for _ , reviewer := range mergeRequest .Reviewers {
115
- reviews = append (reviews , clients.Review {
116
- Author : & clients.User {ID : int64 (reviewer .ID )},
117
- State : "" ,
118
- })
91
+ if fmt .Sprintf ("%v" , mr .IID ) != mr .IID {
92
+ continue
93
+ }
94
+
95
+ // Check approvers
96
+ for _ , approver := range mr .Approvers .Nodes {
97
+ reviews [approver .Username ] = clients.Review {
98
+ Author : & clients.User {Login : approver .Username },
99
+ State : "APPROVED" ,
119
100
}
101
+ break
102
+ }
120
103
121
- // Casting the Labels into []clients.Label.
122
- var labels []clients.Label
123
- for _ , label := range mergeRequest .Labels {
124
- labels = append (labels , clients.Label {
125
- Name : label ,
126
- })
104
+ // Check reviewers (sometimes unofficial approvals end up here)
105
+ for _ , reviewer := range mr .Reviewers .Nodes {
106
+ if reviewer .MergeRequestInteraction .ReviewState != "REVIEWED" {
107
+ continue
127
108
}
109
+ reviews [reviewer .Username ] = clients.Review {
110
+ Author : & clients.User {Login : reviewer .Username },
111
+ State : "APPROVED" ,
112
+ }
113
+ break
114
+ }
128
115
129
- // append the commits to the handler.
130
- handler .commits = append (handler .commits ,
131
- clients.Commit {
132
- CommittedDate : * commit .CommittedDate ,
133
- Message : commit .Message ,
134
- SHA : commit .ID ,
135
- AssociatedMergeRequest : clients.PullRequest {
136
- Number : mergeRequest .ID ,
137
- MergedAt : * mergeRequest .MergedAt ,
138
- HeadSHA : mergeRequest .SHA ,
139
- Author : clients.User {ID : int64 (mergeRequest .Author .ID )},
140
- Labels : labels ,
141
- Reviews : reviews ,
142
- MergedBy : clients.User {ID : int64 (mergeRequest .MergedBy .ID )},
143
- },
144
- Committer : clients.User {ID : int64 (user .ID )},
145
- })
116
+ vals := []clients.Review {}
117
+ for _ , v := range reviews {
118
+ vals = append (vals , v )
146
119
}
147
- })
148
120
149
- return handler .errSetup
150
- }
121
+ var mrno int
122
+ mrno , err := strconv .Atoi (mr .IID )
123
+ if err != nil {
124
+ mrno = mr .ID .ID
125
+ }
151
126
152
- func (handler * commitsHandler ) listCommits () ([]clients.Commit , error ) {
153
- if err := handler .setup (); err != nil {
154
- return nil , fmt .Errorf ("error during commitsHandler.setup: %w" , err )
127
+ iidToMr [mr .IID ] = clients.PullRequest {
128
+ Number : mrno ,
129
+ MergedAt : mr .MergedAt ,
130
+ HeadSHA : mr .MergeCommitSHA ,
131
+ Author : clients.User {Login : mr .Author .Username , ID : int64 (mr .Author .ID .ID )},
132
+ Reviews : vals ,
133
+ MergedBy : clients.User {Login : mr .MergedBy .Username , ID : int64 (mr .MergedBy .ID .ID )},
134
+ }
135
+ }
136
+
137
+ // Associate Merge Requests with Commits based on the GitLab Merge Request IID
138
+ commits := []clients.Commit {}
139
+ for _ , cRaw := range commitsRaw {
140
+ // Get IID of Merge Request that this commit was merged as part of
141
+ mrIID := commitToMRIID [cRaw .ID ]
142
+ associatedMr := iidToMr [mrIID ]
143
+
144
+ commits = append (commits ,
145
+ clients.Commit {
146
+ CommittedDate : * cRaw .CommittedDate ,
147
+ Message : cRaw .Message ,
148
+ SHA : cRaw .ID ,
149
+ AssociatedMergeRequest : associatedMr ,
150
+ })
155
151
}
156
152
157
- return handler . commits , nil
153
+ return commits
158
154
}
159
155
160
156
// Expected email form: <firstname>.<lastname>@<namespace>.com.
0 commit comments