-
Notifications
You must be signed in to change notification settings - Fork 0
chore(hooks): restore the canonical git hooks #57
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
WomB0ComB0
wants to merge
4
commits into
main
Choose a base branch
from
chore/canonical-git-hooks
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
824923c
chore(hooks): restore the canonical git hooks
WomB0ComB0 8be786f
chore(hooks): restore Rust validation, update the README, pick up tem…
WomB0ComB0 442b2ca
chore(hooks): resync pre-push after the shellcheck fix
WomB0ComB0 b61351b
chore(hooks): address review — ref-aware scoping, nested lockfiles, docs
WomB0ComB0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,62 +1,55 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
| # | ||
| # Copyright 2026 ResQ | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| # commit-msg | ||
| # | ||
| # Validates the commit message format. | ||
| # Ensures the subject line follows the Conventional Commits specification. | ||
| # | ||
| # Usage: | ||
| # .git-hooks/commit-msg COMMIT_MSG_FILE | ||
| # | ||
| # Arguments: | ||
| # $1 - Path to the file containing the commit message. | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Exit codes: | ||
| # 0 Commit message is valid. | ||
| # 1 Commit message is invalid. | ||
| # Canonical ResQ commit-msg shim — source: resq-software/dev. | ||
| # Enforces Conventional Commits; blocks fixup/squash/WIP on main/master. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| [ -n "${GIT_HOOKS_SKIP:-}" ] && exit 0 | ||
|
|
||
| # INPUT_FILE stores the path to the commit message file. | ||
| INPUT_FILE=${1:-} | ||
| # PATTERN is the regular expression for a valid Conventional Commit message. | ||
| INPUT_FILE="${1:-}" | ||
| # git always passes the message file, but the `${1:-}` default means a hook run | ||
| # by hand, or by a tool that forgets the argument, reaches `head -1 ""` instead — | ||
| # and `set -e` turns that into a bare tool error naming neither the hook nor the | ||
| # cause. Fail here, where the message can say what was expected. | ||
| if [ -z "$INPUT_FILE" ] || [ ! -f "$INPUT_FILE" ]; then | ||
| echo "❌ commit-msg: expected a commit-message file as \$1, got '$INPUT_FILE'." >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+$" | ||
|
|
||
| # Validate only the first line (subject). Strip an optional [TICKET-123] prefix | ||
| # inserted by prepare-commit-msg so the two hooks don't conflict. | ||
| FIRST_LINE=$(head -1 "$INPUT_FILE") | ||
| SUBJECT=$(echo "$FIRST_LINE" | sed 's/^\[[A-Z][A-Z]*-[0-9]*\] //') | ||
| # Ticket-prefix regex matches what prepare-commit-msg prepends ({2,} chars). | ||
| SUBJECT=$(sed -E 's/^\[[A-Z]{2,}-[0-9]+\][[:space:]]*//' <<<"$FIRST_LINE") | ||
|
|
||
| # WIP / fixup! / squash! guard runs *before* the format check so users get | ||
| # a branch-specific error message on main/master instead of the generic | ||
| # "Invalid commit message format". | ||
| BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "") | ||
| case "$BRANCH" in | ||
| main|master) | ||
| if grep -qiE "^(\[[A-Z]{2,}-[0-9]+\] )?(fixup!|squash!|wip[: ])" <<<"$FIRST_LINE"; then | ||
| echo "Error: fixup!/squash!/WIP commits are not allowed on $BRANCH." | ||
| echo "Create a feature branch instead." | ||
| exit 1 | ||
| fi | ||
| ;; | ||
| esac | ||
|
|
||
| if ! echo "$SUBJECT" | grep -qE "$PATTERN"; then | ||
| if ! grep -qE "$PATTERN" <<<"$SUBJECT"; then | ||
| echo "Error: Invalid commit message format." | ||
| echo "Expected format: type(scope): subject" | ||
| echo "Expected: type(scope)(!): subject" | ||
| echo "Examples:" | ||
| echo " feat(core): add new feature" | ||
| echo " feat!: remove deprecated API (breaking change marker)" | ||
| echo " fix(ui): fix button color" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Block fixup!/WIP commits on main | ||
| BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "") | ||
| if [ "$BRANCH" = "main" ]; then | ||
| if echo "$FIRST_LINE" | grep -qiE "^(\[[A-Z]+-[0-9]+\] )?(fixup!|squash!|wip[: ])"; then | ||
| echo "Error: fixup!/squash!/WIP commits are not allowed on main." | ||
| echo "Create a feature branch instead." | ||
| exit 1 | ||
| fi | ||
| LOCAL_HOOK="$(git rev-parse --show-toplevel)/.git-hooks/local-commit-msg" | ||
| if [ -x "$LOCAL_HOOK" ]; then | ||
| exec "$LOCAL_HOOK" "$@" | ||
| fi | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| #!/usr/bin/env bash | ||
|
|
||
| # Copyright 2026 ResQ Systems, Inc. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # Repo-specific pre-push — runs after the canonical ResQ pre-push hook, which | ||
| # execs this file when it exists. | ||
| # | ||
| # The canonical hook is language-agnostic on purpose: it guards branch naming | ||
| # and force-pushes to main, and delegates anything needing a toolchain to here. | ||
| # Rust/Anchor validation therefore lives in this file rather than in the shared | ||
| # template, which is also what `.git-hooks/README.md` promises for `pre-push`. | ||
| # | ||
| # Skip with SKIP_CARGO_CHECK=1 for a push that cannot affect compilation. | ||
| set -euo pipefail | ||
|
|
||
| [ "${SKIP_CARGO_CHECK:-0}" = "1" ] && { echo " Rust: skipped (SKIP_CARGO_CHECK=1)"; exit 0; } | ||
|
|
||
| if ! command -v cargo >/dev/null 2>&1; then | ||
| echo " Rust: cargo not found, skipping workspace check" | ||
| exit 0 | ||
| fi | ||
|
|
||
| ZERO_SHA="0000000000000000000000000000000000000000" | ||
| HEAD_SHA=$(git rev-parse HEAD 2>/dev/null || echo "") | ||
|
|
||
| # The canonical hook forwards git's ref records, so read them rather than | ||
| # assuming the push is of the checked-out branch: `git push origin feat/rust` | ||
| # from elsewhere, or `git push --all`, sends refs this hook would never see by | ||
| # looking at HEAD alone. | ||
| # | ||
| # Clippy compiles the *working tree*, so it can only ever speak for what is | ||
| # checked out. Rather than pretend otherwise, scope conservatively: lint | ||
| # whenever a pushed ref is something other than HEAD, and use the cheap | ||
| # file-diff skip only when the push is exactly the branch in hand. | ||
| run_check=0 | ||
| scoped=1 | ||
| if [ ! -t 0 ]; then | ||
| while read -r _local_ref local_sha _remote_ref _remote_sha; do | ||
| [ -z "$local_sha" ] && continue | ||
| [ "$local_sha" = "$ZERO_SHA" ] && continue # deletion: nothing to compile | ||
| if [ "$local_sha" != "$HEAD_SHA" ]; then | ||
| scoped=0 | ||
| run_check=1 | ||
| fi | ||
| done | ||
| fi | ||
|
|
||
| if [ "$scoped" = "1" ]; then | ||
| REMOTE="${1:-origin}" | ||
| # Compare against the tracking branch when there is one; a brand-new branch | ||
| # has no upstream yet, so fall back to the remote's main. | ||
| REMOTE_BRANCH=$(git rev-parse --abbrev-ref "@{upstream}" 2>/dev/null || echo "$REMOTE/main") | ||
| if CHANGED_RS=$(git diff --name-only "$REMOTE_BRANCH"...HEAD -- '*.rs' 'Cargo.toml' 'Cargo.lock' 2>/dev/null); then | ||
| [ -n "$CHANGED_RS" ] && run_check=1 | ||
| else | ||
| # The range would not resolve — shallow clone, missing upstream. Fail | ||
| # toward running the check rather than silently skipping it. | ||
| run_check=1 | ||
| fi | ||
| fi | ||
|
|
||
| if [ "$run_check" = "1" ]; then | ||
| # Clippy, not `cargo check`: AGENTS.md names | ||
| # `cargo clippy --workspace -- -D warnings` as this repo's lint gate, and | ||
| # `cargo check` passes happily on the warnings clippy exists to reject. | ||
| echo " Rust: cargo clippy --workspace -- -D warnings" | ||
| if ! cargo clippy --workspace --quiet -- -D warnings; then | ||
| echo "❌ cargo clippy failed. Fix the warnings before pushing." | ||
| echo " Override with: git push --no-verify" | ||
| exit 1 | ||
| fi | ||
| echo "✅ Rust workspace OK" | ||
| fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,71 +1,35 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
| # | ||
| # Copyright 2026 ResQ | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| # post-checkout | ||
| # | ||
| # Automatically installs dependencies when lock files change during branch checkout. | ||
| # Copyright 2026 ResQ Software | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| # | ||
| # Usage: | ||
| # .git-hooks/post-checkout PREV_HEAD NEW_HEAD IS_BRANCH_CHECKOUT | ||
| # | ||
| # Arguments: | ||
| # $1 - Previous HEAD commit hash. | ||
| # $2 - New HEAD commit hash. | ||
| # $3 - Flag indicating if it's a branch checkout (1) or file checkout (0). | ||
| # | ||
| # Exit codes: | ||
| # 0 Always. | ||
| # Canonical ResQ post-checkout shim — source: resq-software/dev. | ||
| # Notifies when lock files change so devs know to resync dependencies. | ||
|
|
||
| set -euo pipefail | ||
|
|
||
| [ -n "${GIT_HOOKS_SKIP:-}" ] && exit 0 | ||
|
|
||
| # PREV_HEAD stores the commit hash before checkout. | ||
| PREV_HEAD="${1:-}" | ||
| # NEW_HEAD stores the commit hash after checkout. | ||
| NEW_HEAD="${2:-}" | ||
| # IS_BRANCH_CHECKOUT is "1" if a branch was checked out, "0" otherwise. | ||
| IS_BRANCH_CHECKOUT="${3:-}" | ||
|
|
||
| # Only run on branch checkouts, not file checkouts | ||
| if [ "$IS_BRANCH_CHECKOUT" != "1" ]; then | ||
| exit 0 | ||
| fi | ||
| if [ "$IS_BRANCH_CHECKOUT" = "1" ] && [ "$PREV_HEAD" != "$NEW_HEAD" ]; then | ||
| CHANGED=$(git diff --name-only "$PREV_HEAD" "$NEW_HEAD" 2>/dev/null || true) | ||
|
|
||
| # Skip if both heads are the same (no actual branch change) | ||
| if [ "$PREV_HEAD" = "$NEW_HEAD" ]; then | ||
| exit 0 | ||
| # Matched by basename rather than anchored to the repository root: a monorepo | ||
| # keeps lockfiles in nested workspaces (`libs/dotnet/flake.lock`, | ||
| # `programs/Cargo.lock`), and a root-anchored pattern ignores them all. | ||
| grep -qE "(^|/)Cargo\.lock$" <<<"$CHANGED" && echo "📦 Cargo.lock changed — run: cargo build" | ||
| # `\?` is a GNU BRE extension; BSD grep on macOS reads it as a literal `?`, | ||
| # so the optional-`b` form needs ERE to match on every developer's box. | ||
| grep -qE "(^|/)bun\.lockb?$" <<<"$CHANGED" && echo "📦 bun.lock changed — run: bun install" | ||
| grep -qE "(^|/)uv\.lock$" <<<"$CHANGED" && echo "📦 uv.lock changed — run: uv sync" | ||
| grep -qE "(^|/)flake\.lock$" <<<"$CHANGED" && echo "📦 flake.lock changed — exit and re-enter: nix develop" | ||
| fi | ||
|
WomB0ComB0 marked this conversation as resolved.
|
||
|
|
||
| # CHANGED stores the list of files that differ between the two heads. | ||
| CHANGED=$(git diff --name-only "$PREV_HEAD" "$NEW_HEAD" 2>/dev/null) | ||
|
|
||
| # Check if Cargo.lock changed | ||
| if echo "$CHANGED" | grep -q "^Cargo\.lock$"; then | ||
| echo "📦 Cargo.lock changed — dependencies will update on next build" | ||
| LOCAL_HOOK="$(git rev-parse --show-toplevel)/.git-hooks/local-post-checkout" | ||
| if [ -x "$LOCAL_HOOK" ]; then | ||
| exec "$LOCAL_HOOK" "$@" | ||
| fi | ||
|
|
||
| # Check if bun.lockb changed | ||
| if echo "$CHANGED" | grep -q "^bun\.lockb$"; then | ||
| if [ -n "${SKIP_BUN_INSTALL:-}" ]; then | ||
| echo "📦 bun.lockb changed — skipping install (SKIP_BUN_INSTALL set)" | ||
| elif command -v bun >/dev/null 2>&1; then | ||
| echo "📦 bun.lockb changed — running bun install..." | ||
| bun install --frozen-lockfile 2>/dev/null || bun install | ||
| echo "✅ JS dependencies updated" | ||
| else | ||
| echo "⚠️ bun not found — run 'bun install' manually" | ||
| fi | ||
| fi | ||
| exit 0 | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.