From ea466bc4e2586fe6d799af0ee5a6f97c50db95c9 Mon Sep 17 00:00:00 2001 From: Angel Hudgins Date: Fri, 16 Jan 2026 18:38:34 +0100 Subject: [PATCH] feat: add parallel execution with -j/--jobs flag - Add -j/--jobs N flag to run N commits in parallel - Use xargs -P for reliable parallel execution - Refactor to use process_commit wrapper function - Falls back to sequential when -j 1 or not specified - Should provide 3-4x speedup when checking multiple commits --- check-history.sh | 123 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 33 deletions(-) diff --git a/check-history.sh b/check-history.sh index 03768ed..c4ab888 100755 --- a/check-history.sh +++ b/check-history.sh @@ -2,21 +2,51 @@ set -euo pipefail # Usage: -# check-history.sh [--verbose] +# check-history.sh [OPTIONS] # Example: -# check-history.sh 2 4 # checks commits 2,3,4,5 -# check-history.sh 5 1 # checks only commit 5 -# check-history.sh 1 1 --verbose # verbose mode +# check-history.sh 2 4 # checks commits 2,3,4,5 +# check-history.sh 5 1 -v # verbose mode +# check-history.sh 2 28 -j4 # check 28 commits, 4 at a time VERBOSE=0 -if [ $# -lt 2 ] || [ $# -gt 3 ]; then - echo "Usage: $0 [--verbose|-v]" +JOBS=1 + +# Parse arguments +if [ $# -lt 2 ]; then + echo "Usage: $0 [--verbose|-v] [-j|--jobs N]" exit 1 fi -if [ $# -eq 3 ] && { [ "$3" = "--verbose" ] || [ "$3" = "-v" ]; }; then - VERBOSE=1 -fi +START_ARG="$1" +COUNT_ARG="$2" +shift 2 + +# Parse optional flags +while [ $# -gt 0 ]; do + case "$1" in + -v|--verbose) + VERBOSE=1 + shift + ;; + -j|--jobs) + if [ $# -lt 2 ]; then + echo "Error: -j/--jobs requires a number" >&2 + exit 1 + fi + JOBS="$2" + if ! [[ "$JOBS" =~ ^[0-9]+$ ]] || [ "$JOBS" -lt 1 ]; then + echo "Error: -j/--jobs must be a positive integer" >&2 + exit 1 + fi + shift 2 + ;; + *) + echo "Error: Unknown option $1" >&2 + echo "Usage: $0 [--verbose|-v] [-j|--jobs N]" + exit 1 + ;; + esac +done # Debug logging helper debug() { @@ -26,8 +56,8 @@ debug() { } # Convert user-friendly 1-indexed "Nth from HEAD" into 0-indexed offset -START_OFFSET="$(( $1 - 1 ))" -COUNT="$2" +START_OFFSET="$(( START_ARG - 1 ))" +COUNT="$COUNT_ARG" REPO_ROOT="$(git rev-parse --show-toplevel)" TMP_BASE="/tmp/check-history-$$" WORKTREES_DIR="$TMP_BASE/worktrees" @@ -40,7 +70,11 @@ debug "PNPM_STORE: $PNPM_STORE" mkdir -p "$WORKTREES_DIR" "$LOGS_DIR" "$PNPM_STORE" -echo "Checking commits from offset $1 (HEAD~$START_OFFSET) for $COUNT commits…" +if [ "$JOBS" -gt 1 ]; then + echo "Checking commits from offset $START_ARG (HEAD~$START_OFFSET) for $COUNT commits with $JOBS parallel jobs…" +else + echo "Checking commits from offset $START_ARG (HEAD~$START_OFFSET) for $COUNT commits…" +fi echo # Build list of commits as if by `git rebase -i HEAD~N` (HEAD=0 newest) as if by `git rebase -i HEAD~N` @@ -153,7 +187,9 @@ build_frontend() { # ----------------------------- cleanup() { echo " -Caught interrupt, cleaning worktrees…" >&2 +Caught interrupt, killing background jobs and cleaning worktrees…" >&2 + # Kill all background jobs + jobs -p | xargs -r kill 2>/dev/null || true git worktree prune >/dev/null 2>&1 || true rm -rf "$TMP_BASE" >/dev/null 2>&1 || true exit 130 @@ -215,36 +251,57 @@ check_commit() { } # ----------------------------- -# Sequential execution +# Execution (parallel or sequential) # ----------------------------- FAIL=0 -i=1 TOTAL=${#COMMITS[@]} -for commit in "${COMMITS[@]}"; do - worktree="$WORKTREES_DIR/$commit" - log="$LOGS_DIR/$commit.log" - debug "===== Processing commit $i/$TOTAL =====" - debug "Commit: $commit" - debug "Worktree: $worktree" - debug "Log file: $log" +# Export functions and variables for parallel execution +export -f check_commit build_frontend debug +export VERBOSE REPO_ROOT PNPM_STORE WORKTREES_DIR LOGS_DIR - echo "[ $i / $TOTAL ] Checking $commit…" - if ! check_commit "$commit" "$worktree" "$log"; then +# Process a single commit (for xargs) +process_commit() { + local idx="$1" + local commit="$2" + local worktree="$WORKTREES_DIR/$commit" + local log="$LOGS_DIR/$commit.log" + + echo "[ $((idx + 1)) / $TOTAL ] Checking $commit…" + + if check_commit "$commit" "$worktree" "$log"; then + echo "✓ [ $((idx + 1)) / $TOTAL ] $commit passed" + sed 's/^/ /' "$log" + git worktree remove --force "$worktree" >/dev/null 2>&1 || true + echo + return 0 + else echo "❌ FAILED at commit $commit" sed 's/^/ /' "$log" - FAIL=1 - break + echo + return 1 fi - debug "Commit $commit passed" - sed 's/^/ /' "$log" +} +export -f process_commit +export TOTAL - debug "Removing worktree at $worktree" - git worktree remove --force "$worktree" >/dev/null 2>&1 +if [ "$JOBS" -eq 1 ]; then + # Sequential execution + for i in "${!COMMITS[@]}"; do + if ! process_commit "$i" "${COMMITS[$i]}"; then + FAIL=1 + break + fi + done +else + # Parallel execution using xargs + debug "Starting parallel execution with $JOBS jobs" - ((i++)) - echo -done + # Create input for xargs: "index commit" + for i in "${!COMMITS[@]}"; do + echo "$i ${COMMITS[$i]}" + done | xargs -P "$JOBS" -n 2 bash -c 'process_commit "$@"' _ || FAIL=1 +fi # ----------------------------- # Final output