From acefd3f41051b7a3ba08e9acb72a43dcea630865 Mon Sep 17 00:00:00 2001 From: Swasti Gupta Date: Thu, 16 Apr 2026 18:32:51 -0700 Subject: [PATCH 1/2] feat: add pre-push PR size check local hook Add local pre-push hook that mirrors the remote PR Size Check workflow. Warns when push exceeds 500 LOC (excluding tests, config, and non-source files). Exclusions match pr-size-check-reusable.yml exactly. Setup: run 'make setup' to enable the hook. Bypass: git push --no-verify Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .githooks/pr-size-check.sh | 91 ++++++++++++++++++++++++++++++++++++++ .githooks/pre-push | 17 +++++++ Makefile | 5 +++ 3 files changed, 113 insertions(+) create mode 100755 .githooks/pr-size-check.sh create mode 100755 .githooks/pre-push create mode 100644 Makefile diff --git a/.githooks/pr-size-check.sh b/.githooks/pr-size-check.sh new file mode 100755 index 0000000000..a627cfb6df --- /dev/null +++ b/.githooks/pr-size-check.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +# ============================================================ +# PR Size Check — Reusable Hook Logic +# +# Do NOT call this directly. It is sourced by the pre-push hook +# with repo-specific config already set: +# +# MAX_LINES — line change limit (default: 500) +# EXTRA_EXCLUDES — bash array of additional regex patterns +# ============================================================ + +MAX_LINES="${MAX_LINES:-500}" + +# ── Common exclusions (all repos) ──────────────────────────── +COMMON_EXCLUDES=( + "\.pbxproj$" + "\.xcscheme$" + "\.xcsettings$" + "\.xcconfig$" + "\.xctestplan$" + "\.xcworkspace/" + "\.xcodeproj/" + "\.plist$" + "\.entitlements$" + "\.storyboard$" + "\.xib$" + "\.xcassets/" + "\.modulemap$" + "\.xcprivacy$" + "\.strings$" + "\.stringsdict$" + "\.xcstrings$" + "\.ya?ml$" + "\.lock$" + "\.(png|jpg|jpeg|svg|pdf|icns|gif|tiff)$" + "\.md$" + "\.mdx$" +) + +# ── Merge common + repo-specific exclusions ─────────────────── +ALL_EXCLUDES=("${COMMON_EXCLUDES[@]}" "${EXTRA_EXCLUDES[@]}") + +# ── Build grep pattern ──────────────────────────────────────── +GREP_PATTERN=$(printf "|%s" "${ALL_EXCLUDES[@]}") +GREP_PATTERN="${GREP_PATTERN:1}" + +# ── Resolve base branch — tries origin/dev, origin/main, origin/master ────── +BASE_BRANCH="" +for candidate in origin/dev origin/main origin/master; do + if git show-ref --verify --quiet "refs/remotes/${candidate}"; then + BASE_BRANCH="$candidate" + break + fi +done + +if [ -z "$BASE_BRANCH" ]; then + echo "ℹ️ PR size check skipped: no remote base branch found." + exit 0 +fi + +# ── Get list of changed files (excluding patterns) ─────────── +mapfile -t CHANGED_FILES < <(git diff --name-only "$BASE_BRANCH"...HEAD 2>/dev/null | grep -vE "$GREP_PATTERN") + +if [ "${#CHANGED_FILES[@]}" -eq 0 ]; then + exit 0 +fi + +# ── Count lines changed ─────────────────────────────────────── +TOTAL_LINES=$(git diff "$BASE_BRANCH"...HEAD -- "${CHANGED_FILES[@]}" 2>/dev/null \ + | grep -E "^\+|^-" \ + | grep -vE "^\+\+\+|^---" \ + | wc -l \ + | tr -d ' ') + +# ── Warn if over limit ──────────────────────────────────────── +if [ "$TOTAL_LINES" -gt "$MAX_LINES" ]; then + echo "" + echo "⚠️ WARNING: Your push contains ~${TOTAL_LINES} line changes (limit: ${MAX_LINES})." + echo " Consider splitting this into smaller PRs." + echo "" + echo " To push anyway: git push --no-verify" + echo " To cancel: Ctrl+C" + echo "" + read -r -p " Push anyway? [y/N]: " response + if [[ ! "$response" =~ ^[Yy]$ ]]; then + echo "Push cancelled." + exit 1 + fi +fi + +exit 0 diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000000..9478ae4ada --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# ============================================================ +# Pre-push hook — PR Size Soft Warning +# Repo: microsoft-authentication-library-for-objc +# +# Fires on every push to any remote branch. +# Bypass with: git push --no-verify +# ============================================================ + +EXTRA_EXCLUDES=( + "^MSAL/test/" + "^MSAL/IdentityCore/" + "^Samples/" +) + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/pr-size-check.sh" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..80f9c0a276 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +setup: +git config core.hooksPath .githooks +@echo "✅ Git hooks configured. Pre-push PR size check is now active." + +.PHONY: setup From c822e728b3853c8b80dd62d70a65175dfb63b677 Mon Sep 17 00:00:00 2001 From: Swasti Gupta Date: Thu, 16 Apr 2026 21:19:17 -0700 Subject: [PATCH 2/2] fix: align local hook exclusions with unified workflow - Add extended image formats (bmp, webp, heic, heif, ico) - Add audio/video exclusions (mp4, mov, mp3, wav, etc.) - Add binary/submodule detection (warns on non-excluded binaries) - Use git diff --numstat for accurate line counting - Handle non-interactive shells gracefully Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .githooks/pr-size-check.sh | 57 +++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/.githooks/pr-size-check.sh b/.githooks/pr-size-check.sh index a627cfb6df..ba710b49bb 100755 --- a/.githooks/pr-size-check.sh +++ b/.githooks/pr-size-check.sh @@ -12,6 +12,7 @@ MAX_LINES="${MAX_LINES:-500}" # ── Common exclusions (all repos) ──────────────────────────── +# Kept in sync with .github/workflows/pr-size-check-reusable.yml COMMON_EXCLUDES=( "\.pbxproj$" "\.xcscheme$" @@ -32,7 +33,8 @@ COMMON_EXCLUDES=( "\.xcstrings$" "\.ya?ml$" "\.lock$" - "\.(png|jpg|jpeg|svg|pdf|icns|gif|tiff)$" + "\.(png|jpe?g|gif|bmp|svg|webp|heic|heif|tiff?|ico|pdf|icns)$" + "\.(mp4|mov|m4v|avi|mpe?g|webm|mp3|wav|aiff?|m4a)$" "\.md$" "\.mdx$" ) @@ -40,10 +42,6 @@ COMMON_EXCLUDES=( # ── Merge common + repo-specific exclusions ─────────────────── ALL_EXCLUDES=("${COMMON_EXCLUDES[@]}" "${EXTRA_EXCLUDES[@]}") -# ── Build grep pattern ──────────────────────────────────────── -GREP_PATTERN=$(printf "|%s" "${ALL_EXCLUDES[@]}") -GREP_PATTERN="${GREP_PATTERN:1}" - # ── Resolve base branch — tries origin/dev, origin/main, origin/master ────── BASE_BRANCH="" for candidate in origin/dev origin/main origin/master; do @@ -58,33 +56,54 @@ if [ -z "$BASE_BRANCH" ]; then exit 0 fi -# ── Get list of changed files (excluding patterns) ─────────── -mapfile -t CHANGED_FILES < <(git diff --name-only "$BASE_BRANCH"...HEAD 2>/dev/null | grep -vE "$GREP_PATTERN") - -if [ "${#CHANGED_FILES[@]}" -eq 0 ]; then +MERGE_BASE=$(git merge-base HEAD "$BASE_BRANCH" 2>/dev/null) +if [ -z "$MERGE_BASE" ]; then + echo "ℹ️ PR size check skipped: no common ancestor with ${BASE_BRANCH}." exit 0 fi -# ── Count lines changed ─────────────────────────────────────── -TOTAL_LINES=$(git diff "$BASE_BRANCH"...HEAD -- "${CHANGED_FILES[@]}" 2>/dev/null \ - | grep -E "^\+|^-" \ - | grep -vE "^\+\+\+|^---" \ - | wc -l \ - | tr -d ' ') +# ── Count lines changed (excluding patterns & binaries) ────── +TOTAL_LINES=0 +while IFS=$'\t' read -r added deleted filepath; do + [ -z "$filepath" ] && continue + + # Check exclusion first + excluded=false + for pattern in "${ALL_EXCLUDES[@]}"; do + if [[ "$filepath" =~ $pattern ]]; then + excluded=true + break + fi + done + [ "$excluded" = true ] && continue + + # Binary/submodule: git diff --numstat reports "-" for these + if [ "$added" = "-" ] || [ "$deleted" = "-" ]; then + echo "⚠️ Binary or submodule change in non-excluded file: $filepath" + continue + fi + + TOTAL_LINES=$((TOTAL_LINES + added + deleted)) +done < <(git diff --numstat "$MERGE_BASE"...HEAD 2>/dev/null) # ── Warn if over limit ──────────────────────────────────────── if [ "$TOTAL_LINES" -gt "$MAX_LINES" ]; then echo "" echo "⚠️ WARNING: Your push contains ~${TOTAL_LINES} line changes (limit: ${MAX_LINES})." + echo " Excluded paths match the PR Size Check workflow." echo " Consider splitting this into smaller PRs." echo "" echo " To push anyway: git push --no-verify" echo " To cancel: Ctrl+C" echo "" - read -r -p " Push anyway? [y/N]: " response - if [[ ! "$response" =~ ^[Yy]$ ]]; then - echo "Push cancelled." - exit 1 + if [ -t 1 ] && [ -r /dev/tty ]; then + read -r -p " Push anyway? [y/N]: " response < /dev/tty + if [[ ! "$response" =~ ^[Yy]$ ]]; then + echo "Push cancelled." + exit 1 + fi + else + echo " Non-interactive shell detected; allowing push." fi fi