mirror of
https://code.forgejo.org/actions/cascading-pr.git
synced 2025-04-23 01:00:50 +00:00
because the head ref may be either a branch or a reference, depending on wether the PR was merged and the branch deleted. The SHA (either from the head or the merged commit) always represents the most up to date state of the PR.
370 lines
9.1 KiB
Bash
Executable file
370 lines
9.1 KiB
Bash
Executable file
#!/bin/bash
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
set -e
|
|
set -o posix
|
|
|
|
SELF=${BASH_SOURCE[0]}
|
|
SELF_DIR="$( cd "$( dirname "$SELF" )" && pwd )"
|
|
source $SELF_DIR/cascading-pr-lib.sh
|
|
|
|
trap "rm -fr $TMPDIR" EXIT
|
|
|
|
function repo_login() {
|
|
local repo="$1"
|
|
(
|
|
export DOT=$TMPDIR/$repo
|
|
forgejo-curl.sh logout
|
|
forgejo-curl.sh --token "${options[destination_token]}" login "${options[destination_url]}"
|
|
)
|
|
}
|
|
|
|
function repo_curl() {
|
|
local repo=$1
|
|
shift
|
|
DOT=$TMPDIR/$repo forgejo-curl.sh "$@"
|
|
}
|
|
|
|
function exists_branch() {
|
|
local direction=$1
|
|
|
|
repo_curl ${options[${direction}_repo]} api_json ${options[${direction}_api]}/branches/${options[${direction}_head]} >& /dev/null
|
|
}
|
|
|
|
function delete_branch() {
|
|
local direction=$1
|
|
|
|
if ! $(exists_branch $direction) ; then
|
|
log_info "branch ${options[${direction}_head]} does not exists"
|
|
return
|
|
fi
|
|
repo_curl ${options[${direction}_repo]} api_json -X DELETE ${options[${direction}_api]}/branches/${options[${direction}_head]}
|
|
log_info "branch ${options[${direction}_head]} deleted"
|
|
}
|
|
|
|
function pr_origin_comment_body() {
|
|
echo "cascading-pr updated at ${options[destination_url]}/${options[destination_repo]}/pulls/$(pr_number destination)"
|
|
}
|
|
|
|
function comment_origin_pr() {
|
|
cat > $TMPDIR/data <<EOF
|
|
{
|
|
"body":"$(pr_origin_comment_body)"
|
|
}
|
|
EOF
|
|
repo_curl ${options[origin_repo]} api_json --data @$TMPDIR/data ${options[origin_api]}/issues/${options[origin_pr]}/comments
|
|
log_info "comment added to $(pr_url origin)"
|
|
}
|
|
|
|
function upsert_destination_branch() {
|
|
if $(exists_branch destination) ; then
|
|
log_info "branch ${options[destination_head]} already exists"
|
|
return
|
|
fi
|
|
cat > $TMPDIR/data <<EOF
|
|
{
|
|
"new_branch_name":"${options[destination_head]}",
|
|
"old_branch_name":"${options[destination_base]}"
|
|
}
|
|
EOF
|
|
repo_curl ${options[destination_repo]} api_json --data @$TMPDIR/data ${options[destination_api]}/branches
|
|
log_info "branch ${options[destination_head]} created"
|
|
}
|
|
|
|
function pr_destination_title() {
|
|
echo "cascading-pr from ${options[origin_url]}/${options[origin_repo]}/pulls/${options[origin_pr]}"
|
|
}
|
|
|
|
function pr_destination_body() {
|
|
echo "cascading-pr from ${options[origin_url]}/${options[origin_repo]}/pulls/${options[origin_pr]}"
|
|
}
|
|
|
|
function upsert_destination_pr() {
|
|
url=$(pr_url destination)
|
|
state=$(pr_state destination)
|
|
if test "$url" != "null" -a "$state" = "open"; then
|
|
log_info "an open PR already exists $url"
|
|
return
|
|
fi
|
|
local title=$(pr_destination_title)
|
|
cat > $TMPDIR/data <<EOF
|
|
{
|
|
"title":"$(pr_destination_title)",
|
|
"body":"$(pr_destination_body)",
|
|
"base":"${options[destination_base]}",
|
|
"head":"${options[destination_head]}"
|
|
}
|
|
EOF
|
|
retry repo_curl ${options[destination_repo]} api_json --data @$TMPDIR/data ${options[destination_api]}/pulls > $TMPDIR/destination-pr.json
|
|
log_info "PR created $(pr_url destination)"
|
|
}
|
|
|
|
function close_pr() {
|
|
local direction=destination
|
|
|
|
if test "$(pr_state ${direction})" = "open"; then
|
|
log_info "closing $(pr_url ${direction})"
|
|
local number=$(pr_number $direction)
|
|
repo_curl ${options[${direction}_repo]} api_json -X PATCH --data '{"state":"closed"}' ${options[${direction}_api]}/issues/$number
|
|
delete_branch ${direction}
|
|
else
|
|
log_info "no open PR found"
|
|
fi
|
|
}
|
|
|
|
function pr_get_origin() {
|
|
repo_curl ${options[origin_repo]} api_json ${options[origin_api]}/pulls/${options[origin_pr]} > $TMPDIR/origin-pr.json
|
|
}
|
|
|
|
function pr_get_destination() {
|
|
local title=$(pr_destination_title)
|
|
repo_curl ${options[destination_repo]} api --get --data state=open --data type=pulls --data-urlencode q="$title" ${options[destination_api]}/issues | jq --raw-output .[0] > $TMPDIR/destination-pr.json
|
|
}
|
|
|
|
function pr_get() {
|
|
local direction=$1
|
|
if ! test -f $TMPDIR/${direction}-pr.json; then
|
|
pr_get_$direction
|
|
fi
|
|
}
|
|
|
|
function pr() {
|
|
cat $TMPDIR/$1-pr.json
|
|
}
|
|
|
|
function pr_state() {
|
|
pr_get $1
|
|
pr $1 | jq --raw-output .state
|
|
}
|
|
|
|
function pr_url() {
|
|
pr_get $1
|
|
pr $1 | jq --raw-output .url
|
|
}
|
|
|
|
function pr_number() {
|
|
pr_get $1
|
|
pr $1 | jq --raw-output .number
|
|
}
|
|
|
|
function pr_sha() {
|
|
pr_get $1
|
|
merged=$(pr $1 | jq --raw-output .merged)
|
|
if "$merged"; then
|
|
pr $1 | jq --raw-output .merge_commit_sha
|
|
else
|
|
pr $1 | jq --raw-output .head.sha
|
|
fi
|
|
}
|
|
|
|
function pr_merged() {
|
|
pr_get $1
|
|
pr $1 | jq --raw-output .merged
|
|
}
|
|
|
|
function upsert_clone() {
|
|
local direction=$1 ref="$2" sha="$3" clone=$4
|
|
|
|
if ! test -d $TMPDIR/$direction; then
|
|
git -c credential.helper="store --file=$TMPDIR/$direction.git-credentials" clone $clone $TMPDIR/$direction
|
|
fi
|
|
(
|
|
cd $TMPDIR/$direction
|
|
if test "$ref"; then
|
|
git switch -c $direction origin/$ref
|
|
fi
|
|
if test "$sha"; then
|
|
git switch -c $direction $sha
|
|
fi
|
|
git config credential.helper "store --file=$TMPDIR/$direction.git-credentials"
|
|
git config user.email cascading-pr@example.com
|
|
git config user.name cascading-pr
|
|
)
|
|
}
|
|
|
|
function sha_pushed() {
|
|
local direction=$1
|
|
if test -f $TMPDIR/$direction.sha ; then
|
|
cat $TMPDIR/$direction.sha
|
|
fi
|
|
}
|
|
|
|
function push() {
|
|
local direction=$1 branch=$2 clone=$3
|
|
|
|
(
|
|
cd $TMPDIR/$direction
|
|
git add .
|
|
if git commit -m 'cascading-pr update'; then
|
|
git push --force origin $direction:$branch
|
|
git rev-parse HEAD > ../$direction.sha
|
|
log_info "pushed"
|
|
else
|
|
log_info "nothing to push"
|
|
fi
|
|
)
|
|
}
|
|
|
|
function wait_destination_ci() {
|
|
local sha="$1"
|
|
local repo_api=${options[destination_url]}/api/v1/repos/${options[destination_repo]}
|
|
wait_success $repo_api $sha
|
|
}
|
|
|
|
function update() {
|
|
upsert_clone origin "${options[origin_head]}" "${options[origin_sha]}" ${options[origin_clone]}
|
|
upsert_clone destination "${options[destination_head]}" "${options[destination_sha]}" ${options[destination_clone]}
|
|
(
|
|
cd $TMPDIR/origin
|
|
${options[update]} $TMPDIR/destination $TMPDIR/destination-pr.json $TMPDIR/origin-pr.json
|
|
)
|
|
push destination ${options[destination_head]} ${options[destination_clone]}
|
|
}
|
|
|
|
function set_clone() {
|
|
local direction=$1
|
|
local token=${options[${direction}_token]}
|
|
|
|
if [[ "$token" =~ ^@ ]] ; then
|
|
local file=${token##@}
|
|
(
|
|
echo -n ${options[${direction}_scheme]}://any:
|
|
cat $file
|
|
echo @${options[${direction}_host_port]}/${options[${direction}_repo]}
|
|
) > $TMPDIR/$direction.git-credentials
|
|
else
|
|
echo ${options[${direction}_scheme]}://any:${options[${direction}_token]}@${options[${direction}_host_port]}/${options[${direction}_repo]} > $TMPDIR/$direction.git-credentials
|
|
fi
|
|
options[${direction}_clone]=${options[${direction}_scheme]}://${options[${direction}_host_port]}/${options[${direction}_repo]}
|
|
}
|
|
|
|
function finalize_options() {
|
|
options[origin_api]=${options[origin_url]}/api/v1/repos/${options[origin_repo]}
|
|
options[origin_scheme]=$(scheme ${options[origin_url]})
|
|
options[origin_host_port]=$(host_port ${options[origin_url]})
|
|
set_clone origin
|
|
options[origin_head]=
|
|
options[origin_sha]=$(pr_sha origin)
|
|
options[destination_api]=${options[destination_url]}/api/v1/repos/${options[destination_repo]}
|
|
options[destination_scheme]=$(scheme ${options[destination_url]})
|
|
options[destination_host_port]=$(host_port ${options[destination_url]})
|
|
set_clone destination
|
|
options[destination_base]=${options[destination_branch]}
|
|
: ${options[prefix]:=${options[origin_repo]}}
|
|
options[destination_head]=${options[prefix]}-${options[origin_pr]}
|
|
options[destination_sha]=
|
|
}
|
|
|
|
function run() {
|
|
local state=$(pr_state origin)
|
|
|
|
case "$state" in
|
|
open)
|
|
log_info "PR is open, update or create the cascade branch and PR"
|
|
repo_login ${options[destination_repo]}
|
|
upsert_destination_branch
|
|
update
|
|
local sha=$(sha_pushed destination)
|
|
if test "$sha" ; then
|
|
upsert_destination_pr
|
|
repo_login ${options[origin_repo]}
|
|
comment_origin_pr
|
|
wait_destination_ci "$sha"
|
|
fi
|
|
;;
|
|
closed)
|
|
if "$(pr_merged origin)"; then
|
|
log_info "PR was merged, update the cascade PR"
|
|
repo_login ${options[destination_repo]}
|
|
pr_get origin
|
|
pr_get destination
|
|
update
|
|
else
|
|
log_info "PR is closed, close the cascade PR and remove the branch"
|
|
repo_login ${options[destination_repo]}
|
|
close_pr
|
|
fi
|
|
;;
|
|
*)
|
|
log_info "state '$state', do nothing"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
function main() {
|
|
while true; do
|
|
case "$1" in
|
|
--verbose)
|
|
shift
|
|
verbose
|
|
;;
|
|
--debug)
|
|
shift
|
|
debug
|
|
;;
|
|
--origin-url)
|
|
shift
|
|
options[origin_url]=$1
|
|
shift
|
|
;;
|
|
--origin-repo)
|
|
shift
|
|
options[origin_repo]=$1
|
|
shift
|
|
;;
|
|
--origin-token)
|
|
shift
|
|
options[origin_token]=$1
|
|
shift
|
|
;;
|
|
--origin-pr)
|
|
shift
|
|
options[origin_pr]=$1
|
|
shift
|
|
;;
|
|
--destination-url)
|
|
shift
|
|
options[destination_url]=$1
|
|
shift
|
|
;;
|
|
--destination-repo)
|
|
shift
|
|
options[destination_repo]=$1
|
|
shift
|
|
;;
|
|
--destination-token)
|
|
shift
|
|
options[destination_token]=$1
|
|
shift
|
|
;;
|
|
--destination-branch)
|
|
shift
|
|
options[destination_branch]=$1
|
|
shift
|
|
;;
|
|
--update)
|
|
shift
|
|
options[update]=$1
|
|
shift
|
|
;;
|
|
--prefix)
|
|
shift
|
|
options[prefix]=$1
|
|
shift
|
|
;;
|
|
*)
|
|
finalize_options
|
|
"${1:-run}"
|
|
return 0
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
dependencies
|
|
|
|
if echo "${@}" | grep --quiet -e '--debug' ; then
|
|
main "${@}"
|
|
else
|
|
stash_debug "${@}"
|
|
fi
|