Last active
November 6, 2019 16:02
-
-
Save aks/464a89ed68dc2e8cdcb291a2335706bf to your computer and use it in GitHub Desktop.
Bash script to merge a set of branch names into a target branch
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 | |
# merge-branches [options] -f BRANCHES_FILE TARGET_BRANCH | |
PROG="${0##*/}" | |
DIR="${0%/*}" | |
GCLOG='.git/gc.log' | |
usage() { | |
if (( $# > 0 )) ; then | |
talk "$*" | |
fi | |
cat 1>&2 <<USAGE | |
usage: $PROG [options] -f BRANCHES_FILE TARGET_BRANCH | |
This script merges a list of branches, read from BRANCHES_FILE, into one | |
TARGET_BRANCH to enable testing of all the new features in a single, deployable | |
branch (tugboat instance). | |
The branches file can contain abbreviations of branch names as long as the abbreviation | |
is distinct. For example, if all the branch names follow a pattern of, say, | |
"USER/JIRAISSUE-LONGNAME", then the file of branch name patterns can be the unique | |
"USER/JIRAISSUE". | |
When reading the branches file, empty lines and comment lines (#) are ignored. | |
Options: | |
-h show this help | |
-f FILE Read branch list from FILE | |
-n show the commands but do not run them (norun) | |
-v explain more (verbose) | |
-y pre-confirm the process (otherwise, user must confirm interactively) | |
USAGE | |
exit 1 ; | |
} | |
talk() { echo 1>&2 "$*" ; } | |
vtalk() { (( verbose )) && talk "$*" ; } | |
nrtalk() { (( norun )) && talk "(norun) $*" ; } | |
error() { talk "$*" ; exit 1 ; } | |
run() { | |
if (( norun )) ; then | |
nrtalk "$*" | |
else | |
vtalk "--> $*" | |
eval "$*" | |
fi | |
} | |
GIT() { run "git $*" ; } | |
RM() { run "rm $*" ; } | |
read_branches_file() { | |
while read line ; do | |
[[ "$line" =~ ^\\s*#.*$ ]] && continue # skip comments | |
[[ "$line" =~ ^\\s*$ ]] && continue # skip blank lines | |
echo "$line" | |
done <"$1" | |
} | |
get_current_branches() { | |
current_branches=( `git branch | sed -e 's/^..//'` ) | |
} | |
validate_branches() { | |
local branch cb branch_ok matched | |
local new_branches=() | |
for branch in "${BRANCHES[@]}" ; do | |
branch_ok= matched= | |
for cb in "${current_branches[@]}" ; do | |
if [[ "$branch" == "$cb" ]] ; then | |
branch_ok=1 | |
break | |
fi | |
if [[ "$cb" =~ $branch ]]; then | |
if (( matched )); then | |
error "Ambiguous branch: '$branch'; matches two or more actual branches" | |
fi | |
matched="$cb" | |
fi | |
done | |
if (( !branch_ok )); then | |
if [[ -n "$matched" ]] ; then | |
branch="$matched" | |
else | |
error "No actual branch named or matching '$branch'" | |
fi | |
fi | |
new_branches+=( "$branch" ) | |
done | |
# replace BRANCHES with filtered, matched actual branch names | |
BRANCHES=( "${new_branches[@]}" ) | |
} | |
show_branches() { | |
talk "Branches to process:" | |
for branch in "${BRANCHES[@]}" ; do | |
talk " '$branches'" | |
done | |
} | |
go_ahead_or_confirm() { | |
while (( ! go_ahead )); do | |
read -p "Proceed? " ans | |
case "${ans,,*}" in | |
yes|y|y) go_ahead=1 ;; | |
no|n) error "Nothing done!" ;; | |
esac | |
done | |
} | |
################################################## | |
norun= verbose= branches_file= go_ahead= | |
while getopts 'hf:nvy' opt ; do | |
case "$opt" in | |
f) branches_file="$OPTARG" ;; | |
h) usage ;; | |
n) norun=1 ;; | |
v) verbose=1 ;; | |
y) go_ahead=1 ;; | |
esac | |
done | |
shift $((OPTIND - 1)) | |
(( $# > 0 )) || usage "No TARGET_BRANCH given" | |
[[ -n "$branches_file" ]] || usage "No branches file given!" | |
[[ -e "$branches_file" ]] || error "Cannot read '$branches_file'!" | |
TARGET_BRANCH="$1" | |
BRANCHES=( `read_branches_file $branches_file` ) | |
get_current_branches | |
validate_branches | |
show_branches | |
talk "The target branch to merge into: ${target_branch}" | |
go_ahead_or_confirm | |
set -e # fail immediately on any error | |
GIT checkout $TARGET_BRANCH | |
[[ -e $GCLOG ]] && RM -f $GCLOG | |
GIT prune | |
GIT remote prune origin | |
GIT pull | |
for branch in ${BRANCHES[@]} ; do | |
talk "Refreshing $branch .." | |
GIT checkout $branch | |
GIT pull | |
talk "Merging $branch into $TARGET_BRANCH .." | |
GIT checkout $TARGET_BRANCH | |
GIT merge $branch --commit --no-edit | |
done | |
talk "Done" | |
GIT status | |
exit |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment