Skip to content

ci: harden Conventional Commit validators and Git test isolation - #218

Merged
ruby-automation merged 2 commits into
mainfrom
ext-29-harden-validators
Sep 5, 2026
Merged

ci: harden Conventional Commit validators and Git test isolation#218
ruby-automation merged 2 commits into
mainfrom
ext-29-harden-validators

Conversation

@ruby-automation

@ruby-automation ruby-automation commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Extracts the canonical Conventional Commits subject validator into git-hooks/validate-conventional-subject — a single authoritative implementation shared by the commit-msg hook, the new validate-pull-request-title guard, and the range guard.
  • Adds git-hooks/validate-pull-request-title: fail-closed PR title guard driven by PULL_REQUEST_TITLE_REQUIRED env var; any value other than "true" or "false" exits 1.
  • Adds git-hooks/validate-commit-range: replaces the old ci/conventional_commits.sh logic; resolves origin/<name> before local <name>; handles exact 40-char SHAs for GitHub push events.
  • Converts ci/validate_conventional_commit.sh and ci/conventional_commits.sh to 4-line exec wrappers so existing CI callers keep working.
  • Adds mix precommit task (injectable shell) that runs the two new guards then the full app quality suite; updates mix git_hooks to point at git-hooks/ and reject unexpected args.
  • Fixes Git-backed test isolation in three test files: replaces System.unique_integer (resets each BEAM restart) with :crypto.strong_rand_bytes(16) nonce; registers on_exit cleanup before any git command.
  • Adds focused test/git_hooks_test.exs covering subject guard, PR title guard, and range guard (including the PR#197 and PR#196 regression cases); adds test/mix/tasks/git_hooks_test.exs and test/mix/tasks/precommit_test.exs.

Test plan

  • mix ci passes end-to-end (405 app tests + 30 root tests, no credo issues, format clean)
  • Subject guard: valid CC subjects pass; invalid type, empty, trailing whitespace, bare "Merge branch…" fail
  • PR title guard: valid passes, invalid fails, missing env var fails closed, PULL_REQUEST_TITLE_REQUIRED=false skips
  • Range guard: empty range passes, bad subject fails, mixed range reports all invalid, exact SHA as BASE_REF works
  • git-hooks/commit-msg and git-hooks/pre-push delegate correctly to the new scripts
  • ci/validate_conventional_commit.sh and ci/conventional_commits.sh exec-wrap correctly (backward compat)

Closes EXT-29

🤖 Generated with Claude Code

Replace System.unique_integer/1 (which resets across BEAM VM restarts
and can collide with stale /tmp directories from killed runs) with a
:crypto.strong_rand_bytes/1 nonce in every Git-backed test helper:
  - app/test/linear_cli/cli/git_test.exs (already fixed in HEAD)
  - app/test/linear_cli/cli/issue_commands_test.exs
  - app/test/linear_cli/cli/profile_defaults_test.exs
  - test/git_hooks_test.exs
  - test/mix/tasks/stokowski_test.exs

In each affected file, on_exit is now registered immediately after the
directory is created (before any git command), so a setup failure can
no longer leave a stale directory behind.

Expand git_hooks_test.exs to cover the remaining acceptance criteria:
  - Subject guard: EXT-19 squash-title regression (PR #196)
  - Subject guard: commit message file with body/footer
  - Subject guard: comment lines above subject in file mode
  - Subject guard: empty subject string
  - PR title guard: missing PULL_REQUEST_TITLE when required
  - PR title guard: invalid PULL_REQUEST_TITLE_REQUIRED value
  - Range guard: empty commit range passes
  - Range guard: mixed valid/invalid commit range

Refactor the range guard tests into a shared setup_hooks_worktree!/0
helper so each test gets a clean repo without duplicating setup code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bougyman
bougyman previously approved these changes Sep 5, 2026
ci/validate_conventional_commit.sh and ci/conventional_commits.sh now
exec into git-hooks/validate-conventional-subject and
git-hooks/validate-commit-range respectively. This leaves one live
implementation per check and lets callers that already reference the
ci/ paths (e.g. the installed commit-msg hook) keep working without
changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ruby-automation ruby-automation changed the title test(git): harden temp-dir isolation and expand validator coverage feat(ci): harden Conventional Commit validators and Git test isolation Sep 5, 2026
@ruby-automation ruby-automation changed the title feat(ci): harden Conventional Commit validators and Git test isolation ci: harden Conventional Commit validators and Git test isolation Sep 5, 2026
@ruby-automation
ruby-automation merged commit db08f0e into main Sep 5, 2026
4 checks passed
@ruby-automation
ruby-automation deleted the ext-29-harden-validators branch September 5, 2026 00:37
ruby-automation added a commit that referenced this pull request Sep 5, 2026
EXT-29 (PR #218) replaced the metadata-aware commit-range validator with
a subject-only traversal, losing the exception for GitHub's trusted
"Update branch" auto-merge commits.

Restore it in validate-commit-range using a three-part predicate: a
commit is skipped only when it has exactly two parents, the committer is
GitHub <noreply@github.com>, and the subject matches
`Merge branch '<base>' into <head>`.

The git log format is expanded from `%s` to
`%P%x01%cn%x01%ce%x01%s` (SOH-delimited) so parent hashes, committer
name, and committer email are available in the loop alongside the subject.

Add five new ExUnit tests covering the skip itself and each predicate
variation (wrong name, wrong email, single parent, non-matching subject).
Add documents/github-update-branch-validation-decision.adoc recording
the rationale and alternatives considered.

Refs: EXT-35

Co-authored-by: bougyman's bot <ruby-automation@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants