feat: initial implementation #5

Merged
jank merged 21 commits from main into release 2025-04-09 16:25:18 +00:00
6 changed files with 154 additions and 0 deletions
Showing only changes of commit 09380fb8d0 - Show all commits

5
action.yml Normal file
View 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
View 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
View 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
View 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)
}
}

View 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
View file

@ -0,0 +1,11 @@
package pullrequestlint
import (
"fmt"
"os"
)
func main() {
prTitle := os.Getenv("GITEA_PULL_REQUEST_TITLE")
fmt.Println(prTitle)
}