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.
498 lines
16 KiB
Bash
Executable file
498 lines
16 KiB
Bash
Executable file
#!/bin/bash
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
set -e
|
|
set -o posix
|
|
|
|
SELF_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
TMPDIR=/tmp/cascading-pr-test
|
|
export LOOP_DELAY=5
|
|
mkdir -p $TMPDIR
|
|
source $SELF_DIR/../cascading-pr-lib.sh
|
|
|
|
function push_self() {
|
|
log_verbose "push cascading-pr action to user1/cascading-pr"
|
|
forgejo-test-helper.sh push_self_action http://user1:admin1234@${options[host_port]} user1 cascading-pr vTest
|
|
}
|
|
|
|
function user_login() {
|
|
local username=$1
|
|
(
|
|
export DOT=$TMPDIR/$username
|
|
forgejo-curl.sh logout
|
|
forgejo-curl.sh --user $username --password "${options[password]}" login ${options[url]}
|
|
)
|
|
}
|
|
|
|
function user_curl() {
|
|
local username=$1
|
|
shift
|
|
DOT=$TMPDIR/$username forgejo-curl.sh "$@"
|
|
}
|
|
|
|
function user_token() {
|
|
local username=$1 name=$2
|
|
|
|
curl -sS -f -H Content-Type:application/json --user "$username:${options[password]}" --data '{"name":"'$name'","scopes":["write:repository","write:issue","read:organization","read:user"]}' ${options[url]}/api/v1/users/$username/tokens | jq --raw-output .sha1 | tee $TMPDIR/$username/repo-token
|
|
}
|
|
|
|
function user_secret() {
|
|
local username=$1 name=$2 token=$3
|
|
|
|
log_verbose "(re)set ${username} secret ${name}"
|
|
user_curl $username api_json -X PUT --data '{"data":"'$token'"}' ${options[url]}/api/v1/user/actions/secrets/$name
|
|
}
|
|
|
|
function orgs_delete() {
|
|
forgejo-curl.sh api_json ${options[url]}/api/v1/orgs | jq --raw-output '.[] | .name' | while read owner ; do
|
|
forgejo-curl.sh api_json ${options[url]}/api/v1/orgs/$owner/repos | jq --raw-output '.[] | .name' | while read repo ; do
|
|
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/repos/$owner/$repo
|
|
done
|
|
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/orgs/$owner
|
|
done
|
|
}
|
|
|
|
function user_create() {
|
|
local username="$1" email="$2"
|
|
log_verbose "(re)create user $username"
|
|
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/admin/users/$username?purge=true >& /dev/null || true
|
|
forgejo-curl.sh api_json --data '{"username":"'$username'","email":"'$email'","password":"'${options[password]}'","must_change_password":false}' ${options[url]}/api/v1/admin/users
|
|
user_login $username
|
|
}
|
|
|
|
function close_pull_request() {
|
|
local repo=$1
|
|
|
|
log_verbose "close all pull requests in user1/$repo"
|
|
forgejo-curl.sh api_json ${options[url]}/api/v1/repos/user1/${repo}/pulls | jq --raw-output '.[] | .number' | while read pr ; do
|
|
forgejo-curl.sh api_json -X PATCH --data '{"state":"closed"}' ${options[url]}/api/v1/repos/user1/${repo}/issues/$pr
|
|
done
|
|
}
|
|
|
|
function merge_pull_request() {
|
|
local repo=$1
|
|
|
|
log_verbose "merge all pull requests in user1/$repo"
|
|
forgejo-curl.sh api_json ${options[url]}/api/v1/repos/user1/${repo}/pulls | jq --raw-output '.[] | .number' | while read pr ; do
|
|
forgejo-curl.sh api_json --data '{"Do":"merge"}' ${options[url]}/api/v1/repos/user1/${repo}/pulls/$pr/merge
|
|
done
|
|
}
|
|
|
|
function has_cascade_pull_request() {
|
|
local repo="${1:-destinationrepo}"
|
|
|
|
log_verbose "verify an open cascade pull request exists"
|
|
test "$(cascade_open_pull_request_count $repo)" -gt 0
|
|
}
|
|
|
|
function has_no_cascade_pull_request() {
|
|
local repo="${1:-destinationrepo}"
|
|
|
|
log_verbose "verify there is no open cascade pull request"
|
|
test "$(cascade_open_pull_request_count $repo)" = 0
|
|
}
|
|
|
|
function cascade_open_pull_request_count() {
|
|
local repo="$1"
|
|
|
|
cascade_pull_request $repo | jq '[ .[] | select(.state == "open") ] | length'
|
|
}
|
|
|
|
function cascade_pull_request_count() {
|
|
local repo="$1"
|
|
|
|
cascade_pull_request $repo | jq '. | length'
|
|
}
|
|
|
|
function cascade_pull_request() {
|
|
local repo="$1"
|
|
|
|
forgejo-curl.sh api_json ${options[url]}/api/v1/repos/user2/${repo}/pulls
|
|
}
|
|
|
|
function create_branch1() {
|
|
local owner=$1 repo=$2 modify=$3
|
|
|
|
log_verbose "(re)create branch1 in $owner/$repo"
|
|
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/repos/$owner/${repo}/branches/branch1 >& /dev/null || true
|
|
forgejo-curl.sh api_json --data '{"new_branch_name":"branch1"}' ${options[url]}/api/v1/repos/$owner/${repo}/branches
|
|
(
|
|
cd $TMPDIR
|
|
rm -fr ${repo}
|
|
git clone -b branch1 http://user1:admin1234@${options[host_port]}/$owner/${repo}
|
|
cd ${repo}
|
|
echo CONTENT > README
|
|
if test "$modify" ; then
|
|
log_verbose "modify branch1 in $owner/$repo with $modify"
|
|
$modify
|
|
fi
|
|
git config user.email root@example.com
|
|
git config user.name username
|
|
git add .
|
|
git commit -m 'update'
|
|
git push origin branch1
|
|
git rev-parse HEAD > ../${owner}-${repo}.sha
|
|
)
|
|
}
|
|
|
|
function delete_pull_requests() {
|
|
local owner=$1 repo=$2
|
|
|
|
forgejo-curl.sh api_json ${options[url]}/api/v1/repos/$owner/${repo}/pulls | jq --raw-output '.[] | .number' | while read pr ; do
|
|
forgejo-curl.sh api_json -X DELETE ${options[url]}/api/v1/repos/$owner/${repo}/issues/$pr
|
|
done
|
|
}
|
|
|
|
|
|
function create_pull_request() {
|
|
local baseowner=$1 headowner=$2 repo=$3
|
|
|
|
local head
|
|
if test $baseowner == $headowner; then
|
|
head=branch1
|
|
else
|
|
head=$headowner:branch1
|
|
fi
|
|
cat > $TMPDIR/data <<EOF
|
|
{"title":"PR","base":"main","head":"$head"}
|
|
EOF
|
|
log_verbose "create pull request in $baseowner/$repo from $head"
|
|
forgejo-curl.sh api_json --data @$TMPDIR/data ${options[url]}/api/v1/repos/$baseowner/${repo}/pulls
|
|
}
|
|
|
|
function create_pull_request_case1() {
|
|
local baseowner=$1 headowner=$2 repo=$3
|
|
|
|
delete_pull_requests $baseowner $repo
|
|
create_branch1 $headowner $repo
|
|
create_pull_request $baseowner $headowner $repo
|
|
}
|
|
|
|
function unit_finalize_options() {
|
|
sanity_check_pr_or_ref A B || test $? = 1
|
|
sanity_check_pr_or_ref '' '' || test $? = 2
|
|
sanity_check_pr_or_ref A ''
|
|
sanity_check_pr_or_ref '' B
|
|
}
|
|
|
|
function unit_retry_fail() {
|
|
local file=$1
|
|
local value=$(cat $file)
|
|
expr $value - 1 > $file
|
|
echo RESULT
|
|
if test $value -gt 0 ; then
|
|
return 1
|
|
else
|
|
return 0
|
|
fi
|
|
}
|
|
|
|
function unit_retry() {
|
|
local two=$TMPDIR/unit_retry_two
|
|
|
|
rm -f $TMPDIR/retry.*
|
|
|
|
#
|
|
# Succeeds after two tries
|
|
#
|
|
echo 2 > $TMPDIR/unit_retry_two
|
|
if ! RETRY_DELAYS='1 1 1 1' retry unit_retry_fail $two > $TMPDIR/retry.test-result 2> $TMPDIR/retry.test-log ; then
|
|
cat $TMPDIR/retry.test-result $TMPDIR/retry.test-log
|
|
return 1
|
|
fi
|
|
test "$(cat $TMPDIR/retry.test-result)" = "RESULT"
|
|
if test "$(grep -c '^waiting 1 unit_retry_fail' $TMPDIR/retry.test-log)" != 2 ; then
|
|
cat $TMPDIR/retry.test-log
|
|
return 1
|
|
fi
|
|
#
|
|
# Succeeds immediately
|
|
#
|
|
if ! RETRY_DELAYS='1' retry unit_retry_fail $two > $TMPDIR/retry.test-result 2> $TMPDIR/retry.test-log ; then
|
|
cat $TMPDIR/retry.test-result $TMPDIR/retry.test-log
|
|
return 1
|
|
fi
|
|
test "$(cat $TMPDIR/retry.test-result)" = "RESULT"
|
|
if test "$(grep -c 'waiting 1 unit_retry_fail' $TMPDIR/retry.test-log)" != 0 ; then
|
|
cat $TMPDIR/retry.test-log
|
|
return 1
|
|
fi
|
|
|
|
#
|
|
# Verify the output is only the output of the last run and is not polluted by
|
|
# unrelated output
|
|
#
|
|
echo 2 > $TMPDIR/unit_retry_two
|
|
test "$(RETRY_DELAYS='1 1 1' retry unit_retry_fail $two)" = "RESULT"
|
|
|
|
#
|
|
# Fails after one try
|
|
#
|
|
echo 2 > $TMPDIR/unit_retry_two
|
|
if RETRY_DELAYS='1' retry unit_retry_fail $two > $TMPDIR/retry.test-result 2> $TMPDIR/retry.test-log ; then
|
|
cat $TMPDIR/retry.test-result $TMPDIR/retry.test-log
|
|
return 1
|
|
fi
|
|
grep --quiet 'retry failed' $TMPDIR/retry.test-log
|
|
}
|
|
|
|
function fixture() {
|
|
local origin=$1
|
|
local destination=$2
|
|
|
|
orgs_delete
|
|
|
|
user_create user2 user2@example.com
|
|
log_verbose push tests/${destination} repository to user2/${destination}
|
|
forgejo-test-helper.sh push tests/${destination} http://user2:admin1234@${options[host_port]} user2 ${destination}
|
|
|
|
user_create user3 user3@example.com
|
|
log_verbose create organization destination-fork
|
|
user_curl user3 api_json --data '{"username":"destination-fork"}' ${options[url]}/api/v1/orgs
|
|
|
|
user_create user1 user1@example.com
|
|
log_verbose push tests/${origin} repository to user1/${origin}
|
|
forgejo-test-helper.sh push tests/${origin} http://user1:admin1234@${options[host_port]} user1 ${origin} cascading-pr
|
|
log_verbose create organization origin-fork
|
|
user_curl user1 api_json --data '{"username":"origin-fork"}' ${options[url]}/api/v1/orgs
|
|
log_verbose fork user1/${origin} to origin-fork/${origin}
|
|
user_curl user1 api_json --data '{"organization":"origin-fork"}' ${options[url]}/api/v1/repos/user1/${origin}/forks
|
|
user_secret user1 ORIGIN_TOKEN $(user_token user1 ORIGIN_TOKEN)
|
|
|
|
push_self
|
|
}
|
|
|
|
function no_change_no_cascade_pr() {
|
|
fixture originrepo-do-nothing destinationrepo
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
|
|
|
|
create_pull_request_case1 user1 user1 originrepo-do-nothing
|
|
wait_success ${options[url]}/api/v1/repos/user1/originrepo-do-nothing $(cat $TMPDIR/user1-originrepo-do-nothing.sha)
|
|
has_no_cascade_pull_request
|
|
}
|
|
|
|
function branch_and_success() {
|
|
local origin_repo=origin-branch
|
|
local destination_repo=destinationrepo
|
|
|
|
fixture ${origin_repo} ${destination_repo}
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
|
|
|
|
test $(cascade_pull_request_count ${destination_repo}) = 0
|
|
create_branch1 user1 ${origin_repo}
|
|
wait_success ${options[url]}/api/v1/repos/user1/${origin_repo} $(cat $TMPDIR/user1-${origin_repo}.sha)
|
|
test $(cascade_pull_request_count ${destination_repo}) = 1
|
|
has_no_cascade_pull_request ${destination_repo}
|
|
}
|
|
|
|
function branch_and_fail() {
|
|
local origin_repo=origin-branch-fail
|
|
local destination_repo=destination-fail
|
|
|
|
fixture ${origin_repo} ${destination_repo}
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
|
|
|
|
test $(cascade_pull_request_count ${destination_repo}) = 0
|
|
create_branch1 user1 ${origin_repo}
|
|
wait_failure ${options[url]}/api/v1/repos/user1/${origin_repo} $(cat $TMPDIR/user1-${origin_repo}.sha)
|
|
test $(cascade_pull_request_count ${destination_repo}) = 1
|
|
has_no_cascade_pull_request ${destination_repo}
|
|
}
|
|
|
|
function create_and_close() {
|
|
fixture originrepo destinationrepo
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
|
|
|
|
create_pull_request_case1 user1 user1 originrepo
|
|
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/user1-originrepo.sha)
|
|
has_cascade_pull_request
|
|
close_pull_request originrepo
|
|
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/user1-originrepo.sha)
|
|
}
|
|
|
|
function taint_update() {
|
|
if ! test -f upgraded ; then
|
|
echo upgraded file not found
|
|
return 1
|
|
fi
|
|
echo 'TAINTED' > upgraded
|
|
}
|
|
|
|
function create_from_origin_fork_and_close() {
|
|
fixture originrepo destinationrepo
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
|
|
|
|
delete_pull_requests user1 originrepo
|
|
create_branch1 origin-fork originrepo taint_update
|
|
create_pull_request user1 origin-fork originrepo
|
|
|
|
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/origin-fork-originrepo.sha)
|
|
has_cascade_pull_request
|
|
close_pull_request originrepo
|
|
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/origin-fork-originrepo.sha)
|
|
}
|
|
|
|
function create_and_merge() {
|
|
fixture originrepo destinationrepo
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
|
|
|
|
create_pull_request_case1 user1 user1 originrepo
|
|
|
|
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/user1-originrepo.sha)
|
|
has_cascade_pull_request
|
|
merge_pull_request originrepo
|
|
wait_success ${options[url]}/api/v1/repos/user1/originrepo $(cat $TMPDIR/user1-originrepo.sha)
|
|
has_cascade_pull_request
|
|
}
|
|
|
|
function create_in_destination_fork_and_close() {
|
|
fixture origin-fork-destination destinationrepo
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user3 DESTINATION_TOKEN)
|
|
|
|
create_pull_request_case1 user1 user1 origin-fork-destination
|
|
|
|
wait_success ${options[url]}/api/v1/repos/user1/origin-fork-destination $(cat $TMPDIR/user1-origin-fork-destination.sha)
|
|
has_cascade_pull_request
|
|
close_pull_request origin-fork-destination
|
|
wait_success ${options[url]}/api/v1/repos/user1/origin-fork-destination $(cat $TMPDIR/user1-origin-fork-destination.sha)
|
|
}
|
|
|
|
function create_in_existing_destination_fork_and_close() {
|
|
fixture origin-organization-fork-destination destinationrepo
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user3 DESTINATION_TOKEN)
|
|
log_verbose fork user2/destinationrepo to destination-fork/destinationrepo
|
|
user_curl user3 api_json --data '{"organization":"destination-fork"}' ${options[url]}/api/v1/repos/user2/destinationrepo/forks
|
|
|
|
create_pull_request_case1 user1 user1 origin-organization-fork-destination
|
|
|
|
wait_success ${options[url]}/api/v1/repos/user1/origin-organization-fork-destination $(cat $TMPDIR/user1-origin-organization-fork-destination.sha)
|
|
has_cascade_pull_request
|
|
close_pull_request origin-organization-fork-destination
|
|
wait_success ${options[url]}/api/v1/repos/user1/origin-organization-fork-destination $(cat $TMPDIR/user1-origin-organization-fork-destination.sha)
|
|
}
|
|
|
|
function create_in_organization_destination_fork_and_close() {
|
|
fixture origin-organization-fork-destination destinationrepo
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user3 DESTINATION_TOKEN)
|
|
|
|
create_pull_request_case1 user1 user1 origin-organization-fork-destination
|
|
|
|
wait_success ${options[url]}/api/v1/repos/user1/origin-organization-fork-destination $(cat $TMPDIR/user1-origin-organization-fork-destination.sha)
|
|
has_cascade_pull_request
|
|
close_pull_request origin-organization-fork-destination
|
|
wait_success ${options[url]}/api/v1/repos/user1/origin-organization-fork-destination $(cat $TMPDIR/user1-origin-organization-fork-destination.sha)
|
|
}
|
|
|
|
function create_and_merge_close() {
|
|
fixture originrepo-close-merge destinationrepo
|
|
user_secret user1 DESTINATION_TOKEN $(user_token user2 DESTINATION_TOKEN)
|
|
|
|
create_pull_request_case1 user1 user1 originrepo-close-merge
|
|
|
|
wait_success ${options[url]}/api/v1/repos/user1/originrepo-close-merge $(cat $TMPDIR/user1-originrepo-close-merge.sha)
|
|
has_cascade_pull_request
|
|
merge_pull_request originrepo-close-merge
|
|
wait_success ${options[url]}/api/v1/repos/user1/originrepo-close-merge $(cat $TMPDIR/user1-originrepo-close-merge.sha)
|
|
has_no_cascade_pull_request
|
|
}
|
|
|
|
function run() {
|
|
local fun=$1
|
|
shift
|
|
|
|
echo "Start running $fun ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
|
|
if $DEBUG ; then
|
|
$fun "$@"
|
|
else
|
|
mkdir -p $TMPDIR
|
|
> $TMPDIR/$fun.out
|
|
tail --follow $TMPDIR/$fun.out | sed --unbuffered -n -e "/^$PREFIX/s/^$PREFIX //p" &
|
|
pid=$!
|
|
if ! ${BASH_SOURCE[0]} --debug ${options[args]} $fun "$@" >& $TMPDIR/$fun.out ; then
|
|
kill $pid
|
|
cat $TMPDIR/$fun.out
|
|
echo Failure running $fun
|
|
return 1
|
|
fi
|
|
kill $pid
|
|
fi
|
|
echo "Success running $fun ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
|
|
}
|
|
|
|
function integration() {
|
|
run branch_and_success
|
|
run no_change_no_cascade_pr
|
|
run create_in_destination_fork_and_close
|
|
run create_and_close
|
|
run create_from_origin_fork_and_close
|
|
run create_and_merge
|
|
run create_in_destination_fork_and_close
|
|
run create_in_existing_destination_fork_and_close
|
|
run create_in_organization_destination_fork_and_close
|
|
run create_and_merge_close
|
|
}
|
|
|
|
function unit() {
|
|
unit_finalize_options
|
|
unit_retry
|
|
}
|
|
|
|
function run_tests() {
|
|
unit
|
|
integration
|
|
}
|
|
|
|
function finalize_options() {
|
|
if test -f $DIR/forgejo-ip; then
|
|
: ${options[host_port]:=$(cat $DIR/forgejo-ip):3000}
|
|
fi
|
|
options[url]=http://${options[host_port]}
|
|
if test -f $DIR/forgejo-token; then
|
|
: ${options[token]:=$(cat $DIR/forgejo-token)}
|
|
fi
|
|
options[password]=admin1234
|
|
}
|
|
|
|
function main() {
|
|
local command=run
|
|
|
|
while true; do
|
|
case "$1" in
|
|
--verbose)
|
|
shift
|
|
verbose
|
|
;;
|
|
--debug)
|
|
shift
|
|
debug
|
|
;;
|
|
--host_port)
|
|
shift
|
|
options[args]="${options[args]} --host_port $1"
|
|
options[host_port]=$1
|
|
shift
|
|
;;
|
|
--url)
|
|
shift
|
|
options[args]="${options[args]} --url $1"
|
|
options[url]=$1
|
|
shift
|
|
;;
|
|
--token)
|
|
shift
|
|
options[args]="${options[args]} --token $1"
|
|
options[token]=$1
|
|
shift
|
|
;;
|
|
*)
|
|
finalize_options
|
|
"${1:-run_tests}" "${@:2}"
|
|
return 0
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
dependencies
|
|
|
|
${MAIN:-main} "${@}"
|