Created
April 4, 2019 15:29
-
-
Save chtitux/f0def1bc40de579a68786284454005c3 to your computer and use it in GitHub Desktop.
Merge master script
This file contains hidden or 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/bash | |
# Example of the slack-usernames file: | |
# [email protected] @UABCDEF123 | |
# Fail script if any command fails | |
set -e | |
GITLAB_HOST=${GITLAB_HOST:-gitlab.com} | |
GITLAB_UPSTREAM_NAMESPACE=${GITLAB_UPSTREAM_NAMESPACE:-mycorpcompany} | |
TOOLS_DIR=$(dirname "$0") | |
NEWLINE=$'\n' | |
function usage { | |
local script_name | |
script_name=$(basename "$0") | |
cat <<HELP | |
$script_name (options) (<remote> <branch>) | |
Check build completion and prepare fabric commands. | |
Arguments <remote> and <branch> are optional. If provided, they let merge a custom remote branch to master instead of official dev branch. | |
Typical usage: | |
$script_name | |
Example of hotfix merge: | |
$script_name fmenou security/cve-1234 | |
Options: | |
-h: this help message | |
-c: specific Slack Channel to speak to | |
-d: branch name destination. Defaults to 'master' | |
-f: force the operation without asking confirmation | |
-j: launch from a build Job (implies -f force) | |
HELP | |
exit 0 | |
} | |
function is_not_human { | |
git config --global --get user.name || git config --global user.name "${GITLAB_USER_EMAIL}" | |
git config --global --get user.email || git config --global user.email "${GITLAB_USER_EMAIL}" | |
} | |
function check_args { | |
SLACK_CHANNEL="#eu-releases" | |
to_shift=0 | |
force=0 | |
destination="master" | |
while getopts "c:d:fjh" option; do | |
case $option in | |
c) SLACK_CHANNEL=$OPTARG; to_shift=$((to_shift+2)) ;; | |
d) destination=$OPTARG; to_shift=$((to_shift+2)) ;; | |
f) force=1; to_shift=$((to_shift+1)) ;; | |
h) usage ;; | |
j) is_not_human; force=1; to_shift=$((to_shift+1)) ;; | |
esac | |
done | |
} | |
function main { | |
check_args "$@" | |
shift $to_shift | |
check_working_copy | |
check_tools_up_to_date | |
if [ $# = 2 ]; then | |
remote_fetch=$1 | |
head=$1/$2 | |
else | |
remote_fetch=$(remote_name "push") | |
head=$remote_fetch/dev | |
fi | |
remote_push=$(remote_name "push") | |
target_head="$remote_push/$destination" | |
git fetch "$remote_fetch" | |
git fetch "$remote_push" | |
check_current_master | |
check_git_username | |
project=$(basename "$(git rev-parse --show-toplevel)") | |
author=$(git config user.name) | |
changelog=$(prepare_changelog) | |
formatted_changelog=$(prepare_formatted_changelog) | |
committers=$(prepare_committers) | |
formatted_commiters=$(prepare_formatted_committers) | |
echo -e "$changelog" | |
echo -e "$committers" | |
if [ "$force" == "0" ]; then | |
echo -n -e "Do you want to merge and push? [y/N] " | |
read answer | |
else | |
answer="y" | |
fi | |
if [ "$answer" == "y" ]; then | |
merge | |
fi | |
} | |
function check_working_copy { | |
if [ "$(git ls-files -cdmsu | wc -l)" -ne 0 ]; then | |
echo "Working copy and index not clear, can't proceed" | |
git status -s | |
echo "Aborting.." | |
exit 1 | |
fi | |
} | |
function check_tools_up_to_date { | |
if [ -d "$TOOLS_DIR/.git" ]; then | |
pushd "$TOOLS_DIR" > /dev/null | |
local tools_remote="$(remote_name "fetch")" | |
git fetch $tools_remote | |
if [ $(git diff $tools_remote/master | wc -l) -ne 0 ]; then | |
echo -e '\033[41m /!\' "Your tools are not up-to-date." '/!\ \033[0m' | |
echo "Please pull latest changes from $tools_remote" | |
echo -n -e "Do you still want to continue? [y/N] " | |
read answer | |
if [ "$answer" != "y" ]; then | |
exit 1 | |
fi | |
fi | |
popd > /dev/null | |
fi | |
} | |
function check_git_username { | |
if [ -z "$(git config user.name)" ]; then | |
echo "Please set a git user name." | |
exit 1 | |
fi | |
} | |
function upstream_remote { | |
local direction="($1)" | |
local gitlab_host="(${GITLAB_HOST}|gitlab.com)" | |
local main_repos="(${GITLAB_UPSTREAM_NAMESPACE}|myteam|myanotherteam)" | |
local remote | |
remote=$(git remote -v | grep -E "$gitlab_host.$main_repos" | grep "$direction" | head -n1) | |
if [ -z "$remote" ]; then | |
>&2 echo -e '\033[41m' "No upstream remote found within /${gitlab_host}:${main_repos}/" '\033[0m' | |
>&2 echo "Check/change remote url with 'git remote -v' or change the env variable GITLAB_HOST" | |
exit 1 | |
fi | |
echo "$remote" | |
} | |
function remote_group { | |
local upstream | |
upstream=$(upstream_remote "$@") | |
upstream=${upstream#*https://} | |
upstream=${upstream#*git@} | |
upstream=${upstream#*:} | |
upstream=${upstream%/*} | |
upstream=${upstream#*/} | |
echo "$upstream" | |
} | |
function remote_name { | |
upstream_remote "$@" | cut -f 1 | |
} | |
function check_current_master { | |
if [ -f ".git/refs/heads/$destination" ]; then | |
local unshared_master_commits | |
unshared_master_commits=$(git rev-list "${target_head}..${destination}" | wc -l) | |
if [ "$unshared_master_commits" -ne 0 ]; then | |
echo "local '$destination' is ahead of $target_head by $unshared_master_commits commits:" | |
git log --boundary --graph --oneline "${target_head}..${destination}" | |
echo "Aborting.." | |
exit 2 | |
fi | |
fi | |
} | |
function prepare_changelog { | |
echo "CHANGELOG $project - $author" | |
git log --boundary --graph --oneline "${target_head}..$head" | |
} | |
function prepare_formatted_changelog { | |
local scm_url | |
scm_url="https://${GITLAB_HOST}/$(remote_group "push")/${project}/commit/" | |
local git_format | |
git_format="• <${scm_url}%h|%h> %s (%an)" | |
local graph | |
graph=$(git log --graph --boundary --pretty=format:"${git_format}" "${target_head}..$head") | |
local simplify_graph='s/\*[ |\/\\]*//g' | |
local indent='s/|[ \\\/]\{1,\}/ /g' | |
local replace_boundary='s/^ *o • <\(.*\)>/◦ <\1>/g' | |
local cleaned_graph="$(echo -e "$graph" | grep '[*o]' - | sed -e "$simplify_graph;$indent;$replace_boundary")" | |
echo "$cleaned_graph" | |
} | |
function prepare_committers { | |
echo "Includes changes from $(commiters_emails | comma_join)" | |
} | |
function prepare_formatted_committers { | |
echo "Includes changes from $(email2slack "$(commiters_emails)" | comma_join)" | slack_links | |
} | |
function commiters_emails { | |
local revision_range="$remote_push/$destination..$head" | |
git_authors "$revision_range" | |
} | |
function git_authors { | |
local revision_range="$1" | |
git log --pretty="format:%ae" --no-merges "$revision_range" | sort | uniq | |
} | |
function email2slack { | |
local text="$1" | |
# This is a bit of hackery, but actually makes sense. Pipes creates subshells, so the content | |
# of the "while read" block is executed in a different environment and the | |
# outer $text is never mutated. Manually creating a subshell (with the parenthesis) | |
# fixes this problem, cf https://serverfault.com/a/259342 | |
cat "$TOOLS_DIR/slack-usernames" | (while read line; do | |
email=$(echo $line | cut -d ' ' -f1) | |
slack=$(echo $line | cut -d ' ' -f2) | |
text="${text//$email/$slack}" | |
done | |
echo -e "$text" | |
) | |
} | |
function slack_links { | |
xargs | sed -E "s/@([^[[:blank:],]+]*)/<@\1|\1>/g" | |
} | |
function comma_join { | |
xargs | sed 's/ /, /g' | |
} | |
function merge { | |
git checkout "$destination" | |
git reset --hard "${target_head}" | |
git merge --no-edit --commit --no-ff "$head" | |
# Back to previous branch | |
git checkout - | |
if [ -n "$(git diff "$head..$destination")" ]; then | |
echo "ERROR: git diff $head..$destination is not empty." | |
echo "Ensure that dev won't override code on '$destination' (maybe a bad executed cherry-pick)." | |
echo "Aborting.." | |
exit 3 | |
else | |
git push "$remote_push" "$destination" | |
sha1=$(git rev-parse --short "$destination") | |
formatted_subject="Merging into $destination ($author) ${sha1}$NEWLINE$formatted_commiters" | |
echo -e "$formatted_changelog" | slack-speak -n "${project}" -c "${SLACK_CHANNEL}" -s "$formatted_subject" | |
fi | |
} | |
main "$@" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment