Skip to content

Instantly share code, notes, and snippets.

@royling
Last active March 6, 2025 03:16
Show Gist options
  • Save royling/3b4997c2155ba945d05b to your computer and use it in GitHub Desktop.
Save royling/3b4997c2155ba945d05b to your computer and use it in GitHub Desktop.
run git command in multiple repositories

Run git command in multiple repositories

It discovers all repositories in the base directory and run the git command in each of them.

  • set base directory: via GITR_BASE_DIR env variable or --gitr-base argument (which would override env variable)
  • exclude repository: by putting an empty file named gitr-excluded in the repository (this is ignored if --repos argument passed)
  • run in specific repositories only: via --repos <repo>.... option

gitr --help for simple usage.

Examples

# print usage
gitr --help
# run git status in all repos in the base directory (GITR_BASE_DIR env variable)
gitr status
# run git status in the 2 repos in ~/projects/ directory
gitr status --repos hello-repo world-repo --gitr-base ~/projects/
#! /bin/bash
CWD=`pwd`
printUsage() {
echo "gitr - Git for multiple repositories made easy!"
echo -e "Usage: gitr <branch|status|push|pull|fetch|merge|stash|checkout [options] [arguments]> [--repos <repo>...] [--gitr-base <path>]"
}
println() {
echo "$1"
}
_pushd() {
pushd "$1" > /dev/null
}
_popd() {
popd > /dev/null
}
is_git_repo() {
_pushd "$1" && git rev-parse --is-inside-work-tree &> /dev/null
local result=$?
_popd
return $result
}
_exit() {
cd $CWD
exit $1
}
GITR_ARGS=("--repos" "--gitr-base")
is_gitr_arg() {
for arg in ${GITR_ARGS[@]}; do
if [[ "$1" == "$arg" ]]; then
return 0
fi
done
return 1
}
if [[ "$#" == "0" || "$1" == "-h" || "$1" == "--help" ]]; then
printUsage
_exit 1
fi
GIT_CMD_INDEX=0
until [[ "$#" == "0" ]] || is_gitr_arg "$1"; do
GIT_CMD[$GIT_CMD_INDEX]=$1
GIT_CMD_INDEX=$GIT_CMD_INDEX+1
shift
done
if [[ "$GIT_CMD_INDEX" == "0" ]]; then
printUsage
echo No git command found, what do you want to do?
_exit 1
fi
# parse gitr arguments: --repos, --gitr-base
REPO_INDEX=0
until [[ "$#" == "0" ]]; do
case "$1" in
("--repos")
shift
until [[ "$#" == "0" ]] || is_gitr_arg "$1"; do
REPOS[$REPO_INDEX]=$1
REPO_INDEX=$REPO_INDEX+1
shift
done
;;
("--gitr-base")
shift
GITR_BASE_DIR=$1
shift
;;
esac
done
BASE_DIR=${GITR_BASE_DIR:-$CWD}
echo -e "The base directory is: $BASE_DIR"
println
if [[ $REPO_INDEX -gt 0 ]]; then
for repo in ${REPOS[@]}; do
if ! is_git_repo $BASE_DIR/$repo/; then
echo "Unknown git repository: $repo in directory $BASE_DIR"
_exit 1
fi
done
else
REPO_COUNT=0
for dir in `find $BASE_DIR -mindepth 1 -maxdepth 1 -type d`; do
if is_git_repo $dir; then
# TODO: exclude repo via arguments, e.g. `-x <repo>...`
if [ ! -e $dir/gitr-excluded ]; then
ALL_REPOS[$REPO_COUNT]=`basename $dir`
REPO_COUNT=$REPO_COUNT+1
fi
fi
done
if [[ "$REPO_COUNT" == "0" ]]; then
echo "No git repository found in $BASE_DIR or are all excluded?"
_exit 1
fi
# All by default
REPOS=${ALL_REPOS[@]}
fi
for repo in ${REPOS[@]}; do
_pushd $BASE_DIR/$repo/
echo -e "\033[1;33m[$repo] \033[1;36m${GIT_CMD[0]}\033[0m"
# Need to quote git commands here in order to parse arguments with spaces (e.g. commit message)
git "${GIT_CMD[@]}"
GIT_CMD_STATUS=$?
if [ $GIT_CMD_STATUS -ne 0 ]; then
_exit $GIT_CMD_STATUS
fi
println
_popd
done
_exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment