Skip to content

Instantly share code, notes, and snippets.

@markuskreitzer
Created March 30, 2026 15:21
Show Gist options
  • Select an option

  • Save markuskreitzer/fb22697135d434812d909d6e9641e3b2 to your computer and use it in GitHub Desktop.

Select an option

Save markuskreitzer/fb22697135d434812d909d6e9641e3b2 to your computer and use it in GitHub Desktop.
git-pull: Pull all git repos in the current directory with a summary table

git-pull

A shell function that runs git pull on every git repository in the current directory and prints a color-coded summary table.

Features

  • Scans immediate subdirectories for git repos
  • Pulls the currently checked-out branch in each repo
  • Skips repos with uncommitted changes (with a warning)
  • Detects submodules and runs git submodule update --init --recursive
  • Prints a summary table with repo name, branch, status, submodule info, and details
  • Color-coded output: green for success, yellow for skipped, red for failed
  • Compatible with both bash and zsh

Example Output

  Pulling my-api...                    done
  Pulling frontend...                  done
  Pulling infra-charts...              skipped
  Pulling shared-lib...                done

REPO                         BRANCH             STATUS           SUBMODULES   DETAIL
──────────────────────────── ────────────────── ──────────────── ──────────── ────────────────────
my-api                       main               ✓ pulled         —            3 files changed, 10 insertions(+)
frontend                     develop            ✓ up-to-date     ✓ synced     —
infra-charts                 main               ⚠ SKIPPED        yes          uncommitted changes
shared-lib                   feat/new-parser    ✓ pulled         —            1 file changed, 2 insertions(+)

4 repos: 2 pulled, 1 up-to-date, 1 skipped, 0 failed

Installation

Add to your shell profile (.bashrc, .zshrc, or a shared file like .aliases):

source /path/to/git-pull.sh

Or paste the function directly into your profile.

Usage

cd ~/projects
git-pull

Exit Code

Returns 0 if all repos pulled successfully, 1 if any were skipped or failed.

# git-pull — Pull all git repos in the current directory
# Compatible with bash and zsh. Source this file or paste into your shell profile.
git-pull() {
local rows had_errors total pulled uptodate skipped failed
local dir repo branch submods pull_out pull_rc reason pstat detail stat
local row r_repo r_branch r_status r_submods r_detail color
rows=()
had_errors=0
total=0 pulled=0 uptodate=0 skipped=0 failed=0
for dir in */; do
[ -d "$dir/.git" ] || continue
repo="${dir%/}"
branch=$(git -C "$dir" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "???")
submods="—"
[ -f "$dir/.gitmodules" ] && submods="yes"
total=$((total + 1))
printf " Pulling %-30s" "$repo..."
# Check for dirty working tree
if ! git -C "$dir" diff --quiet HEAD 2>/dev/null || \
! git -C "$dir" diff --cached --quiet HEAD 2>/dev/null; then
rows+=("$repo|$branch|⚠ SKIPPED|$submods|uncommitted changes")
skipped=$((skipped + 1))
had_errors=1
printf "skipped\n"
continue
fi
# Capture pull output
pull_out=$(git -C "$dir" pull 2>&1)
pull_rc=$?
if [ $pull_rc -ne 0 ]; then
reason=$(echo "$pull_out" | tail -1)
rows+=("$repo|$branch|✗ FAILED|$submods|$reason")
failed=$((failed + 1))
had_errors=1
printf "failed\n"
continue
fi
# Determine what happened
if echo "$pull_out" | grep -q "Already up to date"; then
pstat="✓ up-to-date"
detail="—"
uptodate=$((uptodate + 1))
else
stat=$(echo "$pull_out" | grep -E "^\s*[0-9]+ files? changed" | sed 's/^ *//')
pstat="✓ pulled"
detail="${stat:-updated}"
pulled=$((pulled + 1))
fi
# Update submodules if present
if [ -f "$dir/.gitmodules" ]; then
git -C "$dir" submodule update --init --recursive >/dev/null 2>&1
submods="✓ synced"
fi
rows+=("$repo|$branch|$pstat|$submods|$detail")
printf "done\n"
done
if [ $total -eq 0 ]; then
echo "No git repos found in $(pwd)"
return 0
fi
# Print summary table
printf "\n"
printf "\033[1m%-28s %-18s %-16s %-12s %s\033[0m\n" "REPO" "BRANCH" "STATUS" "SUBMODULES" "DETAIL"
printf "%-28s %-18s %-16s %-12s %s\n" "────────────────────────────" "──────────────────" "────────────────" "────────────" "────────────────────"
for row in "${rows[@]}"; do
r_repo=${row%%|*}; row=${row#*|}
r_branch=${row%%|*}; row=${row#*|}
r_status=${row%%|*}; row=${row#*|}
r_submods=${row%%|*}
r_detail=${row#*|}
# Color the status
color="\033[0m"
case "$r_status" in
*SKIPPED*) color="\033[1;33m" ;;
*FAILED*) color="\033[1;31m" ;;
*pulled*) color="\033[1;32m" ;;
*) color="\033[0;32m" ;;
esac
printf "%-28s %-18s ${color}%-16s\033[0m %-12s %s\n" "$r_repo" "$r_branch" "$r_status" "$r_submods" "$r_detail"
done
# Totals
printf "\n\033[1m%d repos\033[0m: %d pulled, %d up-to-date, %d skipped, %d failed\n" \
"$total" "$pulled" "$uptodate" "$skipped" "$failed"
return $had_errors
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment