Skip to content

Instantly share code, notes, and snippets.

@aks
Last active November 6, 2019 16:02
Show Gist options
  • Save aks/464a89ed68dc2e8cdcb291a2335706bf to your computer and use it in GitHub Desktop.
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
#!/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