11package main
22
33import (
4+ "context"
45 "encoding/hex"
56 "errors"
67 "fmt"
@@ -9,13 +10,15 @@ import (
910 "os"
1011 "path"
1112 "regexp"
13+ "strconv"
1214 "strings"
1315 "unicode"
1416 "unicode/utf8"
1517
16- "github.com/go-git/go-git/v5"
17- "github.com/go-git/go-git/v5/plumbing"
18- "github.com/go-git/go-git/v5/plumbing/object"
18+ "github.com/google/go-github/v35/github"
19+
20+ "github.com/xanzy/go-gitlab"
21+ "golang.org/x/oauth2"
1922 yaml "gopkg.in/yaml.v2"
2023)
2124
@@ -78,6 +81,7 @@ TagOrder:
7881 MAXSUBJECTLEN = 100
7982
8083 GITHUB = "Github"
84+ GITLAB = "Gitlab"
8185)
8286
8387var ErrSubjectMessageFormat = errors .New ("invalid subject message format" )
@@ -208,44 +212,45 @@ func (c CommitPolicyConfig) IsEmpty() bool {
208212}
209213
210214type gitEnv struct {
211- EnvName string
212- Event string
213- Ref string
214- Base string
215+ EnvName string
216+ URL string
217+ Token string
218+ ProjectID string
219+ PMRequestID string
215220}
216221
217222type gitEnvVars struct {
218- EnvName string
219- EventVar string
220- RefVar string
221- BaseVar string
223+ EnvName string
224+ ApiUrl string
225+ ApiToken string
226+ ProjectID string
227+ RequestID string
222228}
223229
224230var ErrGitEnvironment = errors .New ("git environment error" )
225231
226232func readGitEnvironment () (* gitEnv , error ) {
227233 knownVars := []gitEnvVars {
228- {GITHUB , "GITHUB_EVENT_NAME" , "GITHUB_SHA" , "GITHUB_BASE_REF" },
229- {"Gitlab" , "CI_PIPELINE_SOURCE" , "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" , "CI_MERGE_REQUEST_TARGET_BRANCH_NAME" },
230- {"Gitlab-commit" , "CI_PIPELINE_SOURCE" , "CI_COMMIT_SHA" , "CI_DEFAULT_BRANCH" },
234+ {GITHUB , "GITHUB_API_URL" , "GITHUB_TOKEN" , "GITHUB_REPOSITORY" , "GITHUB_SHA" },
235+ {GITLAB , "CI_API_V4_URL" , "CI_JOB_TOKEN" , "CI_MERGE_REQUEST_PROJECT_ID" , "CI_MERGE_REQUEST_ID" },
231236 }
232237
233- var ref , base string
234-
235238 for _ , vars := range knownVars {
236- event := os .Getenv (vars .EventVar )
237- ref = os .Getenv (vars .RefVar )
238- base = os .Getenv (vars .BaseVar )
239+ url := os .Getenv (vars .ApiUrl )
240+ token := os .Getenv (vars .ApiToken )
241+ project := os .Getenv (vars .ProjectID )
242+ request := os .Getenv (vars .RequestID )
239243
240- if ! (ref == "" && base == "" ) || ( vars . EnvName == GITHUB && event == "push " ) {
244+ if ! (url == "" && token == "" && project == "" && request == "" ) {
241245 log .Printf ("detected %s environment\n " , vars .EnvName )
242- log .Printf ("using event '%s' with refs '%s' and '%s' \n " , event , ref , base )
246+ log .Printf ("using api url '%s'\n " , url )
243247
244248 return & gitEnv {
245- EnvName : vars .EnvName ,
246- Event : event ,
247- Ref : ref ,
248- Base : base ,
249+ EnvName : vars .EnvName ,
250+ URL : url ,
251+ Token : token ,
252+ ProjectID : project ,
253+ PMRequestID : request ,
249254 }, nil
250255 }
251256 }
@@ -273,89 +278,86 @@ func LoadCommitPolicy(filename string) (CommitPolicyConfig, error) {
273278 return commitPolicy , nil
274279}
275280
276- func hashesFromRefs (repo * git.Repository , repoEnv * gitEnv ) ([]* plumbing.Hash , []* object.Commit ) {
277- var refStrings []string
278- refStrings = append (refStrings , repoEnv .Ref )
279-
280- if ! (repoEnv .EnvName == GITHUB && repoEnv .Event == "push" ) { // for Github push we only have the last commit
281- refStrings = append (refStrings , fmt .Sprintf ("refs/remotes/origin/%s" , repoEnv .Base ))
282- }
281+ func getGithubCommitSubjects (token string , repo string , sha string ) ([]string , error ) {
282+ ctx := context .Background ()
283283
284- hashes := make ([]* plumbing.Hash , 0 , 2 )
284+ ts := oauth2 .StaticTokenSource (
285+ & oauth2.Token {AccessToken : token },
286+ )
287+ tc := oauth2 .NewClient (ctx , ts )
288+ githubClient := github .NewClient (tc )
285289
286- for _ , refString := range refStrings {
287- hash , err := repo .ResolveRevision (plumbing .Revision (refString ))
288- if err != nil {
289- log .Fatalf ("unable to resolve revision %s to hash: %s" , refString , err )
290- }
290+ repoSlice := strings .SplitN (repo , "/" , 2 )
291291
292- hashes = append (hashes , hash )
292+ prs , _ , err := githubClient .PullRequests .ListPullRequestsWithCommit (ctx , repoSlice [0 ], repoSlice [1 ], sha , & github.PullRequestListOptions {})
293+ if err != nil {
294+ return nil , fmt .Errorf ("error fetching prs for commit %s: %w" , sha , err )
293295 }
294296
295- commits := make ([]* object.Commit , 0 , 2 )
296-
297- for _ , hash := range hashes {
298- commit , err := repo .CommitObject (* hash )
297+ subjects := []string {}
298+ if len (prs ) > 0 {
299+ // Check the latest PR with this commit
300+ prNo := prs [0 ].GetNumber ()
301+ commits , _ , err := githubClient .PullRequests .ListCommits (ctx , repoSlice [0 ], repoSlice [1 ], prNo , & github.ListOptions {})
299302 if err != nil {
300- log .Fatalf ("unable to find commit %s" , hash .String ())
303+ return nil , fmt .Errorf ("error fetching commits: %w" , err )
304+ }
305+ for _ , c := range commits {
306+ l := strings .SplitN (c .Commit .GetMessage (), "\n " , 2 )
307+ if len (l ) > 0 {
308+ subjects = append (subjects , l [0 ])
309+ }
310+ }
311+ } else {
312+ // no PRs, event was a direct push, check only latest commit
313+ c , _ , err := githubClient .Repositories .GetCommit (ctx , repoSlice [0 ], repoSlice [1 ], sha )
314+ if err != nil {
315+ return nil , fmt .Errorf ("error fetching commit %s: %w" , sha , err )
316+ }
317+ l := strings .SplitN (c .Commit .GetMessage (), "\n " , 2 )
318+ if len (l ) > 0 {
319+ subjects = append (subjects , l [0 ])
301320 }
302-
303- commits = append (commits , commit )
304321 }
305322
306- return hashes , commits
323+ return subjects , nil
307324}
308325
309- var ErrReachedMergeBase = errors .New ("reached Merge Base" )
310-
311- func getCommitSubjects (repo * git.Repository , repoEnv * gitEnv ) ([]string , error ) {
312- hashes , commits := hashesFromRefs (repo , repoEnv )
313-
314- if len (commits ) == 1 { // just the last commit
315- return []string {strings .Split (commits [0 ].Message , "\n " )[0 ]}, nil
326+ func gitGitlabCommitSubjects (url string , token string , project string , mr string ) ([]string , error ) {
327+ gitlabClient , err := gitlab .NewClient (token , gitlab .WithBaseURL (url ))
328+ if err != nil {
329+ log .Fatalf ("Failed to create gitlab client: %v" , err )
316330 }
317331
318- mergeBase , err := commits [ 0 ]. MergeBase ( commits [ 1 ] )
332+ mrID , err := strconv . Atoi ( mr )
319333 if err != nil {
320- log . Fatalf ( "repo history error %s" , err )
334+ return nil , fmt . Errorf ( "invalid merge request id %s" , mr )
321335 }
322-
323- logOptions := new (git.LogOptions )
324- logOptions .From = * hashes [0 ]
325- logOptions .Order = git .LogOrderCommitterTime
326-
327- cIter , err := repo .Log (logOptions )
336+ commits , _ , err := gitlabClient .MergeRequests .GetMergeRequestCommits (project , mrID , & gitlab.GetMergeRequestCommitsOptions {})
328337 if err != nil {
329- log . Fatalf ("error getting commit log %s " , err )
338+ return nil , fmt . Errorf ("error fetching commits: %w " , err )
330339 }
331340
332- var subjects []string
333-
334- gitlabMergeRegex := regexp .MustCompile (`Merge \w{40} into \w{40}` )
335-
336- err = cIter .ForEach (func (c * object.Commit ) error {
337- if c .Hash == mergeBase [0 ].Hash {
338- return ErrReachedMergeBase
339- }
340- subjectOnly := strings .Split (c .Message , "\n " )[0 ]
341-
342- if ! (repoEnv .EnvName == GITHUB && repoEnv .Event == "pull_request" && gitlabMergeRegex .Match ([]byte (c .Message ))) {
343- // ignore github pull request commits with subject "Merge x into y", these get added automatically by github
344- subjects = append (subjects , subjectOnly )
345- log .Printf ("collected commit hash %s, subject '%s'" , c .Hash , subjectOnly )
346- } else {
347- log .Printf ("ignoring a pull_request Merge commit hash, %s subject '%s'" , c .Hash , subjectOnly )
341+ subjects := []string {}
342+ for _ , c := range commits {
343+ l := strings .SplitN (c .Message , "\n " , 2 )
344+ if len (l ) > 0 {
345+ subjects = append (subjects , l [0 ])
348346 }
349-
350- return nil
351- })
352- if ! errors .Is (err , ErrReachedMergeBase ) {
353- return []string {}, fmt .Errorf ("error tracing commit history: %w" , err )
354347 }
355348
356349 return subjects , nil
357350}
358351
352+ func getCommitSubjects (repoEnv * gitEnv ) ([]string , error ) {
353+ if repoEnv .EnvName == GITHUB {
354+ return getGithubCommitSubjects (repoEnv .Token , repoEnv .ProjectID , repoEnv .PMRequestID )
355+ } else if repoEnv .EnvName == GITLAB {
356+ return gitGitlabCommitSubjects (repoEnv .URL , repoEnv .Token , repoEnv .ProjectID , repoEnv .PMRequestID )
357+ }
358+ return nil , fmt .Errorf ("unrecognized git environment %s" , repoEnv .EnvName )
359+ }
360+
359361var ErrSubjectList = errors .New ("subjects contain errors" )
360362
361363func (c CommitPolicyConfig ) CheckSubjectList (subjects []string ) error {
@@ -402,12 +404,7 @@ func main() {
402404 log .Fatalf ("couldn't auto-detect running environment, please set GITHUB_REF and GITHUB_BASE_REF manually: %s" , err )
403405 }
404406
405- repo , err := git .PlainOpen (repoPath )
406- if err != nil {
407- log .Fatalf ("couldn't open git local git repo: %s" , err )
408- }
409-
410- subjects , err := getCommitSubjects (repo , gitEnv )
407+ subjects , err := getCommitSubjects (gitEnv )
411408 if err != nil {
412409 log .Fatalf ("error getting commit subjects: %s" , err )
413410 }
0 commit comments