Run One Git Command Across Several Repositories
git for-each-repo runs one Git subcommand across a whole list of repositories in one go, instead of you typing it into ten separate clones by hand. You will finish with a Git configuration key holding several repository paths and a repeatable command that runs one Git subcommand in each of them. This guide uses the version included in Git 2.43.0, installed on this machine.
The route
Jump straight to the step you need, or tick off Done means at the end.
- Time: about fifteen minutes, if the repositories already exist.
- Scope: the examples only inspect repositories; the final maintenance example can change repository state.
No elevated privileges are needed for anything shown here. Use sudo only if the repositories or their Git configuration are deliberately readable by root alone, and check ownership and the service account first: running as root can leave root-owned files in a working tree.
1. Check the installed command
Start by confirming the version and the local option set:
$ git --version
git version 2.43.0
$ git for-each-repo -h
usage: git for-each-repo --config=<config> [--] <arguments>
The command is marked experimental in the installed manual. Its central option is --config=<config>, and the named configuration variable must hold a multi-valued list of absolute repository paths. Every argument after that option, or after --, becomes the argument list for a Git subprocess.
Checkpoint
The help output should show --config. Do not copy options from a newer machine without checking this output first: current upstream documentation also describes a --keep-going option, but it is not present in Git 2.43.0, so this guide does not use it.
2. Choose a dedicated configuration key
Pick two or more existing repositories and write down their absolute paths. The key name below, fleet.repo, is deliberately separate from Git's documented maintenance.repo example, so this tutorial's list cannot silently become part of another maintenance workflow. Check that the key is unused before adding anything:
$ git config --global --get-all fleet.repo
$ printf '%s\n' "exit status: $?"
exit status: 1
Status 1 normally means the key has no value. If it prints paths instead, stop and either choose another key or review the existing list before changing it.
Add each repository as a separate value; replace the examples with real absolute paths:
$ git config --global --add fleet.repo /srv/src/project-a
$ git config --global --add fleet.repo /srv/src/project-b
$ git config --global --get-all fleet.repo
/srv/src/project-a
/srv/src/project-b
Do not put both paths into one value: the repeated key is what makes the configuration multi-valued. Quote a path containing spaces as one shell argument:
$ git config --global --add fleet.repo '/srv/src/Project With Spaces'
Recovery
Because this uses a dedicated key, remove the whole tutorial list with git config --global --unset-all fleet.repo. Review the key first, and never run that against a key another workflow depends on.
3. Run a read-only check in every repository
Use rev-parse --show-toplevel as a harmless smoke test. Git changes directory to each configured path before starting the subprocess, equivalent to running git -C <repo> for each value:
$ git for-each-repo --config=fleet.repo rev-parse --show-toplevel
/srv/src/project-a
/srv/src/project-b
Output order follows the configuration value order, which also makes this a good way to catch a stale path before running anything that writes. Use -- when the boundary needs to be explicit, especially if the child command has options that could be mistaken for options to for-each-repo:
$ git for-each-repo --config=fleet.repo -- status --short
A clean repository produces no status lines; any output belongs to the individual git status --short invocation. Standard input, standard output and standard error are inherited by each child, so diagnostics land in the same terminal rather than a combined report.
4. Know which configuration actually gets read
Git reads system, global and local configuration where available, but the local configuration is the one for the directory you invoke for-each-repo from, not the local configuration of each target repository. Run the command outside a Git repository and only system and global configuration are available.
That distinction is a common trap: a list stored locally in project A will not automatically be available when you run the command from your home directory or project B. Put a cross-project list in global configuration instead, or invoke the command from the repository whose local configuration holds it. Inspect the effective values before a batch run:
$ git config --show-origin --get-all fleet.repo
file:/home/USER/.gitconfig /srv/src/project-a
file:/home/USER/.gitconfig /srv/src/project-b
The home directory and path details are host-specific; the useful check is that every value is absolute and comes from the configuration scope you intended.
5. Plan for failure before running a batch command
In Git 2.43.0, a non-zero child result makes for-each-repo return that result and stops it from starting later subprocesses. A missing directory, a non-repository path or a failed Git operation can leave the rest of the list untouched. Test the failure behaviour with a read-only command if you need to document it:
$ git for-each-repo --config=fleet.repo rev-parse --verify refs/heads/does-not-exist
fatal: Needed a single revision
$ printf '%s\n' "exit status: $?"
exit status: 128
The exact diagnostic depends on the child command. Treat a non-zero result as a batch failure, inspect the first repository that failed, and do not assume later repositories were ever checked.
6. Apply a state-changing command carefully
Once the read-only check is sound, the same pattern works for something like Git maintenance:
$ git for-each-repo --config=fleet.repo maintenance run
Warning
This can write Git maintenance data and consume CPU, disk or network resources. Run it in a maintenance window when the repositories are large or shared with other automation, and read the installed git-maintenance documentation and confirm the configured paths before starting. There is no general undo command for maintenance work, so keep backups and recovery procedures separate from this loop. If you only need to verify the list, go back to rev-parse --show-toplevel or status --short.
Done means
- Option set checked:
git --versionconfirmed what your machine'sfor-each-repoactually supports. - Dedicated key used: the repository list lives in its own multi-valued key with absolute paths.
- Read-only run verified:
git for-each-repo --config=fleet.repo rev-parse --show-toplevelprinted every expected repository. - Scope understood: you checked the configuration origin and confirmed local versus global scope.
- Failure behaviour known: a failing child is expected to stop later children on Git 2.43.0.
- Maintenance window planned: any state-changing batch command has an explicit window and recovery plan.