Compare commits
	
		
			51 commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 40a2f9b4bf | |||
| 5338260fa5 | |||
| 782a65defd | |||
| 1072f56978 | |||
| cb8f334cf1 | |||
| 1124e9e9d2 | |||
| 5f10d4db1f | |||
| c37db05f27 | |||
| 387939ae90 | |||
| 2be3eff534 | |||
| f3f357eaeb | |||
| 41d9dd7604 | |||
| 2f146b5f0c | |||
|  | 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@v6 | ||||||
|  |         with: | ||||||
|  |           go-version: 1.25.1 | ||||||
|  |       - 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@v6 | ||||||
|  |         with: | ||||||
|  |           go-version: 1.25.1 | ||||||
|  |       - 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@v6 | ||||||
|  |         with: | ||||||
|  |           go-version: 1.25.1 | ||||||
|  | 
 | ||||||
|  |       - 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.6 | ||||||
							
								
								
									
										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
	
	