Created
November 11, 2019 20:29
-
-
Save SergeyAlekseevN/55bcb80ee37cd44e16310f0bbe068493 to your computer and use it in GitHub Desktop.
Mass operations for git local repos
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
#!/bin/sh | |
COMMAND="$1" | |
IGNORED_MODULES=$(cat .ignored_modules 2> /dev/null) | |
RED_COLOR='\033[0;31m' | |
GREEN_COLOR='\033[0;32m' | |
GRAY_COLOR='\033[0;2m' | |
NO_COLOR='\033[0m' | |
_EXPOSE_PARAMS="shift 2" | |
is_allowed() { | |
for M in $IGNORED_MODULES | |
do | |
if [ "./$M/" = "$1" ] | |
then | |
return 1 | |
fi | |
done | |
} | |
has_branch() { | |
BRANCH="$1" | |
[ "$BRANCH" ] || die "No branch was specified" | |
git rev-parse --quiet --verify "$BRANCH" > /dev/null | |
return $? | |
} | |
die() { | |
echo "$1" | |
exit 1 | |
} | |
# Executed before switching directory to a module, as many times as there are modules. | |
# | |
# Parameters: | |
# 1: module name | |
_before_module() { | |
MODULE="$1" | |
if [ -d "$MODULE/.git" ] | |
then | |
cd "$MODULE" || return 1 | |
return | |
else | |
echo "$GRAY_COLOR$MODULE$NO_COLOR> not a Git repo" | |
return 2 | |
fi | |
} | |
# Executed after switching directory to a module, as many times as there are | |
# modules. | |
# | |
# Parameters: | |
# none | |
_after_module() { | |
cd .. || return 1 | |
} | |
cmd_switch_module_branch() { | |
MODULE="$1" | |
shift 2 # eat module name and command name | |
BRANCH="$1" | |
[ "$BRANCH" ] || die "No branch was specified" | |
if has_branch "$BRANCH" | |
then | |
git checkout --quiet "$BRANCH" | |
echo "$GREEN_COLOR$MODULE$NO_COLOR switched to $BRANCH" | |
else | |
echo "$GRAY_COLOR$MODULE$NO_COLOR> No branch $BRANCH" | |
fi | |
} | |
cmd_pull_rebase() { | |
MODULE="$1" | |
shift 2 # eat module name and command name | |
RESULT=$(git pull --rebase) | |
echo "$GREEN_COLOR$MODULE$NO_COLOR> $RESULT" | |
} | |
cmd_full_merge() { | |
MODULE="$1"; | |
shift 2 # eat module name and command name | |
OPTIND="" | |
GIT_PUSH="true" | |
COMMIT_MESSAGE_TEXT="" | |
COMMIT_MESSAGE_PARAM="--log --no-edit -m" | |
while getopts "pm:" OPT "$@" | |
do | |
case "$OPT" in | |
p) | |
GIT_PUSH="git push" | |
;; | |
m) | |
COMMIT_MESSAGE_TEXT="$OPTARG" | |
;; | |
\?) | |
echo "Invalid option -$OPT" >&2 | |
exit 1 | |
;; | |
:) | |
echo "Option $OPT requires an argument" >&2 | |
exit 1 | |
;; | |
esac | |
done | |
eval SOURCE_BRANCH='$'$OPTIND | |
eval TARGET_BRANCH='$'$((OPTIND + 1)) | |
[ "$SOURCE_BRANCH" ] || die "No source branch was specified" | |
[ "$TARGET_BRANCH" ] || die "No target branch was specified" | |
echo "----------------------------------------------" | |
echo "Merge $SOURCE_BRANCH -> $TARGET_BRANCH> $MODULE" | |
if [ -z "$COMMIT_MESSAGE_TEXT" ] | |
then | |
COMMIT_MESSAGE_TEXT="Merge $SOURCE_BRANCH into $TARGET_BRANCH" | |
fi | |
# shellcheck disable=SC2086 | |
git checkout "$TARGET_BRANCH" && \ | |
git branch --set-upstream-to=origin/"$TARGET_BRANCH" && \ | |
git pull --rebase && \ | |
git merge --no-ff $COMMIT_MESSAGE_PARAM "$COMMIT_MESSAGE_TEXT" "$SOURCE_BRANCH" && \ | |
"$GIT_PUSH" && \ | |
git checkout "$SOURCE_BRANCH" | |
} | |
cmd_last_commit_date() { | |
MODULE="$1" | |
shift 2 # eat module name and command name | |
BRANCH="$1" | |
[ "$BRANCH" ] || die "No branch was specified" | |
LAST_COMMIT_DATE=$(git log --all --format="%ar;%d" | grep "$BRANCH" | cut -d";" -f1) | |
printf "%-64s %s\n" "$MODULE" "$LAST_COMMIT_DATE" | |
} | |
cmd_list_module() { | |
MODULE="$1" | |
pwd | |
} | |
cmd_reset_hard() { | |
MODULE="$1" | |
shift 2 # eat module name and command name | |
BRANCH="$1" | |
[ "$BRANCH" ] || die "No branch was specified" | |
echo "----------------------------------------------" | |
echo "Resetting (hard) to branch $BRANCH > $MODULE" | |
git reset --hard "$BRANCH" | |
} | |
cmd_execute_command() { | |
MODULE="$1" | |
shift 2 # eat module name and command name | |
echo "----------------------------------------------" | |
echo "$MODULE> git" "$@" | |
git "$@" | |
} | |
cmd_fwdiff() { | |
MODULE="$1" | |
shift 2 # eat module name and command name | |
SOURCE_BRANCH="$1" | |
TARGET_BRANCH="$2" | |
VERBOSE="$3" | |
[ "$SOURCE_BRANCH" ] || die "No source branch was specified" | |
[ "$TARGET_BRANCH" ] || die "No target branch was specified" | |
if ! has_branch "$SOURCE_BRANCH" || ! has_branch "$TARGET_BRANCH" | |
then | |
if [ "$VERBOSE" ] | |
then | |
echo "$MODULE> No branch $SOURCE_BRANCH" 2> /dev/null | |
fi | |
return | |
fi | |
if git merge-base --is-ancestor "$SOURCE_BRANCH" "$TARGET_BRANCH" | |
then | |
if [ "$VERBOSE" ] | |
then | |
echo "$MODULE> No need to merge $SOURCE_BRANCH to $TARGET_BRANCH" | |
fi | |
else | |
echo "$GREEN_COLOR$MODULE$NO_COLOR> $SOURCE_BRANCH is behind $TARGET_BRANCH" | |
fi | |
} | |
cmd_diff_name_only() { | |
MODULE="$1" | |
shift 2 # eat module name and command name | |
SOURCE_BRANCH="$1" | |
TARGET_BRANCH="$2" | |
VERBOSE="$3" | |
[ "$SOURCE_BRANCH" ] || die "No source branch was specified" | |
[ "$TARGET_BRANCH" ] || die "No target branch was specified" | |
if ! has_branch "$SOURCE_BRANCH" || ! has_branch "$TARGET_BRANCH" | |
then | |
if [ "$VERBOSE" ] | |
then | |
echo "$MODULE> No branch $SOURCE_BRANCH" 2> /dev/null | |
fi | |
return | |
fi | |
DIFF_RESULT=$(git diff --name-only "$SOURCE_BRANCH" "$TARGET_BRANCH") | |
if [ "$DIFF_RESULT" ] | |
then | |
DIFF_RESULT=$(echo "$DIFF_RESULT" | xargs printf "\t%s\n") | |
echo "$GREEN_COLOR$MODULE$NO_COLOR>" | |
echo "$DIFF_RESULT" | |
else | |
[ "$VERBOSE" ] && echo "$GRAY_COLOR$MODULE$NO_COLOR> No diff" | |
fi | |
} | |
# Remove a branch that has no changes or diffs with | |
# the source branch. | |
cmd_cleanup_unchanged_branches() { | |
MODULE="$1" | |
shift 2 | |
SOURCE_BRANCH="$1" | |
TARGET_BRANCH="$2" | |
VERBOSE="$3" | |
[ "$SOURCE_BRANCH" ] || die "No source branch was specified" | |
[ "$TARGET_BRANCH" ] || die "No target branch was specified" | |
if ! has_branch "$SOURCE_BRANCH" || ! has_branch "$TARGET_BRANCH" | |
then | |
if [ "$VERBOSE" ] | |
then | |
echo "$MODULE> No branch $SOURCE_BRANCH" 2> /dev/null | |
fi | |
return | |
fi | |
DIFF_RESULT=$(git diff --name-only "$SOURCE_BRANCH" "$TARGET_BRANCH") | |
if [ -z "$DIFF_RESULT" ] | |
then | |
# read does not support color codes. We print colored message without \n | |
# shellcheck disable=SC2059 | |
printf "$GREEN_COLOR$MODULE$NO_COLOR> $TARGET_BRANCH has no differences with $SOURCE_BRANCH. Type 'y' to delete it>" | |
read -r INPUT | |
if [ "y" = "$INPUT" ] | |
then | |
git checkout "$SOURCE_BRANCH" | |
git branch -d "$TARGET_BRANCH" | |
else | |
echo "$MODULE> $TARGET_BRANCH left intact" | |
fi | |
fi | |
} | |
cmd_grep_string() { | |
MODULE="$1" | |
shift 2 # eat module name and command name | |
REGEXP="$1" | |
GIT_LOG=$(git log --all --regexp-ignore-case --pretty=oneline --grep="$REGEXP") | |
if [ "$GIT_LOG" ] | |
then | |
# shellcheck disable=SC2059 | |
printf "$GREEN_COLOR$MODULE$NO_COLOR>\n" | |
GIT_LOG=$(echo "$GIT_LOG" | xargs -I{} printf '\t%s\n' {}) | |
echo "$GIT_LOG" | |
fi | |
} | |
cmd_update_all_remote_branches() { | |
MODULE="$1" | |
shift 2 # eat module name and command name | |
git fetch --all > /dev/null || die "Unable to fetch all remote branches" | |
# maintain a list of already tracked branches | |
ALREADY_TRACKED_BRANCHES="" | |
# lists all remotes, except entries like 'origin/HEAD -> origin/master' | |
for REMOTE_BRANCH in $(git branch --remote | grep -Fv ' -> ') | |
do | |
LOCAL_BRANCH="${REMOTE_BRANCH#*/}" | |
if has_branch "$LOCAL_BRANCH" | |
then | |
ALREADY_TRACKED_BRANCHES="$ALREADY_TRACKED_BRANCHES $LOCAL_BRANCH" | |
else | |
git branch "$LOCAL_BRANCH" --track "$REMOTE_BRANCH" | |
fi | |
done | |
if [ "$ALREADY_TRACKED_BRANCHES" ] | |
then | |
echo "$GREEN_COLOR$MODULE$NO_COLOR> Already tracked: $ALREADY_TRACKED_BRANCHES" | |
fi | |
} | |
[ "$COMMAND" ] || die "No command was specified" | |
for MODULE in ./*/ | |
do | |
[ -d "$MODULE" ] || die "No module subdirectories. Nothing to do" | |
if is_allowed "$MODULE" | |
then | |
if _before_module "$MODULE" | |
then | |
case "$COMMAND" in | |
list) | |
cmd_list_module "$MODULE" "$@" | |
;; | |
checkout) | |
cmd_switch_module_branch "$MODULE" "$@" | |
;; | |
pull-rebase) | |
cmd_pull_rebase "$MODULE" "$@" | |
;; | |
full-merge) | |
cmd_full_merge "$MODULE" "$@" | |
;; | |
last-commit-date) | |
cmd_last_commit_date "$MODULE" "$@" | |
;; | |
reset-hard) | |
cmd_reset_hard "$MODULE" "$@" | |
;; | |
fwdiff) | |
cmd_fwdiff "$MODULE" "$@" | |
;; | |
dirdiff) | |
cmd_diff_name_only "$MODULE" "$@" | |
;; | |
namediff) | |
cmd_diff_name_only "$MODULE" "$@" | |
;; | |
cleanup-unchanged-branches) | |
cmd_cleanup_unchanged_branches "$MODULE" "$@" | |
;; | |
cub) | |
cmd_cleanup_unchanged_branches "$MODULE" "$@" | |
;; | |
grep) | |
cmd_grep_string "$MODULE" "$@" | |
;; | |
uarb) | |
cmd_update_all_remote_branches "$MODULE" "$@" | |
;; | |
update-all-remote-branches) | |
cmd_update_all_remote_branches "$MODULE" "$@" | |
;; | |
exec) | |
cmd_execute_command "$MODULE" "$@" | |
;; | |
*) | |
echo "Unknown command: $COMMAND" | |
exit 1 | |
;; | |
esac | |
_after_module | |
fi | |
fi | |
done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment