Skip to content

Instantly share code, notes, and snippets.

@MurphysChaos
Created December 11, 2018 17:06
Show Gist options
  • Save MurphysChaos/5bff2ee8a8ca2edb567615293fbc146a to your computer and use it in GitHub Desktop.
Save MurphysChaos/5bff2ee8a8ca2edb567615293fbc146a to your computer and use it in GitHub Desktop.
agit - Script for multiple git repositories
#!/usr/bin/env bash
# agit - Looping Git script
# Copyright (C) 2018 Joel Murphy
# All rights reserved
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Test for MacOS and, if so, coreutils
if [[ "$(uname -o)" == "Darwin" ]]; then
if [[ -n "$(which gecho)" ]]; then
G="g"
else
${G}echo "
ERROR: The command-line utilities distributed with MacOS are unsupported. You
will need to install the coreutils package from either \e[1;34mMacPorts\e[0m or
\e[1;34mhomewbrew\e[0m.
"
exit
fi
fi
# Functions
function repeat_char {
printf "%.0s${1}" $(seq ${2})}
}
C_0="\e[0m"
function light_theme {
C_TRIM1="\e[0;32m"
C_TRIM2="\e[0;32m"
C_DIR_N="\e[0;38;5;32m"
C_BRANC="\e[1;30m"
C_GIT_C="\e[1;30m"
}
function dark_theme {
C_TRIM1="\e[0;32m"
C_TRIM2="\e[0;1;32m"
C_DIR_N="\e[0;38;5;38m"
C_BRANC="\e[0;1;36m"
C_GIT_C="\e[0;1;37m"
}
THEME=${AGIT_THEME:=dark_theme}
${THEME}
# case ${THEME} in
# DARK ) dark_theme ;;
# LIGHT) light_theme ;;
# PLAIN) ;;
# esac
function append {
if [[ ${#@} -eq 1 ]]; then
echo ${1}
else
echo ${1} ${2}
fi
}
function do_git_command {
GROUP_HEAD="${C_TRIM1}$(repeat_char ${SEPARATOR} 2) ${C_DIR_N}$(basename ${D}) ${C_0}[${C_BRANC}$(git_branch)${C_0}]"
GROUP_PLAIN="$(${G}echo ${GROUP_HEAD} | sed -E 's/\\e\[[0-9;]*[a-zA-Z]//g')"
${G}echo -e "${GROUP_HEAD} ${C_TRIM1}$(repeat_char ${SEPARATOR} $((79-${#GROUP_PLAIN})))"
${G}echo -e "${C_0}> ${C_GIT_C}git ${@}${C_0}"
${G}echo
if [[ -z ${TEST} ]]; then
git ${@}
${G}echo -e "${C_0}"
fi
}
function print_usage {
${G}echo -e "
\e[0;1mNAME${C_0}
$(basename ${0}) - loops through immediate subdirectories, performing
the same git command in each.
\e[0;1mSYNOPSIS${C_0}
$(basename ${0}) [options] <command> [<args>]
$(basename ${0}) [options] -- [git-options] <command> [<args>]
\e[0;1mDESCRIPTION${C_0}
Performs a specified git command on each subdirectory. If the present
working directory is part of a git repository, the loop is skipped and
the git command is run once in the current directory.
\e[0;1mOPTIONS${C_0}
-b <branch>, --branch=<branch>
Executes the command in directories where the specified branch is
currently checked out.
-B <branch>, --exclude-branch=<branch>
Prevents execution of the command in all directories except where
the specified branch is currently checked out. Takes precedence over
--branch.
-d <directory>, --include-dir=<directory>
Executes the command only in the specified directory.
-D <directory>, --exclude-dir=<directory>
Prevent execeution of the command in the specified directory. Takes
precedence over --include-dir.
--help
You're reading it.
--test
Prints debug information and skips the git call.
"
exit
}
# Functions
function git_branch {
git branch --no-color 2>/dev/null | grep \* | ${G}sed -e "s/* //"
}
function test_echo_arg {
if [[ ${TEST} -gt 0 ]]; then
${G}echo "Remaining Args:${@}"
${G}echo "OPT: ${OPT}, VAL: ${VAL}"
${G}echo
fi
}
# Look for specific arguments before parsing
if [[ "${@}" == *"--test"* ]]; then
${G}echo
TEST=1
fi
if [[ "${1}" == "--help" ]]; then
print_usage
fi
# Option Parser
while [[ ${#@} > 0 ]]; do
# Get option and argument value
if [[ ${1} == --?*=?* ]]; then
# Long-form option
T=( $(${G}echo "${1}" | ${G}sed 's/--\(.*\)=\(.*\)/-\1 \2/') )
OPT=${T[0]}
VAL=${T[1]}
test_echo_arg ${@}
elif [[ ${1} == --?* ]]; then
# Long-form option without an argument
OPT=${1}
VAL=${1}
test_echo_arg ${@}
elif [[ ${1} == -? && ${1} != -- ]]; then
# Short-form
OPT=${1}
VAL=${2}
test_echo_arg ${@}
# Extra shift for short form. If you add a short form without a VAL, don't shift in the case expression.
shift
else
# Option without an argument
OPT=${1}
VAL=${1}
test_echo_arg ${@}
fi
# Option/Argument logic
case ${OPT} in
-b | -branch)
BRANCH=$"(append "${BRANCH}" "${VAL}")"
shift
;;
-B | -not-branch)
NOTBRANCH="$(append "${NOTBRANCH}" "${VAL}")"
shift
;;
-d | -include-dir)
DIR="$(append "${DIR}" "$(basename ${VAL})")"
shift
;;
-D | -exclude-dir)
NOTDIR="$(append "${NOTDIR}" "$(basename ${VAL})")"
shift
;;
--test)
shift
;;
--help)
# This isn't the first argument, ignoring
shift
;;
--)
shift
break
;;
-*)
${G}echo
${G}echo -e "\e[1;31mINVALID ARGUMENT${C_0}: ${OPT}"
${G}echo "Use $(basename ${0}) --help to see a list of options."
${G}echo
exit
;;
*)
break
;;
esac
done
# Specific Processing
if [[ ${1} == "checkout" ]]; then
ARGS=(${@})
NOTBRANCH=$"$(append "${NOTBRANCH}" "${ARGS[-1]}")"
fi
# Test Output
if [[ ${TEST} -gt 0 ]]; then
${G}echo -e "\e[1;32m========${C_0}"
${G}echo
${G}echo "USING --"
${G}echo " ARGS: ${@}"
${G}echo " BRANCH: ${BRANCH}"
${G}echo " NOTBRANCH: ${NOTBRANCH}"
${G}echo " DIR: ${DIR}"
${G}echo " NOTDIR: ${NOTDIR}"
${G}echo
fi
if [[ ${#@} < 1 ]]; then
print_usage
fi
# Build list of working directories
DIRS=( $(find . -regex ".*/[^\.].*" -type d -depth 1) )
for D in ${DIRS[@]}; do
if [[ (-z "${NOTDIR}" || "${NOTDIR}" != *"$(basename ${D})"*) &&
(-z "${DIR}" || "${DIR}" == *"$(basename ${D})"*)]]; then
# Directory isn't excluded
LOOPDIRS=( "${LOOPDIRS[@]}" "${D}" )
fi
done
# Main routine
SEPARATOR=\\u2500
BOOKEND_1="$(repeat_char \\u2550 80)"
BOOKEND_2="$(repeat_char \\u2550 80)"
${G}echo
${G}echo -e "${C_TRIM2}${BOOKEND_1}"
${G}echo
if [[ -z $(git_branch) ]]; then
for D in ${LOOPDIRS[@]}; do
pushd "${D}" > /dev/null
if [[ -n $(git_branch) ]]; then
if [[(( -z "${BRANCH}" ) || ( "${BRANCH}" == *"$(git_branch)"* )) &&
(( -z "${NOTBRANCH}" ) || ( "${NOTBRANCH}" != *"$(git_branch)"* )) ]]; then
# Branch isn't excluded
do_git_command "${@}"
fi
else
${G}echo -e "\e[1m${D}${C_0}"
${G}echo -e "${C_0}Not a git repository."
fi
popd > /dev/null
# Print separator
if [[ "${D}" == "${LOOPDIRS[-1]}" ]]; then
${G}echo -e "${C_TRIM2}${BOOKEND_2}${C_0}"
${G}echo
fi
done
else
D=$(pwd)
do_git_command "${@}"
${G}echo -e "${C_TRIM2}${BOOKEND_2}${C_0}"
${G}echo
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment