Compare commits
38 commits
Author | SHA1 | Date | |
---|---|---|---|
|
a992d1cfd9 | ||
c142133a34 | |||
653e80ee73 | |||
e332ef87cb | |||
1b86948c60 | |||
667ccdd9a1 | |||
66f0f05e55 | |||
9a125449d5 | |||
c420551cdb | |||
7e2de542b6 | |||
08b43cc51b | |||
|
372ed39a62 | ||
041db18447 | |||
86febd99e5 | |||
|
befdff3be1 | ||
e26143661c | |||
64260f301a | |||
9b1a6a928e | |||
c84b1ecd26 | |||
8981c8367e | |||
19b376ac40 | |||
044823552b | |||
2c943b845f | |||
ce06de60eb | |||
80ab70da04 | |||
6d9c48078c | |||
da4a8b3030 | |||
72b5dd18f3 | |||
7ded9bab13 | |||
3253d5ac63 | |||
142ef2e8f8 | |||
50c451f69e | |||
c9fd83e531 | |||
5fd5b36be7 | |||
6c118fb9ff | |||
aa1601e751 | |||
09380fb8d0 | |||
|
d0e7c0bec7 |
12 changed files with 287 additions and 4 deletions
29
.gitea/workflows/CI.yml
Normal file
29
.gitea/workflows/CI.yml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
name: "Go CI"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
golangci:
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.24.2
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v7
|
||||||
|
with:
|
||||||
|
version: v2.0
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.24.2
|
||||||
|
- name: Test
|
||||||
|
run: go test ./...
|
21
.gitea/workflows/pr.yml
Normal file
21
.gitea/workflows/pr.yml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
name: "Lint Pull Request"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, reopened, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: Lint PR Title
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
if: github.base_ref != 'release'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.24.2
|
||||||
|
|
||||||
|
- name: Run Pull Request Lint Action
|
||||||
|
uses: https://git.kjan.de/actions/pull-request-lint@main
|
|
@ -52,23 +52,25 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
git add docs/
|
git add docs/
|
||||||
git commit -m "chore(release): [skip ci]"
|
git commit -m "chore(release): [skip ci]"
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Push changes to release branch
|
- name: Push changes to release branch
|
||||||
if: ${{ steps.semantic.outputs.new_release_published }}
|
if: ${{ steps.semantic.outputs.new_release_published }}
|
||||||
run: |
|
run: |
|
||||||
git remote set-url origin git@${{ env.GITEA_DOMAIN }}:${{ github.repository }}.git
|
git remote set-url origin git@${{ env.GITEA_DOMAIN }}:${{ github.repository }}.git
|
||||||
git push origin HEAD:${{ env.RELEASE_BRANCH }}
|
git push origin HEAD:${{ env.RELEASE_BRANCH }}
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
- name: Checkout target branch
|
- name: Checkout target branch
|
||||||
if: ${{ steps.semantic.outputs.new_release_published && !(env.SKIP_MERGE == true) }}
|
if: ${{ !(env.SKIP_MERGE == true) }}
|
||||||
run: git reset --hard && git checkout ${{ env.TARGET_BRANCH }} && git pull
|
run: git reset --hard && git checkout ${{ env.TARGET_BRANCH }} && git pull
|
||||||
|
|
||||||
- name: Merge release
|
- name: Merge release
|
||||||
if: ${{ steps.semantic.outputs.new_release_published && !(env.SKIP_MERGE == true) }}
|
if: ${{ !(env.SKIP_MERGE == true) }}
|
||||||
run: git merge ${{ env.RELEASE_BRANCH }}
|
run: git merge ${{ env.RELEASE_BRANCH }}
|
||||||
|
|
||||||
- name: Push changes to target branch
|
- name: Push changes to target branch
|
||||||
if: ${{ steps.semantic.outputs.new_release_published && !(env.SKIP_MERGE == true) }}
|
if: ${{ !(env.SKIP_MERGE == true) }}
|
||||||
run: |
|
run: |
|
||||||
git remote set-url origin git@${{ env.GITEA_DOMAIN }}:${{ github.repository }}.git
|
git remote set-url origin git@${{ env.GITEA_DOMAIN }}:${{ github.repository }}.git
|
||||||
git push origin HEAD:${{ env.TARGET_BRANCH }}
|
git push origin HEAD:${{ env.TARGET_BRANCH }}
|
||||||
|
|
4
.golangci.yml
Normal file
4
.golangci.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
version: "2"
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- revive
|
40
README.md
40
README.md
|
@ -1,3 +1,41 @@
|
||||||
# pull-request-lint
|
# pull-request-lint
|
||||||
|
|
||||||
Lints the title of a pull request
|
A GitHub/Gitea Action that lints pull request titles to ensure they follow the [Conventional Commits](https://www.conventionalcommits.org/) format.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add the following to your GitHub/Gitea workflow:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Pull Request Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, reopened, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: 1.24.2
|
||||||
|
- uses: https://git.kjan.de/actions/pull-request-lint@release
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Rules
|
||||||
|
|
||||||
|
The action enforces the following Conventional Commits rules for PR titles:
|
||||||
|
|
||||||
|
- Format must be: `type(scope)!: description` (scope and breaking change marker `!` are optional)
|
||||||
|
- Type and scope must be lowercase
|
||||||
|
- Description must start with lowercase
|
||||||
|
- Breaking change indicator and footer are mutually exclusive
|
||||||
|
- Body must be separated from description by a blank line
|
||||||
|
- Footer tokens must use hyphens instead of spaces (except for "BREAKING CHANGE")
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
See [LICENSE](LICENSE) file for details.
|
||||||
|
|
5
action.yml
Normal file
5
action.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
name: 'Pull Request Lint'
|
||||||
|
description: 'A gitea action to validate if a pr follows the semantic commit guidelines'
|
||||||
|
runs:
|
||||||
|
using: 'go'
|
||||||
|
main: 'main.go'
|
|
@ -1,3 +1,46 @@
|
||||||
|
# [1.1.0](https://git.kjan.de/actions/pull-request-lint/compare/v1.0.0...v1.1.0) (2025-04-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add linters ([#11](https://git.kjan.de/actions/pull-request-lint/issues/11)) ([e332ef8](https://git.kjan.de/actions/pull-request-lint/commit/e332ef87cbab6a7e0ebde5bd17a37b50c9aea967))
|
||||||
|
|
||||||
|
# [1.0.0](https://git.kjan.de/actions/pull-request-lint/compare/v0.3.0...v1.0.0) (2025-04-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Code Refactoring
|
||||||
|
|
||||||
|
* some polishing ([86febd9](https://git.kjan.de/actions/pull-request-lint/commit/86febd99e507d250adb18fdfede0a01f6e6f8d46))
|
||||||
|
|
||||||
|
|
||||||
|
### BREAKING CHANGES
|
||||||
|
|
||||||
|
* Version 1
|
||||||
|
|
||||||
|
# [0.3.0](https://git.kjan.de/actions/pull-request-lint/compare/v0.2.1...v0.3.0) (2025-04-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **main:** improve error message for invalid PR title ([c84b1ec](https://git.kjan.de/actions/pull-request-lint/commit/c84b1ecd26d7cbd4530fed79e6078ced5d222e18))
|
||||||
|
* **main:** improve error message for invalid PR title ([8981c83](https://git.kjan.de/actions/pull-request-lint/commit/8981c8367edc7beff7a477510ba3a3a98b6e6751))
|
||||||
|
* **validation:** update error message for invalid commit format ([9b1a6a9](https://git.kjan.de/actions/pull-request-lint/commit/9b1a6a928e3483fd4ea6579cdcd8c8a9e95bb669))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **action:** update to use Go for pull request linting ([6d9c480](https://git.kjan.de/actions/pull-request-lint/commit/6d9c48078c912b91bb60d8147b1b2d7e74ad8643))
|
||||||
|
* add GitHub event handling and pull request title display ([0448235](https://git.kjan.de/actions/pull-request-lint/commit/044823552b5fecf0b076098d1bc47d213c00f604))
|
||||||
|
* add pull request linting action and validation logic ([09380fb](https://git.kjan.de/actions/pull-request-lint/commit/09380fb8d002b200cebb4d56e8800569a0b60f16))
|
||||||
|
* **invoke-binary:** add ls command execution in runGo function ([da4a8b3](https://git.kjan.de/actions/pull-request-lint/commit/da4a8b3030406f5ba8c82093f2385060cd0d48b8))
|
||||||
|
|
||||||
|
## [0.2.1](https://git.kjan.de/actions/pull-request-lint/compare/v0.2.0...v0.2.1) (2025-04-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix release pipeline ([bc0bf00](https://git.kjan.de/actions/pull-request-lint/commit/bc0bf00947c563652b674cab947f863c5c477dd2))
|
||||||
|
|
||||||
# [0.2.0](https://git.kjan.de/actions/pull-request-lint/compare/v0.1.0...v0.2.0) (2025-04-09)
|
# [0.2.0](https://git.kjan.de/actions/pull-request-lint/compare/v0.1.0...v0.2.0) (2025-04-09)
|
||||||
|
|
||||||
|
|
||||||
|
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module git.kjan.de/actions/pull-request-lint
|
||||||
|
|
||||||
|
go 1.24.2
|
0
go.sum
Normal file
0
go.sum
Normal file
78
internal/validation/validation.go
Normal file
78
internal/validation/validation.go
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// Package validation General validation tasks
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateConventionalCommit validates if string follows conventional commit
|
||||||
|
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>.+)$`)
|
||||||
|
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")
|
||||||
|
|
||||||
|
commitType := match[typeIndex]
|
||||||
|
scope := match[scopeIndex]
|
||||||
|
breaking := match[breakingIndex]
|
||||||
|
|
||||||
|
// Type MUST be lowercase
|
||||||
|
if commitType != strings.ToLower(commitType) {
|
||||||
|
return fmt.Errorf("type must be 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
|
||||||
|
}
|
54
main.go
Normal file
54
main.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// Package main is the main package
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.kjan.de/actions/pull-request-lint/internal/validation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GithubEvent represents a github actions event
|
||||||
|
type GithubEvent struct {
|
||||||
|
PullRequest struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
} `json:"pull_request"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
eventPath := os.Getenv("GITHUB_EVENT_PATH")
|
||||||
|
if eventPath == "" {
|
||||||
|
fmt.Println("GITHUB_EVENT_PATH not set")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
eventFile, err := os.Open(eventPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error opening %s: %v\n", eventPath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
closeFileErr := eventFile.Close()
|
||||||
|
if closeFileErr != nil {
|
||||||
|
fmt.Println("Error closing eventFile")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var event GithubEvent
|
||||||
|
decoder := json.NewDecoder(eventFile)
|
||||||
|
err = decoder.Decode(&event)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error decoding event.json: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
prTitle := event.PullRequest.Title
|
||||||
|
semanticValidationErr := validation.ValidateConventionalCommit(prTitle)
|
||||||
|
if semanticValidationErr != nil {
|
||||||
|
fmt.Println(semanticValidationErr)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
6
renovate.json
Normal file
6
renovate.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"local>Renovate/renovate-config"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue