Skip to content

Instantly share code, notes, and snippets.

@johannjacobsohn
Created June 11, 2019 12:18
Show Gist options
  • Save johannjacobsohn/fb1a545d77203faee0952e0dd3a76d4b to your computer and use it in GitHub Desktop.
Save johannjacobsohn/fb1a545d77203faee0952e0dd3a76d4b to your computer and use it in GitHub Desktop.
Sync redmine issues to gitlab merge requests and submit both in one go
#!/bin/bash
set -eu -o pipefail -o errexit -o noclobber -o nounset
IFS=$'\n\t' # http://redsymbol.net/articles/unofficial-bash-strict-mode/
# set -x # DEBUG
#
# todo
# --time
# http://www.redmine.org/projects/redmine/wiki/Rest_TimeEntries#Creating-a-time-entry
# POST /time_entries.json
# time_entry (required): a hash of the time entry attributes, including:
# issue_id or project_id (only one is required): the issue id or project id to log time on
# spent_on: the date the time was spent (default to the current date)
# hours (required): the number of spent hours
# activity_id: the id of the time activity. This parameter is required unless a default activity is defined in Redmine.
# comments: short description for the entry (255 characters max)
#
function headline {
echo "git fix -- sync redmine issue and gitlab merge request"
echo ""
}
function usage {
echo "Usage:"
echo " /path/to/project $ git fix <ticket number> [--wip] [--nocommit] [--nohooks] [-h]"
}
function help {
headline
echo "A quick shell scripts that automatically integrates into git and does"
echo "all the annoying house-keeping tasks that are required for one-commit"
echo "bugfix ticket."
echo ""
echo "Will do the following (in this order)"
echo " - read config and access token from git config"
echo " - retrieve ticket data from redmine"
echo " - create bugfix branch from ticket title"
echo " - commit staged changes into said branch using ticket title and number"
echo " - push branch to origin (read from gitconfig remote)"
echo " - create merge request to develop in gitlab (link to redmine ticket)"
echo " - link merge request in redmine ticket"
echo ""
echo "Installation"
echo " - put this file into a directory that is in \$PATH (eg. ~/bin/)"
echo " - create access token in gitlab and redmine"
echo " - configure globally in ~/.gitconfig or project-dependent in /path/to/project/.git/config"
echo ""
echo "Configuration"
echo " Add this to your local or global git config:"
echo ""
echo " >[gitfix]"
echo " > gitlabToken = <token>"
echo " > redmineToken = <token>"
echo " > gitlabUrl = https://<gitlab>/api/v4/projects"
echo " > redmineUrl = https://<redmine>/issues"
echo ""
usage
echo ""
echo "Further reading"
echo " - http://www.redmine.org/projects/redmine/wiki/Rest_api_with_curl"
echo " - https://docs.gitlab.com/ce/api/merge_requests.html"
echo ""
echo "Dependencies"
echo " - curl"
echo " - jq"
echo " - sed"
echo " - git"
echo ""
}
function fail {
echo "❌ $1"
exit -1
}
function success {
echo "✔ $1"
}
function errorOutputOnly {
out=`${1+"$@"}`
if [ $? != 0 ]; then
echo "$out"
fi
}
type=bugfix
issue=-1
wip=0
commit=1
runhooks=1
hooks=""
# Parse command line options
while [[ "$#" > 0 ]]; do
case $1 in
--wip)
wip=1
;;
-h)
help
exit 0
;;
--nocommit)
commit=0
;;
--nohooks)
runhooks=0
hooks="--no-verify"
;;
*) # unknown parameter
case "$1" in
''|*[!0-9]*) # unknown and not a number
headline
usage
echo ""
fail "unknown option $1"
exit -1
;;
*)
issue=$1
;;
esac
;;
esac
shift
done
# issue number is required, fail otherwise
if [ $issue = -1 ]
then
headline
usage
exit -1
fi
if [ $commit = 1 ]
then
# check if there is something to be commited, otherwise this is pointless
git diff --quiet --exit-code --cached && fail "Nothing to be commited, genius. Maybe use --nocommit?"
fi
# check git hooks before doing all this work
if [ -f .git/hooks/pre-commit ] && [ $runhooks = 1 ]
then
errorOutputOnly .git/hooks/pre-commit
success "ran pre-commit hooks"
fi
if [ -f .git/hooks/pre-push ] && [ $runhooks = 1 ]
then
errorOutputOnly .git/hooks/pre-push
success "ran pre-push hooks"
fi
# read data from config
gitlab_token=`git config --get gitfix.gitlabToken` || fail "failed to read gitlabToken from git config"
redmine_token=`git config --get gitfix.redmineToken` || fail "failed to read redmineToken from git config"
gitlab_url=`git config --get gitfix.gitlabUrl` || fail "failed to read gitlabUrl from git config"
redmine_url=`git config --get gitfix.redmineUrl` || fail "failed to read redmineUrl from git config"
gitlab_project=`git config --get remote.origin.url | sed -n 's/.*:\(.*\)\.git/\1/p' | sed -r 's/\//%2F/g'` || fail "failed to parse remote origin url from git config"
success "read data from git config"
# redmine
# - get issue data
# - create branchname
issue_json=`curl -s -H "Content-Type: application/json" -X GET -H "X-Redmine-API-Key: $redmine_token" "$redmine_url/$issue.json"` || fail "failed to curl redmine data"
if [ "$issue_json" == "" ]; then fail "failed to curl redmine data ($redmine_url/$issue)"; fi
title=$(echo $issue_json | jq -r .issue.subject | sed -r 's/["]+/_/g')
id=$(echo $issue_json | jq -r .issue.id)
branch=$(echo "$type/$id-$title" | sed -r 's/[. :]+/-/g')
issue_url="$redmine_url/$issue"
success "read data from redmine (title: \"$title\", branch: \"$branch\")"
# git
# - create branch
# - commit
# - push
containsFECode=$(git diff --cached --name-only | grep --quiet "src/frontend" && echo 1 || echo 0)
containsBECode=$(git diff --cached --name-only | grep --quiet "src/main" && echo 1 || echo 0)
if [ `git rev-parse -q --verify "$branch"` ]
then
echo "Craptastic, branch already exists. Use existing branch? (y/n)"
read ans
if [ "$ans" = "y" ]
then
errorOutputOnly git checkout "$branch"
else
fail "failed to create or reuse branch"
fi
else
git checkout --quiet -b "$branch" || fail "failed to create branch"
fi
if [ $commit = 1 ]
then
git commit --quiet $hooks -em "fix $id: $title"
fi
errorOutputOnly git stash --include-untracked # stash uncommitted changes to make sure unit tests in pre-push hooks catch missing files
errorOutputOnly git push --quiet $hooks # hopefully, a pre-push hook will kick in and test the validity of this change
errorOutputOnly git stash pop # restore pre-commit state
success "branch created and pushed"
# gitlab
# - create merge request
# - link redmine issue
# - get merge request url
payload=$(echo '{
"source_branch": "'"$branch"'",
"target_branch": "develop",
"title": "'$([ "$wip" = 1 ] && echo "WIP:")"$id: $title"'",
"description": "'"$issue_url"'",
"labels": [
'$([ "$containsBECode" = 1 ] && echo "\"Backend\",")'
'$([ "$containsFECode" = 1 ] && echo "\"Frontend\"")'
]
}')
mr_json=`curl -s -H "Content-Type: application/json" -X POST -H "Private-Token: $gitlab_token" "$gitlab_url/$gitlab_project/merge_requests" -d "$payload"`
mr_url=`echo $mr_json | jq -r .web_url`
if [ "$mr_url" = "null" ] || [ "$mr_url" = "" ]
then
echo "merge request was not submitted, maybe it already exits?"
echo "payload: $payload"
echo " Anyway, this is what came back:"
echo $mr_json
exit -1
fi
success "merge request submitted: $mr_url"
# redmine:
# - link pullrequest url
payload='{
"issue": {
"notes": "'$mr_url'"
}
}'
errorOutputOnly curl -s -H "Content-Type: application/json" -X PUT -H "X-Redmine-API-Key: $redmine_token" "$redmine_url/$issue.json" -d "$payload"
success "issue updated: $redmine_url/$issue"
errorOutputOnly git checkout develop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment