GRM-41: feat: enforce commit naming conventions and workflow discipline
This commit is contained in:
@@ -94,6 +94,33 @@ jobs:
|
||||
echo "No user-facing files changed — skipping release dry-run."
|
||||
fi
|
||||
|
||||
validate-merge:
|
||||
if: github.event_name == 'push'
|
||||
runs-on: docker
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- name: Validate commit message format
|
||||
run: |
|
||||
set -euo pipefail
|
||||
MSG=$(git log -1 --pretty=%s)
|
||||
echo "Commit message: $MSG"
|
||||
# Allowed formats:
|
||||
# GRM-N <type>: <description> (squash-merge)
|
||||
# release: vX.Y.Z (release commits)
|
||||
# GRM-N <type>: <description> (#M) (squash-merge with PR ref)
|
||||
if echo "$MSG" | grep -qE '^GRM-[0-9]+ [a-z]+: .+'; then
|
||||
echo "OK: GRM-N <conventional> format"
|
||||
elif echo "$MSG" | grep -qE '^release: v[0-9]+\.[0-9]+\.[0-9]+'; then
|
||||
echo "OK: release commit format"
|
||||
else
|
||||
echo "FAIL: commit message does not follow naming convention"
|
||||
echo "Expected: GRM-N <type>: <description> or release: vX.Y.Z"
|
||||
echo "Got: $MSG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
discover-runners:
|
||||
needs: [detect-changes]
|
||||
if: needs.detect-changes.outputs.ansible-changed == 'true'
|
||||
|
||||
@@ -103,13 +103,19 @@ REPO_TOKEN=<token> python3 scripts/ci/review_pr.py <pr_number> <owner/repo> \
|
||||
```
|
||||
|
||||
Then add the `ready-to-merge` label. The auto-merge workflow will:
|
||||
1. **Validate** PR title format and match against Vikunja task title
|
||||
1. **Validate** PR title format (`GRM-N: <vikunja task title>`) and match against Vikunja task title
|
||||
2. **Check** that at least one APPROVE review exists
|
||||
3. Wait for all CI checks to pass
|
||||
4. Squash-merge with title: `GRM-N <conventional commit message>` (space-separated)
|
||||
4. Squash-merge with title: `GRM-N <conventional commit message>` (space-separated, no colon after GRM-N)
|
||||
5. The post-merge workflow marks the Vikunja task as done
|
||||
6. The release workflow automatically versions, tags, and publishes (see below)
|
||||
|
||||
> **IMPORTANT**: Never manually merge PRs via the API. Always use the auto-merge
|
||||
> workflow by adding the `ready-to-merge` label. Manual merges bypass the
|
||||
> `GRM-N <conventional>` format enforcement, producing incorrectly named commits.
|
||||
> The CI `validate-merge` job checks every push to master and will fail if a
|
||||
> commit message doesn't match `GRM-N <type>: <description>` or `release: vX.Y.Z`.
|
||||
|
||||
### CI Path Filtering
|
||||
|
||||
The CI workflow includes a `detect-changes` job that checks whether any files
|
||||
|
||||
@@ -78,8 +78,19 @@ def main(commit_msg: str, commit_sha: str) -> None:
|
||||
|
||||
task_id = extract_task_id(commit_msg)
|
||||
if not task_id:
|
||||
click.echo(_("No task ID in commit message, skipping Vikunja update. All good — nothing to do here!"))
|
||||
return
|
||||
# Allow release commits without GRM-N prefix
|
||||
first_line = commit_msg.split("\n")[0]
|
||||
if re.match(r"^release: v\d+\.\d+\.\d+", first_line):
|
||||
click.echo(_("Release commit without task ID, skipping Vikunja update."))
|
||||
return
|
||||
# Non-release commits must have GRM-N prefix — fail loudly
|
||||
raise click.ClickException(
|
||||
_(
|
||||
"No task ID (GRM-N) found in commit message: {msg}\n"
|
||||
"All non-release commits on master must follow format: GRM-N <type>: <description>",
|
||||
msg=first_line,
|
||||
)
|
||||
)
|
||||
|
||||
client = VikunjaClient(VIKUNJA_API_URL, token)
|
||||
vikunja_task_id = 0
|
||||
|
||||
@@ -130,10 +130,20 @@ class TestMain:
|
||||
assert "VIKUNJA_TOKEN" in result.output
|
||||
|
||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
||||
def test_no_task_id_skips(self) -> None:
|
||||
def test_no_task_id_non_release_fails(self) -> None:
|
||||
"""Non-release commits without GRM-N prefix should fail."""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["fix: resolve bug"])
|
||||
assert result.exit_code == 1
|
||||
assert "No task ID" in result.output
|
||||
|
||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
||||
def test_release_commit_without_task_id_skips(self) -> None:
|
||||
"""Release commits without GRM-N prefix should skip gracefully."""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(main, ["release: v0.3.2"])
|
||||
assert result.exit_code == 0
|
||||
assert "Release commit" in result.output
|
||||
assert "skipping" in result.output
|
||||
|
||||
@patch.dict("os.environ", {"VIKUNJA_TOKEN": "tok"})
|
||||
|
||||
Reference in New Issue
Block a user