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

View file

@ -115,6 +115,18 @@ function scheme() {
echo "${url%%://*}" echo "${url%%://*}"
} }
function owner() {
local repo="$1"
echo "${repo%%/*}"
}
function repository() {
local repo="$1"
echo "${repo##*/}"
}
function get_status() { function get_status() {
local api="$1" local api="$1"
local sha="$2" local sha="$2"

View file

@ -64,21 +64,6 @@ EOF
log_info "comment added to $(pr_url origin)" 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() { function pr_destination_title() {
echo "cascading-pr from ${options[origin_url]}/${options[origin_repo]}/pulls/${options[origin_pr]}" 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" log_info "an open PR already exists $url"
return return
fi 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) local title=$(pr_destination_title)
cat > $TMPDIR/data <<EOF cat > $TMPDIR/data <<EOF
{ {
"title":"$(pr_destination_title)", "title":"$(pr_destination_title)",
"body":"$(pr_destination_body)", "body":"$(pr_destination_body)",
"base":"${options[destination_base]}", "base":"${options[destination_base]}",
"head":"${options[destination_head]}" "head":"$head"
} }
EOF EOF
retry repo_curl ${options[destination_repo]} api_json --data @$TMPDIR/data ${options[destination_api]}/pulls > $TMPDIR/destination-pr.json 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 pr $1 | jq --raw-output .head.repo.fork
} }
function upsert_clone() { function git_clone() {
local direction=$1 ref="$2" clone=$3 local direction=$1 url=$2
if ! test -d $TMPDIR/$direction; then 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 fi
( (
cd $TMPDIR/$direction 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 credential.helper "store --file=$TMPDIR/$direction.git-credentials"
git config user.email cascading-pr@example.com git config user.email cascading-pr@example.com
git config user.name cascading-pr 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() { function sha_pushed() {
local direction=$1 local direction=$1
if test -f $TMPDIR/$direction.sha ; then if test -f $TMPDIR/$direction.sha ; then
@ -193,13 +212,13 @@ function sha_pushed() {
} }
function push() { function push() {
local direction=$1 branch=$2 clone=$3 local direction=$1 remote=$2 branch=$3
( (
cd $TMPDIR/$direction cd $TMPDIR/$direction
git add . git add .
if git commit -m 'cascading-pr update'; then 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 git rev-parse HEAD > ../$direction.sha
log_info "pushed" log_info "pushed"
else else
@ -214,9 +233,53 @@ function wait_destination_ci() {
wait_success $repo_api $sha 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() { function update() {
upsert_clone origin "${options[origin_head]}" ${options[origin_clone]}
upsert_clone destination "${options[destination_head]}" ${options[destination_clone]}
( (
local update=${options[update]} local update=${options[update]}
if ! [[ "$update" =~ ^/ ]] ; then if ! [[ "$update" =~ ^/ ]] ; then
@ -234,7 +297,11 @@ function update() {
cd $TMPDIR cd $TMPDIR
$update $TMPDIR/destination $TMPDIR/destination-pr.json $TMPDIR/origin $TMPDIR/origin-pr.json $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() { function set_clone() {
@ -254,12 +321,22 @@ function set_clone() {
options[${direction}_clone]=${options[${direction}_scheme]}://${options[${direction}_host_port]}/${options[${direction}_repo]} 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() { function finalize_options() {
options[origin_api]=${options[origin_url]}/api/v1/repos/${options[origin_repo]} options[origin_api]=${options[origin_url]}/api/v1/repos/${options[origin_repo]}
options[origin_scheme]=$(scheme ${options[origin_url]}) options[origin_scheme]=$(scheme ${options[origin_url]})
options[origin_host_port]=$(host_port ${options[origin_url]}) options[origin_host_port]=$(host_port ${options[origin_url]})
set_clone origin set_clone origin
options[origin_head]=refs/pull/${options[origin_pr]}/head options[origin_head]=refs/pull/${options[origin_pr]}/head
options[destination_api]=${options[destination_url]}/api/v1/repos/${options[destination_repo]} options[destination_api]=${options[destination_url]}/api/v1/repos/${options[destination_repo]}
options[destination_scheme]=$(scheme ${options[destination_url]}) options[destination_scheme]=$(scheme ${options[destination_url]})
options[destination_host_port]=$(host_port ${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[destination_base]=${options[destination_branch]}
: ${options[prefix]:=${options[origin_repo]}} : ${options[prefix]:=${options[origin_repo]}}
options[destination_head]=${options[prefix]}-${options[origin_pr]} 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} : ${options[close_merge]:=false}
} }
@ -279,7 +364,7 @@ function run() {
case "$state" in case "$state" in
open) open)
log_info "PR is open, update or create the cascade branch and PR" log_info "PR is open, update or create the cascade branch and PR"
upsert_destination_branch checkout
update update
local sha=$(sha_pushed destination) local sha=$(sha_pushed destination)
if test "$sha" ; then if test "$sha" ; then
@ -297,6 +382,7 @@ function run() {
log_info "PR was merged, update the cascade PR" log_info "PR was merged, update the cascade PR"
pr_get origin pr_get origin
pr_get destination pr_get destination
checkout
update update
fi fi
else else
@ -351,6 +437,11 @@ function main() {
options[destination_repo]=$1 options[destination_repo]=$1
shift shift
;; ;;
--destination-fork-repo)
shift
options[destination_fork_repo]=$1
shift
;;
--destination-token) --destination-token)
shift shift
options[destination_token]=$1 options[destination_token]=$1