├── CODEOWNERS ├── docs ├── .gitignore ├── themes │ └── hugo-geekdoc │ │ ├── VERSION │ │ ├── layouts │ │ ├── taxonomy │ │ │ ├── list.html │ │ │ └── taxonomy.html │ │ ├── partials │ │ │ ├── head │ │ │ │ ├── custom.html │ │ │ │ ├── favicons.html │ │ │ │ ├── meta.html │ │ │ │ └── others.html │ │ │ ├── svg-icon-symbols.html │ │ │ ├── title.html │ │ │ ├── content.html │ │ │ ├── search.html │ │ │ ├── menu.html │ │ │ ├── foot.html │ │ │ ├── menu-extra.html │ │ │ ├── site-footer.html │ │ │ ├── menu-filetree.html │ │ │ ├── page-footer.html │ │ │ ├── menu-bundle.html │ │ │ ├── page-header.html │ │ │ └── site-header.html │ │ ├── shortcodes │ │ │ ├── hint.html │ │ │ ├── icon.html │ │ │ ├── columns.html │ │ │ ├── toc.html │ │ │ ├── tab.html │ │ │ ├── mermaid.html │ │ │ ├── expand.html │ │ │ ├── include.html │ │ │ ├── tabs.html │ │ │ ├── button.html │ │ │ ├── katex.html │ │ │ ├── toc-tree.html │ │ │ └── img.html │ │ ├── _default │ │ │ ├── _markup │ │ │ │ ├── render-image.html │ │ │ │ ├── render-link.html │ │ │ │ └── render-heading.html │ │ │ ├── list.html │ │ │ ├── single.html │ │ │ └── baseof.html │ │ ├── posts │ │ │ ├── single.html │ │ │ └── list.html │ │ └── 404.html │ │ ├── static │ │ ├── custom.css │ │ ├── favicon │ │ │ ├── favicon.ico │ │ │ ├── mstile-70x70.png │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── mstile-144x144.png │ │ │ ├── mstile-150x150.png │ │ │ ├── mstile-310x150.png │ │ │ ├── mstile-310x310.png │ │ │ ├── apple-touch-icon.png │ │ │ ├── android-chrome-192x192.png │ │ │ ├── android-chrome-512x512.png │ │ │ ├── browserconfig.xml │ │ │ ├── site.webmanifest │ │ │ └── safari-pinned-tab.svg │ │ ├── js │ │ │ ├── clipboard-loader-f0b5fbd5f6.min.js │ │ │ ├── katex-loader-3cfedeea38.min.js │ │ │ ├── mermaid-loader-1bd1515cbf.min.js │ │ │ ├── groupBy-174feb11c7.min.js │ │ │ ├── darkmode-ce906ea916.min.js │ │ │ ├── auto-render-e6e57901eb.min.js │ │ │ └── clipboard-27784b7376.min.js │ │ ├── fonts │ │ │ ├── Metropolis.woff │ │ │ ├── GeekdocIcons.woff │ │ │ ├── GeekdocIcons.woff2 │ │ │ ├── Metropolis.woff2 │ │ │ ├── KaTeX_Main-Bold.ttf │ │ │ ├── KaTeX_Main-Bold.woff │ │ │ ├── LiberationMono.woff │ │ │ ├── LiberationMono.woff2 │ │ │ ├── LiberationSans.woff │ │ │ ├── LiberationSans.woff2 │ │ │ ├── KaTeX_AMS-Regular.ttf │ │ │ ├── KaTeX_AMS-Regular.woff │ │ │ ├── KaTeX_AMS-Regular.woff2 │ │ │ ├── KaTeX_Fraktur-Bold.ttf │ │ │ ├── KaTeX_Fraktur-Bold.woff │ │ │ ├── KaTeX_Main-Bold.woff2 │ │ │ ├── KaTeX_Main-Italic.ttf │ │ │ ├── KaTeX_Main-Italic.woff │ │ │ ├── KaTeX_Main-Italic.woff2 │ │ │ ├── KaTeX_Main-Regular.ttf │ │ │ ├── KaTeX_Main-Regular.woff │ │ │ ├── KaTeX_Math-Italic.ttf │ │ │ ├── KaTeX_Math-Italic.woff │ │ │ ├── KaTeX_Math-Italic.woff2 │ │ │ ├── KaTeX_Size1-Regular.ttf │ │ │ ├── KaTeX_Size2-Regular.ttf │ │ │ ├── KaTeX_Size3-Regular.ttf │ │ │ ├── KaTeX_Size4-Regular.ttf │ │ │ ├── KaTeX_Fraktur-Bold.woff2 │ │ │ ├── KaTeX_Fraktur-Regular.ttf │ │ │ ├── KaTeX_Main-BoldItalic.ttf │ │ │ ├── KaTeX_Main-Regular.woff2 │ │ │ ├── KaTeX_Math-BoldItalic.ttf │ │ │ ├── KaTeX_SansSerif-Bold.ttf │ │ │ ├── KaTeX_SansSerif-Bold.woff │ │ │ ├── KaTeX_Script-Regular.ttf │ │ │ ├── KaTeX_Script-Regular.woff │ │ │ ├── KaTeX_Size1-Regular.woff │ │ │ ├── KaTeX_Size1-Regular.woff2 │ │ │ ├── KaTeX_Size2-Regular.woff │ │ │ ├── KaTeX_Size2-Regular.woff2 │ │ │ ├── KaTeX_Size3-Regular.woff │ │ │ ├── KaTeX_Size3-Regular.woff2 │ │ │ ├── KaTeX_Size4-Regular.woff │ │ │ ├── KaTeX_Size4-Regular.woff2 │ │ │ ├── LiberationSans-Bold.woff │ │ │ ├── LiberationSans-Bold.woff2 │ │ │ ├── KaTeX_Caligraphic-Bold.ttf │ │ │ ├── KaTeX_Caligraphic-Bold.woff │ │ │ ├── KaTeX_Caligraphic-Bold.woff2 │ │ │ ├── KaTeX_Fraktur-Regular.woff │ │ │ ├── KaTeX_Fraktur-Regular.woff2 │ │ │ ├── KaTeX_Main-BoldItalic.woff │ │ │ ├── KaTeX_Main-BoldItalic.woff2 │ │ │ ├── KaTeX_Math-BoldItalic.woff │ │ │ ├── KaTeX_Math-BoldItalic.woff2 │ │ │ ├── KaTeX_SansSerif-Bold.woff2 │ │ │ ├── KaTeX_SansSerif-Italic.ttf │ │ │ ├── KaTeX_SansSerif-Italic.woff │ │ │ ├── KaTeX_SansSerif-Italic.woff2 │ │ │ ├── KaTeX_SansSerif-Regular.ttf │ │ │ ├── KaTeX_SansSerif-Regular.woff │ │ │ ├── KaTeX_Script-Regular.woff2 │ │ │ ├── KaTeX_Typewriter-Regular.ttf │ │ │ ├── LiberationSans-Italic.woff │ │ │ ├── LiberationSans-Italic.woff2 │ │ │ ├── KaTeX_Caligraphic-Regular.ttf │ │ │ ├── KaTeX_Caligraphic-Regular.woff │ │ │ ├── KaTeX_SansSerif-Regular.woff2 │ │ │ ├── KaTeX_Typewriter-Regular.woff │ │ │ ├── KaTeX_Typewriter-Regular.woff2 │ │ │ ├── LiberationSans-BoldItalic.woff │ │ │ ├── KaTeX_Caligraphic-Regular.woff2 │ │ │ └── LiberationSans-BoldItalic.woff2 │ │ ├── print-f79fc3e5d7.min.css │ │ ├── mobile-c344439d04.min.css │ │ └── brand.svg │ │ ├── data │ │ ├── assets-static.json │ │ └── assets.json │ │ ├── images │ │ ├── tn.png │ │ ├── readme.png │ │ └── screenshot.png │ │ ├── archetypes │ │ ├── posts.md │ │ └── docs.md │ │ ├── theme.toml │ │ ├── assets │ │ ├── search-data.json │ │ └── js │ │ │ └── search.js │ │ ├── LICENSE │ │ └── README.md ├── content │ ├── usage │ │ ├── source.md │ │ ├── circleci.md │ │ ├── gobinaries.md │ │ ├── docker.md │ │ ├── gitlab.md │ │ ├── binary.md │ │ └── github.md │ ├── intro.md │ ├── contributing │ │ ├── pipelines.mdx │ │ ├── filtering.mdx │ │ └── get_started.md │ └── configuration │ │ ├── flags.md │ │ └── config-file.md ├── archetypes │ └── default.md └── config.toml ├── .tool-versions ├── FUNDING.yml ├── .mise.toml ├── .replit ├── testdata ├── .gitignore ├── long-history.bundle ├── commits-on-master.bundle ├── commits-on-different-branches.bundle └── setup-test-repos.sh ├── renovate.json ├── internal ├── prpipeline │ ├── docs.go │ ├── types.go │ ├── pipeline.go │ ├── title.go │ ├── run.go │ └── run_test.go ├── commitpipeline │ ├── docs.go │ ├── log_branch_test.go │ ├── log_branch.go │ ├── pipeline.go │ ├── commits_between_hashes_test.go │ ├── commits_between_hashes.go │ ├── identify_same_branch.go │ ├── commits_between_branches_test.go │ ├── commits_between_branches.go │ └── run.go ├── dispatcher │ ├── handle_errors.go │ ├── success_handler.go │ ├── pipeline.go │ ├── work.go │ ├── dispatcher_test.go │ └── dispatcher.go ├── version_runner │ ├── version_runner.go │ └── version_runner_test.go └── root_runner │ ├── root_runner.go │ ├── run.go │ └── run_test.go ├── config ├── testdata │ └── .commitsar.yaml ├── commits.go └── commits_test.go ├── .gitignore ├── tools.go ├── .dockerignore ├── pkg ├── text │ ├── docs.go │ ├── is_revert_commit.go │ ├── is_initial_commit_test.go │ ├── is_initial_commit.go │ ├── is_merge_commit.go │ ├── is_revert_commit_test.go │ ├── check_required_scopes.go │ ├── format_failing_commits.go │ ├── is_merge_commit_test.go │ ├── format_failing_commits_test.go │ ├── check_required_scopes_test.go │ ├── check_message_title.go │ └── check_message_title_test.go └── jira │ ├── build_regex_test.go │ ├── find_references.go │ ├── build_regex.go │ └── find_references_test.go ├── .commitsar.yaml ├── CONTRIBUTING.md ├── entrypoint.sh ├── mage.go ├── action.yml ├── .github └── workflows │ ├── semgrep.yml │ ├── goreleaser-check.yml │ ├── release.yml │ ├── build.yml │ ├── linters.yml │ └── test.yml ├── .goreleaser.yml ├── Makefile ├── scripts └── publish_to_pages.sh ├── LICENSE ├── README.md ├── Dockerfile ├── .kodiak.toml ├── go.mod ├── CODE_OF_CONDUCT.md └── main.go /CODEOWNERS: -------------------------------------------------------------------------------- 1 | @aevea/OSS -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | public -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | golang 1.25.5 -------------------------------------------------------------------------------- /FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: fallion 2 | -------------------------------------------------------------------------------- /.mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | go = "1.25.5" 3 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/VERSION: -------------------------------------------------------------------------------- 1 | v0.17.1 2 | -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | language = "go" 2 | run = "make test" 3 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/taxonomy/list.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/taxonomy/taxonomy.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /testdata/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !*.bundle 3 | !setup-test-repos.sh -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/custom.css: -------------------------------------------------------------------------------- 1 | /* You can add custom styles here. */ 2 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/data/assets-static.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom.css": "custom.css" 3 | } 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "postUpdateOptions": ["gomodTidy"], 3 | "extends": ["config:base"] 4 | } 5 | -------------------------------------------------------------------------------- /docs/content/usage/source.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: source 3 | title: Building from source 4 | --- 5 | 6 | TODO 7 | -------------------------------------------------------------------------------- /testdata/long-history.bundle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/testdata/long-history.bundle -------------------------------------------------------------------------------- /testdata/commits-on-master.bundle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/testdata/commits-on-master.bundle -------------------------------------------------------------------------------- /internal/prpipeline/docs.go: -------------------------------------------------------------------------------- 1 | /*package prpipeline handles all actions related to PR checking */ 2 | package prpipeline 3 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/images/tn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/images/tn.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/head/custom.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /internal/commitpipeline/docs.go: -------------------------------------------------------------------------------- 1 | // Package commitpipeline handles all work related to commits 2 | package commitpipeline 3 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/archetypes/posts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | --- 5 | -------------------------------------------------------------------------------- /docs/archetypes/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ replace .Name "-" " " | title }}" 3 | date: {{ .Date }} 4 | draft: true 5 | --- 6 | 7 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/images/readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/images/readme.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/images/screenshot.png -------------------------------------------------------------------------------- /testdata/commits-on-different-branches.bundle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/testdata/commits-on-different-branches.bundle -------------------------------------------------------------------------------- /config/testdata/.commitsar.yaml: -------------------------------------------------------------------------------- 1 | verbose: true 2 | commits: 3 | strict: false 4 | limit: 100 5 | all: true 6 | upstreamBranch: origin/main -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | testdata/commits_on_branch 3 | testdata/git_tags 4 | www/**/_gen/** 5 | **/node_modules/** 6 | www/public 7 | tmp 8 | .idea 9 | dist -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/head/favicons.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/favicon.ico -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/js/clipboard-loader-f0b5fbd5f6.min.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded",function(n){new ClipboardJS(".clip")}); -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/Metropolis.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/Metropolis.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/js/katex-loader-3cfedeea38.min.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded",function(){renderMathInElement(document.body)}); -------------------------------------------------------------------------------- /tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | package tools 4 | 5 | import ( 6 | _ "github.com/magefile/mage" 7 | ) 8 | 9 | func main() { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | testdata 2 | www 3 | scripts 4 | renovate.json 5 | .drone.yml 6 | .goreleaser.yml 7 | .kodiak.toml 8 | CNAME 9 | .github 10 | .commitsar.yaml -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/hint.html: -------------------------------------------------------------------------------- 1 |
2 | {{ .Inner | $.Page.RenderString }} 3 |
4 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/mstile-70x70.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/GeekdocIcons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/GeekdocIcons.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/GeekdocIcons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/GeekdocIcons.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/Metropolis.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/Metropolis.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/mstile-144x144.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/mstile-150x150.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/mstile-310x150.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/mstile-310x310.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Bold.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Bold.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationMono.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationMono.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationMono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationMono.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationSans.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationSans.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationSans.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationSans.woff2 -------------------------------------------------------------------------------- /pkg/text/docs.go: -------------------------------------------------------------------------------- 1 | // Package text contains functions to parse Git Commit texts and some basic helpers for identifying type of commit by its message. 2 | package text 3 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_AMS-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_AMS-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_AMS-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_AMS-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_AMS-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_AMS-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Bold.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Bold.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Bold.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Italic.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Italic.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Italic.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-Italic.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-Italic.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-Italic.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size1-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size1-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size2-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size2-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size3-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size3-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size4-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size4-Regular.ttf -------------------------------------------------------------------------------- /.commitsar.yaml: -------------------------------------------------------------------------------- 1 | version: 1 2 | verbose: true 3 | commits: 4 | strict: true 5 | disabled: false 6 | ## Needs investigation 7 | # pull_request: 8 | # conventional: true 9 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Bold.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-BoldItalic.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-BoldItalic.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Bold.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Bold.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Script-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Script-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Script-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Script-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size1-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size1-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size1-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size1-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size2-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size2-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size2-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size2-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size3-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size3-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size3-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size3-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size4-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size4-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size4-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Size4-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationSans-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationSans-Bold.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationSans-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationSans-Bold.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Bold.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Bold.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Bold.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Fraktur-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-BoldItalic.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Main-BoldItalic.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-BoldItalic.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Math-BoldItalic.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Bold.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Italic.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Italic.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Italic.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Script-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Script-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Typewriter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Typewriter-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationSans-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationSans-Italic.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationSans-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationSans-Italic.woff2 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are most welcome. Please check the [CONTRIBUTING](https://commitsar.aevea.ee/contributing/get_started) section of documentation. 4 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Regular.ttf -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_SansSerif-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Typewriter-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Typewriter-Regular.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Typewriter-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Typewriter-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationSans-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationSans-BoldItalic.woff -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/icon.html: -------------------------------------------------------------------------------- 1 | {{ $id := .Get 0 }} 2 | 3 | {{- with $id -}} 4 | 5 | {{- end -}} 6 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/KaTeX_Caligraphic-Regular.woff2 -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/fonts/LiberationSans-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aevea/commitsar/HEAD/docs/themes/hugo-geekdoc/static/fonts/LiberationSans-BoldItalic.woff2 -------------------------------------------------------------------------------- /internal/prpipeline/types.go: -------------------------------------------------------------------------------- 1 | package prpipeline 2 | 3 | type PRStyle = string 4 | 5 | const ( 6 | JiraStyle PRStyle = "jira" 7 | ConventionalStyle PRStyle = "conventional" 8 | ) 9 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/archetypes/docs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "{{ .Name | humanize | title }}" 3 | weight: 1 4 | # geekdocFlatSection: false 5 | # geekdocToc: 6 6 | # geekdocHidden: false 7 | --- 8 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/_default/_markup/render-image.html: -------------------------------------------------------------------------------- 1 | {{ .Text }} 2 | {{- /* Drop trailing newlines */ -}} 3 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/svg-icon-symbols.html: -------------------------------------------------------------------------------- 1 | {{ range resources.Match "sprites/*.svg" }} 2 | {{ printf "" . | safeHTML }} 3 | {{ .Content | safeHTML }} 4 | {{ end }} 5 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # Fix git safe.directory for GitHub Actions 5 | # The workspace is mounted at /github/workspace and may have different ownership 6 | git config --global --add safe.directory '*' 7 | 8 | exec "$@" 9 | -------------------------------------------------------------------------------- /docs/content/usage/circleci.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: circleci 3 | title: CircleCI 4 | --- 5 | 6 | Minimal usage example: 7 | 8 | ```yaml 9 | validate-commits: 10 | docker: 11 | - image: aevea/commitsar 12 | steps: 13 | - checkout 14 | - run: commitsar 15 | ``` 16 | -------------------------------------------------------------------------------- /pkg/text/is_revert_commit.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import "strings" 4 | 5 | func IsRevertCommit(commitMessage string) bool { 6 | if strings.HasPrefix(commitMessage, "Revert") { 7 | return true 8 | } 9 | 10 | return strings.HasPrefix(commitMessage, "revert") 11 | } 12 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/columns.html: -------------------------------------------------------------------------------- 1 |
2 | {{ range split .Inner "<--->" }} 3 |
4 | {{ . | $.Page.RenderString }} 5 |
6 | {{ end }} 7 |
8 | -------------------------------------------------------------------------------- /testdata/setup-test-repos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $0) 4 | 5 | for bundle in *.bundle; do 6 | repo="${bundle%.*}" 7 | rm -rf ./$repo; 8 | echo "testdata/$repo directory does not exist at the root; creating..."; 9 | git clone $bundle; 10 | echo "done"; 11 | done 12 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/toc.html: -------------------------------------------------------------------------------- 1 | {{ $tocLevels := default (default 6 .Site.Params.GeekdocToC) .Page.Params.GeekdocToC }} 2 | 3 | {{ if and $tocLevels .Page.TableOfContents }} 4 |
{{ .Page.TableOfContents }}
5 | {{ end }} 6 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/_default/list.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | {{ partial "page-header" . }} 3 |
4 |

{{ partial "title" . }}

5 | {{ partial "content" . }} 6 |
7 | {{ end }} 8 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/_default/_markup/render-link.html: -------------------------------------------------------------------------------- 1 | {{- $raw := or (hasPrefix .Text "{{ .Text | safeHTML }} 3 | {{- /* Drop trailing newlines */ -}} 4 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/_default/single.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | {{ partial "page-header" . }} 3 | 4 |
5 |

{{ partial "title" . }}

6 | {{ partial "content" . }} 7 |
8 | {{ end }} 9 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #2f333e 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/title.html: -------------------------------------------------------------------------------- 1 | {{ $title := "" }} 2 | 3 | {{ if .Title }} 4 | {{ $title = .Title }} 5 | {{ else if and .IsSection .File }} 6 | {{ $title = path.Base .File.Dir | humanize | title }} 7 | {{ else if and .IsPage .File }} 8 | {{ $title = .File.BaseFileName | humanize | title }} 9 | {{ end }} 10 | 11 | {{ return $title }} 12 | -------------------------------------------------------------------------------- /pkg/text/is_initial_commit_test.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "testing" 6 | ) 7 | 8 | func TestIsInitialCommit(t *testing.T) { 9 | tests := map[string]bool { 10 | "Initial commit": true, 11 | } 12 | 13 | for test, expected := range tests { 14 | assert.Equal(t, expected, IsInitialCommit(test)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/js/mermaid-loader-1bd1515cbf.min.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded",function(e){var a=localStorage.getItem(THEME),t=window.matchMedia("(prefers-color-scheme: dark)");let r="#ececff",o=!1;(a===DARK_MODE||a===AUTO_MODE&&t.matches)&&(r="#6C617E",o=!0),mermaid.initialize({flowchart:{useMaxWidth:!0},theme:"base",themeVariables:{darkMode:o,primaryColor:r}})}); -------------------------------------------------------------------------------- /mage.go: -------------------------------------------------------------------------------- 1 | //+build mage 2 | 3 | package main 4 | 5 | import ( 6 | "github.com/aevea/magefiles" 7 | "github.com/magefile/mage/sh" 8 | ) 9 | 10 | func Test() error { 11 | err := sh.RunV("sh", "./testdata/setup-test-repos.sh") 12 | 13 | if err != nil { 14 | return err 15 | } 16 | 17 | return magefiles.Test() 18 | } 19 | 20 | func GoModTidy() error { 21 | return magefiles.GoModTidy() 22 | } 23 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/content.html: -------------------------------------------------------------------------------- 1 | {{- $content := .Content -}} 2 | {{- $content = $content | replaceRE `` `` | safeHTML -}} 3 | {{- $content = $content | replaceRE `((?:.|\n)+?
)` `
${1}
` | safeHTML -}} 4 | {{- $content -}} 5 | -------------------------------------------------------------------------------- /pkg/jira/build_regex_test.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestBuildRegex(t *testing.T) { 10 | tests := map[string][]string{ 11 | defaultJiraRegex: {}, 12 | `(TEST|PROJ|FEOP)-[0-9]+`: {"TEST", "PROJ", "FEOP"}, 13 | } 14 | 15 | for expected, test := range tests { 16 | assert.Equal(t, expected, buildRegex(test)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /pkg/text/is_initial_commit.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import "regexp" 4 | 5 | var initCommitRegex = regexp.MustCompile(`^Initial .+`) 6 | 7 | // IsInitialCommit checks if a commit needs to be filtered, because it is the init commit of any given repo 8 | func IsInitialCommit(commitMessage string) bool{ 9 | initCommitMatch := initCommitRegex.FindStringSubmatch(commitMessage) 10 | 11 | return initCommitMatch != nil 12 | } 13 | -------------------------------------------------------------------------------- /pkg/text/is_merge_commit.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | var mergeCommitRegex = regexp.MustCompile(`^Merge .+`) 8 | 9 | // IsMergeCommit tests message string against expected format of a merge commit and returns true/false based on it 10 | func IsMergeCommit(message string) bool { 11 | mergeCommitMatch := mergeCommitRegex.FindStringSubmatch(message) 12 | 13 | return mergeCommitMatch != nil 14 | } 15 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/tab.html: -------------------------------------------------------------------------------- 1 | {{ if .Parent }} 2 | {{ $name := .Get 0 }} 3 | {{ $group := printf "tabs-%s" (.Parent.Get 0) }} 4 | 5 | {{ if not (.Parent.Scratch.Get $group) }} 6 | {{ .Parent.Scratch.Set $group slice }} 7 | {{ end }} 8 | 9 | {{ .Parent.Scratch.Add $group (dict "Name" $name "Content" .Inner) }} 10 | {{ else }} 11 | {{ errorf "%q: 'tab' shortcode must be inside 'tabs' shortcode" .Page.Path }} 12 | {{ end}} 13 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/theme.toml: -------------------------------------------------------------------------------- 1 | name = "Geekdoc" 2 | license = "MIT" 3 | licenselink = "https://github.com/thegeeklab/hugo-geekdoc/blob/main/LICENSE" 4 | description = "Hugo theme made for documentation" 5 | homepage = "https://geekdocs.de/" 6 | demosite = "https://geekdocs.de/" 7 | tags = ["docs", "documentation", "responsive", "simple"] 8 | min_version = "0.83.0" 9 | 10 | [author] 11 | name = "Robert Kaussow" 12 | homepage = "https://thegeeklab.de/" 13 | -------------------------------------------------------------------------------- /internal/dispatcher/handle_errors.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/apex/log" 7 | ) 8 | 9 | func (dispatch *Dispatcher) handleErrors( 10 | wg *sync.WaitGroup, 11 | channel <-chan PipelineError, 12 | results *Results, 13 | ) { 14 | defer wg.Done() 15 | 16 | for message := range channel { 17 | log.Debugf("[%s] %s", message.PipelineName, message.Error) 18 | 19 | results.Errors = append(results.Errors, message) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Commitsar Action" 2 | description: "Check commit message compliance with conventional commits" 3 | author: "Simon Prochazka" 4 | branding: 5 | icon: "code" 6 | color: "blue" 7 | inputs: 8 | config: 9 | description: "Path to the folder where your .commitsar.yaml is" 10 | required: false 11 | default: "." 12 | runs: 13 | using: "docker" 14 | image: "Dockerfile" 15 | args: 16 | - commitsar 17 | - "--config-path=${{ inputs.config }}" 18 | -------------------------------------------------------------------------------- /docs/config.toml: -------------------------------------------------------------------------------- 1 | baseURL = "https://commitsar.aevea.ee" 2 | title = "Commitsar" 3 | theme = "hugo-geekdoc" 4 | 5 | pluralizeListTitles = false 6 | 7 | # Geekdoc required configuration 8 | pygmentsUseClasses = true 9 | pygmentsCodeFences = true 10 | disablePathToLower = true 11 | 12 | # Needed for mermaid shortcodes 13 | [markup] 14 | [markup.goldmark.renderer] 15 | # Needed for mermaid shortcode 16 | unsafe = true 17 | [markup.tableOfContents] 18 | startLevel = 1 19 | endLevel = 9 -------------------------------------------------------------------------------- /docs/content/usage/gobinaries.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: gobinaries 3 | title: GoBinaries 4 | --- 5 | 6 | Install commitsar on your machine using 7 | 8 | ```sh 9 | curl -sf https://gobinaries.com/aevea/commitsar | sh 10 | ``` 11 | 12 | Or a specific version: 13 | 14 | ```sh 15 | curl -sf https://gobinaries.com/aevea/commitsar[@VERSION] | sh 16 | ``` 17 | 18 | **To use:** 19 | 20 | ```sh 21 | commitsar 22 | ``` 23 | 24 | Or with a path: 25 | ```sh 26 | commitsar ./path-to-repo 27 | ``` -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/assets/search-data.json: -------------------------------------------------------------------------------- 1 | [ 2 | {{ range $index, $page := (where .Site.Pages "Params.GeekdocProtected" "ne" true) }} 3 | {{ if ne $index 0 }},{{ end }} 4 | { 5 | "id": {{ $index }}, 6 | "href": "{{ $page.RelPermalink }}", 7 | "title": {{ (partial "title" $page) | jsonify }}, 8 | "parent": {{ with $page.Parent }}{{ (partial "title" .) | jsonify }}{{ else }}""{{ end }}, 9 | "content": {{ $page.Plain | jsonify }} 10 | } 11 | {{ end }} 12 | ] 13 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/mermaid.html: -------------------------------------------------------------------------------- 1 | {{ if not (.Page.Scratch.Get "mermaid") }} 2 | 3 | 4 | 5 | {{ .Page.Scratch.Set "mermaid" true }} 6 | {{ end }} 7 | 8 |
 9 |   {{- .Inner -}}
10 | 
11 | -------------------------------------------------------------------------------- /internal/dispatcher/success_handler.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/apex/log" 7 | ) 8 | 9 | func (dispatch *Dispatcher) handleSuccess( 10 | wg *sync.WaitGroup, 11 | channel <-chan PipelineSuccess, 12 | results *Results, 13 | ) { 14 | defer wg.Done() 15 | 16 | for message := range channel { 17 | log.Debugf("[%s] %s", message.PipelineName, message.Message) 18 | 19 | results.SuccessfulPipelines = append(results.SuccessfulPipelines, message) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /pkg/jira/find_references.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "regexp" 5 | ) 6 | 7 | // FindReferences scans a given message looking for all issues that match the keys. Default to the JIRA default regex if no keys are provided. 8 | func FindReferences(keys []string, message string) ([]string, error) { 9 | regex, err := regexp.Compile(buildRegex(keys)) 10 | 11 | if err != nil { 12 | return nil, err 13 | } 14 | 15 | matches := regex.FindAllString(message, -1) 16 | 17 | return matches, nil 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | # name: Semgrep 2 | # on: [pull_request] 3 | # jobs: 4 | # semgrep: 5 | # name: Scan 6 | # runs-on: ubuntu-latest 7 | # steps: 8 | # - uses: actions/checkout@v1 9 | # - uses: returntocorp/semgrep-action@v1 10 | # env: # Optional environment variable for inline PR comments (beta) 11 | # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 12 | # with: 13 | # publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} 14 | # publishDeployment: 174 15 | -------------------------------------------------------------------------------- /internal/version_runner/version_runner.go: -------------------------------------------------------------------------------- 1 | package version_runner 2 | 3 | import "github.com/apex/log" 4 | 5 | // VersionInfo houses all info regarding to the version. It is here to avoid global variables in this package. 6 | type VersionInfo struct { 7 | Version string 8 | Date string 9 | } 10 | 11 | // Run executes the command to get version and prints it into stdout 12 | func Run(versionInfo VersionInfo) error { 13 | log.Infof("Commitsar version: %s\t Built on: %s", versionInfo.Version, versionInfo.Date) 14 | return nil 15 | } 16 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/print-f79fc3e5d7.min.css: -------------------------------------------------------------------------------- 1 | @media print{.editpage,.gdoc-footer .container span:not(:first-child),.gdoc-nav{display:none}.gdoc-footer{border-top:1px solid #dee2e6}.gdoc-markdown pre{white-space:pre-wrap;overflow-wrap:break-word}.chroma code{border:1px solid #dee2e6;padding:.5rem!important;font-weight:400!important}.gdoc-markdown code{font-weight:700}a,a:visited{color:inherit!important;text-decoration:none!important}.gdoc-toc{flex:none}.gdoc-toc nav{position:relative;width:auto}.wrapper{display:block}.wrapper main{display:block}} -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 2 | # Make sure to check the documentation at https://goreleaser.com 3 | version: 2 4 | 5 | before: 6 | hooks: 7 | - go mod download 8 | 9 | builds: 10 | - env: 11 | - CGO_ENABLED=0 12 | main: ./main.go 13 | goos: 14 | - windows 15 | - linux 16 | - darwin 17 | 18 | checksum: 19 | name_template: "checksums.txt" 20 | 21 | snapshot: 22 | version_template: "{{ .Tag }}-next" 23 | 24 | changelog: 25 | disable: true 26 | -------------------------------------------------------------------------------- /docs/content/usage/docker.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: docker 3 | title: Docker 4 | --- 5 | 6 | For running in docker just use the following command: 7 | 8 | ```sh 9 | docker run --rm --name="commitsar" -w /src -v "$(pwd)":/src aevea/commitsar 10 | ``` 11 | 12 | ```sh 13 | docker run --rm --name="commitsar" -w /src -v "$(pwd)":/src aevea/commitsar ./path-to-repo 14 | ``` 15 | 16 | Make sure to load the working directory where `.git` folder is present. Commitsar will not work without the `.git` folder. This can be overridden by setting the path argument. 17 | -------------------------------------------------------------------------------- /docs/content/usage/gitlab.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: gitlab 3 | title: Gitlab CI 4 | --- 5 | 6 | For Gitlab usage you can include the following job: 7 | 8 | ```yaml 9 | validate-commits: 10 | stage: test 11 | image: aevea/commitsar 12 | script: 13 | - git fetch origin master 14 | - commitsar 15 | ``` 16 | 17 | **Important** In case of an error such as: `reference not found` please set a higher `GIT_DEPTH` variable setting. Commitsar currently relies on full commit objects which do not get pulled in on the shallow clone that `GIT_DEPTH` uses. 18 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/search.html: -------------------------------------------------------------------------------- 1 | {{ if default true .Site.Params.GeekdocSearch }} 2 | 9 | {{ end }} 10 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/js/groupBy-174feb11c7.min.js: -------------------------------------------------------------------------------- 1 | const groupBy=(r,...e)=>{let t=r.map(t=>e.map(e=>e(t))),a={};return t.forEach((e,t)=>{t=(_simpleAt(a,e)||[]).concat([r[t]]);_simpleSet(a,e,t)}),a},_isPlainObject=e=>null!=e&&"object"==typeof e&&e.constructor==Object,_parsePath=e=>Array.isArray(e)?e:`${e}`.split("."),_simpleAt=(e,t)=>_parsePath(t).reduce((e,t)=>null!=e&&e.hasOwnProperty(t)?e[t]:void 0,e),_simpleSet=(e,t,s)=>_parsePath(t).reduce((e,t,r,a)=>{a=r===a.length-1;return e.hasOwnProperty(t)&&(a||_isPlainObject(e[t]))||(e[t]={}),a?e[t]=s:e[t]},e); -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/expand.html: -------------------------------------------------------------------------------- 1 | {{ $id := substr (sha1 .Inner) 0 8 }} 2 |
3 | 7 | 8 |
9 | {{ .Inner | $.Page.RenderString }} 10 |
11 |
12 | -------------------------------------------------------------------------------- /pkg/text/is_revert_commit_test.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIsRevertCommit(t *testing.T) { 10 | tests := map[string]bool{ 11 | "Revert some commit": true, 12 | "revert \"some commit\"": true, 13 | "chore: something\n": false, 14 | "fix: test": false, 15 | "fix: Kodiak style regex": false, 16 | } 17 | 18 | for test, expected := range tests { 19 | err := IsRevertCommit(test) 20 | assert.Equal(t, expected, err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/favicon/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/favicon/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#2f333e", 17 | "background_color": "#2f333e", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/head/meta.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ $description := default (default .Site.Title .Site.Params.description) (default .Summary .Description) }} 6 | {{ $keywords := default .Site.Params.Keywords .Keywords }} 7 | 8 | {{ with $description }} 9 | 10 | {{ end }} 11 | {{ with $keywords }} 12 | 13 | {{ end }} 14 | -------------------------------------------------------------------------------- /internal/version_runner/version_runner_test.go: -------------------------------------------------------------------------------- 1 | package version_runner 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/apex/log" 7 | "github.com/apex/log/handlers/memory" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestVersionRun(t *testing.T) { 12 | handler := memory.New() 13 | 14 | log.SetHandler(handler) 15 | 16 | err := Run(VersionInfo{ 17 | Version: "development", 18 | Date: "2012-1-1", 19 | }, 20 | ) 21 | 22 | assert.NoError(t, err) 23 | assert.Equal(t, "Commitsar version: development\t Built on: 2012-1-1", handler.Entries[0].Message) 24 | } 25 | -------------------------------------------------------------------------------- /pkg/jira/build_regex.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import "strings" 4 | 5 | const ( 6 | defaultJiraRegex = `([A-Z][A-Z0-9]{1,10}-[0-9]+)` 7 | ) 8 | 9 | func buildRegex(keys []string) string { 10 | if len(keys) == 0 { 11 | return defaultJiraRegex 12 | } 13 | 14 | result := strings.Builder{} 15 | 16 | result.WriteString("(") 17 | 18 | for index, key := range keys { 19 | result.WriteString(key) 20 | 21 | if (index + 1) != len(keys) { 22 | result.WriteString("|") 23 | } 24 | } 25 | 26 | result.WriteString(")") 27 | 28 | result.WriteString("-[0-9]+") 29 | 30 | return result.String() 31 | } 32 | -------------------------------------------------------------------------------- /docs/content/usage/binary.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: binary 3 | title: Binary 4 | --- 5 | 6 | Adjust for version and distribution. Please check [Releases](https://github.com/aevea/commitsar/releases). 7 | 8 | ```sh 9 | - curl -L -O https://github.com/aevea/commitsar/releases/download/v0.0.2/commitsar_v0.0.2_Linux_x86_64.tar.gz 10 | - tar -xzf commitsar_v0.0.2_Linux_x86_64.tar.gz 11 | - ./commitsar 12 | ``` 13 | 14 | ```sh 15 | - curl -L -O https://github.com/aevea/commitsar/releases/download/v0.0.2/commitsar_v0.0.2_Linux_x86_64.tar.gz 16 | - tar -xzf commitsar_v0.0.2_Linux_x86_64.tar.gz 17 | - ./commitsar ./path-to-repo 18 | ``` 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | define prepare_build_vars 2 | $(eval DATE_FLAG := -X 'main.date=$(shell date)') 3 | $(eval VERSION_FLAG=-X 'main.version=$(shell git name-rev --tags --name-only $(shell git rev-parse HEAD))') 4 | $(eval COMMIT_FLAG=-X 'main.commit=$(shell git rev-parse HEAD)') 5 | endef 6 | 7 | build/docker: 8 | $(call prepare_build_vars) 9 | CGO_ENABLED=0 go build -a -tags "osusergo netgo" --ldflags "${DATE_FLAG} ${VERSION_FLAG} ${COMMIT_FLAG}" -o build/commitsar ./main.go 10 | 11 | build/local: 12 | $(call prepare_build_vars) 13 | go build -a --ldflags "${DATE_FLAG} ${VERSION_FLAG} ${COMMIT_FLAG}" -o build/commitsar ./main.go -------------------------------------------------------------------------------- /internal/prpipeline/pipeline.go: -------------------------------------------------------------------------------- 1 | package prpipeline 2 | 3 | type Options struct { 4 | // Path to the git repository 5 | Path string 6 | // Styles are the styles of PR title to enforce (can be multiple) 7 | Styles []PRStyle 8 | // Keys checks for required keys in the PR title 9 | Keys []string 10 | } 11 | 12 | // Pip 13 | type Pipeline struct { 14 | options Options 15 | } 16 | 17 | func New(options Options) (*Pipeline, error) { 18 | if options.Path == "" { 19 | options.Path = "." 20 | } 21 | 22 | return &Pipeline{options: options}, nil 23 | } 24 | 25 | func (pipeline *Pipeline) Name() string { 26 | return "pr-pipeline" 27 | } 28 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/include.html: -------------------------------------------------------------------------------- 1 | {{ $file := .Get "file" }} 2 | {{ $page := .Site.GetPage $file }} 3 | {{ $type := .Get "type" }} 4 | {{ $language := .Get "language" }} 5 | {{ $options :=.Get "options" }} 6 | 7 |
8 | {{- if (.Get "language") -}} 9 | {{- highlight ($file | readFile) $language (default "linenos=table" $options) -}} 10 | {{- else if eq $type "html" -}} 11 | {{- $file | readFile | safeHTML -}} 12 | {{- else if eq $type "page" -}} 13 | {{- with $page }}{{ .Content }}{{ end -}} 14 | {{- else -}} 15 | {{- $file | readFile | $.Page.RenderString -}} 16 | {{- end -}} 17 |
18 | -------------------------------------------------------------------------------- /pkg/text/check_required_scopes.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var errMissingRequiredScope = errors.New("required scope missing") 8 | 9 | // RequiredScopeChecker creates case sensitive strict checker, validating that 10 | // provided scope matches one from required scopes list 11 | func RequiredScopeChecker(requiredScopes []string) func(string) error { 12 | return func(scope string) error { 13 | if len(requiredScopes) < 1 { 14 | return nil 15 | } 16 | 17 | for _, rs := range requiredScopes { 18 | if scope == rs { 19 | return nil 20 | } 21 | } 22 | 23 | return errMissingRequiredScope 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/goreleaser-check.yml: -------------------------------------------------------------------------------- 1 | name: GoReleaser Check 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".goreleaser.yml" 7 | - ".github/workflows/goreleaser-check.yml" 8 | push: 9 | branches: 10 | - master 11 | paths: 12 | - ".goreleaser.yml" 13 | - ".github/workflows/goreleaser-check.yml" 14 | 15 | jobs: 16 | check: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Check out code 20 | uses: actions/checkout@v6 21 | 22 | - name: Check GoReleaser config 23 | uses: goreleaser/goreleaser-action@v6 24 | with: 25 | version: latest 26 | args: check 27 | -------------------------------------------------------------------------------- /docs/content/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: intro 3 | title: Introduction 4 | slug: / 5 | --- 6 | 7 | Tool to make sure your commits are compliant with [conventional commits](https://www.conventionalcommits.org). It is aimed mainly at CIs to prevent branches with commits that don't comply. Usage as a pre-commit hook is also under consideration. 8 | 9 | ## Usage 10 | 11 | Commitsar is shipped as a Dockerfile. This is the easiest way to add it to your CI. 12 | 13 | **Important: Commitsar currently needs to be run in the same folder as the git repository you want checked, currently no override is provided for setting path to git repo see https://github.com/aevea/commitsar/issues/93** 14 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/tabs.html: -------------------------------------------------------------------------------- 1 | {{ if .Inner }}{{ end }} 2 | {{ $id := .Get 0 }} 3 | {{ $group := printf "tabs-%s" $id }} 4 | 5 |
6 | {{ range $index, $tab := .Scratch.Get $group }} 7 | 9 | 12 |
13 | {{ .Content | $.Page.RenderString }} 14 |
15 | {{ end }} 16 |
17 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/button.html: -------------------------------------------------------------------------------- 1 | {{ $ref := "" }} 2 | {{ $target := "" }} 3 | {{ $size := default "regular" (.Get "size" | lower) }} 4 | 5 | {{ if not (in (slice "regular" "large") $size) }} 6 | {{ $size = "regular" }} 7 | {{ end }} 8 | 9 | {{ with .Get "href" }} 10 | {{ $ref = . }} 11 | {{ $target = "_blank" }} 12 | {{ end }} 13 | 14 | {{ with .Get "relref" }} 15 | {{ $ref = relref $ . }} 16 | {{ end }} 17 | 18 | 19 | 20 | {{ $.Inner }} 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/menu.html: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /internal/commitpipeline/log_branch_test.go: -------------------------------------------------------------------------------- 1 | package commitpipeline 2 | 3 | import ( 4 | "testing" 5 | 6 | history "github.com/aevea/git/v4" 7 | "github.com/apex/log" 8 | "github.com/apex/log/handlers/memory" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestLogBranch(t *testing.T) { 13 | handler := memory.New() 14 | 15 | log.SetHandler(handler) 16 | 17 | runner, err := New(nil) 18 | 19 | assert.NoError(t, err) 20 | 21 | gitRepo, err := history.OpenGit("../../testdata/commits-on-master") 22 | 23 | assert.NoError(t, err) 24 | 25 | err = runner.logBranch(gitRepo) 26 | 27 | assert.NoError(t, err) 28 | assert.Equal(t, "Starting analysis of commits on branch refs/heads/master", handler.Entries[0].Message) 29 | } 30 | -------------------------------------------------------------------------------- /internal/root_runner/root_runner.go: -------------------------------------------------------------------------------- 1 | package root_runner 2 | 3 | type Runner struct { 4 | } 5 | 6 | type RunnerOptions struct { 7 | // Path to repository 8 | Path string 9 | // UpstreamBranch is the branch against which to check 10 | UpstreamBranch string 11 | // Limit will limit how far back to check on upstream branch. 12 | Limit int 13 | // AllCommits will check all the commits on the upstream branch. Regardless of Limit setting. 14 | AllCommits bool 15 | Strict bool 16 | // RequiredScopes will check scope in commit message against list of required ones 17 | RequiredScopes []string 18 | } 19 | 20 | // New returns a new instance of a RootRunner with fallback for logging 21 | func New() *Runner { 22 | 23 | return &Runner{} 24 | } 25 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/foot.html: -------------------------------------------------------------------------------- 1 | {{ if default true .Site.Params.GeekdocSearch }} 2 | {{ .Scratch.Set "geekdocSearchConfig" .Site.Params.GeekdocSearchConfig }} 3 | 4 | {{ $searchJSFile := printf "js/%s.search.js" .Language.Lang }} 5 | {{ $searchJS := resources.Get "js/search.js" | resources.ExecuteAsTemplate $searchJSFile . | resources.Minify | fingerprint }} 6 | 7 | {{ end }} 8 | 9 | {{ if default true .Site.Params.GeekdocAnchorCopy }} 10 | 11 | 12 | {{ end }} 13 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/katex.html: -------------------------------------------------------------------------------- 1 | {{ if not (.Page.Scratch.Get "katex") }} 2 | 3 | 4 | 5 | 6 | 7 | {{ .Page.Scratch.Set "katex" true }} 8 | {{ end }} 9 | 10 | 11 | {{ cond (in .Params "display") "\\[" "\\(" -}} 12 | {{- trim .Inner "\n" -}} 13 | {{- cond (in .Params "display") "\\]" "\\)" }} 14 | 15 | -------------------------------------------------------------------------------- /internal/dispatcher/pipeline.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | // Pipeliner interface describes the requirements for pipelines the dispatcher can run 4 | type Pipeliner interface { 5 | Name() string 6 | Run() (*PipelineSuccess, error) 7 | } 8 | 9 | // FailureData is used to build the table output 10 | // Example: "hash": "somehash". It is built as a struct to enforce order 11 | type FailureData struct { 12 | Name string 13 | Value string 14 | } 15 | 16 | // PipelineSuccess is returned by a pipeline if it encounters no errors 17 | type PipelineSuccess struct { 18 | PipelineName string 19 | Message string 20 | } 21 | 22 | // PipelineError is used to group errors based on PipelineName 23 | type PipelineError struct { 24 | PipelineName string 25 | Data []FailureData 26 | Error error 27 | } 28 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/posts/single.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 |
3 |
4 |

{{ .Title }}

5 | 14 |
15 |
16 | {{ partial "content" . }} 17 |
18 |
19 | {{ end }} 20 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/js/darkmode-ce906ea916.min.js: -------------------------------------------------------------------------------- 1 | const DARK_MODE="dark",LIGHT_MODE="light",AUTO_MODE="auto",THEME="hugo-geekdoc",TOGGLE_MODES=[AUTO_MODE,DARK_MODE,LIGHT_MODE];function toggle(e=[],t){return current=e.indexOf(t),max=e.length-1,next=0,current{const t=document.getElementById("gdoc-dark-mode");t.onclick=function(){var e=localStorage.getItem(THEME),e=toggle(TOGGLE_MODES,e);localStorage.setItem(THEME,TOGGLE_MODES[e]),applyTheme(!1)}}); -------------------------------------------------------------------------------- /scripts/publish_to_pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "`git status -s`" ] 4 | then 5 | echo "The working directory is dirty. Please commit any pending changes." 6 | git status 7 | exit 1; 8 | fi 9 | 10 | echo "Deleting old publication" 11 | mkdir tmp 12 | cp ../CNAME ./tmp/CNAME 13 | rm -rf public 14 | mkdir public 15 | git worktree prune 16 | rm -rf .git/worktrees/public/ 17 | 18 | echo "Checking out gh-pages branch into public" 19 | git worktree add -B gh-pages public origin/gh-pages 20 | 21 | echo "Removing existing files" 22 | rm -rf public/* 23 | 24 | 25 | echo "Generating site" 26 | hugo 27 | mv ./tmp/CNAME ./public/CNAME 28 | 29 | echo "Updating gh-pages branch" 30 | cd public && git add --all && git commit -m "Publishing to gh-pages (publish.sh) [ci skip]" || true 31 | 32 | echo "Pushing to github" 33 | git push origin gh-pages -------------------------------------------------------------------------------- /pkg/text/format_failing_commits.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/jedib0t/go-pretty/table" 7 | ) 8 | 9 | // FailingCommit is just a formatted commit struct 10 | type FailingCommit struct { 11 | Hash string 12 | Message string 13 | Error error 14 | } 15 | 16 | // FormatFailingCommits takes in slice of commit hashes and messages and formats it for nice output 17 | func FormatFailingCommits(commits []FailingCommit) table.Writer { 18 | t := table.NewWriter() 19 | t.AppendHeader(table.Row{"hash", "failure", "text"}) 20 | 21 | builder := strings.Builder{} 22 | // Extra spacing to make it nicer 23 | builder.WriteString("\nFollowing commits failed the check: \n") 24 | 25 | for _, commit := range commits { 26 | t.AppendRow(table.Row{commit.Hash, commit.Error.Error(), commit.Message}) 27 | } 28 | 29 | return t 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | 7 | jobs: 8 | release-notary: 9 | name: Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out code 13 | uses: actions/checkout@v6 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Install mise 18 | uses: jdx/mise-action@v3 19 | 20 | - name: Install tools 21 | run: mise install 22 | 23 | - name: Run GoReleaser 24 | uses: goreleaser/goreleaser-action@v6 25 | with: 26 | version: latest 27 | args: release --clean 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | 31 | - name: Release Notary Action 32 | uses: docker://outillage/release-notary:0.9.4 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/_default/_markup/render-heading.html: -------------------------------------------------------------------------------- 1 | {{- $showAnchor := (and (default true .Page.Params.GeekdocAnchor) (default true .Page.Site.Params.GeekdocAnchor)) -}} 2 | 3 | {{- if $showAnchor -}} 4 |
5 | 6 | {{ .Text | safeHTML }} 7 | 8 | 9 | 10 | 11 |
12 | {{- else -}} 13 |
14 | 15 | {{ .Text | safeHTML }} 16 | 17 |
18 | {{- end -}} 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Docker build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | tags: 7 | - "v*" 8 | 9 | jobs: 10 | docker: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | - name: Kaniko build 15 | uses: aevea/action-kaniko@master 16 | with: 17 | image: aevea/commitsar 18 | username: ${{ secrets.DOCKERHUB_USERNAME }} 19 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 20 | cache: true 21 | cache_registry: aevea/cache 22 | strip_tag_prefix: v 23 | 24 | - name: Sync Readme 25 | uses: ms-jpq/sync-dockerhub-readme@v1 26 | with: 27 | username: ${{ secrets.DOCKERHUB_USERNAME }} 28 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 29 | repository: aevea/commitsar 30 | readme: "./README.md" 31 | -------------------------------------------------------------------------------- /internal/dispatcher/work.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/apex/log" 7 | ) 8 | 9 | func (dispatch *Dispatcher) work( 10 | wg *sync.WaitGroup, 11 | pipelineChannel chan Pipeliner, 12 | successChan chan PipelineSuccess, 13 | errorChannel chan PipelineError, 14 | ) { 15 | defer wg.Done() 16 | defer close(successChan) 17 | defer close(errorChannel) 18 | 19 | for { 20 | pipeline, more := <-pipelineChannel 21 | 22 | if more { 23 | log.Infof("Starting pipeline: %s", pipeline.Name()) 24 | success, err := pipeline.Run() 25 | 26 | if err != nil { 27 | errorChannel <- PipelineError{ 28 | Error: err, 29 | PipelineName: pipeline.Name(), 30 | } 31 | } 32 | 33 | if success != nil { 34 | successChan <- *success 35 | } 36 | } else { 37 | log.Debug("All pipelines complete") 38 | return 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/data/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "custom.css": "custom.css", 3 | "js/auto-render.min.js": "js/auto-render-e6e57901eb.min.js", 4 | "js/clipboard-loader.min.js": "js/clipboard-loader-f0b5fbd5f6.min.js", 5 | "js/clipboard.min.js": "js/clipboard-27784b7376.min.js", 6 | "js/darkmode.min.js": "js/darkmode-ce906ea916.min.js", 7 | "js/flexsearch.min.js": "js/flexsearch-e54a90f706.min.js", 8 | "js/groupBy.min.js": "js/groupBy-174feb11c7.min.js", 9 | "js/katex-loader.min.js": "js/katex-loader-3cfedeea38.min.js", 10 | "js/katex.min.js": "js/katex-b7063e58c5.min.js", 11 | "js/mermaid-loader.min.js": "js/mermaid-loader-1bd1515cbf.min.js", 12 | "js/mermaid.min.js": "js/mermaid-1fc9ef3e82.min.js", 13 | "katex.min.css": "katex-38042a7abd.min.css", 14 | "main.min.css": "main-0c0de99286.min.css", 15 | "mobile.min.css": "mobile-c344439d04.min.css", 16 | "print.min.css": "print-f79fc3e5d7.min.css" 17 | } -------------------------------------------------------------------------------- /docs/content/contributing/pipelines.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: pipelines 3 | title: Worker pipelines 4 | --- 5 | 6 | Commitsar uses pipelines in order to use as much CPU power as possible. These are based on number of CPU \* 2 (assuming 1 physical and 1 logical core available). In order to add a new pipeline 7 | you just need to build a pipeline that adheres to the types provided here: https://github.com/aevea/commitsar/blob/master/internal/dispatcher/pipeline.go and register it in the root_runner. 8 | Each pipeline returns either a success type or pushes errors into the error channel. 9 | 10 | At the end all of this is collected and the results are presented to the user. 11 | 12 | This will also be used to introduce an easy plugin system in the future. 13 | 14 | import Mermaid from "@theme/Mermaid"; 15 | 16 | B(Commit pipeline) 20 | A-->C(PR title pipeline) 21 | `} 22 | /> 23 | -------------------------------------------------------------------------------- /pkg/text/is_merge_commit_test.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestIsMergeCommit(t *testing.T) { 10 | tests := map[string]bool{ 11 | "Merge commit '900a395d573f2b046d0b901be22808bf55319fc7'\n": true, 12 | "Merge branch 'master' into three\n": true, 13 | "Merge branch 'master' into feature/something-word": true, 14 | "Merge master into renovate/docker-alpine-3.x": true, 15 | "Merge pull request #5 from D-Nice/feat/ci": true, 16 | "chore: something\n": false, 17 | "fix: test": false, 18 | "fix: Kodiak style regex": false, 19 | } 20 | 21 | for test, expected := range tests { 22 | err := IsMergeCommit(test) 23 | assert.Equal(t, expected, err) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /pkg/text/format_failing_commits_test.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestFormatFailingCommits(t *testing.T) { 11 | var testString bytes.Buffer 12 | 13 | var commits []FailingCommit 14 | 15 | commits = append(commits, FailingCommit{Hash: "testhash", Message: "chore:add seomthing", Error: errCategoryMissing}, FailingCommit{Hash: "testhash2", Message: "broken", Error: errCategoryMissing}) 16 | 17 | table := FormatFailingCommits(commits) 18 | table.SetOutputMirror(&testString) 19 | table.Render() 20 | 21 | assert.Equal(t, "+-----------+------------------+---------------------+\n| HASH | FAILURE | TEXT |\n+-----------+------------------+---------------------+\n| testhash | category missing | chore:add seomthing |\n| testhash2 | category missing | broken |\n+-----------+------------------+---------------------+\n", testString.String()) 22 | } 23 | -------------------------------------------------------------------------------- /pkg/text/check_required_scopes_test.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRequiredScopesChecker(t *testing.T) { 10 | tests := map[error][]struct { 11 | scope string 12 | requiredScopes []string 13 | }{ 14 | nil: { 15 | {"ci", []string{}}, 16 | {"ui", nil}, 17 | {"ci", []string{"ci", "project-1", "repo", "ui"}}, 18 | {"ci", []string{"ui", "project-1", "repo", "ci"}}, 19 | {"repo", []string{"repo"}}, 20 | }, 21 | errMissingRequiredScope: { 22 | {"SECURITY", []string{"security", "ci", "ui"}}, 23 | {"SeCuRiTy", []string{"security", "ci", "ui"}}, 24 | {"sth", []string{"security", "ci", "ui"}}, 25 | {"se curity", []string{"security", "ci", "ui"}}, 26 | {"", []string{"repo"}}, 27 | }, 28 | } 29 | 30 | for expected, testCases := range tests { 31 | for _, testCase := range testCases { 32 | err := RequiredScopeChecker(testCase.requiredScopes)(testCase.scope) 33 | assert.Equal(t, expected, err) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/content/contributing/filtering.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | id: filtering 3 | title: How commit filtering works 4 | --- 5 | 6 | import Mermaid from "@theme/Mermaid"; 7 | 8 | B(merge branch feat/docs into master) 12 | B-->C(Third commit on master) 13 | B-->|branch| D(first commit on branch) 14 | D-->E(second commit on branch) 15 | E-->F(merge master into branch) 16 | C-->F 17 | `} 18 | /> 19 | 20 | ### Branch diff 21 | 22 | By default commitsar will run git log on the base branch and the branch which is going to get merged and gets a diff of the commit objects. 23 | This allows us to get commits only on the branch regardless of what was merged from master. 24 | 25 | ### Filtering by type 26 | 27 | In the above example when running commitsar on master or the branch the commit `merge branch feat/docs into master` and `merge master into branch` will get filtered out. 28 | This is to prevent commitsar checking the default merge commits which are not going to be conventional commit compliant. 29 | -------------------------------------------------------------------------------- /internal/commitpipeline/log_branch.go: -------------------------------------------------------------------------------- 1 | package commitpipeline 2 | 3 | import ( 4 | "fmt" 5 | 6 | history "github.com/aevea/git/v4" 7 | "github.com/apex/log" 8 | ) 9 | 10 | // logBranch outputs the branch which is being checked into the console 11 | func (pipeline *Pipeline) logBranch(gitRepo *history.Git) error { 12 | log.Debug("Attempting to get current branch...") 13 | 14 | branch, err := gitRepo.CurrentBranch() 15 | 16 | if err != nil { 17 | log.Errorf("Failed to get current branch: %v", err) 18 | return fmt.Errorf("failed to get current branch (this often indicates a detached HEAD state or shallow clone): %w", err) 19 | } 20 | 21 | log.Debugf("Current branch name: %s, hash: %s", branch.Name(), branch.Hash()) 22 | 23 | // Detect and log detached HEAD state 24 | if branch.Name() == "HEAD" { 25 | log.Warn("Running in detached HEAD state. This is common in CI environments.") 26 | log.Debugf("Detached HEAD at commit: %s", branch.Hash()) 27 | } 28 | 29 | log.Infof("Starting analysis of commits on branch %s", branch.Name()) 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /internal/commitpipeline/pipeline.go: -------------------------------------------------------------------------------- 1 | package commitpipeline 2 | 3 | type Pipeline struct { 4 | args []string 5 | options Options 6 | } 7 | 8 | type Options struct { 9 | // Path to repository 10 | Path string 11 | // UpstreamBranch is the branch against which to check 12 | UpstreamBranch string 13 | // Limit will limit how far back to check on upstream branch. 14 | Limit int 15 | // AllCommits will check all the commits on the upstream branch. Regardless of Limit setting. 16 | AllCommits bool 17 | Strict bool 18 | // RequiredScopes will check scope in commit message against list of required ones 19 | RequiredScopes []string 20 | } 21 | 22 | func New(options *Options, args ...string) (*Pipeline, error) { 23 | if options == nil { 24 | options = &Options{ 25 | Path: ".", 26 | UpstreamBranch: "master", 27 | Limit: 0, 28 | AllCommits: false, 29 | Strict: true, 30 | } 31 | } 32 | 33 | return &Pipeline{ 34 | options: *options, 35 | args: args, 36 | }, nil 37 | } 38 | 39 | func (Pipeline) Name() string { 40 | return "commit-pipeline" 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Simon Prochazka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /internal/commitpipeline/commits_between_hashes_test.go: -------------------------------------------------------------------------------- 1 | package commitpipeline 2 | 3 | import ( 4 | "testing" 5 | 6 | history "github.com/aevea/git/v4" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCommitsBetweenHashes(t *testing.T) { 11 | gitRepo, err := history.OpenGit("../../testdata/commits-on-different-branches") 12 | 13 | assert.NoError(t, err) 14 | 15 | commits, err := commitsBetweenHashes(gitRepo, []string{"d0240d3ed34685d0a5329b185e120d3e8c205be4...7dbf3e7db93ae2e02902cae9d2f1de1b1e5c8c92"}) 16 | 17 | // TODO: Allow including to commit 18 | assert.Len(t, commits, 1) 19 | assert.NoError(t, err) 20 | 21 | commit, err := gitRepo.Commit(commits[0]) 22 | 23 | assert.Equal(t, "feat: second commit on master\n", commit.Message) 24 | assert.NoError(t, err) 25 | } 26 | 27 | func TestCommitsBetweenHashesOnlyTo(t *testing.T) { 28 | gitRepo, err := history.OpenGit("../../testdata/commits-on-different-branches") 29 | 30 | assert.NoError(t, err) 31 | 32 | commits, err := commitsBetweenHashes(gitRepo, []string{"d0240d3ed34685d0a5329b185e120d3e8c205be4"}) 33 | 34 | assert.Len(t, commits, 2) 35 | assert.NoError(t, err) 36 | } 37 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Robert Kaussow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/menu-extra.html: -------------------------------------------------------------------------------- 1 | {{ $current := .current }} 2 | {{ template "menu-extra" dict "sect" .source "current" $current "site" $current.Site "target" .target }} 3 | 4 | 5 | {{ define "menu-extra" }} 6 | {{ $current := .current }} 7 | {{ $site := .site }} 8 | {{ $target := .target }} 9 | {{ $sect := .sect }} 10 | 11 | {{ range sort (default (seq 0) $sect) "weight" }} 12 | {{ if isset . "ref" }} 13 | {{ $this := $site.GetPage .ref }} 14 | {{ $isCurrent := eq $current $this }} 15 | {{ $icon := default false .icon }} 16 | 17 | {{ if not .icon }} 18 | {{ errorf "Missing 'icon' attribute in data file for '%s' menu item '%s'" $target .name }} 19 | {{ end }} 20 | 21 | {{ if eq $target "header" }} 22 | 23 | 24 | {{ .name }} 25 | 26 | 27 | 28 | {{ end }} 29 | {{ end }} 30 | {{ end }} 31 | {{ end }} 32 | -------------------------------------------------------------------------------- /docs/content/configuration/flags.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: flags 3 | title: Flags 4 | --- 5 | 6 | For more advanced usage a set of flags are provided. 7 | 8 | | Name | Flag | Required | Default | Description | 9 | | ------- | ----- | -------- | ------- | ---------------------------------------------------------------------------------- | 10 | | Verbose | --v | false | false | Debug output into console | 11 | | Strict | --s | false | true | Strict check of category types | 12 | | All | --all | false | false | Whether to check all commits on given branch. **Takes precedence over LIMIT flag** | 13 | | Config path | --config-path | false | current directory | Path where .commitsar.yaml file is | 14 | 15 | On top of that a single argument is allowed: 16 | 17 | `commitsar ...` 18 | 19 | e.g. `commitsar 7dbf3e7db93ae2e02902cae9d2f1de1b1e5c8c92...d0240d3ed34685d0a5329b185e120d3e8c205be4` 20 | 21 | If only one commit hash is used then commitsar will assume it to be the TO commit. -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/mobile-c344439d04.min.css: -------------------------------------------------------------------------------- 1 | @media screen and (max-width:39rem){.gdoc-nav{margin-left:-16rem;font-size:16px}.gdoc-nav__control{display:inline-block}.gdoc-header .icon{width:1.5rem;height:1.5rem}.gdoc-brand{font-size:1.5rem}.gdoc-brand__img{display:none}.gdoc-menu-header__items{display:none}.gdoc-menu-header__control{display:inline-block}.gdoc-error{padding:6rem 1rem}.gdoc-error .icon{width:6rem;height:6rem}.gdoc-error__message{padding-left:2rem}.gdoc-error__line{padding:.25rem 0}.gdoc-error__title{font-size:2rem}.gdoc-page__header .breadcrumb,.hidden-mobile{display:none}.gdoc-footer__item--row{width:100%}#menu-control:checked~main .gdoc-nav nav,#menu-control:checked~main .gdoc-page{transform:translateX(16rem)}#menu-control:checked~main .gdoc-page{opacity:.25}#menu-control:checked~.gdoc-header .gdoc-nav__control .icon.gdoc_menu{display:none}#menu-control:checked~.gdoc-header .gdoc-nav__control .icon.gdoc_arrow_back{display:inline-block}#menu-header-control:checked~.gdoc-header .gdoc-brand__title{display:none}#menu-header-control:checked~.gdoc-header .gdoc-menu-header__items{display:inline-block}#menu-header-control:checked~.gdoc-header .gdoc-menu-header__control .icon.gdoc_keyborad_arrow_left{display:none}} -------------------------------------------------------------------------------- /internal/commitpipeline/commits_between_hashes.go: -------------------------------------------------------------------------------- 1 | package commitpipeline 2 | 3 | import ( 4 | "strings" 5 | 6 | history "github.com/aevea/git/v4" 7 | ) 8 | 9 | func commitsBetweenHashes(gitRepo *history.Git, args []string) ([]history.Hash, error) { 10 | var commits []history.Hash 11 | var fromCommit history.Hash 12 | var toCommit history.Hash 13 | 14 | arg := args[0] 15 | 16 | splitArgs := strings.Split(arg, "...") 17 | 18 | if len(splitArgs) == 1 { 19 | currentCommit, err := gitRepo.CurrentCommit() 20 | 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | fromCommit = currentCommit.Hash 26 | 27 | toCommit, err = history.NewHash(splitArgs[0]) 28 | if err != nil { 29 | return nil, err 30 | } 31 | } 32 | 33 | if len(splitArgs) == 2 { 34 | var err error 35 | fromCommit, err = history.NewHash(splitArgs[1]) 36 | if err != nil { 37 | return nil, err 38 | } 39 | toCommit, err = history.NewHash(splitArgs[0]) 40 | if err != nil { 41 | return nil, err 42 | } 43 | } 44 | 45 | logCommits, err := gitRepo.CommitsBetween(fromCommit, toCommit) 46 | 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | commits = logCommits 52 | 53 | return commits, nil 54 | } 55 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/posts/list.html: -------------------------------------------------------------------------------- 1 | {{ define "main" }} 2 | {{ range .Paginator.Pages }} 3 |
4 |
5 |

{{ .Title }}

6 | 15 |
16 |
17 | {{ .Summary }} 18 |
19 | {{ if .Truncated }} 20 |
21 | Read full post 22 |
23 | {{ end }} 24 |
25 | {{ end }} 26 | {{ end }} 27 | -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | validate-commits: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Check out code into the Go module directory 10 | uses: actions/checkout@v6 11 | with: 12 | fetch-depth: 0 13 | - name: Install mise 14 | uses: jdx/mise-action@v3 15 | - uses: actions/cache@v5 16 | with: 17 | path: ~/go/pkg/mod 18 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 19 | restore-keys: | 20 | ${{ runner.os }}-go- 21 | - run: mise install 22 | - run: mise exec -- go mod download 23 | - name: Run Commitsar 24 | run: mise exec -- go run main.go 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | LOG_LEVEL: debug 28 | 29 | golangci-lint: 30 | name: runner / golangci-lint 31 | runs-on: ubuntu-latest 32 | steps: 33 | - name: Check out code into the Go module directory 34 | uses: actions/checkout@v6 35 | - name: golangci-lint 36 | uses: docker://reviewdog/action-golangci-lint@sha256:9f8af9633d7a14d2100936693dc609249e13c29a352ce88fdc8a3d0aaadd8b26 37 | with: 38 | github_token: ${{ secrets.github_token }} 39 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/toc-tree.html: -------------------------------------------------------------------------------- 1 | {{ $tocLevels := default (default 6 .Site.Params.GeekdocToC) .Page.Params.GeekdocToC }} 2 | 3 | {{ if $tocLevels }} 4 |
5 | {{ template "toc-tree" dict "sect" .Page.Pages }} 6 |
7 | {{ end }} 8 | 9 | 10 | {{ define "toc-tree" }} 11 |
    12 | {{ range .sect.GroupBy "Weight" }} 13 | {{ range .ByTitle }} 14 | {{ if or (not .Params.GeekdocHidden) (not (default true .Params.GeekdocHiddenTocTree)) }} 15 |
  • 16 | {{ if or .Content .Params.GeekdocFlatSection }} 17 | 18 | {{ partial "title" . }}{{ with .Params.GeekdocDescription }}: {{ . }}{{ else }}{{ end }} 19 | 20 | {{ else }} 21 | {{ partial "title" . }}{{ with .Params.GeekdocDescription }}: {{ . }}{{ end }} 22 | {{ end }} 23 | 24 | {{ $numberOfPages := (add (len .Pages) (len .Sections)) }} 25 | {{ if and (ne $numberOfPages 0) (not .Params.GeekdocFlatSection) }} 26 | {{ template "toc-tree" dict "sect" .Pages }} 27 | {{ end }} 28 |
  • 29 | {{ end }} 30 | {{ end }} 31 | {{ end }} 32 |
33 | {{ end }} 34 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/shortcodes/img.html: -------------------------------------------------------------------------------- 1 | {{ $source := ($.Page.Resources.ByType "image").GetMatch (printf "%s" (.Get "name")) }} 2 | {{ $customAlt := .Get "alt" }} 3 | {{ $customSize := .Get "size" }} 4 | {{ $lazyLoad := default (default true $.Site.Params.GeekdocImageLazyLoading) (.Get "lazy") }} 5 | 6 | {{ with $source }} 7 | {{ $caption := default .Title $customAlt }} 8 | 9 | {{ $tiny := (.Resize "320x").RelPermalink }} 10 | {{ $small := (.Resize "600x").RelPermalink }} 11 | {{ $medium := (.Resize "1200x").RelPermalink }} 12 | {{ $large := (.Resize "1800x").RelPermalink }} 13 | 14 | {{ $size := dict "tiny" $tiny "small" $small "medium" $medium "large" $large }} 15 | 16 |
17 |
18 | 19 | 20 | 21 | {{ $caption }} 22 | 23 | 24 | {{ with $caption -}} 25 |
{{ . }}{{ with $source.Params.credits }} ({{ . | $.Page.RenderString }}){{ end }}
26 | {{- end }} 27 |
28 |
29 | {{ end }} 30 | -------------------------------------------------------------------------------- /internal/commitpipeline/identify_same_branch.go: -------------------------------------------------------------------------------- 1 | package commitpipeline 2 | 3 | import ( 4 | "fmt" 5 | 6 | history "github.com/aevea/git/v4" 7 | "github.com/apex/log" 8 | ) 9 | 10 | // IdentifySameBranch breaks up the reference names and tries to identify if the branches are the same 11 | func IdentifySameBranch(branchA, branchB string, gitRepo *history.Git) (bool, error) { 12 | log.Debugf("Getting latest commit on branch '%s'...", branchA) 13 | commitBranchA, err := gitRepo.LatestCommitOnBranch(branchA) 14 | 15 | if err != nil { 16 | log.Errorf("Failed to get latest commit on branch '%s': %v", branchA, err) 17 | return false, fmt.Errorf("failed to get latest commit on branch '%s': %w", branchA, err) 18 | } 19 | 20 | log.Debugf("Branch '%s' latest commit hash: %s", branchA, commitBranchA.Hash) 21 | 22 | log.Debugf("Getting latest commit on upstream branch '%s'...", branchB) 23 | commitBranchB, err := gitRepo.LatestCommitOnBranch(branchB) 24 | 25 | if err != nil { 26 | log.Errorf("Failed to get latest commit on upstream branch '%s': %v", branchB, err) 27 | return false, fmt.Errorf("failed to get latest commit on upstream branch '%s' (ensure the branch exists and is fetched): %w", branchB, err) 28 | } 29 | 30 | log.Debugf("Branch '%s' latest commit hash: %s", branchB, commitBranchB.Hash) 31 | 32 | return commitBranchA.Hash == commitBranchB.Hash, nil 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Commitsar 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/aevea/commitsar)](https://goreportcard.com/report/github.com/aevea/commitsar) 4 | ![Test](https://github.com/aevea/commitsar/workflows/Test/badge.svg) 5 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/aevea/commitsar?style=flat-square) 6 | ![GitHub commits since latest release](https://img.shields.io/github/commits-since/aevea/commitsar/latest?style=flat-square) 7 | [![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg)](https://conventionalcommits.org) 8 | [![Run on Repl.it](https://repl.it/badge/github/aevea/commitsar)](https://repl.it/github/aevea/commitsar) 9 | 10 | Tool to make sure your commits are compliant with [conventional commits](https://www.conventionalcommits.org). It is aimed mainly at CIs to prevent branches with commits that don't comply. Usage as a pre-commit hook is also under consideration. 11 | 12 | ## Usage 13 | 14 | Please check [Documentation](https://commitsar.aevea.ee). 15 | 16 | ## Contributing 17 | 18 | Contributions are most welcome. Please check the [CONTRIBUTING](https://commitsar.aevea.ee/contributing/get_started) section of documentation. 19 | 20 | ## Sponsoring 21 | 22 | If you feel like showing even more support for this project you can sponsor it using the Sponsor button on GitHub. A little help goes a long way when it comes with OSS and it'll help support this project. 23 | -------------------------------------------------------------------------------- /internal/prpipeline/title.go: -------------------------------------------------------------------------------- 1 | package prpipeline 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "strings" 7 | 8 | history "github.com/aevea/git/v4" 9 | "github.com/apex/log" 10 | "github.com/google/go-github/v32/github" 11 | "github.com/spf13/viper" 12 | "golang.org/x/oauth2" 13 | ) 14 | 15 | func getPRTitle(path string) (*string, error) { 16 | ghToken := viper.GetString("GITHUB_TOKEN") 17 | 18 | if !viper.IsSet("GITHUB_REPOSITORY") { 19 | return nil, errors.New("missing GITHUB_REPOSITORY env variable. Please provide one in owner/repository format") 20 | } 21 | 22 | split := strings.Split(viper.GetString("GITHUB_REPOSITORY"), "/") 23 | 24 | gitRepo, err := history.OpenGit(path) 25 | 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | ctx := context.Background() 31 | 32 | ts := oauth2.StaticTokenSource( 33 | &oauth2.Token{AccessToken: ghToken}, 34 | ) 35 | tc := oauth2.NewClient(ctx, ts) 36 | 37 | client := github.NewClient(tc) 38 | 39 | currentCommit, err := gitRepo.CurrentCommit() 40 | 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | prs, response, err := client.PullRequests.ListPullRequestsWithCommit(ctx, split[0], split[1], currentCommit.Hash.String(), nil) 46 | 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | if len(prs) == 0 { 52 | log.Debugf("current commit %s", currentCommit.Hash.String()) 53 | log.Debugf("%s", response) 54 | 55 | return nil, errors.New("no linked PullRequests found") 56 | } 57 | 58 | return prs[0].Title, nil 59 | } 60 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | go-test: 7 | name: "Go Test" 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Check out code into the Go module directory 12 | uses: actions/checkout@v6 13 | - name: Install mise 14 | uses: jdx/mise-action@v3 15 | - uses: actions/cache@v5 16 | with: 17 | path: ~/go/pkg/mod 18 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 19 | restore-keys: | 20 | ${{ runner.os }}-go- 21 | - name: Install tools 22 | run: mise install 23 | - name: Install mage 24 | run: mise exec -- go install github.com/magefile/mage@latest 25 | - name: Test 26 | run: mise exec -- mage test 27 | 28 | go-mod-tidy: 29 | name: "Go mod tidy" 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Check out code into the Go module directory 33 | uses: actions/checkout@v6 34 | - name: Install mise 35 | uses: jdx/mise-action@v3 36 | - uses: actions/cache@v5 37 | with: 38 | path: ~/go/pkg/mod 39 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 40 | restore-keys: | 41 | ${{ runner.os }}-go- 42 | - name: Install tools 43 | run: mise install 44 | - name: Install mage 45 | run: mise exec -- go install github.com/magefile/mage@latest 46 | - name: Tidy check 47 | run: mise exec -- mage gomodtidy 48 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ partial "head/meta" . }} 6 | Lost? Don't worry 7 | 8 | {{ partial "head/favicons" . }} 9 | {{ partial "head/others" . }} 10 | 11 | 12 | 13 | {{ partial "svg-icon-symbols" . }} 14 | 15 |
16 | 17 | {{ partial "site-header" (dict "Root" . "MenuEnabled" false) }} 18 | 19 |
20 |
21 |
22 | 23 |
24 |
25 |
Lost?
26 |
Error 404
27 |
28 | Seems like what you are looking for can't be found. Don't worry we can 29 | bring you back to the homepage. 30 |
31 |
32 |
33 |
34 | 35 | {{ partial "site-footer" . }} 36 | 37 |
38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/site-footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | Built with Hugo and 6 | 7 | 8 | {{ with .Site.Params.GeekdocLegalNotice }} 9 | 10 | Legal Notice 11 | 12 | {{ end }} 13 | {{ with .Site.Params.GeekdocPrivacyPolicy }} 14 | 15 | Privacy Policy 16 | 17 | {{ end }} 18 |
19 | {{ if (default true .Site.Params.GeekdocBackToTop) }} 20 |
21 | 22 | 23 | Back to top 24 | 25 | 26 |
27 | {{ end }} 28 |
29 |
30 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/_default/baseof.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ partial "head/meta" . }} 6 | {{ if not (eq .Kind "home") }}{{ partial "title" . }} | {{ end }}{{ .Site.Title }} 7 | 8 | 9 | {{ partial "head/favicons" . }} 10 | {{ partial "head/others" . }} 11 | {{ partial "head/custom" . }} 12 | 13 | 14 | 15 | {{ partial "svg-icon-symbols" . }} 16 | 17 |
18 | 19 | 20 | {{ $navEnabled := default true .Page.Params.GeekdocNav }} 21 | {{ partial "site-header" (dict "Root" . "MenuEnabled" $navEnabled) }} 22 | 23 |
24 | {{ if $navEnabled }} 25 | 28 | {{ end }} 29 | 30 |
31 | {{ template "main" . }} 32 | {{ partial "page-footer" . }} 33 |
34 |
35 | 36 | {{ partial "site-footer" . }} 37 |
38 | 39 | {{ partial "foot" . }} 40 | 41 | 42 | -------------------------------------------------------------------------------- /internal/dispatcher/dispatcher_test.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDispatcher(t *testing.T) { 11 | dispatcher := New() 12 | dispatcher.maxWorkers = 1 13 | 14 | pipelines := []Pipeliner{TestPipeline{TestName: "pipeline1", TestFn: successPipeline}} 15 | 16 | results := dispatcher.RunPipelines(pipelines) 17 | 18 | assert.Equal(t, "pipeline1", results.SuccessfulPipelines[0].PipelineName) 19 | assert.Equal(t, "It succeeded", results.SuccessfulPipelines[0].Message) 20 | 21 | pipelines2 := []Pipeliner{TestPipeline{TestName: "pipeline1", TestFn: successPipeline}, TestPipeline{TestName: "pipeline2", TestFn: failPipeline}, TestPipeline{TestName: "pipeline3", TestFn: failPipeline}, TestPipeline{TestName: "pipeline4", TestFn: successPipeline}} 22 | 23 | results2 := dispatcher.RunPipelines(pipelines2) 24 | 25 | assert.Equal(t, 2, len(results2.SuccessfulPipelines)) 26 | assert.Equal(t, 2, len(results2.Errors)) 27 | } 28 | 29 | type TestPipeline struct { 30 | TestName string 31 | TestFn func() (*PipelineSuccess, error) 32 | } 33 | 34 | func (p TestPipeline) Name() string { 35 | return p.TestName 36 | } 37 | 38 | func (p TestPipeline) Run() (*PipelineSuccess, error) { 39 | return p.TestFn() 40 | } 41 | 42 | func successPipeline() (*PipelineSuccess, error) { 43 | return &PipelineSuccess{ 44 | PipelineName: "pipeline1", 45 | Message: "It succeeded", 46 | }, nil 47 | } 48 | 49 | func failPipeline() (*PipelineSuccess, error) { 50 | 51 | return nil, errors.New("some error") 52 | } 53 | -------------------------------------------------------------------------------- /docs/content/contributing/get_started.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: get_started 3 | title: Get started 4 | --- 5 | 6 | ## Requirements 7 | 8 | - [Go](https://golang.org/) (See pinned version in go.mod) 9 | - [git](https://git-scm.com/) (while git is not required to run commitsar, it is needed to set up tests) 10 | 11 | This project also uses [magefiles](https://magefile.org/). To get up and running please run: `go install github.com/magefile/mage`. These allow us to set up templates and cross-platform commands. 12 | 13 | ## Running tests 14 | 15 | Run `mage test`. This will git clone all git bundles and run tests in the entire repo. To run single tests use the VSCode functionality or provide the full path to `go test`. 16 | 17 | ## Git bundles 18 | 19 | In order to test commitsar against complicated real-life examples we use [git bundles](https://git-scm.com/docs/git-bundle). These can be `git cloned` like real repositories. They allow us to create real history and prevent edge cases that can happen when creating git history using shell (all commits have the same timestamp, for example). 20 | 21 | ### Creating a new bundle 22 | 23 | - create a new folder testdata/ 24 | - cd into it 25 | - git initgitz 26 | - commit away 27 | - once ready run `git bundle create name_of_repo.bundle --all` 28 | - copy the bundle to testdata/ 29 | 30 | All files outside of .bundle files are ignored in the testdata folder so you don't have to worry about cleanup. 31 | 32 | ## Commit style 33 | 34 | This project uses the [conventional commit style](https://www.conventionalcommits.org/en/v1.0.0/), it is also the default style commitsar uses. 35 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/head/others.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | {{ with .OutputFormats.Get "rss" -}} 19 | {{ printf `` .Rel .MediaType.Type .Permalink $.Site.Title | safeHTML }} 20 | {{ end -}} 21 | 22 | {{ if (default false $.Site.Params.GeekdocOverwriteHTMLBase) }} 23 | 24 | {{ end }} 25 | 26 | {{ printf "" "Made with Geekdoc theme https://github.com/thegeeklab/hugo-geekdoc" | safeHTML }} 27 | -------------------------------------------------------------------------------- /internal/dispatcher/dispatcher.go: -------------------------------------------------------------------------------- 1 | package dispatcher 2 | 3 | import ( 4 | "runtime" 5 | "sync" 6 | ) 7 | 8 | // Dispatcher is the central place which runs Pipelines. maxWorkers is based by default on number of CPUs * 2 accounting for modern CPU architectures. 9 | type Dispatcher struct { 10 | maxWorkers int 11 | } 12 | 13 | // Results contains the aggregated results of both the succesful and error pipelines. 14 | type Results struct { 15 | SuccessfulPipelines []PipelineSuccess 16 | Errors []PipelineError 17 | } 18 | 19 | // New returns a set up instance of Dispatcher 20 | func New() *Dispatcher { 21 | 22 | return &Dispatcher{maxWorkers: runtime.NumCPU() * 2} 23 | } 24 | 25 | // RunPipelines will run asynchronously all pipelines passed to it. It is limited only by the maxWorkers field on Dispatcher. 26 | func (dispatch *Dispatcher) RunPipelines(pipelines []Pipeliner) *Results { 27 | // pipelineChannel is limited to the amount of CPU to prevent overloading the machie 28 | pipelineChannel := make(chan Pipeliner, dispatch.maxWorkers) 29 | successChannel := make(chan PipelineSuccess, 10) 30 | errChannel := make(chan PipelineError, 10) 31 | results := &Results{} 32 | 33 | var wg sync.WaitGroup 34 | 35 | wg.Add(1) 36 | go func() { 37 | for _, pipeline := range pipelines { 38 | pipelineChannel <- pipeline 39 | } 40 | close(pipelineChannel) 41 | wg.Done() 42 | }() 43 | 44 | wg.Add(1) 45 | go dispatch.handleSuccess(&wg, successChannel, results) 46 | 47 | wg.Add(1) 48 | go dispatch.handleErrors(&wg, errChannel, results) 49 | 50 | wg.Add(1) 51 | go dispatch.work(&wg, pipelineChannel, successChannel, errChannel) 52 | 53 | wg.Wait() 54 | 55 | return results 56 | } 57 | -------------------------------------------------------------------------------- /internal/commitpipeline/commits_between_branches_test.go: -------------------------------------------------------------------------------- 1 | package commitpipeline 2 | 3 | import ( 4 | "testing" 5 | 6 | history "github.com/aevea/git/v4" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestAllCommits(t *testing.T) { 11 | gitRepo, err := history.OpenGit("../../testdata/long-history") 12 | 13 | assert.NoError(t, err) 14 | 15 | options := Options{ 16 | Path: "", 17 | UpstreamBranch: "master", 18 | Limit: 0, 19 | AllCommits: true, 20 | } 21 | 22 | pipeline, err := New(&options) 23 | 24 | assert.NoError(t, err) 25 | 26 | commits, err := pipeline.commitsBetweenBranches(gitRepo) 27 | 28 | assert.NoError(t, err) 29 | assert.Len(t, commits, 102) 30 | 31 | lastCommit, err := gitRepo.Commit(commits[0]) 32 | 33 | assert.NoError(t, err) 34 | assert.Equal(t, "chore: add 100 file\n", lastCommit.Message) 35 | 36 | firstCommit, err := gitRepo.Commit(commits[101]) 37 | 38 | assert.NoError(t, err) 39 | assert.Equal(t, "Initial commit\n", firstCommit.Message) 40 | } 41 | 42 | func TestLimitCommits(t *testing.T) { 43 | gitRepo, err := history.OpenGit("../../testdata/long-history") 44 | 45 | assert.NoError(t, err) 46 | 47 | options := Options{ 48 | Path: "", 49 | UpstreamBranch: "master", 50 | Limit: 50, 51 | AllCommits: false, 52 | } 53 | 54 | pipeline, err := New(&options) 55 | 56 | assert.NoError(t, err) 57 | 58 | commits, err := pipeline.commitsBetweenBranches(gitRepo) 59 | 60 | assert.NoError(t, err) 61 | assert.Len(t, commits, 50) 62 | 63 | lastCommit, err := gitRepo.Commit(commits[0]) 64 | 65 | assert.NoError(t, err) 66 | assert.Equal(t, "chore: add 100 file\n", lastCommit.Message) 67 | 68 | firstCommit, err := gitRepo.Commit(commits[49]) 69 | 70 | assert.NoError(t, err) 71 | assert.Equal(t, "chore: add 51 file\n", firstCommit.Message) 72 | } 73 | -------------------------------------------------------------------------------- /pkg/jira/find_references_test.go: -------------------------------------------------------------------------------- 1 | package jira 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFindReferences(t *testing.T) { 10 | tests := []struct { 11 | expected []string 12 | keys []string 13 | message string 14 | }{ 15 | { 16 | expected: []string{"TES-1"}, 17 | keys: nil, 18 | message: "TES-1: added a tes feature", 19 | }, 20 | { 21 | expected: []string{"TES-1"}, 22 | keys: []string{"TES"}, 23 | message: "TES-1: added a tes feature", 24 | }, 25 | { 26 | expected: nil, 27 | keys: []string{"TES"}, 28 | message: "REST-1: added a tes feature", 29 | }, 30 | { 31 | expected: []string{"REST-1", "TEST-2"}, 32 | keys: []string{"TEST", "REST"}, 33 | message: "REST-1: added a tes feature TEST-2", 34 | }, 35 | { 36 | expected: []string{"TEST-2"}, 37 | keys: []string{"TEST"}, 38 | message: "REST-1: added a tes feature TEST-2", 39 | }, 40 | { 41 | expected: []string{"REST-1", "TEST-2"}, 42 | keys: nil, 43 | message: "REST-1: added a tes feature TEST-2", 44 | }, 45 | { 46 | expected: []string{"QA-336"}, 47 | keys: nil, 48 | message: "QA-336 test: add workaround for test until issue fixed", 49 | }, 50 | { 51 | expected: []string{"L11-336"}, 52 | keys: nil, 53 | message: "L11-336 test: add workaround for test until issue fixed", 54 | }, 55 | { 56 | expected: []string{"L11-336"}, 57 | keys: []string{"L11"}, 58 | message: "L11-336 test: add workaround for test until issue fixed", 59 | }, 60 | { 61 | expected: nil, 62 | keys: nil, 63 | message: "l11-336 test: add workaround for test until issue fixed 4l4-234", 64 | }, 65 | } 66 | 67 | for _, test := range tests { 68 | references, err := FindReferences(test.keys, test.message) 69 | 70 | assert.NoError(t, err) 71 | assert.Equal(t, test.expected, references) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:13-slim as builder 2 | RUN mkdir /app 3 | WORKDIR /app 4 | 5 | # Install mise and dependencies 6 | RUN apt-get update && \ 7 | apt-get -y --no-install-recommends install \ 8 | sudo curl git ca-certificates build-essential make gcc && \ 9 | rm -rf /var/lib/apt/lists/* 10 | 11 | # Set up mise environment variables (before installation) 12 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 13 | ENV MISE_DATA_DIR="/mise" 14 | ENV MISE_CONFIG_DIR="/mise" 15 | ENV MISE_CACHE_DIR="/mise/cache" 16 | ENV MISE_INSTALL_PATH="/usr/local/bin/mise" 17 | ENV PATH="/mise/shims:/usr/local/bin:$PATH" 18 | 19 | # Install mise 20 | RUN curl https://mise.run | sh 21 | 22 | # Copy mise config 23 | COPY .mise.toml /app/ 24 | 25 | # Trust the mise config file 26 | RUN mise trust 27 | 28 | # Install Go via mise 29 | RUN mise install 30 | 31 | # This step is done separately than `COPY . /app/` in order to 32 | # cache dependencies. 33 | COPY go.mod go.sum Makefile /app/ 34 | RUN mise exec -- go mod download 35 | 36 | COPY . /app/ 37 | # Fix git ownership issue 38 | RUN git config --global --add safe.directory /app 39 | RUN mise exec -- make build/docker 40 | 41 | FROM debian:13-slim 42 | 43 | LABEL repository="https://github.com/aevea/commitsar" 44 | LABEL homepage="https://github.com/aevea/commitsar" 45 | LABEL maintainer="Simon Prochazka " 46 | 47 | LABEL com.github.actions.name="Commitsar Action" 48 | LABEL com.github.actions.description="Check commit message compliance with conventional commits" 49 | LABEL com.github.actions.icon="code" 50 | LABEL com.github.actions.color="blue" 51 | 52 | RUN apt-get update && \ 53 | apt-get -y --no-install-recommends install ca-certificates git && \ 54 | rm -rf /var/lib/apt/lists/* && \ 55 | mkdir /app && \ 56 | git config --system --add safe.directory '*' 57 | 58 | WORKDIR /app 59 | COPY --from=builder /app/build/commitsar ./commitsar 60 | COPY entrypoint.sh /entrypoint.sh 61 | 62 | RUN ln -s $PWD/commitsar /usr/local/bin && \ 63 | chmod +x /entrypoint.sh 64 | 65 | ENTRYPOINT ["/entrypoint.sh"] 66 | CMD ["commitsar"] 67 | -------------------------------------------------------------------------------- /pkg/text/check_message_title.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/aevea/quoad" 9 | ) 10 | 11 | var ( 12 | errCategoryMissing = errors.New("category missing") 13 | errCategoryWrongFormat = errors.New("category wrong format") 14 | errNonStandardCategory = errors.New("category not one of " + strings.Join(allowedCategories[:], ",")) 15 | errScopeNonConform = errors.New("malformed scope") 16 | errMissingBCBody = errors.New("breaking change must contain commit body") 17 | errBCMissingText = errors.New("breaking change commit body must start with BREAKING CHANGE: ") 18 | 19 | // Fields such as category and chore should contain only word characters 20 | categoryRegex = regexp.MustCompile(`^\w+$`) 21 | scopeRegex = regexp.MustCompile(`^\w+(-\w+|_\w+|/\w+| \w+)*$`) 22 | 23 | // Commits with breaking changes should contain text with BREAKING CHANGE: at start 24 | bcRegex = regexp.MustCompile(`^BREAKING CHANGE: `) 25 | 26 | // Types allowed by the commitlint's conventional preset 27 | allowedCategories = []string{"build", "ci", "chore", "docs", "feat", "fix", "perf", "refactor", "revert", "style", "test"} 28 | ) 29 | 30 | func isAllowedCategory(category string) bool { 31 | for _, val := range allowedCategories { 32 | if val == category { 33 | return true 34 | } 35 | } 36 | 37 | return false 38 | } 39 | 40 | // CheckMessageTitle verifies that the message title conforms to 41 | // conventional commit standard https://www.conventionalcommits.org/en/v1.0.0-beta.4/#summary 42 | func CheckMessageTitle(commit quoad.Commit, strict bool) error { 43 | if commit.Category == "" { 44 | return errCategoryMissing 45 | } 46 | categoryMatch := categoryRegex.FindStringSubmatch(commit.Category) 47 | 48 | if categoryMatch == nil { 49 | return errCategoryWrongFormat 50 | } 51 | 52 | if strict && !isAllowedCategory(categoryMatch[0]) { 53 | return errNonStandardCategory 54 | } 55 | 56 | scopeMatch := scopeRegex.FindStringSubmatch(commit.Scope) 57 | if commit.Scope != "" && scopeMatch == nil { 58 | return errScopeNonConform 59 | } 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/menu-filetree.html: -------------------------------------------------------------------------------- 1 | {{ $current := . }} 2 | {{ template "tree-nav" dict "sect" .Site.Home.Sections "current" $current }} 3 | 4 | 5 | {{ define "tree-nav" }} 6 | {{ $current := .current }} 7 | 8 |
    9 | {{ range .sect.GroupBy "Weight" }} 10 | {{ range .ByTitle }} 11 | {{ if not .Params.GeekdocHidden }} 12 | 13 | {{ $numberOfPages := (add (len .Pages) (len .Sections)) }} 14 | {{ $isParent := and (ne $numberOfPages 0) (not .Params.GeekdocFlatSection) }} 15 | {{ $isCurrent := eq $current . }} 16 | {{ $isAncestor := .IsAncestor $current }} 17 | {{ $id := substr (sha1 .Permalink) 0 8 }} 18 | {{ $doCollapse := and $isParent (or .Params.GeekdocCollapseSection (default false .Site.Params.GeekdocCollapseAllSections)) }} 19 | 20 |
  • 21 | {{ if $doCollapse }} 22 | 23 | 38 | {{ end }} 39 | 40 | {{ if $isParent }} 41 | {{ template "tree-nav" dict "sect" .Pages "current" $current}} 42 | {{ end }} 43 |
  • 44 | {{ end }} 45 | {{ end }} 46 | {{ end }} 47 |
48 | {{ end }} 49 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/favicon/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.14, written by Peter Selinger 2001-2017 9 | 10 | 12 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /internal/root_runner/run.go: -------------------------------------------------------------------------------- 1 | package root_runner 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/aevea/commitsar/internal/commitpipeline" 7 | "github.com/aevea/commitsar/internal/dispatcher" 8 | "github.com/aevea/commitsar/internal/prpipeline" 9 | "github.com/apex/log" 10 | "github.com/spf13/viper" 11 | ) 12 | 13 | // Run executes the base command for Commitsar 14 | func (runner *Runner) Run(options RunnerOptions, args ...string) error { 15 | dispatch := dispatcher.New() 16 | 17 | var pipelines []dispatcher.Pipeliner 18 | 19 | if !viper.GetBool("commits.disabled") { 20 | commitOptions := commitpipeline.Options{ 21 | AllCommits: options.AllCommits, 22 | Limit: options.Limit, 23 | Path: options.Path, 24 | Strict: options.Strict, 25 | UpstreamBranch: options.UpstreamBranch, 26 | RequiredScopes: options.RequiredScopes, 27 | } 28 | 29 | commitPipe, err := commitpipeline.New(&commitOptions, args...) 30 | 31 | if err != nil { 32 | return err 33 | } 34 | 35 | pipelines = append(pipelines, commitPipe) 36 | } else { 37 | log.Info("Commit section skipped due to commits.disabled set to true in .commitsar.yaml") 38 | } 39 | 40 | if viper.IsSet("pull_request") { 41 | log.Debug("PR pipeline") 42 | 43 | prOptions := prpipeline.Options{ 44 | Path: options.Path, 45 | Styles: []prpipeline.PRStyle{}, 46 | } 47 | 48 | if viper.IsSet("pull_request.jira_title") { 49 | prOptions.Styles = append(prOptions.Styles, prpipeline.JiraStyle) 50 | prOptions.Keys = viper.GetStringSlice("pull_request.jira_keys") 51 | } 52 | 53 | if viper.IsSet("pull_request.conventional") { 54 | prOptions.Styles = append(prOptions.Styles, prpipeline.ConventionalStyle) 55 | } 56 | 57 | prPipe, err := prpipeline.New(prOptions) 58 | 59 | if err != nil { 60 | return err 61 | } 62 | 63 | pipelines = append(pipelines, prPipe) 64 | } 65 | 66 | if len(pipelines) == 0 { 67 | return errors.New("no pipelines defined") 68 | } 69 | 70 | result := dispatch.RunPipelines(pipelines) 71 | 72 | if len(result.Errors) != 0 { 73 | for _, err := range result.Errors { 74 | log.Errorf("pipeline: %s, error: %s", err.PipelineName, err.Error) 75 | } 76 | return errors.New("some errors were found") 77 | } 78 | 79 | for _, successMessage := range result.SuccessfulPipelines { 80 | log.Info(successMessage.Message) 81 | } 82 | 83 | return nil 84 | } 85 | -------------------------------------------------------------------------------- /config/commits.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/aevea/commitsar/internal/root_runner" 5 | "github.com/aevea/integrations" 6 | "github.com/spf13/viper" 7 | "fmt" 8 | "os" 9 | "github.com/apex/log" 10 | ) 11 | 12 | const ( 13 | // CommitsarConfigPath is used an env variable to override the default location of the config file. 14 | CommitsarConfigPath = "COMMITSAR_CONFIG_PATH" 15 | ) 16 | 17 | // CommitConfig will return the RunnerOptions using defaults unless overridden in config or flags 18 | func CommitConfig() root_runner.RunnerOptions { 19 | // defaults 20 | strict := true 21 | limit := 0 22 | all := false 23 | upstreamBranch := integrations.FindCompareBranch() 24 | requiredScopes := []string{} 25 | 26 | if err := LoadConfig(); err != nil { 27 | fmt.Println(err) 28 | os.Exit(1) 29 | } 30 | 31 | if viper.GetBool("verbose") { 32 | log.SetLevel(log.DebugLevel) 33 | } 34 | 35 | if viper.IsSet("commits.strict") { 36 | strict = viper.GetBool("commits.strict") 37 | } 38 | 39 | if viper.IsSet("commits.limit") { 40 | limit = viper.GetInt("commits.limit") 41 | } 42 | 43 | if viper.IsSet("commits.all") { 44 | all = viper.GetBool("commits.all") 45 | } 46 | 47 | if viper.IsSet("commits.upstreamBranch") { 48 | upstreamBranch = viper.GetString("commits.upstreamBranch") 49 | } 50 | 51 | if viper.IsSet("commits.required-scopes") { 52 | requiredScopes = viper.GetStringSlice("commits.required-scopes") 53 | } 54 | 55 | return root_runner.RunnerOptions{ 56 | Strict: strict, 57 | Limit: limit, 58 | AllCommits: all, 59 | UpstreamBranch: upstreamBranch, 60 | RequiredScopes: requiredScopes, 61 | } 62 | } 63 | 64 | // LoadConfig iterates through possible config paths. No config will be loaded if no files are present. 65 | func LoadConfig() error { 66 | viper.AutomaticEnv() 67 | viper.SetConfigName(".commitsar") 68 | viper.SetConfigType("yaml") 69 | 70 | if viper.IsSet(CommitsarConfigPath) { 71 | viper.AddConfigPath(viper.GetString(CommitsarConfigPath)) 72 | } 73 | 74 | viper.AddConfigPath(viper.GetString("config-path")) 75 | 76 | if err := viper.ReadInConfig(); err != nil { 77 | if _, ok := err.(viper.ConfigFileNotFoundError); ok { 78 | // Config file not found; ignore error if desired 79 | log.Warn("config file not found, using defaults") 80 | } else { 81 | // Config file was found but another error was produced 82 | return err 83 | } 84 | } 85 | return nil 86 | } 87 | -------------------------------------------------------------------------------- /internal/prpipeline/run.go: -------------------------------------------------------------------------------- 1 | package prpipeline 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/aevea/commitsar/internal/dispatcher" 9 | "github.com/aevea/commitsar/pkg/jira" 10 | "github.com/aevea/commitsar/pkg/text" 11 | "github.com/aevea/quoad" 12 | "github.com/logrusorgru/aurora" 13 | ) 14 | 15 | func (pipeline *Pipeline) Run() (*dispatcher.PipelineSuccess, error) { 16 | title, err := getPRTitle(pipeline.options.Path) 17 | 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | return pipeline.validateTitle(*title) 23 | } 24 | 25 | // validateTitle validates a PR title against the configured styles 26 | func (pipeline *Pipeline) validateTitle(title string) (*dispatcher.PipelineSuccess, error) { 27 | if len(pipeline.options.Styles) == 0 { 28 | return nil, errors.New("pr checking is configured, but no style has been chosen") 29 | } 30 | 31 | var successMessages []string 32 | var allErrors []error 33 | 34 | for _, style := range pipeline.options.Styles { 35 | switch style { 36 | case JiraStyle: 37 | references, err := jira.FindReferences(pipeline.options.Keys, title) 38 | 39 | if err != nil { 40 | allErrors = append(allErrors, err) 41 | continue 42 | } 43 | 44 | if len(references) > 0 { 45 | successMessages = append(successMessages, aurora.Sprintf(aurora.Green("Found the following JIRA issue references: %v"), references)) 46 | } else { 47 | allErrors = append(allErrors, errors.New("no JIRA issue references found in PR title")) 48 | } 49 | case ConventionalStyle: 50 | commit := quoad.ParseCommitMessage(title) 51 | 52 | err := text.CheckMessageTitle(commit, true) 53 | 54 | if err != nil { 55 | allErrors = append(allErrors, err) 56 | continue 57 | } 58 | 59 | successMessages = append(successMessages, aurora.Sprintf(aurora.Green("PR title is compliant with conventional commit"))) 60 | } 61 | } 62 | 63 | if len(allErrors) > 0 { 64 | // Combine all errors into a single error message 65 | errorMessages := make([]string, len(allErrors)) 66 | for i, err := range allErrors { 67 | errorMessages[i] = err.Error() 68 | } 69 | return nil, fmt.Errorf("validation failed: %s", strings.Join(errorMessages, "; ")) 70 | } 71 | 72 | // Combine all success messages 73 | combinedMessage := "Success! " + strings.Join(successMessages, " ") 74 | 75 | return &dispatcher.PipelineSuccess{ 76 | PipelineName: pipeline.Name(), 77 | Message: combinedMessage, 78 | }, nil 79 | } 80 | -------------------------------------------------------------------------------- /.kodiak.toml: -------------------------------------------------------------------------------- 1 | # .kodiak.toml 2 | version = 1 3 | 4 | [merge] 5 | 6 | require_automerge_label = false 7 | 8 | # if this title regex matches, Kodiak will not merge the PR. this is useful 9 | # to prevent merging work in progress PRs 10 | blacklist_title_regex = "" # default: "^WIP.*", options: "" (disables regex), a regex string (e.g. ".*DONT\s*MERGE.*") 11 | 12 | # if these labels are set Kodiak will not merge the PR 13 | blacklist_labels = ["wip"] # default: [], options: list of label names (e.g. ["wip"]) 14 | 15 | # choose a merge method. If the configured merge method is disabled for a 16 | # repository, Kodiak will report an error in a status message. 17 | method = "rebase" # default: "merge", options: "merge", "squash", "rebase" 18 | 19 | # once a PR is merged into master, delete the branch 20 | delete_branch_on_merge = true # default: false 21 | 22 | # if you request review from a user, don't merge until that user provides a 23 | # review, even if the PR is passing all checks 24 | block_on_reviews_requested = true # default: false 25 | 26 | # if there are running status checks on a PR when it's up for merge, don't 27 | # wait for those to finish before updating the branch 28 | optimistic_updates = true # default: true 29 | 30 | # use this for status checks that run indefinitely, like deploy jobs or the 31 | # WIP GitHub App 32 | dont_wait_on_status_checks = [] # default: [], options: list of check names (e.g. ["ci/circleci: lint_api"]) 33 | 34 | 35 | [merge.message] 36 | # by default, github uses the first commit title for the PR of a merge. 37 | # "pull_request_title" uses the PR title. 38 | title = "github_default" # default: "github_default", options: "github_default", "pull_request_title" 39 | 40 | # by default, GithHub combines the titles a PR's commits to create the body 41 | # text of a merge. "pull_request_body" uses the content of the PR to generate 42 | # the body content while "empty" simple gives an empty string. 43 | body = "github_default" # default: "github_default", options: "github_default", "pull_request_body", "empty" 44 | 45 | # GitHub adds the PR number to the title of merges created through the UI. 46 | # This setting replicates that feature. 47 | include_pr_number = true # default: true 48 | 49 | # markdown is the normal format for GitHub merges 50 | body_type = "markdown" # default: "markdown", options: "plain_text", "markdown", "html" 51 | 52 | # useful for stripping HTML comments created by PR templates when the `markdown` `body_type` is used. 53 | strip_html_comments = false # default: false -------------------------------------------------------------------------------- /docs/content/usage/github.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: github 3 | title: Github Actions 4 | --- 5 | 6 | ## Important prefaces 7 | 8 | ### JIRA pipeline 9 | 10 | When using [JIRA](https://commitsar.aevea.ee/configuration/config-file) make sure to set `actions/checkout@v2` to pull from the pull_request HEAD. Github uses a single merge commit by default which is not referenced in git. This will cause commitsar JIRA check to fail as this commit will not be found by the API getting queried for PR by commit. 11 | 12 | ### actions/checkout@v2 13 | 14 | When using `actions/checkout@v2` please set `fetch_depth` to 0. Currently commitsar needs full git objects to work correctly. This will be fixed in an upcoming release. 15 | 16 | ## Using the Github Action 17 | 18 | A minimal example: 19 | 20 | ```yaml 21 | name: Linters 22 | 23 | on: [pull_request] 24 | 25 | jobs: 26 | validate-commits: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Check out code into the Go module directory 30 | uses: actions/checkout@v1 31 | - name: Commitsar check 32 | uses: aevea/commitsar@v0.11.0 (substitute for the current version) 33 | ``` 34 | 35 | This will run `commitsar` on every pull request and validate the commits for it. 36 | 37 | ## Using Github Actions + Docker 38 | 39 | This is a faster method since you don't have to build the Docker image in your Github action. If you need maximum security provided by Github actions please the [Github Action Flow](#github-action). 40 | 41 | ```yaml 42 | name: Linters 43 | 44 | on: [pull_request] 45 | 46 | jobs: 47 | validate-commits: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - name: Check out code into the Go module directory 51 | uses: actions/checkout@v1 52 | - name: Commitsar check 53 | uses: docker://aevea/commitsar 54 | ``` 55 | 56 | ## Using with JIRA pipeline 57 | 58 | This pipeline example uses the checkout at PR HEAD. 59 | 60 | ```yaml 61 | validate-commits: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - name: Set up Go 1.13 65 | uses: actions/setup-go@v1 66 | with: 67 | go-version: 1.13 68 | - name: Check out code into the Go module directory 69 | uses: actions/checkout@v2 70 | with: 71 | fetch-depth: 0 72 | ref: ${{ github.event.pull_request.head.sha }} 73 | - name: Commitsar check 74 | uses: docker://aevea/commitsar 75 | env: 76 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | ``` 78 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/page-footer.html: -------------------------------------------------------------------------------- 1 | {{ $current := . }} 2 | {{ $site := .Site }} 3 | {{ $current.Scratch.Set "prev" false }} 4 | {{ $current.Scratch.Set "getNext" false }} 5 | 6 | {{ $current.Scratch.Set "nextPage" false }} 7 | {{ $current.Scratch.Set "prevPage" false }} 8 | 9 | {{ template "menu_nextprev" dict "sect" $.Site.Data.menu.main.main "current" $current "site" $site }} 10 | 11 | {{ define "menu_nextprev" }} 12 | {{ $current := .current }} 13 | {{ $site := .site }} 14 | 15 | {{ range sort (default (seq 0) .sect) "weight" }} 16 | {{ $current.Scratch.Set "current" $current }} 17 | {{ $current.Scratch.Set "site" $site }} 18 | 19 | {{ $ref := default false .ref }} 20 | {{ if $ref}} 21 | {{ $site := $current.Scratch.Get "site" }} 22 | {{ $this := $site.GetPage .ref }} 23 | {{ $current := $current.Scratch.Get "current" }} 24 | 25 | {{ if $current.Scratch.Get "getNext" }} 26 | {{ $current.Scratch.Set "nextPage" (dict "name" .name "this" $this) }} 27 | {{ $current.Scratch.Set "getNext" false }} 28 | {{ end }} 29 | 30 | {{ if eq $current $this }} 31 | {{ $current.Scratch.Set "prevPage" ($current.Scratch.Get "prev") }} 32 | {{ $current.Scratch.Set "getNext" true }} 33 | {{ end }} 34 | 35 | {{ $current.Scratch.Set "prev" (dict "name" .name "this" $this) }} 36 | {{ end }} 37 | 38 | {{ $sub := default false .sub }} 39 | {{ if $sub }} 40 | {{ template "menu_nextprev" dict "sect" $sub "current" ($current.Scratch.Get "current") "site" ($current.Scratch.Get "site") }} 41 | {{ end }} 42 | {{ end }} 43 | {{ end }} 44 | 45 | 46 | 61 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/menu-bundle.html: -------------------------------------------------------------------------------- 1 | {{ $current := .current }} 2 | {{ template "menu-file" dict "sect" .source "current" $current "site" $current.Site }} 3 | 4 | 5 | {{ define "menu-file" }} 6 | {{ $current := .current }} 7 | {{ $site := .site }} 8 | 9 |
    10 | {{ range sort (default (seq 0) .sect) "weight" }} 11 | {{ $current.Scratch.Set "current" $current }} 12 | {{ $current.Scratch.Set "site" $site }} 13 | 14 |
  • 15 | {{ $ref := default false .ref }} 16 | {{ if $ref}} 17 | {{ $site := $current.Scratch.Get "site" }} 18 | {{ $this := $site.GetPage .ref }} 19 | {{ $current := $current.Scratch.Get "current" }} 20 | {{ $icon := default false .icon }} 21 | {{ $numberOfPages := (add (len $this.Pages) (len $this.Sections)) }} 22 | {{ $isCurrent := eq $current $this }} 23 | {{ $isAncestor := $this.IsAncestor $current }} 24 | {{ $id := substr (sha1 $this.Permalink) 0 8 }} 25 | {{ $doCollapse := and (isset . "sub") (or $this.Params.GeekdocCollapseSection (default false .Site.Params.GeekdocCollapseAllSections)) }} 26 | 27 | {{ if $doCollapse }} 28 | 29 | 42 | {{ end }} 43 | {{ else }} 44 | {{ .name }} 45 | {{ end }} 46 | 47 | {{ with .sub }} 48 | {{ template "menu-file" dict "sect" . "current" ($current.Scratch.Get "current") "site" ($current.Scratch.Get "site") }} 49 | {{ end }} 50 |
  • 51 | 52 | {{ end }} 53 |
54 | {{ end }} 55 | -------------------------------------------------------------------------------- /config/commits_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | "github.com/spf13/viper" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCommitConfig(t *testing.T) { 11 | os.Clearenv() 12 | 13 | defaultConfig := CommitConfig() 14 | 15 | assert.Equal(t, true, defaultConfig.Strict, "expect strict to be true by default") 16 | assert.Equal(t, 0, defaultConfig.Limit, "expect the limit to be 0 by default") 17 | assert.Equal(t, false, defaultConfig.AllCommits, "expect AllCommits to be true by default") 18 | assert.Equal(t, "origin/master", defaultConfig.UpstreamBranch, "expect UpstreamBranch to be origin/master by default") 19 | assert.Equal(t, []string{}, defaultConfig.RequiredScopes, "expect required scopes to be empty slice by default") 20 | 21 | err := os.Setenv(CommitsarConfigPath, "./testdata") 22 | assert.NoError(t, err) 23 | 24 | err = LoadConfig() 25 | assert.NoError(t, err) 26 | 27 | commitConfig := CommitConfig() 28 | 29 | assert.Equal(t, false, commitConfig.Strict, "expect strict to be false as opposed to the default of true") 30 | assert.Equal(t, 100, commitConfig.Limit, "expect limit to be 100 as opposed to the default of 0") 31 | assert.Equal(t, true, commitConfig.AllCommits, "expect strict to be false as opposed to the default of false") 32 | assert.Equal(t, "origin/main", commitConfig.UpstreamBranch, "expect UpstreamBranch to be origin/main as opposed to the default of origin/master") 33 | assert.Equal(t, []string{}, commitConfig.RequiredScopes, "expect required scopes to be empty slice same as default for backward compatibility") 34 | 35 | os.Clearenv() 36 | 37 | } 38 | 39 | func TestDefaultConfig(t *testing.T) { 40 | viper.Reset() 41 | 42 | err := LoadConfig() 43 | assert.NoError(t, err) 44 | assert.Equal(t, false, viper.GetBool("verbose")) 45 | 46 | os.Clearenv() 47 | } 48 | 49 | func TestLoadConfigCustomPathFromEnv(t *testing.T) { 50 | viper.Reset() 51 | err := os.Setenv(CommitsarConfigPath, "./testdata") 52 | assert.NoError(t, err) 53 | 54 | err = LoadConfig() 55 | assert.NoError(t, err) 56 | assert.Equal(t, true, viper.GetBool("verbose")) 57 | 58 | os.Clearenv() 59 | } 60 | 61 | func TestLoadConfigCustomPathFromParams(t *testing.T){ 62 | viper.Reset() 63 | viper.Set("config-path", "./testdata") 64 | 65 | err := LoadConfig() 66 | assert.NoError(t, err) 67 | assert.Equal(t, true, viper.GetBool("verbose")) 68 | 69 | os.Clearenv() 70 | } 71 | 72 | func TestLoadConfigCustomPathParamOverridesEnv(t *testing.T){ 73 | viper.Reset() 74 | err := os.Setenv(CommitsarConfigPath, "./wrong-path") 75 | assert.NoError(t, err) 76 | viper.Set("config-path", "./testdata") 77 | 78 | err = LoadConfig() 79 | assert.NoError(t, err) 80 | assert.Equal(t, true, viper.GetBool("verbose")) 81 | 82 | os.Clearenv() 83 | } 84 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/page-header.html: -------------------------------------------------------------------------------- 1 | {{ $geekdocRepo := default (default false .Site.Params.GeekdocRepo) .Page.Params.GeekdocRepo }} 2 | {{ $geekdocEditPath := default (default false .Site.Params.GeekdocEditPath) .Page.Params.GeekdocEditPath }} 3 | {{ if .File }} 4 | {{ $.Scratch.Set "geekdocFilePath" (default .File.Path .Page.Params.GeekdocFilePath) }} 5 | {{ else }} 6 | {{ $.Scratch.Set "geekdocFilePath" false }} 7 | {{ end }} 8 | 9 | {{ define "breadcrumb" }} 10 | {{ $parent := .page.Parent }} 11 | {{ if $parent }} 12 | {{ $name := (partial "title" $parent) }} 13 | {{ $position := (sub .position 1) }} 14 | {{ $value := (printf "
  • %s
  • /
  • %s" $parent.RelPermalink $parent.RelPermalink $name $position .value) }} 15 | {{ template "breadcrumb" dict "page" $parent "value" $value "position" $position }} 16 | {{ else }} 17 | {{ .value | safeHTML }} 18 | {{ end }} 19 | {{ end }} 20 | 21 | {{ $showBreadcrumb := (and (default true .Page.Params.GeekdocBreadcrumb) (default true .Site.Params.GeekdocBreadcrumb)) }} 22 | {{ $showEdit := (and ($.Scratch.Get "geekdocFilePath") $geekdocRepo $geekdocEditPath) }} 23 | 49 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/layouts/partials/site-header.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | {{ if .MenuEnabled }} 4 | 14 | {{ end }} 15 | 16 | 17 | 18 | {{ .Root.Site.Title }} 19 | 20 | 21 |
    22 | {{ if .Root.Site.Data.menu.extra.header }} 23 | 24 | {{ partial "menu-extra" (dict "current" .Root "source" .Root.Site.Data.menu.extra.header "target" "header") }} 25 | {{ end }} 26 | 27 | 28 | Toggle Dark/Light/Auto mode 29 | 30 | 31 | 32 | Toggle Dark/Light/Auto mode 33 | 34 | 35 | 36 | Toggle Dark/Light/Auto mode 37 | 38 | 39 | 40 | {{ if .Root.Site.Data.menu.extra.header }} 41 | 47 | 48 | 54 | {{ end }} 55 |
    56 |
    57 |
    58 | -------------------------------------------------------------------------------- /pkg/text/check_message_title_test.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/aevea/quoad" 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCheckMessageTitleNonStrict(t *testing.T) { 11 | tests := map[error][]quoad.Commit{ 12 | nil: []quoad.Commit{ 13 | quoad.Commit{Category: "chore", Heading: "add something"}, 14 | quoad.Commit{Category: "chore", Scope: "ci", Heading: "added new CI stuff"}, 15 | quoad.Commit{Category: "feat", Heading: "added a new feature"}, 16 | quoad.Commit{Category: "test", Scope: "full", Heading: "a heading", Body: "body is here\nit can have multiple lines"}, 17 | quoad.Commit{Category: "test", Heading: "a heading", Body: "BREAKING CHANGE: this happened", Breaking: true}, 18 | quoad.Commit{Category: "chore", Scope: "new integration", Heading: "added new integration"}, 19 | quoad.Commit{Category: "fix", Breaking: true, Heading: "breaking change"}, 20 | quoad.Commit{Category: "fix", Scope: "security", Breaking: true, Heading: "breaking"}, 21 | quoad.Commit{Category: "test", Heading: "a heading", Body: "body is here", Breaking: true}, 22 | quoad.Commit{Category: "test", Scope: "dashed-scope", Heading: "a heading", Body: "body is here", Breaking: true}, 23 | quoad.Commit{Category: "test", Scope: "undescore_scope", Heading: "a heading", Body: "body is here", Breaking: true}, 24 | quoad.Commit{Category: "test", Scope: "slash/scope", Heading: "a heading", Body: "body is here", Breaking: true}, 25 | }, 26 | errCategoryWrongFormat: []quoad.Commit{ 27 | quoad.Commit{Category: "fix!", Breaking: true, Heading: "breaking"}, 28 | }, 29 | errScopeNonConform: []quoad.Commit{ 30 | quoad.Commit{Category: "fix", Scope: "security(stuff)", Heading: "should break"}, 31 | quoad.Commit{Category: "fix", Scope: "dashed-", Heading: "should break"}, 32 | quoad.Commit{Category: "fix", Scope: "underscore-", Heading: "should break"}, 33 | quoad.Commit{Category: "fix", Scope: "spaced ", Heading: "should break"}, 34 | }, 35 | errCategoryMissing: []quoad.Commit{ 36 | quoad.Commit{}, 37 | quoad.Commit{Heading: "nope"}, 38 | }, 39 | errCategoryWrongFormat: []quoad.Commit{ 40 | quoad.Commit{Category: "chore(", Heading: "bad"}, 41 | quoad.Commit{Category: "perf()", Heading: "nope"}, 42 | }, 43 | } 44 | 45 | for expected, testCases := range tests { 46 | for _, testCase := range testCases { 47 | err := CheckMessageTitle(testCase, false) 48 | assert.Equal(t, expected, err) 49 | } 50 | } 51 | } 52 | 53 | func TestCheckMessageTitleStrict(t *testing.T) { 54 | tests := make(map[error]quoad.Commit) 55 | 56 | for _, cat := range allowedCategories { 57 | tests[nil] = quoad.Commit{Category: cat, Heading: "add something"} 58 | } 59 | 60 | tests[errNonStandardCategory] = quoad.Commit{Category: "thisshouldneverbeacategory", Heading: "add something"} 61 | 62 | for expected, test := range tests { 63 | err := CheckMessageTitle(test, true) 64 | assert.Equal(t, expected, err) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/README.md: -------------------------------------------------------------------------------- 1 | # Geekdoc 2 | 3 | [![Build Status](https://img.shields.io/drone/build/thegeeklab/hugo-geekdoc?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/hugo-geekdoc) 4 | [![Hugo Version](https://img.shields.io/badge/hugo-0.83-blue.svg)](https://gohugo.io) 5 | [![GitHub release](https://img.shields.io/github/v/release/thegeeklab/hugo-geekdoc)](https://github.com/thegeeklab/hugo-geekdoc/releases/latest) 6 | [![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/hugo-geekdoc)](https://github.com/thegeeklab/hugo-geekdoc/graphs/contributors) 7 | [![License: MIT](https://img.shields.io/github/license/thegeeklab/hugo-geekdoc)](https://github.com/thegeeklab/hugo-geekdoc/blob/main/LICENSE) 8 | 9 | Geekdoc is a simple Hugo theme for documentations. It is intentionally designed as a fast and lean theme and may not fit the requirements of complex projects. If a more feature-complete theme is required there are a lot of got alternatives out there. You can find a demo and the full documentation at [https://geekdocs.de](https://geekdocs.de). 10 | 11 | ![Desktop and mobile preview](https://raw.githubusercontent.com/thegeeklab/hugo-geekdoc/main/images/readme.png) 12 | 13 | ## Build and release process 14 | 15 | This theme is subject to a CI driven build and release process common for software development. During the release build, all necessary assets are automatically built by [gulp](https://gulpjs.com/) and bundled in a release tarball. You can download the latest release from the GitHub [release page](https://github.com/thegeeklab/hugo-geekdoc/releases). 16 | 17 | Due to the fact that `gulp` is used as pre-processor the theme cannot be used from the main branch by default. If you want to use the theme from a cloned branch instead of a release tarball you'll need to install `gulp` locally and run the default pipeline once to create all required assets. 18 | 19 | ```Shell 20 | # install required packages from package.json 21 | npm install 22 | 23 | # run gulp pipeline to build required assets 24 | npx gulp default 25 | ``` 26 | 27 | See the [Getting Started Guide](https://geekdocs.de/usage/getting-started/) for details about the different setup options. 28 | 29 | ## Contributors 30 | 31 | Special thanks goes to all [contributors](https://github.com/thegeeklab/hugo-geekdoc/graphs/contributors). If you would like to contribute, 32 | please see the [instructions](https://github.com/thegeeklab/hugo-geekdoc/blob/main/CONTRIBUTING.md). 33 | 34 | Geekdoc is inspired and partially based on the [hugo-book](https://github.com/alex-shpak/hugo-book) theme, thanks [Alex Shpak](https://github.com/alex-shpak/) for your work. 35 | 36 | ## License 37 | 38 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/thegeeklab/hugo-geekdoc/blob/main/LICENSE) file for details. 39 | 40 | The used SVG icons and generated icon fonts are licensed under the license of the respective icon pack: 41 | 42 | - Font Awesome: [CC BY 4.0 License](https://github.com/FortAwesome/Font-Awesome#license) 43 | - IcoMoon Free Pack: [GPL/CC BY 4.0](https://icomoon.io/#icons-icomoon) 44 | - Material Icons: [Apache License 2.0](https://github.com/google/material-design-icons/blob/main/LICENSE) 45 | -------------------------------------------------------------------------------- /internal/commitpipeline/commits_between_branches.go: -------------------------------------------------------------------------------- 1 | package commitpipeline 2 | 3 | import ( 4 | "fmt" 5 | 6 | history "github.com/aevea/git/v4" 7 | "github.com/apex/log" 8 | ) 9 | 10 | func (pipeline *Pipeline) commitsBetweenBranches(gitRepo *history.Git) ([]history.Hash, error) { 11 | var commits []history.Hash 12 | 13 | log.Debug("Getting current branch for commit analysis...") 14 | currentBranch, currentBranchErr := gitRepo.CurrentBranch() 15 | if currentBranchErr != nil { 16 | log.Errorf("Failed to get current branch in commitsBetweenBranches: %v", currentBranchErr) 17 | return nil, fmt.Errorf("failed to get current branch: %w", currentBranchErr) 18 | } 19 | 20 | log.Debugf("Current branch: %s, upstream branch: %s", currentBranch.Name(), pipeline.options.UpstreamBranch) 21 | 22 | log.Debug("Checking if current branch is the same as upstream...") 23 | sameBranch, err := IdentifySameBranch(currentBranch.Name(), pipeline.options.UpstreamBranch, gitRepo) 24 | 25 | if err != nil { 26 | log.Errorf("Failed to identify if same branch: %v", err) 27 | return nil, fmt.Errorf("failed to identify if on same branch as upstream '%s': %w", pipeline.options.UpstreamBranch, err) 28 | } 29 | 30 | log.Debugf("Same branch as upstream: %v", sameBranch) 31 | 32 | if sameBranch { 33 | log.Debugf("On same branch, getting commits on branch with hash: %s", currentBranch.Hash()) 34 | commitsOnSameBranch, err := gitRepo.CommitsOnBranch(currentBranch.Hash()) 35 | 36 | if err != nil { 37 | log.Errorf("Failed to get commits on branch: %v", err) 38 | return nil, fmt.Errorf("failed to get commits on branch '%s': %w", currentBranch.Name(), err) 39 | } 40 | 41 | log.Debugf("Found %d commits on branch", len(commitsOnSameBranch)) 42 | 43 | if pipeline.options.AllCommits { 44 | return commitsOnSameBranch, nil 45 | } 46 | 47 | // If no limit is set then check just the last commits. This is to prevent breaking repositories that did not check commits before. 48 | if pipeline.options.Limit == 0 { 49 | commits = append(commits, commitsOnSameBranch[0]) 50 | return commits, nil 51 | } 52 | 53 | limit := pipeline.options.Limit 54 | 55 | // The limit cannot be longer than the amount of commits found 56 | if limit > len(commitsOnSameBranch) { 57 | limit = len(commitsOnSameBranch) 58 | } 59 | 60 | for index := 0; index < limit; index++ { 61 | commits = append(commits, commitsOnSameBranch[index]) 62 | } 63 | 64 | return commits, nil 65 | } 66 | 67 | log.Debugf("On different branch, getting diff commits between '%s' and '%s'", currentBranch.Name(), pipeline.options.UpstreamBranch) 68 | commitsOnBranch, err := gitRepo.BranchDiffCommits(currentBranch.Name(), pipeline.options.UpstreamBranch) 69 | 70 | if err != nil { 71 | log.Errorf("Failed to get branch diff commits between '%s' and '%s': %v", currentBranch.Name(), pipeline.options.UpstreamBranch, err) 72 | return nil, fmt.Errorf("failed to get commits between branch '%s' and upstream '%s' (ensure full git history is available, not a shallow clone): %w", currentBranch.Name(), pipeline.options.UpstreamBranch, err) 73 | } 74 | 75 | log.Debugf("Found %d commits in branch diff", len(commitsOnBranch)) 76 | commits = commitsOnBranch 77 | 78 | return commits, nil 79 | } 80 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/js/auto-render-e6e57901eb.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={974:function(t){t.exports=e}},r={};function n(e){var a=r[e];if(void 0!==a)return a.exports;var i=r[e]={exports:{}};return t[e](i,i.exports,n),i.exports}n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,{a:t}),t},n.d=function(e,t){for(var r in t)n.o(t,r)&&!n.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var a={};return function(){n.d(a,{default:function(){return s}});var e=n(974),t=n.n(e),r=function(e,t,r){for(var n=r,a=0,i=e.length;n0&&(a.push({type:"text",data:e.slice(0,n)}),e=e.slice(n));var l=t.findIndex((function(t){return e.startsWith(t.left)}));if(-1===(n=r(t[l].right,e,t[l].left.length)))break;var d=e.slice(0,n+t[l].right.length),s=i.test(d)?d:e.slice(t[l].left.length,n);a.push({type:"math",data:s,rawData:d,display:t[l].display}),e=e.slice(n+t[l].right.length)}return""!==e&&a.push({type:"text",data:e}),a},l=function(e,r){var n=o(e,r.delimiters);if(1===n.length&&"text"===n[0].type)return null;for(var a=document.createDocumentFragment(),i=0;i 2 | 3 | 4 | 22 | 24 | 42 | 44 | 45 | 47 | image/svg+xml 48 | 50 | 51 | 52 | 53 | 54 | 59 | 72 | 75 | 81 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/aevea/commitsar/config" 8 | "github.com/aevea/commitsar/internal/version_runner" 9 | "github.com/logrusorgru/aurora" 10 | 11 | "github.com/aevea/commitsar/internal/root_runner" 12 | "github.com/spf13/cobra" 13 | "github.com/spf13/viper" 14 | 15 | "github.com/apex/log" 16 | "github.com/apex/log/handlers/cli" 17 | ) 18 | 19 | // version is a global variable passed during build time 20 | var version string 21 | 22 | // commit is a global variable passed during build time. Should be used if version is not available. 23 | var commit string 24 | 25 | // date is a global variable passed during build time 26 | var date string 27 | 28 | func runRoot(cmd *cobra.Command, args []string) error { 29 | runner := root_runner.New() 30 | 31 | commitConfig := config.CommitConfig() 32 | 33 | commitConfig.Path = "." 34 | 35 | if len(args) > 0 { 36 | commitConfig.Path = args[0] 37 | } 38 | 39 | return runner.Run(commitConfig, args...) 40 | } 41 | 42 | func bindRootFlags(rootCmd *cobra.Command) error { 43 | rootCmd.Flags().BoolP("verbose", "v", false, "verbose output") 44 | err := viper.BindPFlag("verbose", rootCmd.Flags().Lookup("verbose")) 45 | if err != nil { 46 | return err 47 | } 48 | rootCmd.Flags().BoolP("strict", "s", true, "strict mode") 49 | err = viper.BindPFlag("commits.strict", rootCmd.Flags().Lookup("strict")) 50 | if err != nil { 51 | return err 52 | } 53 | rootCmd.Flags().BoolP("all", "a", false, "iterate through all the commits on the branch") 54 | err = viper.BindPFlag("commits.all", rootCmd.Flags().Lookup("all")) 55 | if err != nil { 56 | return err 57 | } 58 | rootCmd.Flags().StringSlice("required-scopes", nil, "forces scope to match one of the provided values") 59 | err = viper.BindPFlag("commits.required-scopes", rootCmd.Flags().Lookup("required-scopes")) 60 | if err != nil { 61 | return err 62 | } 63 | rootCmd.Flags().String("config-path", ".", "path to your .commitsar.yaml config file") 64 | err = viper.BindPFlag("config-path", rootCmd.Flags().Lookup("config-path")) 65 | if err != nil { 66 | return err 67 | } 68 | 69 | // Not used. TODO: Documentation 70 | rootCmd.Flags().StringP("path", "d", ".", "dir points to the path of the repository") 71 | err = viper.BindPFlag("path", rootCmd.Flags().Lookup("path")) 72 | if err != nil { 73 | return err 74 | } 75 | return nil 76 | } 77 | 78 | func main() { 79 | log.SetHandler(cli.Default) 80 | 81 | var rootCmd = &cobra.Command{ 82 | Use: "commitsar ...", 83 | Short: "Checks if commits comply", 84 | Long: "Checks if commits comply with conventional commits", 85 | RunE: runRoot, 86 | SilenceUsage: true, 87 | SilenceErrors: true, 88 | Args: cobra.MinimumNArgs(0), 89 | } 90 | 91 | if err := bindRootFlags(rootCmd); err != nil { 92 | fmt.Println(err) 93 | os.Exit(1) 94 | } 95 | 96 | // Version returns undefined if not on a tag. This needs to reset it. 97 | if version == "undefined" { 98 | version = "" 99 | } 100 | 101 | if version == "" && commit != "" { 102 | version = commit 103 | } 104 | if version == "" && commit == "" { 105 | version = "development" 106 | } 107 | 108 | var versionCmd = &cobra.Command{ 109 | Use: "version", 110 | Short: "Print the version number of Commitsar", 111 | Long: `All software has versions. This is Commitsars.`, 112 | RunE: func(cmd *cobra.Command, args []string) error { 113 | 114 | err := version_runner.Run( 115 | version_runner.VersionInfo{ 116 | Version: version, 117 | Date: date, 118 | }, 119 | ) 120 | return err 121 | }, 122 | } 123 | 124 | rootCmd.AddCommand(versionCmd) 125 | 126 | if err := rootCmd.Execute(); err != nil { 127 | fmt.Println(aurora.Red(err)) 128 | os.Exit(1) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /docs/content/configuration/config-file.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: config-file 3 | title: Configuration File 4 | --- 5 | 6 | **The configuration file is still under development and is subject to changes** 7 | 8 | **Name:** `.commitsar.yml` 9 | 10 | In order to make configuration easier than through flags we provide configuration file support. Most up-to-date examples can be found in . 11 | 12 | By default the current working directory is used to scan for the file. However this can be overridden by specifying `COMMITSAR_CONFIG_PATH` environment variable. Accepts relative or absolute paths. 13 | 14 | Example: `COMMITSAR_CONFIG_PATH=./testdata` will scan for `.commitsar.yaml` in the `testdata` folder. 15 | 16 | ## Global configuration 17 | 18 | These are settings that get used across all runs of commitsar. 19 | 20 | ```yaml 21 | version: 1 22 | verbose: false 23 | ``` 24 | 25 | | Name | Default Value | Description | Available from | 26 | | ------- | ------------- | ----------------------------------------------------------------------------------- | -------------- | 27 | | version | 1 | Currently not in use. Might be used in the future in case of incompatible upgrades. | v0.14.0 | 28 | | verbose | false | Turns on debug logging of commitsar. Useful if you want to submit an issue. | v0.14.0 | 29 | 30 | ## Commit style settings 31 | 32 | ```yaml 33 | commits: 34 | disabled: false 35 | strict: true 36 | limit: 0 37 | all: false 38 | upstreamBranch: origin/master 39 | ``` 40 | 41 | | Name | Default Value | Description | Available from | 42 | | -------------- | ------------- | ------------------------------------------------------------------------------------------------------- | -------------- | 43 | | disabled | false | Disables checking commits. Useful if you want to use commitsar only for PR titles. | v0.14.0 | 44 | | strict | true | Enforces strict category enforcement. | v0.14.0 | 45 | | limit | none | Makes commitsar check only the last x commits. Useful if you want to run commitsar on master. | v0.14.0 | 46 | | all | false | Makes commitsar check all the commits in history. **Overrides the `limit` flag** | v0.14.0 | 47 | | upstreamBranch | origin/master | Makes commitsar check against specific branch (e.g. use `origin/main` if `main` is your default branch) | v0.17.0 | 48 | 49 | ## Pull Request style settings 50 | 51 | **Pull Request pipeline is still in early stages. Please report any bugs** 52 | 53 | #### Conventional style 54 | 55 | ```yaml 56 | pull_request: 57 | conventional: true 58 | ``` 59 | 60 | Setting `conventional` to true will enable the pipeline. This is useful for teams that use squash commits and don't care about having all of the commits in the PR compliant with conventional commits. 61 | 62 | | Name | Default Value | Description | Available from | 63 | | ------------ | ------------- | ----------------------------------------------------------------------- | -------------- | 64 | | conventional | false | Turns on the pipeline and will check for a conventional commit PR title | v0.17.0 | 65 | 66 | #### JIRA style 67 | 68 | ```yaml 69 | pull_request: 70 | jira_title: true 71 | jira_keys: 72 | - TEST 73 | - TSLA 74 | ``` 75 | 76 | Setting `jira_title` to true will enable the pipeline. By default commitsar will use a basic regex to check for any JIRA-like references. Further scoping can be done using the `jira_keys` setting. 77 | 78 | | Name | Default Value | Description | Available from | 79 | | ---------- | ------------- | --------------------------------------------------------------------- | -------------- | 80 | | jira_title | false | Turns on the pipeline and will check for JIRA issues in the PR title. | v0.15.0 | 81 | | jira_keys | none | Array of string project keys from JIRA. | v0.15.0 | 82 | -------------------------------------------------------------------------------- /internal/commitpipeline/run.go: -------------------------------------------------------------------------------- 1 | package commitpipeline 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | 7 | "github.com/aevea/commitsar/internal/dispatcher" 8 | "github.com/aevea/commitsar/pkg/text" 9 | history "github.com/aevea/git/v4" 10 | "github.com/aevea/quoad" 11 | "github.com/apex/log" 12 | "github.com/logrusorgru/aurora" 13 | ) 14 | 15 | func (pipeline *Pipeline) Run() (*dispatcher.PipelineSuccess, error) { 16 | log.Debugf("Opening git repository at path: %s", pipeline.options.Path) 17 | log.Debugf("Pipeline options: AllCommits=%v, Limit=%d, Strict=%v, UpstreamBranch=%s", 18 | pipeline.options.AllCommits, pipeline.options.Limit, pipeline.options.Strict, pipeline.options.UpstreamBranch) 19 | 20 | gitRepo, err := history.OpenGit(pipeline.options.Path) 21 | 22 | if err != nil { 23 | log.Errorf("Failed to open git repository at '%s': %v", pipeline.options.Path, err) 24 | return nil, err 25 | } 26 | 27 | log.Debug("Git repository opened successfully") 28 | 29 | err = pipeline.logBranch(gitRepo) 30 | 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | var commits []history.Hash 36 | 37 | if len(pipeline.args) == 0 { 38 | log.Debug("No specific commit hashes provided, analyzing commits between branches...") 39 | commitsBetweenBranches, err := pipeline.commitsBetweenBranches(gitRepo) 40 | 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | commits = commitsBetweenBranches 46 | } else { 47 | log.Debugf("Analyzing specific commit range: %v", pipeline.args) 48 | commitsBetweenHashes, err := commitsBetweenHashes(gitRepo, pipeline.args) 49 | 50 | if err != nil { 51 | log.Errorf("Failed to get commits between hashes: %v", err) 52 | return nil, err 53 | } 54 | 55 | commits = commitsBetweenHashes 56 | } 57 | 58 | var filteredCommits []history.Hash 59 | 60 | for _, commitHash := range commits { 61 | commitObject, commitErr := gitRepo.Commit(commitHash) 62 | 63 | if commitErr != nil { 64 | return nil, commitErr 65 | } 66 | 67 | parsedCommit := quoad.ParseCommitMessage(commitObject.Message) 68 | 69 | log.Debugf("Commit found: [hash] %v [message] %v", commitObject.Hash, parsedCommit.Heading) 70 | 71 | if !text.IsMergeCommit(commitObject.Message) && !text.IsInitialCommit(commitObject.Message) && !text.IsRevertCommit(commitObject.Message) { 72 | filteredCommits = append(filteredCommits, commitHash) 73 | } 74 | } 75 | 76 | log.Infof("%v commits filtered out", len(commits)-len(filteredCommits)) 77 | log.Infof("Found %v commit to check", len(filteredCommits)) 78 | 79 | if len(filteredCommits) == 0 && len(commits) > 0 { 80 | return &dispatcher.PipelineSuccess{ 81 | Message: aurora.Sprintf(aurora.Green("All commits have been filtered out. Nothing to check."), len(commits)), 82 | PipelineName: pipeline.Name(), 83 | }, nil 84 | } 85 | if len(filteredCommits) == 0 { 86 | return nil, errors.New(aurora.Red("No commits found, please check you are on a branch outside of main").String()) 87 | } 88 | 89 | var faultyCommits []text.FailingCommit 90 | requiredScopeChecker := text.RequiredScopeChecker(pipeline.options.RequiredScopes) 91 | 92 | for _, commitHash := range filteredCommits { 93 | commitObject, commitErr := gitRepo.Commit(commitHash) 94 | 95 | if commitErr != nil { 96 | return nil, commitErr 97 | } 98 | 99 | parsedCommit := quoad.ParseCommitMessage(commitObject.Message) 100 | 101 | textErr := text.CheckMessageTitle(parsedCommit, pipeline.options.Strict) 102 | 103 | if textErr != nil { 104 | faultyCommits = append(faultyCommits, text.FailingCommit{Hash: commitHash.String(), Message: parsedCommit.Heading, Error: textErr}) 105 | continue 106 | } 107 | 108 | scopeErr := requiredScopeChecker(parsedCommit.Scope) 109 | 110 | if scopeErr != nil { 111 | faultyCommits = append(faultyCommits, text.FailingCommit{Hash: commitHash.String(), Message: parsedCommit.Heading, Error: scopeErr}) 112 | } 113 | 114 | } 115 | 116 | if len(faultyCommits) != 0 { 117 | failingCommitTable := text.FormatFailingCommits(faultyCommits) 118 | failingCommitTable.SetOutputMirror(os.Stdout) 119 | failingCommitTable.Render() 120 | 121 | log.Infof("%v of %v commits are not conventional commit compliant", aurora.Red(len(faultyCommits)), aurora.Red(len(commits))) 122 | 123 | log.Info("Expected format is for example: chore(ci): this is a test") 124 | log.Info("Please see https://www.conventionalcommits.org for help on how to structure commits") 125 | 126 | return nil, errors.New(aurora.Red("Not all commits are conventional commits, please check the commits listed above").String()) 127 | } 128 | 129 | return &dispatcher.PipelineSuccess{ 130 | Message: aurora.Sprintf(aurora.Green("All %v commits are conventional commit compliant"), len(filteredCommits)), 131 | PipelineName: pipeline.Name(), 132 | }, nil 133 | } 134 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/assets/js/search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | {{ $searchDataFile := printf "%s.search-data.json" .Language.Lang }} 4 | {{ $searchData := resources.Get "search-data.json" | resources.ExecuteAsTemplate $searchDataFile . | resources.Minify }} 5 | 6 | (function() { 7 | const input = document.querySelector('#gdoc-search-input'); 8 | const results = document.querySelector('#gdoc-search-results'); 9 | let showParent = {{ if .Site.Params.GeekdocSearchShowParent }}true{{ else }}false{{ end }} 10 | 11 | if (input) { 12 | input.addEventListener('focus', init); 13 | input.addEventListener('keyup', search); 14 | } 15 | 16 | function init() { 17 | input.removeEventListener('focus', init); // init once 18 | 19 | loadScript('{{ index .Site.Data.assets "js/groupBy.min.js" | relURL }}'); 20 | loadScript('{{ index .Site.Data.assets "js/flexsearch.min.js" | relURL }}', function() { 21 | const indexCfgDefaults = { 22 | tokenize: 'forward' 23 | } 24 | const indexCfg = {{ with .Scratch.Get "geekdocSearchConfig" }}{{ . | jsonify }}{{ else }}indexCfgDefaults{{ end }}; 25 | const dataUrl = '{{ $searchData.RelPermalink }}' 26 | 27 | indexCfg.document = { 28 | key: 'id', 29 | index: ['title', 'content'], 30 | store: ['title', 'href', 'parent'], 31 | }; 32 | 33 | const index = new FlexSearch.Document(indexCfg); 34 | window.geekdocSearchIndex = index; 35 | 36 | getJson(dataUrl, function(data) { 37 | data.forEach(obj => { 38 | window.geekdocSearchIndex.add(obj); 39 | }); 40 | }); 41 | }); 42 | } 43 | 44 | function search() { 45 | const searchCfg = { 46 | enrich: true, 47 | limit: 10 48 | }; 49 | 50 | while (results.firstChild) { 51 | results.removeChild(results.firstChild); 52 | } 53 | 54 | if (!input.value) { 55 | return results.classList.remove('has-hits'); 56 | } 57 | 58 | let searchHits = flattenHits(window.geekdocSearchIndex.search(input.value, searchCfg)); 59 | if (searchHits.length < 1) { 60 | return results.classList.remove('has-hits'); 61 | } 62 | 63 | results.classList.add('has-hits'); 64 | 65 | if (showParent === true) { 66 | searchHits = groupBy(searchHits, hit => hit.parent); 67 | } 68 | 69 | const items = []; 70 | 71 | if (showParent === true) { 72 | for (const section in searchHits) { 73 | const item = document.createElement('li'), 74 | title = item.appendChild(document.createElement('span')), 75 | subList = item.appendChild(document.createElement('ul')); 76 | 77 | title.textContent = section; 78 | createLinks(searchHits[section], subList); 79 | 80 | items.push(item); 81 | } 82 | } else { 83 | const item = document.createElement('li'), 84 | title = item.appendChild(document.createElement('span')), 85 | subList = item.appendChild(document.createElement('ul')); 86 | 87 | title.textContent = 'Results'; 88 | createLinks(searchHits, subList); 89 | 90 | items.push(item); 91 | } 92 | 93 | items.forEach(item => { 94 | results.appendChild(item); 95 | }) 96 | } 97 | 98 | /** 99 | * Creates links to given fields and either returns them in an array or attaches them to a target element 100 | * @param {Object} fields Page to which the link should point to 101 | * @param {HTMLElement} target Element to which the links should be attatched 102 | * @returns {Array} If target is not specified, returns an array of built links 103 | */ 104 | function createLinks(pages, target) { 105 | const items = []; 106 | 107 | for (const page of pages) { 108 | const item = document.createElement("li"), 109 | entry = item.appendChild(document.createElement("span")), 110 | a = entry.appendChild(document.createElement("a")); 111 | 112 | entry.classList.add('flex') 113 | 114 | a.href = page.href; 115 | a.textContent = page.title; 116 | a.classList.add('gdoc-search__entry') 117 | 118 | if (target) { 119 | target.appendChild(item); 120 | continue 121 | } 122 | 123 | items.push(item); 124 | } 125 | 126 | return items; 127 | } 128 | 129 | function fetchErrors(response) { 130 | if (!response.ok) { 131 | throw Error(response.statusText); 132 | } 133 | return response; 134 | } 135 | 136 | function getJson(src, callback) { 137 | fetch(src) 138 | .then(fetchErrors) 139 | .then(response => response.json()) 140 | .then(json => callback(json)) 141 | .catch(function(error) { 142 | console.log(error); 143 | }); 144 | } 145 | 146 | function flattenHits(results) { 147 | const items = []; 148 | const map = new Map(); 149 | 150 | for (const field of results) { 151 | for (const page of field.result) { 152 | if(!map.has(page.doc.href)){ 153 | map.set(page.doc.href, true); 154 | items.push(page.doc); 155 | } 156 | } 157 | } 158 | 159 | return items 160 | } 161 | 162 | function loadScript(src, callback) { 163 | let script = document.createElement('script'); 164 | script.defer = true; 165 | script.async = false; 166 | script.src = src; 167 | script.onload = callback; 168 | 169 | document.body.appendChild(script); 170 | } 171 | })(); 172 | -------------------------------------------------------------------------------- /internal/prpipeline/run_test.go: -------------------------------------------------------------------------------- 1 | package prpipeline 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestValidateTitle_ConventionalStyle_Success(t *testing.T) { 11 | pipeline := &Pipeline{ 12 | options: Options{ 13 | Styles: []PRStyle{ConventionalStyle}, 14 | }, 15 | } 16 | 17 | result, err := pipeline.validateTitle("feat: add new feature") 18 | 19 | assert.NoError(t, err) 20 | assert.NotNil(t, result) 21 | assert.Contains(t, result.Message, "Success!") 22 | assert.Contains(t, result.Message, "conventional commit") 23 | } 24 | 25 | func TestValidateTitle_ConventionalStyle_Failure(t *testing.T) { 26 | pipeline := &Pipeline{ 27 | options: Options{ 28 | Styles: []PRStyle{ConventionalStyle}, 29 | }, 30 | } 31 | 32 | result, err := pipeline.validateTitle("invalid title without category") 33 | 34 | assert.Error(t, err) 35 | assert.Nil(t, result) 36 | assert.Contains(t, err.Error(), "validation failed") 37 | assert.Contains(t, err.Error(), "category missing") 38 | } 39 | 40 | func TestValidateTitle_JiraStyle_Success(t *testing.T) { 41 | pipeline := &Pipeline{ 42 | options: Options{ 43 | Styles: []PRStyle{JiraStyle}, 44 | Keys: []string{"TEST"}, 45 | }, 46 | } 47 | 48 | result, err := pipeline.validateTitle("TEST-123: add new feature") 49 | 50 | assert.NoError(t, err) 51 | assert.NotNil(t, result) 52 | assert.Contains(t, result.Message, "Success!") 53 | assert.Contains(t, result.Message, "JIRA issue references") 54 | } 55 | 56 | func TestValidateTitle_JiraStyle_Failure(t *testing.T) { 57 | pipeline := &Pipeline{ 58 | options: Options{ 59 | Styles: []PRStyle{JiraStyle}, 60 | Keys: []string{"TEST"}, 61 | }, 62 | } 63 | 64 | result, err := pipeline.validateTitle("add new feature without jira") 65 | 66 | assert.Error(t, err) 67 | assert.Nil(t, result) 68 | assert.Contains(t, err.Error(), "validation failed") 69 | assert.Contains(t, err.Error(), "no JIRA issue references found") 70 | } 71 | 72 | func TestValidateTitle_BothStyles_Success(t *testing.T) { 73 | pipeline := &Pipeline{ 74 | options: Options{ 75 | Styles: []PRStyle{ConventionalStyle, JiraStyle}, 76 | Keys: []string{"TEST"}, 77 | }, 78 | } 79 | 80 | result, err := pipeline.validateTitle("feat(TEST-123): add new feature") 81 | 82 | assert.NoError(t, err) 83 | assert.NotNil(t, result) 84 | assert.Contains(t, result.Message, "Success!") 85 | assert.Contains(t, result.Message, "conventional commit") 86 | assert.Contains(t, result.Message, "JIRA issue references") 87 | } 88 | 89 | func TestValidateTitle_BothStyles_ConventionalFails(t *testing.T) { 90 | pipeline := &Pipeline{ 91 | options: Options{ 92 | Styles: []PRStyle{ConventionalStyle, JiraStyle}, 93 | Keys: []string{"TEST"}, 94 | }, 95 | } 96 | 97 | result, err := pipeline.validateTitle("TEST-123: invalid title without category") 98 | 99 | assert.Error(t, err) 100 | assert.Nil(t, result) 101 | assert.Contains(t, err.Error(), "validation failed") 102 | assert.Contains(t, err.Error(), "category missing") 103 | } 104 | 105 | func TestValidateTitle_BothStyles_JiraFails(t *testing.T) { 106 | pipeline := &Pipeline{ 107 | options: Options{ 108 | Styles: []PRStyle{ConventionalStyle, JiraStyle}, 109 | Keys: []string{"TEST"}, 110 | }, 111 | } 112 | 113 | result, err := pipeline.validateTitle("feat: add new feature without jira") 114 | 115 | assert.Error(t, err) 116 | assert.Nil(t, result) 117 | assert.Contains(t, err.Error(), "validation failed") 118 | assert.Contains(t, err.Error(), "no JIRA issue references found") 119 | } 120 | 121 | func TestValidateTitle_BothStyles_BothFail(t *testing.T) { 122 | pipeline := &Pipeline{ 123 | options: Options{ 124 | Styles: []PRStyle{ConventionalStyle, JiraStyle}, 125 | Keys: []string{"TEST"}, 126 | }, 127 | } 128 | 129 | result, err := pipeline.validateTitle("invalid title without category or jira") 130 | 131 | assert.Error(t, err) 132 | assert.Nil(t, result) 133 | assert.Contains(t, err.Error(), "validation failed") 134 | // Should contain both error messages 135 | errorMsg := err.Error() 136 | assert.True(t, strings.Contains(errorMsg, "category missing") || strings.Contains(errorMsg, "no JIRA issue references found")) 137 | } 138 | 139 | func TestValidateTitle_NoStyles(t *testing.T) { 140 | pipeline := &Pipeline{ 141 | options: Options{ 142 | Styles: []PRStyle{}, 143 | }, 144 | } 145 | 146 | result, err := pipeline.validateTitle("any title") 147 | 148 | assert.Error(t, err) 149 | assert.Nil(t, result) 150 | assert.Equal(t, "pr checking is configured, but no style has been chosen", err.Error()) 151 | } 152 | 153 | func TestValidateTitle_ConventionalWithJiraInScope(t *testing.T) { 154 | // Test the specific use case from the issue: feat(XXX-1234): This is the title 155 | pipeline := &Pipeline{ 156 | options: Options{ 157 | Styles: []PRStyle{ConventionalStyle, JiraStyle}, 158 | Keys: []string{"XXX"}, 159 | }, 160 | } 161 | 162 | result, err := pipeline.validateTitle("feat(XXX-1234): This is the title") 163 | 164 | assert.NoError(t, err) 165 | assert.NotNil(t, result) 166 | assert.Contains(t, result.Message, "Success!") 167 | assert.Contains(t, result.Message, "conventional commit") 168 | assert.Contains(t, result.Message, "JIRA issue references") 169 | } 170 | 171 | func TestValidateTitle_ConventionalWithJiraInTitle(t *testing.T) { 172 | // Test with JIRA in the title part: feat: XXX-1234 This is the title 173 | pipeline := &Pipeline{ 174 | options: Options{ 175 | Styles: []PRStyle{ConventionalStyle, JiraStyle}, 176 | Keys: []string{"XXX"}, 177 | }, 178 | } 179 | 180 | result, err := pipeline.validateTitle("feat: XXX-1234 This is the title") 181 | 182 | assert.NoError(t, err) 183 | assert.NotNil(t, result) 184 | assert.Contains(t, result.Message, "Success!") 185 | assert.Contains(t, result.Message, "conventional commit") 186 | assert.Contains(t, result.Message, "JIRA issue references") 187 | } 188 | 189 | -------------------------------------------------------------------------------- /internal/root_runner/run_test.go: -------------------------------------------------------------------------------- 1 | package root_runner 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/apex/log" 7 | "github.com/apex/log/handlers/memory" 8 | "github.com/spf13/viper" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCommitsOnMaster(t *testing.T) { 13 | handler := memory.New() 14 | 15 | log.SetHandler(handler) 16 | 17 | runner := Runner{} 18 | 19 | options := RunnerOptions{ 20 | Path: "../../testdata/commits-on-master", 21 | UpstreamBranch: "master", 22 | Limit: 0, 23 | AllCommits: false, 24 | Strict: false, 25 | } 26 | 27 | err := runner.Run(options) 28 | 29 | assert.NoError(t, err) 30 | assert.Equal(t, 5, len(handler.Entries)) 31 | assert.Equal(t, "Starting pipeline: commit-pipeline", handler.Entries[0].Message) 32 | assert.Equal(t, "Starting analysis of commits on branch refs/heads/master", handler.Entries[1].Message) 33 | assert.Equal(t, "0 commits filtered out", handler.Entries[2].Message) 34 | assert.Equal(t, "Found 1 commit to check", handler.Entries[3].Message) 35 | assert.Equal(t, "\x1b[32mAll 1 commits are conventional commit compliant\x1b[0m", handler.Entries[4].Message) 36 | } 37 | 38 | func TestCommitsOnBranch(t *testing.T) { 39 | handler := memory.New() 40 | 41 | log.SetHandler(handler) 42 | 43 | runner := Runner{} 44 | 45 | options := RunnerOptions{ 46 | Path: "../../testdata/commits-on-different-branches", 47 | UpstreamBranch: "master", 48 | Limit: 0, 49 | AllCommits: false, 50 | Strict: false, 51 | } 52 | 53 | err := runner.Run(options, "master") 54 | 55 | assert.Error(t, err) 56 | } 57 | 58 | func TestFromToCommits(t *testing.T) { 59 | handler := memory.New() 60 | 61 | log.SetHandler(handler) 62 | 63 | runner := Runner{} 64 | 65 | options := RunnerOptions{ 66 | Path: "../../testdata/commits-on-different-branches", 67 | UpstreamBranch: "master", 68 | Limit: 0, 69 | AllCommits: false, 70 | Strict: false, 71 | } 72 | 73 | err := runner.Run(options, "d0240d3ed34685d0a5329b185e120d3e8c205be4...7dbf3e7db93ae2e02902cae9d2f1de1b1e5c8c92") 74 | 75 | assert.NoError(t, err) 76 | } 77 | 78 | func TestMissingPipelines(t *testing.T) { 79 | handler := memory.New() 80 | 81 | log.SetHandler(handler) 82 | 83 | runner := Runner{} 84 | 85 | viper.Set("commits.disabled", true) 86 | 87 | err := runner.Run(RunnerOptions{}) 88 | 89 | assert.Error(t, err) 90 | assert.Equal(t, "no pipelines defined", err.Error()) 91 | 92 | viper.Set("commits.disabled", "") 93 | } 94 | 95 | func TestPRPipeline_ConventionalOnly(t *testing.T) { 96 | handler := memory.New() 97 | 98 | log.SetHandler(handler) 99 | 100 | runner := Runner{} 101 | 102 | viper.Set("commits.disabled", true) 103 | viper.Set("pull_request.conventional", true) 104 | 105 | // This will fail because it needs GitHub API access, but we can verify the pipeline is created 106 | err := runner.Run(RunnerOptions{}) 107 | 108 | // Should fail due to missing GitHub env vars, not due to missing pipeline 109 | assert.Error(t, err) 110 | assert.NotEqual(t, "no pipelines defined", err.Error()) 111 | 112 | viper.Set("commits.disabled", "") 113 | viper.Set("pull_request.conventional", "") 114 | } 115 | 116 | func TestPRPipeline_JiraOnly(t *testing.T) { 117 | handler := memory.New() 118 | 119 | log.SetHandler(handler) 120 | 121 | runner := Runner{} 122 | 123 | viper.Set("commits.disabled", true) 124 | viper.Set("pull_request.jira_title", true) 125 | 126 | // This will fail because it needs GitHub API access, but we can verify the pipeline is created 127 | err := runner.Run(RunnerOptions{}) 128 | 129 | // Should fail due to missing GitHub env vars, not due to missing pipeline 130 | assert.Error(t, err) 131 | assert.NotEqual(t, "no pipelines defined", err.Error()) 132 | 133 | viper.Set("commits.disabled", "") 134 | viper.Set("pull_request.jira_title", "") 135 | } 136 | 137 | func TestPRPipeline_BothStyles(t *testing.T) { 138 | handler := memory.New() 139 | 140 | log.SetHandler(handler) 141 | 142 | runner := Runner{} 143 | 144 | viper.Set("commits.disabled", true) 145 | viper.Set("pull_request.conventional", true) 146 | viper.Set("pull_request.jira_title", true) 147 | viper.Set("pull_request.jira_keys", []string{"TEST"}) 148 | 149 | // This will fail because it needs GitHub API access, but we can verify the pipeline is created 150 | err := runner.Run(RunnerOptions{}) 151 | 152 | // Should fail due to missing GitHub env vars, not due to missing pipeline 153 | assert.Error(t, err) 154 | assert.NotEqual(t, "no pipelines defined", err.Error()) 155 | 156 | viper.Set("commits.disabled", "") 157 | viper.Set("pull_request.conventional", "") 158 | viper.Set("pull_request.jira_title", "") 159 | viper.Set("pull_request.jira_keys", "") 160 | } 161 | 162 | func TestPRPipeline_OptionsConfiguration(t *testing.T) { 163 | // Test that when both styles are configured, both are added to the Options 164 | viper.Reset() 165 | viper.Set("pull_request.conventional", true) 166 | viper.Set("pull_request.jira_title", true) 167 | viper.Set("pull_request.jira_keys", []string{"TEST", "XXX"}) 168 | 169 | // Manually build the options as the code does 170 | prOptions := struct { 171 | Path string 172 | Styles []string 173 | Keys []string 174 | }{ 175 | Path: ".", 176 | Styles: []string{}, 177 | Keys: []string{}, 178 | } 179 | 180 | if viper.IsSet("pull_request.jira_title") { 181 | prOptions.Styles = append(prOptions.Styles, "jira") 182 | prOptions.Keys = viper.GetStringSlice("pull_request.jira_keys") 183 | } 184 | 185 | if viper.IsSet("pull_request.conventional") { 186 | prOptions.Styles = append(prOptions.Styles, "conventional") 187 | } 188 | 189 | // Verify both styles are present 190 | assert.Equal(t, 2, len(prOptions.Styles)) 191 | assert.Contains(t, prOptions.Styles, "jira") 192 | assert.Contains(t, prOptions.Styles, "conventional") 193 | assert.Equal(t, []string{"TEST", "XXX"}, prOptions.Keys) 194 | 195 | viper.Reset() 196 | } 197 | -------------------------------------------------------------------------------- /docs/themes/hugo-geekdoc/static/js/clipboard-27784b7376.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.8 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return n={134:function(t,e,n){"use strict";n.d(e,{default:function(){return r}});var e=n(279),i=n.n(e),e=n(370),a=n.n(e),e=n(817),o=n.n(e);function c(t){return(c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function u(t,e){for(var n=0;n