1
0
Fork 0
mirror of https://code.forgejo.org/actions/cascading-pr synced 2025-03-14 22:36:58 +01:00
cascading-pr/cascading-pr.sh
Earl Warren 39c71c3f40
always login origin and destination
it may cost an extra login in some cases. But it also simplifies the
codepaths where it would otherwise be necessary to figure out exactly
in which case a login is required to read a private repository.
2023-10-27 15:56:06 +02:00

369 lines
9 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 direction="$1"
local repo=${options[${direction}_repo]}
(
export DOT=$TMPDIR/$repo
forgejo-curl.sh logout
forgejo-curl.sh --token "${options[${direction}_token]}" login "${options[${direction}_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_merged() {
pr_get $1
pr $1 | jq --raw-output .merged
}
function upsert_clone() {
local direction=$1 ref="$2" clone=$3
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 [[ "$ref" =~ ^refs/ ]] ; then
git fetch origin +$ref:$ref
else
ref=origin/$ref
fi
git checkout -b $direction $ref
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_clone]}
upsert_clone destination "${options[destination_head]}" ${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]=refs/pull/${options[origin_pr]}/head
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[close_merge]:=false}
}
function run() {
local state=$(pr_state origin)
repo_login origin
repo_login destination
case "$state" in
open)
log_info "PR is open, update or create the cascade branch and PR"
upsert_destination_branch
update
local sha=$(sha_pushed destination)
if test "$sha" ; then
upsert_destination_pr
comment_origin_pr
wait_destination_ci "$sha"
fi
;;
closed)
if "$(pr_merged origin)"; then
if "${options[close_merge]}" ; then
log_info "PR is merged, close the cascade PR and remove the branch"
close_pr
else
log_info "PR was merged, update the cascade PR"
pr_get origin
pr_get destination
update
fi
else
log_info "PR is closed, close the cascade PR and remove the branch"
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
;;
--close-merge)
shift
options[close_merge]=$1
shift
;;
*)
finalize_options
"${1:-run}"
return 0
;;
esac
done
}
dependencies
if echo "${@}" | grep --quiet -e '--debug' ; then
main "${@}"
else
stash_debug "${@}"
fi