mirror of
https://code.forgejo.org/actions/cascading-pr
synced 2025-03-14 22:36:58 +01:00

When running from a PR, the ref is the head of the PR in the origin repository. The PR is used to: * display comments about the location of the destination PR * asynchronously close / merge the destination PR when something happens in the origin PR When only the ref is available, the destination PR must be closed and the corresponding branch destroyed immediately after it concludes because there is no convenient way to know what or when something else will happen.
243 lines
4.8 KiB
Bash
243 lines
4.8 KiB
Bash
# SPDX-License-Identifier: MIT
|
|
|
|
set -o pipefail
|
|
|
|
declare -A options
|
|
|
|
PREFIX===============
|
|
|
|
VERBOSE=false
|
|
|
|
DEBUG=false
|
|
|
|
: ${EXIT_ON_ERROR:=true}
|
|
|
|
: ${TMPDIR:=$(mktemp -d)}
|
|
|
|
# default loop delay is 3600 sec (1 hour)
|
|
: ${LOOPS:=100}
|
|
: ${LOOP_DELAY:=36}
|
|
|
|
: ${RETRY_DELAYS:=1 1 5 5 15 30}
|
|
|
|
function dependencies() {
|
|
if ! which jq curl > /dev/null ; then
|
|
apt-get update -qq
|
|
apt-get -qq install -y jq curl
|
|
fi
|
|
}
|
|
|
|
function retry() {
|
|
rm -f $TMPDIR/retry.{out,attempt,err}
|
|
local success=false
|
|
for delay in $RETRY_DELAYS ; do
|
|
if "$@" > $TMPDIR/retry.attempt 2>> $TMPDIR/retry.err ; then
|
|
success=true
|
|
break
|
|
fi
|
|
cat $TMPDIR/retry.{err,attempt} >> $TMPDIR/retry.out
|
|
cat $TMPDIR/retry.{err,attempt} >&2
|
|
echo waiting $delay "$@" >&2
|
|
sleep $delay
|
|
done
|
|
if $success ; then
|
|
cat $TMPDIR/retry.attempt
|
|
return 0
|
|
else
|
|
echo retry failed for "$@" >&2
|
|
cat $TMPDIR/retry.out >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function debug() {
|
|
DEBUG=true
|
|
VERBOSE=true
|
|
set -x
|
|
PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: '
|
|
}
|
|
|
|
function verbose() {
|
|
VERBOSE=true
|
|
}
|
|
|
|
function log() {
|
|
echo "$@" >&2
|
|
}
|
|
|
|
function log_error() {
|
|
log "$@"
|
|
}
|
|
|
|
function log_verbose() {
|
|
if $VERBOSE ; then
|
|
log_info "$@"
|
|
fi
|
|
}
|
|
|
|
function log_info() {
|
|
echo "$PREFIX $@"
|
|
}
|
|
|
|
function fatal_error() {
|
|
log_error "$@"
|
|
if $EXIT_ON_ERROR ; then
|
|
exit 1
|
|
else
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function stash_debug() {
|
|
echo start $SELF
|
|
mkdir -p $TMPDIR
|
|
> $TMPDIR/run.out
|
|
tail --follow $TMPDIR/run.out | sed --unbuffered -n -e "/^$PREFIX/s/^$PREFIX //p" &
|
|
pid=$!
|
|
if ! $SELF --debug "$@" >& $TMPDIR/run.out ; then
|
|
kill $pid
|
|
cat $TMPDIR/run.out
|
|
echo fail $SELF
|
|
return 1
|
|
fi
|
|
kill $pid
|
|
echo success $SELF
|
|
}
|
|
|
|
function host_port() {
|
|
local url="$1"
|
|
|
|
local host_port="${url##http*://}"
|
|
echo ${host_port%%/}
|
|
}
|
|
|
|
function scheme() {
|
|
local url="$1"
|
|
|
|
echo "${url%%://*}"
|
|
}
|
|
|
|
function owner() {
|
|
local repo="$1"
|
|
|
|
echo "${repo%%/*}"
|
|
}
|
|
|
|
function repository() {
|
|
local repo="$1"
|
|
|
|
echo "${repo##*/}"
|
|
}
|
|
|
|
function get_status() {
|
|
local api="$1"
|
|
local sha="$2"
|
|
|
|
forgejo-curl.sh api_json $api/commits/$sha/status
|
|
}
|
|
|
|
function check_status() {
|
|
local api="$1"
|
|
local sha="$2"
|
|
local expected_status="$3"
|
|
local expected_description="$4"
|
|
|
|
get_status $api $sha > $TMPDIR/status.json
|
|
local status="$(jq --raw-output .state < $TMPDIR/status.json)"
|
|
local description="$(jq --raw-output .statuses[0].description < $TMPDIR/status.json)"
|
|
if test "$status" = "$expected_status" && test -z "$expected_description" -o "$description" = "$expected_description"; then
|
|
echo OK
|
|
elif test "$status" = "failure" -o "$status" = "success"; then
|
|
echo NOK
|
|
else
|
|
echo RETRY
|
|
fi
|
|
}
|
|
|
|
function wait_success() {
|
|
wait_status success "$@"
|
|
}
|
|
|
|
function wait_failure() {
|
|
wait_status failure "$@"
|
|
}
|
|
|
|
function wait_running() {
|
|
wait_status pending "$@" "Has started running"
|
|
}
|
|
|
|
function wait_log() {
|
|
local sha="$1" expected_status="$2" expected_description="$3"
|
|
local status="$(jq --raw-output .state < $TMPDIR/status.json)"
|
|
local description="$(jq --raw-output .statuses[0].description < $TMPDIR/status.json)"
|
|
if test "$expected_description"; then
|
|
expected_description=" '$expected_description'"
|
|
fi
|
|
log_info "$sha status waiting '$expected_status'$expected_description, currently '$status' '$description'"
|
|
}
|
|
|
|
function wait_status() {
|
|
local status="$1"
|
|
local api="$2"
|
|
local sha="$3"
|
|
local description="$4"
|
|
|
|
for i in $(seq $LOOPS); do
|
|
if test $(check_status "$api" "$sha" "$status" "$description") != RETRY ; then
|
|
break
|
|
fi
|
|
wait_log "$sha" "$status" "$description"
|
|
sleep $LOOP_DELAY
|
|
done
|
|
if test $(check_status "$api" "$sha" "$status" "$description") = "OK" ; then
|
|
log_info "$sha status OK"
|
|
else
|
|
get_status $api $sha | jq .statuses
|
|
log_info "$sha status NOK"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
function sanity_check_pr_or_ref() {
|
|
local pr="$1" ref="$2"
|
|
|
|
if test "$pr" -a "$ref" ; then
|
|
log_error "--origin-pr $pr and --origin-ref $ref are mutually exclusive"
|
|
return 1
|
|
fi
|
|
if test -z "$pr" -a -z "$ref" ; then
|
|
log_error "one of --origin-pr or --origin-ref must be set"
|
|
return 2
|
|
fi
|
|
}
|
|
|
|
function set_origin_head() {
|
|
local pr="${options[origin_pr]}"
|
|
local ref="${options[origin_ref]}"
|
|
|
|
sanity_check_pr_or_ref "$pr" "$ref"
|
|
|
|
if test "$pr"; then
|
|
options[origin_head]=refs/pull/$pr/head
|
|
origin_sanity_check
|
|
else
|
|
options[origin_head]=$ref
|
|
fi
|
|
}
|
|
|
|
function origin_has_pr() {
|
|
test "${options[origin_pr]}"
|
|
}
|
|
|
|
function set_destination_head() {
|
|
local pr="${options[origin_pr]}"
|
|
local ref="${options[origin_ref]}"
|
|
|
|
sanity_check_pr_or_ref "$pr" "$ref"
|
|
|
|
if $(origin_has_pr); then
|
|
options[destination_head]=${options[prefix]}-$pr
|
|
else
|
|
options[destination_head]=${options[prefix]}-$ref
|
|
fi
|
|
}
|