Created
January 30, 2026 02:20
-
-
Save seantunwin/33ee8b60740b1113ab344cd6cab58c12 to your computer and use it in GitHub Desktop.
Cururent Branch Summary Script
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
| #!/bin/bash | |
| # Branch Effort Summary Script | |
| # | |
| # Generates a concise report of commits, files changed, and line statistics | |
| # for commits matching a pattern or within a commit range. | |
| # | |
| # Usage: | |
| # ./branch-summary.sh # All commits vs 'main' | |
| # ./branch-summary.sh <start-hash>^..HEAD # From commit to HEAD (recommended) | |
| # ./branch-summary.sh <start-hash>..<end-hash> # Specific commit range | |
| # ./branch-summary.sh "monitoring" # Commits matching pattern (regex) | |
| # ./branch-summary.sh main "monitoring" # Base branch + filter pattern | |
| # ./branch-summary.sh --by-author # Same as default + per-author breakdown | |
| # ./branch-summary.sh main --by-author # Base branch + per-author breakdown | |
| # ./branch-summary.sh main --author "Name" # Only commits by author (regex) | |
| # ./branch-summary.sh main --author "Name" --by-author # Filter by author + breakdown | |
| # ./branch-summary.sh 7b3ed1504^..HEAD --author "Name" # Range + confine to author (option) | |
| # | |
| # Options (can be combined with any range or base): | |
| # --by-author Show the same stats broken down by git author (after main summary). | |
| # --author PATTERN Optional. Restrict to commits whose author matches PATTERN (regex). | |
| # Works with commit range, base branch, or base+filter. | |
| # | |
| # Examples: | |
| # ./branch-summary.sh 7b3ed1504^..HEAD # All commits from first working branch commit to HEAD | |
| # ./branch-summary.sh 7b3ed1504^..HEAD --author "Sean" # Same range, only that author's commits | |
| # ./branch-summary.sh main "monitoring" --author "Name" # Base + message filter + author | |
| # ./branch-summary.sh "monitoring|composables" # Filter by pattern | |
| # | |
| # Understanding commit ranges: | |
| # - <hash>^ means "the commit before <hash>" (parent commit) | |
| # - <hash>^..HEAD means "from the commit before <hash> to HEAD" (includes <hash>) | |
| # - If <hash> is your FIRST working branch commit, then <hash>^ is the LAST source branch commit | |
| # - Example: 7b3ed1504^..HEAD includes 7b3ed1504 and all commits after it | |
| # | |
| # Finding your first working branch commit: | |
| # git log --oneline main..HEAD | tail -1 # Shows first commit (oldest) | |
| # git log --oneline --reverse main..HEAD | head -1 # Alternative method | |
| # | |
| # Note: This script is useful for IDE agents via slash commands to generate branch summaries. | |
| set -e | |
| BRANCH=$(git branch --show-current) | |
| BY_AUTHOR="" | |
| AUTHOR_FILTER="" | |
| # Parse optional flags (--by-author, --author PATTERN) and collect positional args | |
| POSITIONALS=() | |
| i=1 | |
| while [ $i -le $# ]; do | |
| eval "arg=\${$i}" | |
| case "$arg" in | |
| --by-author) | |
| BY_AUTHOR=1 | |
| ;; | |
| --author) | |
| i=$((i + 1)) | |
| eval "AUTHOR_FILTER=\${$i}" | |
| ;; | |
| *) | |
| POSITIONALS+=("$arg") | |
| ;; | |
| esac | |
| i=$((i + 1)) | |
| done | |
| # Parse positional arguments (same logic as before, using POSITIONALS) | |
| if [ ${#POSITIONALS[@]} -eq 0 ]; then | |
| BASE="main" | |
| FILTER="" | |
| elif [ ${#POSITIONALS[@]} -eq 1 ]; then | |
| if git rev-parse --verify "${POSITIONALS[0]}" >/dev/null 2>&1 || git rev-parse --verify "origin/${POSITIONALS[0]}" >/dev/null 2>&1; then | |
| BASE="${POSITIONALS[0]}" | |
| FILTER="" | |
| elif echo "${POSITIONALS[0]}" | grep -q "\.\."; then | |
| BASE="" | |
| FILTER="" | |
| COMMIT_RANGE="${POSITIONALS[0]}" | |
| else | |
| BASE="main" | |
| FILTER="${POSITIONALS[0]}" | |
| fi | |
| else | |
| BASE="${POSITIONALS[0]}" | |
| FILTER="${POSITIONALS[1]}" | |
| fi | |
| # Git log author option when filtering by author | |
| GIT_AUTHOR_OPT="" | |
| if [ -n "$AUTHOR_FILTER" ]; then | |
| GIT_AUTHOR_OPT="--author=$AUTHOR_FILTER" | |
| fi | |
| # Determine commit range | |
| if [ -n "$COMMIT_RANGE" ]; then | |
| # Explicit commit range provided | |
| COMMITS=$(git log --oneline $GIT_AUTHOR_OPT "$COMMIT_RANGE" 2>/dev/null) | |
| if [ -n "$AUTHOR_FILTER" ]; then | |
| # When confined by author, list files only from those commits | |
| FILES_LIST="" | |
| for hash in $(echo "$COMMITS" | awk '{print $1}'); do | |
| FILES_LIST="$FILES_LIST$(git diff-tree --no-commit-id --name-only -r "$hash" 2>/dev/null)" | |
| done | |
| FILES_LIST=$(echo "$FILES_LIST" | sort -u | grep -v "^$") | |
| else | |
| FILES_LIST=$(git diff --name-only "$COMMIT_RANGE" 2>/dev/null | sort -u) | |
| fi | |
| elif [ -n "$BASE" ]; then | |
| # Compare against base branch | |
| BASE_REMOTE="origin/$BASE" | |
| BASE_COMMIT=$(git merge-base HEAD "$BASE_REMOTE" 2>/dev/null || git merge-base HEAD "$BASE" 2>/dev/null || echo "") | |
| if [ -z "$BASE_COMMIT" ]; then | |
| echo "Error: Could not find base branch '$BASE' or '$BASE_REMOTE'" | |
| exit 1 | |
| fi | |
| if [ -n "$FILTER" ]; then | |
| # Filter commits by pattern (use grep -E for regex) | |
| COMMITS=$(git log --oneline $GIT_AUTHOR_OPT "$BASE_COMMIT"..HEAD 2>/dev/null | grep -iE "$FILTER") | |
| # Get files from filtered commits only | |
| COMMIT_HASHES=$(echo "$COMMITS" | awk '{print $1}') | |
| FILES_LIST="" | |
| for hash in $COMMIT_HASHES; do | |
| FILES_LIST="$FILES_LIST$(git diff-tree --no-commit-id --name-only -r "$hash" 2>/dev/null)" | |
| done | |
| FILES_LIST=$(echo "$FILES_LIST" | sort -u | grep -v "^$") | |
| else | |
| # All commits | |
| COMMITS=$(git log --oneline $GIT_AUTHOR_OPT "$BASE_COMMIT"..HEAD 2>/dev/null) | |
| FILES_LIST=$(git diff --name-only "$BASE_COMMIT"..HEAD 2>/dev/null | sort -u) | |
| fi | |
| else | |
| echo "Error: Invalid arguments" | |
| exit 1 | |
| fi | |
| # Count commits | |
| COMMIT_COUNT=$(echo "$COMMITS" | grep -v "^$" | wc -l) | |
| # Count unique files | |
| FILES_COUNT=$(echo "$FILES_LIST" | grep -v "^$" | wc -l) | |
| # Calculate totals from individual commits | |
| TOTAL_FILES=0 | |
| TOTAL_INS=0 | |
| TOTAL_DEL=0 | |
| while read -r commit; do | |
| if [ -n "$commit" ]; then | |
| COMMIT_HASH=$(echo "$commit" | awk '{print $1}') | |
| STAT=$(git show --stat --oneline "$COMMIT_HASH" 2>/dev/null | tail -1) | |
| if echo "$STAT" | grep -q "files changed"; then | |
| FILES=$(echo "$STAT" | awk '{print $1}') | |
| # Extract insertions (handle both "210 insertions(+)" and "1761 insertions(+)" formats) | |
| INS=$(echo "$STAT" | grep -oE '[0-9]+ insertions' | grep -oE '[0-9]+' || echo "0") | |
| # Extract deletions (may not be present) | |
| DEL=$(echo "$STAT" | grep -oE '[0-9]+ deletions' | grep -oE '[0-9]+' || echo "0") | |
| TOTAL_FILES=$((TOTAL_FILES + ${FILES:-0})) | |
| TOTAL_INS=$((TOTAL_INS + ${INS:-0})) | |
| TOTAL_DEL=$((TOTAL_DEL + ${DEL:-0})) | |
| fi | |
| fi | |
| done <<< "$COMMITS" | |
| NET=$((TOTAL_INS - TOTAL_DEL)) | |
| # Output | |
| echo "==========================================" | |
| echo "Branch Effort Summary: $BRANCH" | |
| if [ -n "$BASE" ]; then | |
| echo "Base: $BASE" | |
| if [ -n "$BASE_COMMIT" ]; then | |
| echo "Base commit: ${BASE_COMMIT:0:8}" | |
| fi | |
| fi | |
| if [ -n "$FILTER" ]; then | |
| echo "Filter: $FILTER" | |
| fi | |
| if [ -n "$COMMIT_RANGE" ]; then | |
| echo "Range: $COMMIT_RANGE" | |
| fi | |
| if [ -n "$AUTHOR_FILTER" ]; then | |
| echo "Author filter: $AUTHOR_FILTER" | |
| fi | |
| echo "==========================================" | |
| echo "" | |
| echo "Commits: $COMMIT_COUNT" | |
| echo "Tracked files touched: $FILES_COUNT" | |
| echo "" | |
| echo "Line changes:" | |
| echo " Insertions: $TOTAL_INS" | |
| echo " Deletions: $TOTAL_DEL" | |
| echo " Net change: +$NET" | |
| echo "" | |
| if [ $COMMIT_COUNT -gt 0 ]; then | |
| echo "Commits:" | |
| echo "$COMMITS" | sed 's/^/ /' | |
| echo "" | |
| fi | |
| # Per-author breakdown (--by-author) | |
| if [ -n "$BY_AUTHOR" ] && [ $COMMIT_COUNT -gt 0 ]; then | |
| AUTHOR_TMP=$(mktemp) | |
| trap 'rm -f "$AUTHOR_TMP"' EXIT | |
| while read -r commit; do | |
| if [ -n "$commit" ]; then | |
| COMMIT_HASH=$(echo "$commit" | awk '{print $1}') | |
| AUTHOR=$(git log -1 --format='%an' "$COMMIT_HASH" 2>/dev/null || echo "?") | |
| echo "$AUTHOR|$COMMIT_HASH" | |
| fi | |
| done <<< "$COMMITS" > "$AUTHOR_TMP" | |
| echo "------------------------------------------" | |
| echo "By author" | |
| echo "------------------------------------------" | |
| for author in $(cut -d'|' -f1 "$AUTHOR_TMP" | sort -u); do | |
| AUTHOR_HASHES=$(grep -F "$author|" "$AUTHOR_TMP" | cut -d'|' -f2) | |
| A_COMMITS=0 | |
| A_INS=0 | |
| A_DEL=0 | |
| while read -r hash; do | |
| [ -z "$hash" ] && continue | |
| A_COMMITS=$((A_COMMITS + 1)) | |
| STAT=$(git show --stat --oneline "$hash" 2>/dev/null | tail -1) | |
| if echo "$STAT" | grep -q "files changed"; then | |
| INS=$(echo "$STAT" | grep -oE '[0-9]+ insertions' | grep -oE '[0-9]+' || echo "0") | |
| DEL=$(echo "$STAT" | grep -oE '[0-9]+ deletions' | grep -oE '[0-9]+' || echo "0") | |
| A_INS=$((A_INS + ${INS:-0})) | |
| A_DEL=$((A_DEL + ${DEL:-0})) | |
| fi | |
| done <<< "$AUTHOR_HASHES" | |
| A_NET=$((A_INS - A_DEL)) | |
| echo "" | |
| echo " $author:" | |
| echo " Commits: $A_COMMITS" | |
| echo " Insertions: $A_INS Deletions: $A_DEL Net: +$A_NET" | |
| done | |
| echo "" | |
| fi | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment