-
-
Save MurphysChaos/5bff2ee8a8ca2edb567615293fbc146a to your computer and use it in GitHub Desktop.
agit - Script for multiple git repositories
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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