Skip to content

Instantly share code, notes, and snippets.

@mistergraphx
Forked from senorihl/README.md
Last active March 10, 2022 13:51
Show Gist options
  • Save mistergraphx/9718cfd2a4175177d557b1b35ed70067 to your computer and use it in GitHub Desktop.
Save mistergraphx/9718cfd2a4175177d557b1b35ed70067 to your computer and use it in GitHub Desktop.
Preserve merge order

Bash script for clean staging branch

Installation

On Mac Os 11 first install wget and tac

brew install wget
brew install coreutils
wget https://gist.githubusercontent.com/senorihl/637430191235b5362a27a8c785f9623c/raw/4510de9df31f8132210fc5a3cf8f08e61a7db7fc/clean-staging-branch.sh -O $HOME/.clean-staging-branch.sh
chmod a+x $HOME/.clean-staging-branch.sh
git config --global alias.clean-staging '!bash $HOME/.clean-staging-branch.sh'

Usage

git clean-staging <environment> [-s|--skip <branch name>] [-v|--verbose] [--dry-run] [-h|--help]

Description

This command will replay all merges on a staging branch resseted from master

This command will fetch your git remote (origin) staging branch (recette-), will reset your local staging branch to latest remote master branch and will try to remerge all previously merged branches in recette-. If conflict occured it isa reverted, you will be warned and script will go on.

You can configure :

  • the staging branch prefix by setting CLEAN_STAGING_PREFIX environment variable (defaults to recette-).
  • the source branch by setting CLEAN_STAGING_ROOT environment variable (defaults to master).
  • the remote name by setting CLEAN_STAGING_ORIGIN environment variable (defaults to origin).

Options

  • -h|--help Display help.
  • -s|--skip Specify name (without origin) to exclude during merge (repeatable).
  • -v|--verbose Display executed commands.
  • --dry-run Don't execute anything, just simulate commands
#!/usr/bin/env bash
VERBOSE=false
DRY=false
BRANCH_PREFIX=${CLEAN_STAGING_PREFIX:=recette-}
MASTER_OR_MAIN=${CLEAN_STAGING_ROOT:=master}
ORIGIN_OR_OTHER=${CLEAN_STAGING_ORIGIN:=origin}
POSITIONAL=()
SKIP=()
command_help () {
echo ""
echo "Clean staging branch ($PREFIX<environment>)."
echo ""
printf '%b\n' "\e[33mUsage:\e[0m"
echo " clean-staging <environment> [-s|--skip <branch name>] [-v|--verbose] [--dry-run] [-h|--help]"
echo ""
printf '%b\n' "\e[33mDescription:\e[0m"
echo " This command will replay all merges on a staging branch resseted from $MASTER_OR_MAIN"
echo ""
printf '%b\n' "\e[33mOptions:\e[0m"
printf '%b\n' " \e[32m-h, --help\e[0m Show this screen."
printf '%b\n' " \e[32m-v, --verbose\e[0m Display executed commands."
printf '%b\n' " \e[32m-s, --skip <branch>\e[0m Specify <branch> name (without origin) to exclude during merge (repeatable)."
printf '%b\n' " \e[32m--dry-run\e[0m Don't execute anything, just simulate commands"
echo ""
printf '%b\n' "\e[33mHelp:\e[0m"
printf '%b\n' " This command will fetch your git remote (\e[33m$ORIGIN_OR_OTHER\e[0m) staging branch (\e[33m$BRANCH_PREFIX<environment>\e[0m),"
printf '%b\n' " will reset your local staging branch to latest remote \e[33m$MASTER_OR_MAIN\e[0m branch"
printf '%b\n' " and will try to remerge all previously merged branches in \e[33m$BRANCH_PREFIX<environment>\e[0m."
echo " If conflict occured it is reverted, you will be warned and script will go on."
echo ""
echo " You can configure :"
printf '%b\n' " - the staging branch prefix by setting \e[33mCLEAN_STAGING_PREFIX\e[0m environment variable (currently \e[33m$BRANCH_PREFIX\e[0m)."
printf '%b\n' " - the source branch by setting \e[33mCLEAN_STAGING_ROOT\e[0m environment variable (currently \e[33m$MASTER_OR_MAIN\e[0m)."
printf '%b\n' " - the remote name by setting \e[33mCLEAN_STAGING_ORIGIN\e[0m environment variable (currently \e[33m$ORIGIN_OR_OTHER\e[0m)."
echo ""
exit 1
}
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
-h|--help)
command_help
shift # past argument
;;
-v|--verbose)
VERBOSE=true
shift # past argument
;;
--dry-run)
DRY=true
shift # past argument
;;
-s|--skip)
SKIP+=("$2")
shift # past argument
shift # past argument
;;
*) # unknown option
POSITIONAL+=("$1") # save it in an array for later
shift # past argument
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters
die () {
echo >&2 "$@"
exit 1
}
command () {
local __resultvar=$2
[[ "$VERBOSE" = "true" ]] && printf '%b\n' "\e[36m$1\e[0m"
if [[ "$DRY" = "false" ]]; then
if [[ "$__resultvar" ]]; then
eval "$__resultvar=\`$1\`"
else
eval $1
fi
fi
}
command_no_dry () {
local __resultvar=$2
[[ "$VERBOSE" = "true" ]] && printf '%b\n' "\e[36m$1\e[0m"
if [[ "$__resultvar" ]]; then
eval "$__resultvar=\`$1\`"
else
eval $1
fi
}
if [[ "$#" -ne 1 ]]; then
echo
printf '%b\n' "\e[1m\e[31m1 argument required, $# provided\e[0m"
command_help;
fi
command_no_dry "git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'" current_branch
staging_branch=`echo $BRANCH_PREFIX$1`;
printf '%b\n' "Current branch is \e[33m$current_branch\e[0m"
[[ "$current_branch" = "$staging_branch" ]] || printf '%b\n' "Switching to \e[33m$staging_branch\e[0m" && command "git checkout ${staging_branch} &> /dev/null"
printf '%b\n' "Refreshing repository"
command_no_dry "git fetch -p &> /dev/null"
printf '%b\n' "Comparing \e[33m$ORIGIN_OR_OTHER/$MASTER_OR_MAIN\e[39m to \e[33m$ORIGIN_OR_OTHER/$staging_branch\e[0m"
command_no_dry "git log $ORIGIN_OR_OTHER/$MASTER_OR_MAIN..$ORIGIN_OR_OTHER/${staging_branch} --oneline --merges 2> /dev/null | sed -e \"s/^.*'\(.*\)'.*$/\1/g\" -e 's/$ORIGIN_OR_OTHER\/\(.*\)/\1/'" merged_branches
merged_branches_list=($(echo ${merged_branches} | tr ' ' "\n"))
merged_branches_list=(`for i in ${merged_branches_list[@]}; do echo $i; done`)
merged_branches_list=(`echo ${merged_branches_list[@]} | tac -s' '`)
printf '%b\n' "Resetting \e[33m$ORIGIN_OR_OTHER/$staging_branch\e[39m at latest \e[33m$ORIGIN_OR_OTHER/$MASTER_OR_MAIN\e[0m"
command "git reset --hard $ORIGIN_OR_OTHER/$MASTER_OR_MAIN &> /dev/null"
printf '%b\n' "Selecting mergeable branches"
contains() {
[[ $1 =~ (^|[[:space:]])$2($|[[:space:]]) ]] && echo "1" || echo "0"
}
mergeable_branches_list=()
treated=()
for element in "${merged_branches_list[@]}"
do
echo "${treated[@]}" | grep -w -q $element
absent=$(echo "$?")
if [[ "$absent" = "1" ]]; then
treated+=("${element}")
command_no_dry "git ls-remote --exit-code --heads $ORIGIN_OR_OTHER ${element} &> /dev/null"
if [[ "$?" -eq 0 ]]; then
valid=1
for skipable in "${SKIP[@]}"
do
if [[ "$element" = "$skipable" ]]; then
valid=0
fi
done
if [[ "$valid" = "0" ]]; then
printf '%b\n' "\e[36m✖ Skipping branch \e[33m$ORIGIN_OR_OTHER/${element}\e[0m"
elif [[ "$element" = "$staging_branch" ]]; then
printf '%b\n' "\e[36m✖ Skipping same branch \e[33m$ORIGIN_OR_OTHER/${element}\e[0m"
elif [[ "$element" = "$MASTER_OR_MAIN" ]]; then
printf '%b\n' "\e[36m✖ Skipping source branch \e[33m$ORIGIN_OR_OTHER/${element}\e[0m"
else
mergeable_branches_list+=(${element})
printf '%b\n' "\e[32m✓ Branch \e[33m$ORIGIN_OR_OTHER/$element\e[32m added to merge list\e[0m"
fi
else
printf '%b\n' "\e[31m✖ Branch \e[33m$ORIGIN_OR_OTHER/$element\e[31m does not exists on the remote\e[0m"
fi
fi
done
printf '%b\n' "${#mergeable_branches_list[@]} branch$([[ "${#mergeable_branches_list[@]}" -le 1 ]] || echo "es") to merge"
errored=()
for element in "${mergeable_branches_list[@]}"
do
if [[ "$element" = "$staging_branch" ]]; then
printf '%b\n' "\e[36mSkipping same branch $ORIGIN_OR_OTHER/${element}\e[0m"
elif [[ "$element" = "$MASTER_OR_MAIN" ]]; then
printf '%b\n' "\e[36mSkipping source branch $ORIGIN_OR_OTHER/${element}\e[0m"
else
printf '%b\n' "Merging \e[33m$ORIGIN_OR_OTHER/${element}\e[0m"
TMP=$(mktemp)
command "git merge --no-ff $ORIGIN_OR_OTHER/${element} 1>$TMP 2>/dev/null"
if [ $? -ne 0 ]; then
printf '%b\n' "\033[0;31mAn error occured during merge of \e[33m$ORIGIN_OR_OTHER/${element}\033[0;31m, reverting.\033[0m";
printf '%b\n' "\e[37m";
cat $TMP
printf '%b\n' "\033[0m";
command "git merge --abort"
errored+=("$ORIGIN_OR_OTHER/${element}")
fi
fi
done
if [ ${#errored[@]} -eq 0 ]; then
printf '%b\n' "Script has ended, you can now force push the branch \e[33m$staging_branch\e[0m"
else
printf '%b\n' "Script has ended, but got error during merge of \e[33m${errored[*]}\e[0m, try merging it manually and force push the branch \e[33m$staging_branch\e[0m"
exit 1
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment