Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/commit-identity.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Commit identity

on:
pull_request:
branches: [main]
push:
branches: [main]

permissions:
contents: read

jobs:
reject-fixture-identity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
persist-credentials: false
- name: Reject fixture commit identities
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: bash scripts/check-commit-identity.sh "$BASE_SHA" "$HEAD_SHA"
53 changes: 53 additions & 0 deletions scripts/check-commit-identity.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Reject fixture identities from a verified commit range.
set -uo pipefail

BASE="${1:?usage: check-commit-identity.sh <base> <head>}"
HEAD="${2:?usage: check-commit-identity.sh <base> <head>}"

if ! git rev-parse --verify --quiet "$BASE^{commit}" >/dev/null || \
! git rev-parse --verify --quiet "$HEAD^{commit}" >/dev/null; then
echo "FAIL: commit identity policy requires two commits" >&2
exit 2
fi

RANGE_BASE="$(git merge-base "$BASE" "$HEAD")" || {
echo "FAIL: commit identity policy cannot derive a merge base" >&2
exit 2
}

is_fixture_name() {
case "$1" in
[Ff][Ii][Xx][Tt][Uu][Rr][Ee]) return 0 ;;
*) return 1 ;;
esac
}

is_fixture_email() {
case "$1" in
[Ff][Ii][Xx][Tt][Uu][Rr][Ee]@[Ee][Xx][Aa][Mm][Pp][Ll][Ee].[Ii][Nn][Vv][Aa][Ll][Ii][Dd]) return 0 ;;
*) return 1 ;;
esac
}

FAILED=0
while IFS= read -r -d '' sha &&
IFS= read -r -d '' author_name &&
IFS= read -r -d '' author_email &&
IFS= read -r -d '' committer_name &&
IFS= read -r -d '' committer_email; do
if is_fixture_name "$author_name" || is_fixture_email "$author_email"; then
echo "FAIL: fixture identity in author for commit $sha" >&2
FAILED=1
fi
if is_fixture_name "$committer_name" || is_fixture_email "$committer_email"; then
echo "FAIL: fixture identity in committer for commit $sha" >&2
FAILED=1
fi
done < <(git log --format='%H%x00%an%x00%ae%x00%cn%x00%ce%x00' "$RANGE_BASE..$HEAD")

if [[ $FAILED -ne 0 ]]; then
exit 1
fi

echo "ok: commit identities accepted"
33 changes: 33 additions & 0 deletions scripts/git-identity-invariant.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Shared Git identity boundary for validation scripts.
# A non-Git copied fixture returns an empty baseline and is intentionally skipped.

git_identity_baseline() {
local repo="$1" repo_root worktree_root config status

repo_root="$(cd "$repo" && pwd -P)" || return 0
[[ "$(git -C "$repo_root" rev-parse --is-inside-work-tree 2>/dev/null)" == "true" ]] || return 0
worktree_root="$(git -C "$repo_root" rev-parse --show-toplevel 2>/dev/null)" || return 0
worktree_root="$(cd "$worktree_root" && pwd -P)" || return 0
[[ "$worktree_root" == "$repo_root" ]] || return 0

config="$(git -C "$repo_root" config --local --get-regexp '^user\.(name|email)$' 2>/dev/null)"; status=$?
[[ $status -eq 0 ]] && printf '%s\n' "$config"
[[ $status -eq 0 || $status -eq 1 ]] && return 0
return "$status"
}

git_identity_unchanged() {
local repo="$1" baseline="$2" observed

if ! observed="$(git_identity_baseline "$repo")"; then
printf '%s\n' '[git-identity] could not read local user.name/user.email' >&2
return 1
fi

if [[ "$observed" == "$baseline" ]]; then
return 0
fi

printf '%s\n' '[git-identity] local user.name/user.email changed during validation' >&2
return 1
}
105 changes: 105 additions & 0 deletions scripts/test-commit-identity-policy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
# Fixture tests for the protected-branch commit identity policy.
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
POLICY="$ROOT/scripts/check-commit-identity.sh"
FAIL_COUNT=0

setup_repo() {
local dir
dir="$(mktemp -d)" || return 1
git -C "$dir" init -q || { rm -rf "$dir"; return 1; }
git -C "$dir" config user.name "Canonical Fixture" || { rm -rf "$dir"; return 1; }
git -C "$dir" config user.email "canonical@example.invalid" || { rm -rf "$dir"; return 1; }
git -C "$dir" config commit.gpgsign false || { rm -rf "$dir"; return 1; }
printf 'base\n' >"$dir/file"
git -C "$dir" add file && git -C "$dir" commit -qm base || { rm -rf "$dir"; return 1; }
printf '%s\n' "$dir"
}

assert_contains() {
local haystack="$1" needle="$2" label="$3"
if [[ "$haystack" == *"$needle"* ]]; then
return 0
fi
echo " assertion failed ($label): expected output to contain: $needle"
return 1
}

run_case() {
local name="$1"
shift
echo "Case $name:"
if "$@"; then
echo " pass"
else
echo " FAIL"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
}

case_normal_identity_accepted() {
local dir base out code
dir="$(setup_repo)" || return 1
base="$(git -C "$dir" rev-parse HEAD)"
printf 'normal\n' >>"$dir/file"
git -C "$dir" add file && git -C "$dir" commit -qm normal || { rm -rf "$dir"; return 1; }
out="$(cd "$dir" && "$POLICY" "$base" HEAD 2>&1)"; code=$?
rm -rf "$dir"
[[ $code -eq 0 ]] && assert_contains "$out" "commit identities accepted" "normal identity"
}

case_fixture_author_rejected() {
local dir base out code
dir="$(setup_repo)" || return 1
base="$(git -C "$dir" rev-parse HEAD)"
git -C "$dir" config user.name fixture
git -C "$dir" config user.email fixture@example.invalid
printf 'fixture author\n' >>"$dir/file"
git -C "$dir" add file && git -C "$dir" commit -qm fixture || { rm -rf "$dir"; return 1; }
out="$(cd "$dir" && "$POLICY" "$base" HEAD 2>&1)"; code=$?
rm -rf "$dir"
[[ $code -ne 0 ]] && assert_contains "$out" "fixture identity" "fixture author diagnostic"
}

case_fixture_committer_rejected() {
local dir base out code
dir="$(setup_repo)" || return 1
base="$(git -C "$dir" rev-parse HEAD)"
printf 'fixture committer\n' >>"$dir/file"
git -C "$dir" add file || { rm -rf "$dir"; return 1; }
GIT_COMMITTER_NAME=fixture GIT_COMMITTER_EMAIL=fixture@example.invalid git -C "$dir" commit -qm committer || { rm -rf "$dir"; return 1; }
out="$(cd "$dir" && "$POLICY" "$base" HEAD 2>&1)"; code=$?
rm -rf "$dir"
[[ $code -ne 0 ]] && assert_contains "$out" "fixture identity" "fixture committer diagnostic"
}

case_diverged_branch_accepted() {
local dir base branch out code
dir="$(setup_repo)" || return 1
branch="$(git -C "$dir" symbolic-ref --short HEAD)"
git -C "$dir" checkout -qb feature || { rm -rf "$dir"; return 1; }
printf 'feature\n' >>"$dir/file"
git -C "$dir" add file && git -C "$dir" commit -qm feature || { rm -rf "$dir"; return 1; }
git -C "$dir" checkout -q "$branch" || { rm -rf "$dir"; return 1; }
printf 'main\n' >>"$dir/file"
git -C "$dir" add file && git -C "$dir" commit -qm main || { rm -rf "$dir"; return 1; }
base="$(git -C "$dir" rev-parse HEAD)"
out="$(cd "$dir" && "$POLICY" "$base" feature 2>&1)"; code=$?
rm -rf "$dir"
[[ $code -eq 0 ]] && assert_contains "$out" "commit identities accepted" "diverged branch"
}

run_case normal-identity-accepted case_normal_identity_accepted
run_case fixture-author-rejected case_fixture_author_rejected
run_case fixture-committer-rejected case_fixture_committer_rejected
run_case diverged-branch-accepted case_diverged_branch_accepted

if [[ $FAIL_COUNT -eq 0 ]]; then
echo "ALL COMMIT IDENTITY POLICY CHECKS PASSED"
exit 0
fi

echo "$FAIL_COUNT commit identity policy check(s) failed"
exit 1
109 changes: 109 additions & 0 deletions scripts/test-git-identity-isolation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# Unit tests for the Git identity boundary used by validation scripts.
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
FAIL_COUNT=0

setup_repo() {
local dir
dir="$(mktemp -d 2>&1)" || { echo " harness error: mktemp -d failed: $dir" >&2; return 1; }
git -C "$dir" init -q || { rm -rf "$dir"; return 1; }
git -C "$dir" config user.name "Canonical Fixture" || { rm -rf "$dir"; return 1; }
git -C "$dir" config user.email "canonical@example.invalid" || { rm -rf "$dir"; return 1; }
printf '%s\n' "$dir"
}

assert_contains() {
local haystack="$1" needle="$2" label="$3"
if [[ "$haystack" == *"$needle"* ]]; then
return 0
fi
echo " assertion failed ($label): expected output to contain: $needle"
return 1
}

assert_equal() {
local actual="$1" expected="$2" label="$3"
if [[ "$actual" == "$expected" ]]; then
return 0
fi
echo " assertion failed ($label): expected '$expected', got '$actual'"
return 1
}

run_case() {
local name="$1"
shift
echo "Case $name:"
if "$@"; then
echo " pass"
else
echo " FAIL"
FAIL_COUNT=$((FAIL_COUNT + 1))
fi
}

case_identity_preserved() {
local dir before after
dir="$(setup_repo)" || return 1
before="$(git_identity_baseline "$dir")"
after="$(git_identity_baseline "$dir")"
rm -rf "$dir"
[[ "$after" == "$before" ]]
}

case_identity_mutation_detected() {
local dir before out code
dir="$(setup_repo)" || return 1
before="$(git_identity_baseline "$dir")"
git -C "$dir" config user.name fixture
out="$(git_identity_unchanged "$dir" "$before" 2>&1)"; code=$?
rm -rf "$dir"
[[ $code -ne 0 ]] && assert_contains "$out" "[git-identity] local user.name/user.email changed during validation" "mutation diagnostic"
}

case_email_mutation_detected() {
local dir before out code
dir="$(setup_repo)" || return 1
before="$(git_identity_baseline "$dir")"
git -C "$dir" config user.email fixture@example.invalid
out="$(git_identity_unchanged "$dir" "$before" 2>&1)"; code=$?
rm -rf "$dir"
[[ $code -ne 0 ]] && assert_contains "$out" "[git-identity] local user.name/user.email changed during validation" "email mutation diagnostic"
}

case_bare_repository_skipped() {
local dir baseline
dir="$(mktemp -d)" || return 1
git -C "$dir" init --bare -q || { rm -rf "$dir"; return 1; }
baseline="$(git_identity_baseline "$dir")"
rm -rf "$dir"
assert_equal "$baseline" "" "bare repository skipped"
}

case_metadata_less_child_skipped() {
local dir baseline
dir="$(setup_repo)" || return 1
mkdir "$dir/fixture"
baseline="$(git_identity_baseline "$dir/fixture")"
rm -rf "$dir"
assert_equal "$baseline" "" "metadata-less child skipped"
}

# shellcheck source=scripts/git-identity-invariant.sh
source "$ROOT/scripts/git-identity-invariant.sh"

run_case identity-preserved case_identity_preserved
run_case identity-mutation-detected case_identity_mutation_detected
run_case bare-repository-skipped case_bare_repository_skipped
run_case metadata-less-child-skipped case_metadata_less_child_skipped
run_case email-mutation-detected case_email_mutation_detected

if [[ $FAIL_COUNT -eq 0 ]]; then
echo "ALL GIT IDENTITY ISOLATION CHECKS PASSED"
exit 0
fi

echo "$FAIL_COUNT Git identity isolation check(s) failed"
exit 1
5 changes: 5 additions & 0 deletions scripts/test-plan-consumer-portability.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$ROOT/scripts/git-identity-invariant.sh"
IDENTITY_BEFORE="$(git_identity_baseline "$ROOT")"
PASS_COUNT=0
FAIL_COUNT=0

Expand Down Expand Up @@ -2764,6 +2766,9 @@ else
fail "phase consumers derive one portable artifact root from the exact progress path"
fi
rm -rf "$fixture" "$consumer_repo"
if ! git_identity_unchanged "$ROOT" "$IDENTITY_BEFORE"; then
fail "local user.name/user.email unchanged"
fi

echo "Summary: $PASS_COUNT passed, $FAIL_COUNT failed"
if [ "$FAIL_COUNT" -ne 0 ]; then
Expand Down
11 changes: 11 additions & 0 deletions scripts/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
set -uo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
source "$ROOT/scripts/git-identity-invariant.sh"
IDENTITY_BEFORE="$(git_identity_baseline "$ROOT")"
FAIL=0

fail() { echo "FAIL: $1"; FAIL=1; }
Expand Down Expand Up @@ -1266,6 +1268,15 @@ fi
if ! bash "$ROOT/scripts/test-release-loop-conformance.sh" static; then
fail "release-loop static conformance failed"
fi
if ! bash "$ROOT/scripts/test-git-identity-isolation.sh"; then
FAIL=1
fi
if ! bash "$ROOT/scripts/test-commit-identity-policy.sh"; then
FAIL=1
fi
if ! git_identity_unchanged "$ROOT" "$IDENTITY_BEFORE"; then
FAIL=1
fi

echo
if [ "$FAIL" -eq 0 ]; then
Expand Down
Loading