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
This commit is contained in:
123
check-history.sh
123
check-history.sh
@@ -2,21 +2,51 @@
|
||||
set -euo pipefail
|
||||
|
||||
# Usage:
|
||||
# check-history.sh <start_offset> <count> [--verbose]
|
||||
# check-history.sh <start_offset> <count> [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 <start_offset_from_head> <count> [--verbose|-v]"
|
||||
JOBS=1
|
||||
|
||||
# Parse arguments
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 <start_offset_from_head> <count> [--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 <start_offset_from_head> <count> [--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
|
||||
|
||||
Reference in New Issue
Block a user