@@ -2,40 +2,38 @@ package main
22
33import (
44 "bytes"
5+ "errors"
56 "fmt"
67 "io/ioutil"
78 "log"
89 "os"
910 "os/exec"
1011 "regexp"
1112 "strings"
13+ "unicode/utf8"
1214
1315 yaml "gopkg.in/yaml.v2"
1416)
1517
16- const guidelinesLink = "Please refer to https://github.com/haproxy/haproxy/blob/master/CONTRIBUTING#L632"
17-
18- type patchScope_t struct {
19- Scope string `yaml:"Scope"`
20- Values []string `yaml:"Values"`
21- }
22- type patchType_t struct {
18+ type patchTypeT struct {
2319 Values []string `yaml:"Values"`
2420 Scope string `yaml:"Scope"`
2521}
26- type tagAlternatives_t struct {
22+
23+ type tagAlternativesT struct {
2724 PatchTypes []string `yaml:"PatchTypes"`
2825 Optional bool `yaml:"Optional"`
2926}
3027
31- type prgConfig struct {
32- PatchScopes map [string ][]string `yaml:"PatchScopes"`
33- PatchTypes map [string ]patchType_t `yaml:"PatchTypes"`
34- TagOrder []tagAlternatives_t `yaml:"TagOrder"`
35- HelpText string `yaml:"HelpText"`
28+ type CommitPolicyConfig struct {
29+ PatchScopes map [string ][]string `yaml:"PatchScopes"`
30+ PatchTypes map [string ]patchTypeT `yaml:"PatchTypes"`
31+ TagOrder []tagAlternativesT `yaml:"TagOrder"`
32+ HelpText string `yaml:"HelpText"`
3633}
3734
38- var defaultConf string = `
35+ const (
36+ defaultConf = `
3937---
4038HelpText: "Please refer to https://github.com/haproxy/haproxy/blob/master/CONTRIBUTING#L632"
4139PatchScopes:
@@ -70,84 +68,114 @@ TagOrder:
7068 - HAProxy Standard Feature Commit
7169`
7270
73- var myConfig prgConfig
71+ minSubjectParts = 3
72+ maxSubjectParts = 15
73+ minSubjectLen = 15
74+ maxSubjectLen = 100
75+ )
76+
77+ var ErrSubjectMessageFormat = errors .New ("invalid subject message format" )
78+
79+ func checkSubjectText (subject string ) error {
80+ subjectLen := utf8 .RuneCountInString (subject )
81+ subjectParts := strings .Fields (subject )
82+ subjectPartsLen := len (subjectParts )
83+
84+ if subject != strings .Join (subjectParts , " " ) {
85+ log .Printf ("malformatted subject string (trailing or double spaces?): '%s'\n " , subject )
86+ }
87+
88+ if subjectPartsLen < minSubjectParts || subjectPartsLen > maxSubjectParts {
89+ return fmt .Errorf (
90+ "subject word count out of bounds [words %d < %d < %d] '%s': %w" ,
91+ minSubjectParts , subjectPartsLen , maxSubjectParts , subjectParts , ErrSubjectMessageFormat )
92+ }
93+
94+ if subjectLen < minSubjectLen || subjectLen > maxSubjectLen {
95+ return fmt .Errorf (
96+ "subject length out of bounds [len %d < %d < %d] '%s': %w" ,
97+ minSubjectLen , subjectLen , maxSubjectLen , subject , ErrSubjectMessageFormat )
98+ }
99+
100+ return nil
101+ }
74102
75- func checkSubject ( rawSubject [] byte ) error {
76- r , _ := regexp . Compile ( "^(?P<match>(?P<tag>[A-Z]+)( \\ /(?P<scope>[A-Z]+))?: )" ) // 5 subgroups, 4. is "/scope", 5. is "scope"
103+ func ( c CommitPolicyConfig ) CheckPatchTypes ( tag , severity string , patchTypeName string ) bool {
104+ tagScopeOK := false
77105
78- t_tag := []byte ("$tag" )
79- t_scope := []byte ("$scope" )
106+ for _ , allowedTag := range c .PatchTypes [patchTypeName ].Values {
107+ if tag == allowedTag {
108+ if severity == "" {
109+ tagScopeOK = true
110+
111+ break
112+ }
113+
114+ if c .PatchTypes [patchTypeName ].Scope == "" {
115+ log .Printf ("unable to verify severity %s without definitions" , severity )
116+
117+ break // subject has severity but there is no definition to verify it
118+ }
119+
120+ for _ , allowedScope := range c .PatchScopes [c .PatchTypes [patchTypeName ].Scope ] {
121+ if severity == allowedScope {
122+ tagScopeOK = true
123+
124+ break
125+ }
126+ }
127+ }
128+ }
129+
130+ return tagScopeOK
131+ }
132+
133+ var ErrTagScope = errors .New ("invalid tag and or severity" )
134+
135+ func (c CommitPolicyConfig ) CheckSubject (rawSubject []byte ) error {
136+ // 5 subgroups, 4. is "/severity", 5. is "severity"
137+ r := regexp .MustCompile (`^(?P<match>(?P<tag>[A-Z]+)(\/(?P<severity>[A-Z]+))?: )` )
138+
139+ tTag := []byte ("$tag" )
140+ tScope := []byte ("$severity" )
80141 result := []byte {}
81142
82- var tag string
83- var scope string
143+ var tag , severity string
144+
145+ for _ , tagAlternative := range c .TagOrder {
84146
85- for _ , tagAlternative := range myConfig .TagOrder {
86- // log.Printf("processing tagalternative %s\n", tagAlternative)
87147 tagOK := tagAlternative .Optional
88- for _ , pType := range tagAlternative .PatchTypes {
89- // log.Printf("processing patchtype %s", pType)
148+ for _ , pType := range tagAlternative .PatchTypes { // we allow more than one set of tags in a position
90149
91150 submatch := r .FindSubmatchIndex (rawSubject )
92151 if len (submatch ) == 0 { // no match
93152 continue
94153 }
154+
95155 tagPart := rawSubject [submatch [0 ]:submatch [1 ]]
96156
97- tag = string (r .Expand (result , t_tag , tagPart , submatch ))
98- scope = string (r .Expand (result , t_scope , tagPart , submatch ))
99-
100- tagScopeOK := false
101-
102- for _ , allowedTag := range myConfig .PatchTypes [pType ].Values {
103- if tag == allowedTag {
104- // log.Printf("found allowed tag %s\n", tag)
105- if scope == "" {
106- tagScopeOK = true
107- } else {
108- if myConfig .PatchTypes [pType ].Scope == "" {
109- log .Printf ("subject scope problem" )
110- break // subject has scope but there is no definition to verify it
111- }
112- for _ , allowedScope := range myConfig .PatchScopes [myConfig .PatchTypes [pType ].Scope ] {
113- if scope == allowedScope {
114- tagScopeOK = true
115- }
116- }
117- }
118- }
119- }
120- if tagScopeOK { //we found what we were looking for, so consume input
157+ tag = string (r .Expand (result , tTag , tagPart , submatch ))
158+ severity = string (r .Expand (result , tScope , tagPart , submatch ))
159+
160+ if c .CheckPatchTypes (tag , severity , pType ) { // we found what we were looking for, so consume input
121161 rawSubject = rawSubject [submatch [1 ]:]
162+ tagOK = tagOK || true
122163 }
123- tagOK = tagOK || tagScopeOK
124- // log.Printf("tag is %s, scope is %s, rest is %s\n", tag, scope, rawSubject)
125164 }
165+
126166 if ! tagOK {
127- return fmt .Errorf ("invalid tag or no tag found: %s/%s " , tag , scope )
167+ return fmt .Errorf ("invalid tag or no tag found: %w " , ErrTagScope )
128168 }
129169 }
130170
131- subject := string (rawSubject )
132- subjectParts := strings . Fields ( subject )
171+ return checkSubjectText ( string (rawSubject ) )
172+ }
133173
134- if subject != strings . Join ( subjectParts , " " ) {
135- log . Printf ( "malformatted subject string (trailing or double spaces?): '%s' \n " , subject )
136- }
174+ func ( c CommitPolicyConfig ) IsEmpty () bool {
175+ c1 , _ := yaml . Marshal ( c )
176+ c2 , _ := yaml . Marshal ( new ( CommitPolicyConfig )) // empty config
137177
138- if len (subjectParts ) < 3 {
139- return fmt .Errorf ("Too short or meaningless commit subject [words %d < 3] '%s'" , len (subjectParts ), subjectParts )
140- }
141- if len (subject ) < 15 {
142- return fmt .Errorf ("Too short or meaningless commit subject [len %d < 15]'%s'" , len (subject ), subject )
143- }
144- if len (subjectParts ) > 15 {
145- return fmt .Errorf ("Too long commit subject [words %d > 15 - use msg body] '%s'" , len (subjectParts ), subjectParts )
146- }
147- if len (subject ) > 100 {
148- return fmt .Errorf ("Too long commit subject [len %d > 100] '%s'" , len (subject ), subject )
149- }
150- return nil
178+ return string (c1 ) == string (c2 )
151179}
152180
153181type gitEnv struct {
@@ -161,70 +189,90 @@ type gitEnvVars struct {
161189 BaseVar string
162190}
163191
164- var knownVars []gitEnvVars = []gitEnvVars {
165- {"Github" , "GITHUB_REF" , "GITHUB_BASE_REF" },
166- {"Gitlab" , "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" , "CI_MERGE_REQUEST_TARGET_BRANCH_NAME" },
167- }
192+ var ErrGitEnvironment = errors .New ("git environment error" )
168193
169194func readGitEnvironment () (* gitEnv , error ) {
195+ knownVars := []gitEnvVars {
196+ {"Github" , "GITHUB_REF" , "GITHUB_BASE_REF" },
197+ {"Gitlab" , "CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" , "CI_MERGE_REQUEST_TARGET_BRANCH_NAME" },
198+ }
199+
170200 var ref , base string
171201 for _ , vars := range knownVars {
172202 ref = os .Getenv (vars .RefVar )
173203 base = os .Getenv (vars .BaseVar )
204+
174205 if ref != "" && base != "" {
175206 log .Printf ("detected %s environment\n " , vars .EnvName )
207+
176208 return & gitEnv {
177209 Ref : ref ,
178210 Base : base ,
179211 }, nil
180212 }
181213 }
182- return nil , fmt .Errorf ("no suitable git environment variables found" )
214+
215+ return nil , fmt .Errorf ("no suitable git environment variables found %w" , ErrGitEnvironment )
183216}
184217
185- func main () {
218+ func LoadCommitPolicy (filename string ) (CommitPolicyConfig , error ) {
219+ var commitPolicy CommitPolicyConfig
220+
186221 var config string
187- if data , err := ioutil . ReadFile ( ".check-commit.yml" ); err == nil {
188- config = string ( data )
189- } else {
190- log . Printf ( "no config found, using built-in fallback configuration (HAProxy defaults)" )
222+
223+ if data , err := ioutil . ReadFile ( filename ); err != nil {
224+ log . Printf ( "error reading config (%s), using built-in fallback configuration (HAProxy defaults)" , err )
225+
191226 config = defaultConf
227+ } else {
228+ config = string (data )
192229 }
193230
194- if err := yaml .Unmarshal ([]byte (config ), & myConfig ); err != nil {
231+ if err := yaml .Unmarshal ([]byte (config ), & commitPolicy ); err != nil {
232+ return CommitPolicyConfig {}, fmt .Errorf ("error loading commit policy: %w" , err )
233+ }
234+
235+ return commitPolicy , nil
236+ }
237+
238+ func main () {
239+ commitPolicy , err := LoadCommitPolicy (".check-commit.yml" )
240+ if err != nil {
195241 log .Fatalf ("error reading configuration: %s" , err )
196242 }
197- c1 , _ := yaml .Marshal (myConfig )
198- c2 , _ := yaml .Marshal (prgConfig {}) // empty config
199- if string (c1 ) == string (c2 ) {
243+
244+ if commitPolicy .IsEmpty () {
200245 log .Printf ("WARNING: using empty configuration (i.e. no verification)" )
201246 }
202247
203- var out []byte
204-
205248 gitEnv , err := readGitEnvironment ()
206249 if err != nil {
207250 log .Fatalf ("couldn't auto-detect running environment, please set GITHUB_REF and GITHUB_BASE_REF manually" )
208251 }
209252
210- out , err = exec .Command ("git" , "log" , fmt .Sprintf ("%s...%s" , gitEnv .Base , gitEnv .Ref ), "--pretty=format:'%s'" ).Output ()
253+ commitRange := fmt .Sprintf ("%s...%s" , gitEnv .Base , gitEnv .Ref )
254+
255+ out , err := exec .Command ("git" , "log" , commitRange , "--pretty=format:'%s'" ).Output ()
211256 if err != nil {
212257 log .Fatalf ("Unable to get log subject '%s'" , err )
213258 }
214259
215260 // Check subject
216261 errors := false
262+
217263 for _ , subject := range bytes .Split (out , []byte ("\n " )) {
264+
218265 subject = bytes .Trim (subject , "'" )
219- if err := checkSubject (subject ); err != nil {
266+ if err := commitPolicy . CheckSubject (subject ); err != nil {
220267 log .Printf ("%s, original subject message '%s'" , err , string (subject ))
268+
221269 errors = true
222270 }
223271 }
224272
225273 if errors {
226274 log .Printf ("encountered one or more commit message errors\n " )
227- log .Fatalf ("%s\n " , myConfig .HelpText )
275+ log .Fatalf ("%s\n " , commitPolicy .HelpText )
228276 } else {
229277 log .Printf ("check completed without errors\n " )
230278 }
0 commit comments