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

forked origin requires a forked destination

Fixes: https://code.forgejo.org/actions/cascading-pr/issues/10
This commit is contained in:
Earl Warren 2023-10-31 22:25:43 +01:00
parent 277569106a
commit dd5427bc63
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
3 changed files with 141 additions and 32 deletions

View file

@ -21,7 +21,7 @@ description: |
The `update` script is expected to be found in the origin repository
running the PR. It is given four arguments:
* A directory in which the destination repository is checked-out
* A directory in which the destination repository (or a fork) is checked-out
on the base branch
* A file with the JSON describing the pull request in the
destination repository
@ -36,6 +36,9 @@ description: |
When the PR is from a forked repository, the `update` script is checked out from
the default branch instead of the head branch of the fork.
If the fork of the destination repository is specified and it does
not exist, it is created.
inputs:
origin-url:
description: 'URL of the Forgejo instance where the PR that triggers the action is located (e.g. https://code.forgejo.org)'
@ -55,6 +58,8 @@ inputs:
destination-repo:
description: 'the repository in which the cascading PR is created or updated'
required: true
destination-fork-repo:
description: 'the fork of {desitnation-repo} in which the {destination-branch} will be created or updated'
destination-branch:
description: 'the base branch of the destination repository for the cascading PR'
required: true
@ -102,6 +107,7 @@ runs:
--origin-pr "${{ inputs.origin-pr }}" \
--destination-url "${{ inputs.destination-url }}" \
--destination-repo "${{ inputs.destination-repo }}" \
--destination-fork-repo "${{ inputs.destination-fork-repo }}" \
--destination-token "@$destination_token" \
--destination-branch "${{ inputs.destination-branch }}" \
--update "${{ inputs.update }}" \

View file

@ -115,6 +115,18 @@ function scheme() {
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"

View file

@ -64,21 +64,6 @@ EOF
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]}"
}
@ -94,13 +79,18 @@ function upsert_destination_pr() {
log_info "an open PR already exists $url"
return
fi
if ${options[destination_is_fork]} ; then
head="$(owner ${options[destination_fork_repo]}):${options[destination_head]}"
else
head=${options[destination_head]}
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]}"
"head":"$head"
}
EOF
retry repo_curl ${options[destination_repo]} api_json --data @$TMPDIR/data ${options[destination_api]}/pulls > $TMPDIR/destination-pr.json
@ -165,26 +155,55 @@ function pr_from_fork() {
pr $1 | jq --raw-output .head.repo.fork
}
function upsert_clone() {
local direction=$1 ref="$2" clone=$3
function git_clone() {
local direction=$1 url=$2
if ! test -d $TMPDIR/$direction; then
git -c credential.helper="store --file=$TMPDIR/$direction.git-credentials" clone $clone $TMPDIR/$direction
git -c credential.helper="store --file=$TMPDIR/$direction.git-credentials" clone $url $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 git_checkout() {
local direction=$1 ref="$3"
local remote=origin
(
cd $TMPDIR/$direction
if [[ "$ref" =~ ^refs/ ]] ; then
git fetch ${remote} +$ref:$ref
else
ref=${remote}/$ref
fi
git checkout -b prbranch $ref
)
}
function git_remote() {
local direction=$1 remote=$2 url=$3
(
cd $TMPDIR/$direction
git remote add $remote $url
)
}
function git_reset_branch() {
local direction=$1 remote=$2 branch=$3
(
cd $TMPDIR/$direction
if git ls-remote --exit-code --heads ${remote} $branch ; then
git fetch --quiet ${remote} $branch
git reset --hard ${remote}/$branch
fi
)
}
function sha_pushed() {
local direction=$1
if test -f $TMPDIR/$direction.sha ; then
@ -193,13 +212,13 @@ function sha_pushed() {
}
function push() {
local direction=$1 branch=$2 clone=$3
local direction=$1 remote=$2 branch=$3
(
cd $TMPDIR/$direction
git add .
if git commit -m 'cascading-pr update'; then
git push --force origin $direction:$branch
git push --force ${remote} prbranch:$branch
git rev-parse HEAD > ../$direction.sha
log_info "pushed"
else
@ -214,9 +233,53 @@ function wait_destination_ci() {
wait_success $repo_api $sha
}
function upsert_fork() {
if repo_curl ${options[destination_repo]} api_json ${options[destination_fork_api]} > $TMPDIR/fork.json 2> /dev/null ; then
if test "$(jq --raw-output .fork)" != true ; then
log_error "the destination fork already exists but is not a fork ${options[destination_fork]}"
return 1
fi
local forked_from_repo=$(jq --raw-output .parent.full_name)
if test "$forked_from_repo" != "${options[destination_repo]}" ; then
log_error "${options[destination_fork]} must be a fork of ${options[destination_repo]} but is a fork of $forked_from_repo instead"
return 1
fi
else
local fork_owner=$(owner ${options[destination_fork]})
local data="{}"
if repo_curl ${options[destination_repo]} api_json ${options[destination_url]}/api/v1/orgs/${fork_owner} >& /dev/null ; then
data='{"organization":"'$fork_owner'"}'
fi
repo_curl ${options[destination_repo]} api_json --data "$data" ${options[destination_url]}/api/v1/${options[destination_repo]}/forks
fi
}
function checkout() {
#
# origin
#
git_clone origin ${options[origin_clone]}
git_checkout origin "${options[origin_head]}"
#
# destination
#
git_clone destination ${options[destination_clone]}
git_checkout destination "${options[destination_base]}"
#
# fork
#
local head_remote=origin
if ${options[destination_is_fork]} ; then
upsert_fork
git_remote destination fork ${options[destination_fetch_fork]}
head_remote=fork
fi
git_reset_branch destination $head_remote "${options[destination_head]}"
}
function update() {
upsert_clone origin "${options[origin_head]}" ${options[origin_clone]}
upsert_clone destination "${options[destination_head]}" ${options[destination_clone]}
(
local update=${options[update]}
if ! [[ "$update" =~ ^/ ]] ; then
@ -234,7 +297,11 @@ function update() {
cd $TMPDIR
$update $TMPDIR/destination $TMPDIR/destination-pr.json $TMPDIR/origin $TMPDIR/origin-pr.json
)
push destination ${options[destination_head]} ${options[destination_clone]}
local remote_head=origin
if ${options[destination_is_fork]} ; then
remote_head=fork
fi
push destination $remote_head ${options[destination_head]}
}
function set_clone() {
@ -254,12 +321,22 @@ function set_clone() {
options[${direction}_clone]=${options[${direction}_scheme]}://${options[${direction}_host_port]}/${options[${direction}_repo]}
}
function fork_sanity_check() {
local fork_repo=${options[destination_fork_repo]}
local repo=${options[destination_repo]}
if test "$(repository $fork_repo)" != "$(repository $repo)"; then
echo "$repo and its fork $fork_repo must have the same repository name (see https://codeberg.org/forgejo/forgejo/issues/1707)"
return 1
fi
}
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]})
@ -267,6 +344,14 @@ function finalize_options() {
options[destination_base]=${options[destination_branch]}
: ${options[prefix]:=${options[origin_repo]}}
options[destination_head]=${options[prefix]}-${options[origin_pr]}
if test "${options[destination_fork_repo]}"; then
fork_sanity_check
options[destination_is_fork]=true
options[destination_fork_api]=${options[destination_url]}/api/v1/repos/${options[destination_fork_repo]}
options[destination_is_fork]=false
fi
: ${options[close_merge]:=false}
}
@ -279,7 +364,7 @@ function run() {
case "$state" in
open)
log_info "PR is open, update or create the cascade branch and PR"
upsert_destination_branch
checkout
update
local sha=$(sha_pushed destination)
if test "$sha" ; then
@ -297,6 +382,7 @@ function run() {
log_info "PR was merged, update the cascade PR"
pr_get origin
pr_get destination
checkout
update
fi
else
@ -351,6 +437,11 @@ function main() {
options[destination_repo]=$1
shift
;;
--destination-fork-repo)
shift
options[destination_fork_repo]=$1
shift
;;
--destination-token)
shift
options[destination_token]=$1