In the past I have already worked with a project that consisted of multiple Git repositories in a common project folder. For tracking each repository’s individual state together, Google’s repo tool was used. I ended up using mostly its powerful repo forall subcommand to execute various bash or git commands over the whole or subset of those projects.
Now, I was faced with the same situation, but without repo already in place. Could I get some of that forall feeling back, but without installing that (relatively) giant tool? It turns out, 9 lines of bash give me most of what I missed:
The following function, added to my ~/.bash_aliases
is sufficient:
function git-forall() { for d in */.git; do local f f="${d%/.git}" echo "${f}" git -C "${f}" "$@" echo "" done }
It enables me to quickly check the status of all repos using git-forall status
, refresh all branches to their current origin state using git-forall fetch
, or even give me a short tree-view of the latest commits on any branch using git-forall lg --all -10
. In the last command, lg
is my custom alias for git log
, but with custom columns and graph drawing enabled.
The only noteworthy bash feature might be the ${VAR%SUFFIX}
syntax which removes the string SUFFIX
from the contents of variable VAR
if present.
Forall for all
With that function in place, I realised that a more general forall
function was in order:
function forall() { for d in */; do echo "${d}" cd "${d}" "$@" echo "" cd .. done }
Now, I can quickly do something like ls -R
but limited to a single level by typing forall ls
, which otherwise would need the following, easy to remember find
one-liner:
find -mindepth 1 -maxdepth 1 \ -exec echo "{}" \; -exec ls "{}" \;
Yeah, quite easy to remember and fast to type!
Update 2024
As usual, snippets such as the above slowly evolve over time. Here is my currently quite stable working configuration, mostly adding robustness and highlighting for the titular output of the subdirectory (line echo -e "...${f}"
) and defaulting to git status
if no additional arguments are given.
function git-forall()
{
for d in */.git; do
local f
f="${d%/.git}"
echo -e "\e[1m\e[94m${f}\e[0m/"
if [[ $# -eq 0 ]]; then
git -C "${f}" status
else
git -C "${f}" "$@"
fi
echo ""
done
}
This snippet, together with some nifty git aliases in my .gitconfig
allow me to quickly update a dozen Git repos belonging to a project by just calling git-forall rup
(where rup expands to remote update --prune
) . You can quickly set such shorthands by calling…
git config --global alias.rup "remote update --prune"
… or by manually editing your ~/.gitconfig
.
Going further
Of course, this simple approach let’s lots of things to be desired. If you want more, but do not want to dive much deeper into bash scripting, see what others already have done:
- gita by nosarthur provides git-forall on steroids with nice output formatting per command in about 2000 lines of Python code.
- mr, short for “my repos” is an older (but well-maintained), 2700 Perl tool which supports not only git, but svn, bzr, cvs, hg, darcs, fossil and veracity. Impressive! Its behaviour is more driven by a central Makefile-like configuration. And even more helpful, tGhe homepage another dozen related and similar tools.
- And of course, finally, repo itself should be considered at some point!