Git Developer Guide

How to git pull or fetch every repo in a folder at once

Fetch everything first

If your repositories sit side by side in one folder, a short loop updates all of them. Start with git fetch: it downloads new commits and moves remote-tracking branches such as origin/main, but never touches your files or your local branches, so it is safe to run on every repo, including ones with work in progress.

for repo in ~/Projects/*/; do
  repo=${repo%/}
  [ -e "$repo/.git" ] || continue
  echo "Fetching $repo"
  git -C "$repo" fetch --all --prune --quiet || echo "  fetch failed: $repo"
done

Folders that are not repositories are skipped. --all fetches every remote, and --prune drops remote-tracking branches that were deleted on the server. Because "$repo" is always quoted, paths with spaces work, and the loop pastes as-is into zsh (the macOS default) or bash.

Repos nested deeper, or fetched in parallel

If some repos live inside group folders, let find locate them. -print0 and read -d '' pass each path through intact, whatever characters it contains:

find ~/Projects -maxdepth 4 -name .git -prune -print0 |
  while IFS= read -r -d '' gitdir; do
    repo=${gitdir%/.git}
    echo "Fetching $repo"
    git -C "$repo" fetch --all --prune --quiet </dev/null || echo "  fetch failed: $repo"
  done

Fetching is mostly waiting on the network, so with dozens of repos it pays to run eight at a time:

find ~/Projects -maxdepth 4 -name .git -prune -print0 |
  xargs -0 -n 1 -P 8 sh -c '
    repo=${1%/.git}
    git -C "$repo" fetch --all --prune --quiet || echo "fetch failed: $repo"
  ' sh

Pull or fetch?

The safe pattern is to fetch everything, pull with --ff-only only where it is a clean fast-forward, and handle the rest by hand.

A pull loop that leaves dirty repos alone

for repo in ~/Projects/*/; do
  repo=${repo%/}
  [ -e "$repo/.git" ] || continue
  if [ -n "$(git -C "$repo" status --porcelain --untracked-files=no)" ]; then
    echo "SKIP  $repo (uncommitted changes)"
  elif ! git -C "$repo" rev-parse --quiet --verify '@{u}' >/dev/null; then
    echo "SKIP  $repo (detached HEAD or no upstream)"
  elif git -C "$repo" pull --ff-only --quiet; then
    echo "OK    $repo"
  else
    echo "FAIL  $repo (diverged: sort it out by hand)"
  fi
done

After the fetch: GitMon

GitMon does not fetch or pull for you: it is read-only and never runs git. It reads the remote-tracking refs your last fetch left behind, so once the loop above has run, the menu bar shows which repos are behind (↓), ahead (↑), dirty, detached, never pushed, or pointing at an upstream that no longer exists. Those are the repos the pull loop skipped or failed on.

Download GitMon Free Trial Learn more

Related Guides