blob: 11abe19abe38a73e07f2d94ecc6e22f977318f99 [file] [log] [blame]
#!/bin/bash
#
# remote-update hook
#
# Copyright 2020 Luigi Santivetti <luigi.santivetti@gmail.com>
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice (including the next
# paragraph) shall be included in all copies or substantial portions of the
# Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# remote-update supports calling from two server side hooks with the following
# list of command line arguments (we only care about --project).
#
# 1. commit-received:
#
# --project <project name>
# --refname <refname>
# --uploader <uploader>
# --uploader-username <username>
# --oldrev <sha1>
# --newrev <sha1>
# --cmdref <refname>
#
# 2. submit:
#
# --project <project name>
# --branch <branch>
# --submitter <submitter>
# --patchset <patchset id>
# --commit <sha1>
#
# RATIONALE: remote-update can reject one change either pushed for review or
# submitted for a merge into Gerrit. It performs a remote update, importing
# into Gerrit all refs/heads/* merged outside Gerrit from an external mirror.
# This allows Gerrit to sit in the middle and act like a reviewing tool, but
# having to follow the line of develpment potentially done also elsewhere.
#
# ` remote-update hook ` TODO
# ` `
# ` `
# (1) push/submit ` (2) fetch ` (3) fetch
# +--------+ ··········> +--------+ ··········> +--------+ ··········>
# | Change | | Gerrit | | Mirror |
# +--------+ <·········· +--------+ <·········· +--------+ <··········
# reject/accept (6) ` update (5) ` update (4)
# ` `
# ` `
# ` `
#
# The assumption is that `Mirror' does not have other remotes set, so there
# no need to `git remote update' it. The only changes to mirror either come
# from Gerrit itself (with change-merged hook) or via direct push for who's
# access granted to it.
echo "Gerrit Code Review: remote-update hook"
# File to dump onto the file system storing this hook stdout and stderr
LOG_FILE=${HOOKS_LOG_DIR:-/tmp}
if [ ! -w "$LOG_FILE" ]; then
echo " ****** warning: skip log" >&2
LOG_FILE="/dev/null" # avoid permission denied
else
LOG_FILE="$LOG_FILE/hooks.remote-update-$(date +'%d%m%Y%H%M%S').txt"
fi
# Host service where replication mirrors and deploy engine live
HOOK_HOST=${HOOKS_REMOTE_HOST:-}
if [ ! -n "$HOOK_HOST" ]; then
echo " ****** warning: no host, skip hook" >&2
exit 0
fi
# User allowed t othe target host to push and eventually deploy changes
HOOK_USER=${HOOKS_REMOTE_USER:-}
if [ ! -n "$HOOK_USER" ]; then
echo " ****** warning: no user, skip hook" >&2
exit 0
fi
# Directory where this hook expects the project mirror to be located
HOOK_PATH=${HOOKS_REMOTE_PATH:-}
if [ ! -n "$HOOK_PATH" ]; then
echo " ****** warning: no path, skip hook" >&2
exit 0
fi
# Alias for the remote tracking a given mirror
GERRIT_R=${HOOKS_REMOTE_ALIAS:-}
if [ ! -n "$GERRIT_R" ]; then
echo " ****** warning: no remote alias, skip hook" >&2
exit 0
fi
SSH_RSA_ID=${HOOKS_REMOTE_RSAID:-}
if [ ! -n "$SSH_RSA_ID" ]; then
echo " ****** error: no rsa id file found" >&2
exit 1
fi
SSH_PORT=${HOOKS_REMOTE_PORT:-22}
if [ ! -n "$SSH_PORT" ]; then
echo " ****** warning: no ssh port, default to 22" >&2
fi
/bin/bash <(/bin/cat <<EOF
set -x
args='${*}'
declare -A CHANGE=(
[project]=""
[refname]=""
[uploader]=""
[uploader-username]=""
[oldrev]=""
[newrev]=""
[cmdref]=""
[branch]=""
[submitter]=""
[patchset]=""
[commit]=""
)
while read -a line; do
for K in \${!CHANGE[@]}; do
if [ "\${line[0]}" == "--\$K" ]; then
CHANGE[\$K]="\${line[@]:1}"
break
fi
done
done <<< "\$(echo -e "\${args// --/\\\\n--}")"
remote_url="ssh://$HOOK_USER@$HOOK_HOST:$HOOK_PATH/\${CHANGE[project]}.git"
# Does Gerrit have \\\$remote_url remote set up?
git remote get-url "$GERRIT_R" | grep -q -- "\$remote_url"
if [ "\$?" -ne 0 ]; then
echo >&2 " ****** info: \${CHANGE[project]} clean url for $GERRIT_R"
git remote rm $GERRIT_R
echo >&2 " ****** info: \${CHANGE[project]} adding remote: $GERRIT_R"
git remote add $GERRIT_R \$remote_url
if [ "\$?" -ne 0 ]; then
echo >&2 " ****** error ¯\\_(ツ)_/¯ : add remote $GERRIT_R failed"
exit 1
fi
fi
git remote show | grep -q -- "$GERRIT_R"
if [ "\$?" -ne 0 ]; then
echo >&2 " ****** info: \${CHANGE[project]} clean remote: $GERRIT_R"
git remote rm $GERRIT_R
echo >&2 " ****** info: \${CHANGE[project]} adding remote: $GERRIT_R"
git remote add $GERRIT_R \$remote_url
if [ "\$?" -ne 0 ]; then
echo >&2 " ****** error ¯\\_(ツ)_/¯ : add remote $GERRIT_R failed"
exit 1
fi
fi
GIT_SSH_COMMAND="ssh"
GIT_SSH_COMMAND+=" -o StrictHostKeyChecking=no"
GIT_SSH_COMMAND+=" -o UserKnownHostsFile=/dev/null"
GIT_SSH_COMMAND+=" -p $SSH_PORT"
GIT_SSH_COMMAND+=" -i $SSH_RSA_ID"
# RATIONALE: \`+refs' in order to allow non-fast-forward fetch. This has two
# consequences. Gerrit's refs/heads/* for \$CHANGE[project] is always hard
# reset to match the refs/heads/* on the remote, which is distruptive from
# a Gerrit point of view. Secondly, \$CHANGE[newref] may not merge against
# non-fast-forwardable heads. In such a case, \$CHANGE[newref] needs to be
# rebased on top of refs/heads/*.
GIT_SSH_COMMAND="\$GIT_SSH_COMMAND" git fetch -v "$GERRIT_R" +refs/heads/*:refs/heads/*
if [ "\$?" -ne 0 ]; then
echo >&2 " ****** error ¯\\_(ツ)_/¯ : failed to update remote: $GERRIT_R"
exit 1
fi
echo " ****** success: remote update done"
set +x
EOF
) &> ${LOG_FILE}; RET=$?
if [ -r "${LOG_FILE}" ]; then
grep -E "^ \*\*\*\*\*\* " ${LOG_FILE} 2>/dev/null
fi
[ "$RET" -ne 0 ] || rm -f ${LOG_FILE}
exit "$RET"