feat: initial implementation #5
6 changed files with 154 additions and 0 deletions
5
action.yml
Normal file
5
action.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
name: 'Pull Request Lint'
|
||||||
|
description: 'Validates a pull request for semantical commit message'
|
||||||
|
runs:
|
||||||
|
using: 'go'
|
||||||
|
main: 'main.go'
|
13
go.mod
Normal file
13
go.mod
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module git.kjan.de/actions/pull-request-lint
|
||||||
|
|
||||||
|
go 1.24.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
code.gitea.io/sdk/gitea v0.21.0 // indirect
|
||||||
|
github.com/42wim/httpsig v1.2.2 // indirect
|
||||||
|
github.com/davidmz/go-pageant v1.0.2 // indirect
|
||||||
|
github.com/go-fed/httpsig v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||||
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
)
|
26
go.sum
Normal file
26
go.sum
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
|
||||||
|
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
|
||||||
|
github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA=
|
||||||
|
github.com/42wim/httpsig v1.2.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY=
|
||||||
|
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
|
||||||
|
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
|
||||||
|
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||||
|
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||||
|
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||||
|
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
16
internal/gitea/gitea.go
Normal file
16
internal/gitea/gitea.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"code.gitea.io/sdk/gitea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getPrTitle() {
|
||||||
|
_, err := gitea.NewClient("https://git.kjan.de")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
83
internal/validation/validation.go
Normal file
83
internal/validation/validation.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidateConventionalCommit(commit string) error {
|
||||||
|
// Regex to match the commit format
|
||||||
|
// type(scope)!: description
|
||||||
|
// or
|
||||||
|
// type!: description
|
||||||
|
// or
|
||||||
|
// type: description
|
||||||
|
re := regexp.MustCompile(`^(?P<type>[a-z]+)(?P<scope>\([a-z]+\))?(?P<breaking>!)?: (?P<description>[a-z].+)$`)
|
||||||
|
match := re.FindStringSubmatch(commit)
|
||||||
|
|
||||||
|
if len(match) == 0 {
|
||||||
|
return fmt.Errorf("invalid commit format")
|
||||||
|
}
|
||||||
|
|
||||||
|
typeIndex := re.SubexpIndex("type")
|
||||||
|
scopeIndex := re.SubexpIndex("scope")
|
||||||
|
breakingIndex := re.SubexpIndex("breaking")
|
||||||
|
descriptionIndex := re.SubexpIndex("description")
|
||||||
|
|
||||||
|
commitType := match[typeIndex]
|
||||||
|
scope := match[scopeIndex]
|
||||||
|
breaking := match[breakingIndex]
|
||||||
|
description := match[descriptionIndex]
|
||||||
|
|
||||||
|
// Type MUST be lowercase
|
||||||
|
if commitType != strings.ToLower(commitType) {
|
||||||
|
return fmt.Errorf("type must be lowercase")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description MUST start with lowercase
|
||||||
|
if description != strings.ToLower(description) {
|
||||||
|
return fmt.Errorf("description must start with lowercase")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scope MUST be lowercase
|
||||||
|
if scope != "" && scope != strings.ToLower(scope) {
|
||||||
|
return fmt.Errorf("scope must be lowercase")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for breaking change indicator
|
||||||
|
hasBreakingChangeFooter := strings.Contains(commit, "BREAKING CHANGE:")
|
||||||
|
if breaking == "!" && hasBreakingChangeFooter {
|
||||||
|
return fmt.Errorf("breaking change indicator and footer are mutually exclusive")
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(commit, "\n")
|
||||||
|
if len(lines) > 1 {
|
||||||
|
body := strings.Join(lines[1:], "\n")
|
||||||
|
// Check if body is separated from description by a blank line
|
||||||
|
if !strings.HasPrefix(body, "\n") {
|
||||||
|
return fmt.Errorf("body must be separated from description by a blank line")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for footers
|
||||||
|
footerRegex := regexp.MustCompile(`(?m)^(?P<token>[A-Za-z-]+|BREAKING CHANGE): (?P<value>.*)$`)
|
||||||
|
footerMatches := footerRegex.FindAllStringSubmatch(commit, -1)
|
||||||
|
|
||||||
|
for _, footerMatch := range footerMatches {
|
||||||
|
tokenIndex := footerRegex.SubexpIndex("token")
|
||||||
|
token := footerMatch[tokenIndex]
|
||||||
|
|
||||||
|
// BREAKING-CHANGE MUST be synonymous with BREAKING CHANGE
|
||||||
|
if token == "BREAKING-CHANGE" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token MUST use - in place of whitespace characters, except for BREAKING CHANGE
|
||||||
|
if token != "BREAKING CHANGE" && strings.Contains(token, " ") {
|
||||||
|
return fmt.Errorf("footer token must use - in place of whitespace characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
11
main.go
Normal file
11
main.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package pullrequestlint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
prTitle := os.Getenv("GITEA_PULL_REQUEST_TITLE")
|
||||||
|
fmt.Println(prTitle)
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue