From 8bd3027c54c40124e35b36b9e5556328b18244b2 Mon Sep 17 00:00:00 2001 From: liuzidi Date: Tue, 1 Sep 2026 19:30:13 +0800 Subject: [PATCH 1/4] ci: mirror pull requests to GitCode Keep the GitCode mirror for hw-native-sys/PTOAS synchronized from GitHub PR updates. --- .github/workflows/mirror-pr-to-gitcode.yml | 201 +++++++++++++++++++++ docs/gitcode-pr-mirror.md | 52 ++++++ 2 files changed, 253 insertions(+) create mode 100644 .github/workflows/mirror-pr-to-gitcode.yml create mode 100644 docs/gitcode-pr-mirror.md diff --git a/.github/workflows/mirror-pr-to-gitcode.yml b/.github/workflows/mirror-pr-to-gitcode.yml new file mode 100644 index 0000000000..1f605fa101 --- /dev/null +++ b/.github/workflows/mirror-pr-to-gitcode.yml @@ -0,0 +1,201 @@ +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software; you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + +name: Mirror PR to GitCode + +on: + pull_request_target: + types: [opened, synchronize, reopened] + workflow_dispatch: + inputs: + pr_number: + description: GitHub PR number to mirror + required: true + type: string + +permissions: + contents: read + pull-requests: read + +concurrency: + group: gitcode-mirror-${{ github.event.pull_request.number || inputs.pr_number }} + cancel-in-progress: true + +env: + GH_TOKEN: ${{ github.token }} + GITCODE_API: https://gitcode.com/api/v5 + GITCODE_UPSTREAM: cann/pto-as + GITCODE_FORK: lllzzzddd/pto-as + GITCODE_TARGET_BRANCH: master + GITCODE_SOURCE_PREFIX: mirror/github-pr- + +jobs: + mirror: + runs-on: ubuntu-22.04 + steps: + - name: Read pull request metadata + env: + EVENT_PR_NUMBER: ${{ github.event.pull_request.number }} + INPUT_PR_NUMBER: ${{ inputs.pr_number }} + shell: bash + run: | + set -euo pipefail + + pr_number="${EVENT_PR_NUMBER:-${INPUT_PR_NUMBER:-}}" + if [[ ! "$pr_number" =~ ^[0-9]+$ ]]; then + echo "Invalid pull request number." >&2 + exit 1 + fi + + metadata="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}")" + base_ref="$(jq -r '.base.ref' <<< "$metadata")" + head_sha="$(jq -r '.head.sha' <<< "$metadata")" + author_login="$(jq -r '.user.login' <<< "$metadata")" + author_id="$(jq -r '.user.id' <<< "$metadata")" + source_repo="$(jq -r '.head.repo.full_name // empty' <<< "$metadata")" + + if [[ "$base_ref" != "main" || ! "$head_sha" =~ ^[0-9a-f]{40}$ || + ! "$author_login" =~ ^[A-Za-z0-9-]+$ || ! "$author_id" =~ ^[0-9]+$ ]]; then + echo "Unsupported pull request metadata." >&2 + exit 1 + fi + if [[ -z "$source_repo" || ! "$source_repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then + echo "The pull request has no valid source repository." >&2 + exit 1 + fi + + { + echo "PR_NUMBER=$pr_number" + echo "GITHUB_BASE_REF=$base_ref" + echo "GITHUB_HEAD_SHA=$head_sha" + echo "PR_AUTHOR_LOGIN=$author_login" + echo "PR_AUTHOR_ID=$author_id" + echo "GITHUB_SOURCE_REPOSITORY=$source_repo" + echo "MIRROR_BRANCH=${GITCODE_SOURCE_PREFIX}${pr_number}" + } >> "$GITHUB_ENV" + + - name: Generate pull request patch + env: + shell: bash + run: | + set -euo pipefail + + github_dir="${RUNNER_TEMP}/github" + mkdir -p "$github_dir" + git -C "$github_dir" init -q + git -C "$github_dir" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" + git -C "$github_dir" fetch --no-tags --filter=blob:none origin \ + "+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/base" \ + "+refs/pull/${PR_NUMBER}/head:refs/remotes/origin/pr-head" + + actual_head="$(git -C "$github_dir" rev-parse refs/remotes/origin/pr-head)" + if [[ "$actual_head" != "$GITHUB_HEAD_SHA" ]]; then + echo "The pull request changed while it was being read." >&2 + exit 1 + fi + + merge_base="$(git -C "$github_dir" merge-base \ + refs/remotes/origin/base refs/remotes/origin/pr-head)" + git -C "$github_dir" -c diff.renames=false diff \ + --no-ext-diff --no-textconv --binary --full-index \ + "$merge_base" refs/remotes/origin/pr-head > "${RUNNER_TEMP}/github-pr.patch" + + - name: Configure GitCode SSH + env: + GITCODE_SSH_KEY: ${{ secrets.GITCODE_MIRROR_SSH_KEY }} + GITCODE_KNOWN_HOSTS: ${{ secrets.GITCODE_KNOWN_HOSTS }} + shell: bash + run: | + set -euo pipefail + if [[ -z "$GITCODE_SSH_KEY" || -z "$GITCODE_KNOWN_HOSTS" ]]; then + echo "GitCode SSH secrets are not configured." >&2 + exit 1 + fi + + install -d -m 700 "$HOME/.ssh" + printf '%s\n' "$GITCODE_SSH_KEY" > "$HOME/.ssh/gitcode_mirror" + printf '%s\n' "$GITCODE_KNOWN_HOSTS" > "$HOME/.ssh/known_hosts" + chmod 600 "$HOME/.ssh/gitcode_mirror" "$HOME/.ssh/known_hosts" + { + echo "GIT_SSH_COMMAND=ssh -i $HOME/.ssh/gitcode_mirror -o IdentitiesOnly=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts" + } >> "$GITHUB_ENV" + + - name: Update GitCode fork branch + env: + GIT_AUTHOR_NAME: lllzzzddd + GIT_AUTHOR_EMAIL: liuzidi1@huawei.com + GIT_COMMITTER_NAME: lllzzzddd + GIT_COMMITTER_EMAIL: liuzidi1@huawei.com + shell: bash + run: | + set -euo pipefail + + gitcode_dir="${RUNNER_TEMP}/gitcode" + git clone -q --single-branch --branch "$GITCODE_TARGET_BRANCH" \ + "git@gitcode.com:${GITCODE_FORK}.git" "$gitcode_dir" + + git -C "$gitcode_dir" checkout -q -B "$MIRROR_BRANCH" "origin/$GITCODE_TARGET_BRANCH" + + old_remote="$(git ls-remote origin "refs/heads/${MIRROR_BRANCH}" | cut -f1)" + git -C "$gitcode_dir" apply --check --binary "${RUNNER_TEMP}/github-pr.patch" + git -C "$gitcode_dir" apply --index --binary "${RUNNER_TEMP}/github-pr.patch" + + if ! git -C "$gitcode_dir" diff --cached --quiet; then + git -C "$gitcode_dir" commit -q \ + -m "$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json title --jq .title)" \ + -m "Mirrored from https://github.com/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}" \ + -m "GitHub-Head: ${GITHUB_HEAD_SHA}" + fi + + git -C "$gitcode_dir" push \ + --force-with-lease="refs/heads/${MIRROR_BRANCH}:${old_remote}" \ + origin "HEAD:refs/heads/${MIRROR_BRANCH}" + + - name: Create GitCode pull request if needed + env: + GITCODE_TOKEN: ${{ secrets.GITCODE_MIRROR_TOKEN }} + PR_TITLE: ${{ github.event.pull_request.title }} + shell: bash + run: | + set -euo pipefail + if [[ -z "$GITCODE_TOKEN" ]]; then + echo "GITCODE_MIRROR_TOKEN is not configured." >&2 + exit 1 + fi + + pulls_file="${RUNNER_TEMP}/gitcode-pulls.json" + curl --fail-with-body --silent --show-error --retry 3 \ + -H "Authorization: Bearer ${GITCODE_TOKEN}" \ + -H 'Accept: application/json' \ + "${GITCODE_API}/repos/${GITCODE_UPSTREAM}/pulls?state=all&per_page=100" \ + > "$pulls_file" + + existing_url="$(jq -r --arg branch "$MIRROR_BRANCH" --arg fork "$GITCODE_FORK" \ + '.[] | select(.head.ref == $branch and .head.repo.full_name == $fork and .state == "open") | .html_url' \ + "$pulls_file" | head -n 1)" + if [[ -n "$existing_url" ]]; then + echo "GitCode pull request already exists: $existing_url" + exit 0 + fi + + body="Mirrored from: https://github.com/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}\n\nGitHub head SHA: ${GITHUB_HEAD_SHA}\n\nThis branch is maintained from the GitHub PR and should not be edited directly." + payload="$(jq -n \ + --arg title "[GitHub #${PR_NUMBER}] ${PR_TITLE:-GitHub pull request}" \ + --arg body "$body" \ + --arg head "${GITCODE_FORK%%/*}:${MIRROR_BRANCH}" \ + --arg base "$GITCODE_TARGET_BRANCH" \ + '{title: $title, body: $body, head: $head, base: $base}')" + response="${RUNNER_TEMP}/gitcode-pr.json" + curl --fail-with-body --silent --show-error --retry 3 \ + -X POST \ + -H "Authorization: Bearer ${GITCODE_TOKEN}" \ + -H 'Accept: application/json' \ + -H 'Content-Type: application/json' \ + "${GITCODE_API}/repos/${GITCODE_UPSTREAM}/pulls" \ + --data "$payload" > "$response" + jq -r '"GitCode pull request: " + (.html_url // .web_url)' "$response" diff --git a/docs/gitcode-pr-mirror.md b/docs/gitcode-pr-mirror.md new file mode 100644 index 0000000000..791c9f305a --- /dev/null +++ b/docs/gitcode-pr-mirror.md @@ -0,0 +1,52 @@ +# GitCode PR mirror + +This repository mirrors GitHub pull requests to the GitCode upstream project +`cann/pto-as`. GitHub `main` maps to GitCode `master`. + +## One-time administrator setup + +The workflow is `.github/workflows/mirror-pr-to-gitcode.yml`. It runs from the +trusted default branch with `pull_request_target`; it reads pull request Git +objects and never executes code from the pull request. + +Configure these GitHub Actions secrets: + +- `GITCODE_MIRROR_SSH_KEY`: a write-enabled key for the `lllzzzddd/pto-as` + fork. It must not be allowed to push `master`. +- `GITCODE_KNOWN_HOSTS`: the verified SSH host key for `gitcode.com`. +- `GITCODE_MIRROR_TOKEN`: a GitCode token owned by `lllzzzddd` with permission + to read pull requests and create them in `cann/pto-as`. + +The GitCode commit identity is intentionally fixed to the required account: + +```text +lllzzzddd +``` + +Use the verified email configured for that account if it changes. + +## Operation + +For each GitHub pull request targeting `main`, the workflow maintains this +branch in the GitCode fork: + +```text +mirror/github-pr- +``` + +It creates one GitCode pull request from that branch to +`cann/pto-as:master`. Later GitHub pushes update the same branch, so the +existing GitCode pull request is updated automatically. + +To mirror an already-open pull request, run the workflow manually and enter +its GitHub pull request number. + +If applying the patch fails, the workflow stops without pushing a new mirror +commit. Resolve the divergence in the GitHub pull request and run it again. + +## Security requirements + +Do not change this workflow to check out or execute the pull request head. A +fork pull request is untrusted input, while the workflow has access to the +GitCode credentials. Rotate any token that has been exposed and keep all +credentials in GitHub Actions secrets only. From b5bcdd53d2023ef2a756c929f58862e1223b0a04 Mon Sep 17 00:00:00 2001 From: liuzidi Date: Tue, 1 Sep 2026 20:15:57 +0800 Subject: [PATCH 2/4] ci: make GitCode identity configurable --- .github/workflows/mirror-pr-to-gitcode.yml | 24 +++++++++++++++------- docs/gitcode-pr-mirror.md | 19 +++++++++++------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/.github/workflows/mirror-pr-to-gitcode.yml b/.github/workflows/mirror-pr-to-gitcode.yml index 1f605fa101..7ef5109500 100644 --- a/.github/workflows/mirror-pr-to-gitcode.yml +++ b/.github/workflows/mirror-pr-to-gitcode.yml @@ -30,7 +30,9 @@ env: GH_TOKEN: ${{ github.token }} GITCODE_API: https://gitcode.com/api/v5 GITCODE_UPSTREAM: cann/pto-as - GITCODE_FORK: lllzzzddd/pto-as + GITCODE_FORK_OWNER: ${{ vars.GITCODE_FORK_OWNER }} + GITCODE_FORK_REPOSITORY: ${{ vars.GITCODE_FORK_REPOSITORY || 'pto-as' }} + GITCODE_COMMIT_EMAIL: ${{ vars.GITCODE_COMMIT_EMAIL }} GITCODE_TARGET_BRANCH: master GITCODE_SOURCE_PREFIX: mirror/github-pr- @@ -80,7 +82,6 @@ jobs: } >> "$GITHUB_ENV" - name: Generate pull request patch - env: shell: bash run: | set -euo pipefail @@ -127,14 +128,23 @@ jobs: - name: Update GitCode fork branch env: - GIT_AUTHOR_NAME: lllzzzddd - GIT_AUTHOR_EMAIL: liuzidi1@huawei.com - GIT_COMMITTER_NAME: lllzzzddd - GIT_COMMITTER_EMAIL: liuzidi1@huawei.com + GIT_AUTHOR_NAME: ${{ vars.GITCODE_FORK_OWNER }} + GIT_AUTHOR_EMAIL: ${{ vars.GITCODE_COMMIT_EMAIL }} + GIT_COMMITTER_NAME: ${{ vars.GITCODE_FORK_OWNER }} + GIT_COMMITTER_EMAIL: ${{ vars.GITCODE_COMMIT_EMAIL }} shell: bash run: | set -euo pipefail + if [[ ! "$GITCODE_FORK_OWNER" =~ ^[A-Za-z0-9_.-]+$ || + ! "$GITCODE_FORK_REPOSITORY" =~ ^[A-Za-z0-9_.-]+$ || + ! "$GITCODE_COMMIT_EMAIL" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$ ]]; then + echo "GitCode fork owner, repository, or commit email is not configured." >&2 + exit 1 + fi + export GITCODE_FORK="${GITCODE_FORK_OWNER}/${GITCODE_FORK_REPOSITORY}" + echo "GITCODE_FORK=$GITCODE_FORK" >> "$GITHUB_ENV" + gitcode_dir="${RUNNER_TEMP}/gitcode" git clone -q --single-branch --branch "$GITCODE_TARGET_BRANCH" \ "git@gitcode.com:${GITCODE_FORK}.git" "$gitcode_dir" @@ -187,7 +197,7 @@ jobs: payload="$(jq -n \ --arg title "[GitHub #${PR_NUMBER}] ${PR_TITLE:-GitHub pull request}" \ --arg body "$body" \ - --arg head "${GITCODE_FORK%%/*}:${MIRROR_BRANCH}" \ + --arg head "${GITCODE_FORK_OWNER}:${MIRROR_BRANCH}" \ --arg base "$GITCODE_TARGET_BRANCH" \ '{title: $title, body: $body, head: $head, base: $base}')" response="${RUNNER_TEMP}/gitcode-pr.json" diff --git a/docs/gitcode-pr-mirror.md b/docs/gitcode-pr-mirror.md index 791c9f305a..c5d0690f20 100644 --- a/docs/gitcode-pr-mirror.md +++ b/docs/gitcode-pr-mirror.md @@ -11,16 +11,23 @@ objects and never executes code from the pull request. Configure these GitHub Actions secrets: -- `GITCODE_MIRROR_SSH_KEY`: a write-enabled key for the `lllzzzddd/pto-as` - fork. It must not be allowed to push `master`. +- `GITCODE_MIRROR_SSH_KEY`: a write-enabled key for the configured fork. It + must not be allowed to push `master`. - `GITCODE_KNOWN_HOSTS`: the verified SSH host key for `gitcode.com`. -- `GITCODE_MIRROR_TOKEN`: a GitCode token owned by `lllzzzddd` with permission - to read pull requests and create them in `cann/pto-as`. +- `GITCODE_MIRROR_TOKEN`: a GitCode token owned by the configured fork owner, + with permission to read pull requests and create them in `cann/pto-as`. -The GitCode commit identity is intentionally fixed to the required account: +Configure these repository variables (Settings → Secrets and variables → +Actions → Variables): + +- `GITCODE_FORK_OWNER`: the GitCode user or namespace that owns the fork. +- `GITCODE_FORK_REPOSITORY`: fork repository name; defaults to `pto-as`. +- `GITCODE_COMMIT_EMAIL`: a verified email for `GITCODE_FORK_OWNER`. + +The GitCode commit identity is taken from the configured fork owner and email: ```text -lllzzzddd + ``` Use the verified email configured for that account if it changes. From 9ad72d983a9af7fab4c33f1629fdd0c5a620b36d Mon Sep 17 00:00:00 2001 From: liuzidi Date: Tue, 1 Sep 2026 20:39:29 +0800 Subject: [PATCH 3/4] ci: support personal GitHub Fork mirrors Run the mirror from each contributor fork with personal GitCode credentials. --- .github/workflows/mirror-pr-to-gitcode.yml | 150 ++++++++++++--------- docs/gitcode-pr-mirror.md | 90 ++++++++----- 2 files changed, 137 insertions(+), 103 deletions(-) diff --git a/.github/workflows/mirror-pr-to-gitcode.yml b/.github/workflows/mirror-pr-to-gitcode.yml index 7ef5109500..fa226cab8e 100644 --- a/.github/workflows/mirror-pr-to-gitcode.yml +++ b/.github/workflows/mirror-pr-to-gitcode.yml @@ -9,75 +9,92 @@ name: Mirror PR to GitCode on: - pull_request_target: - types: [opened, synchronize, reopened] + push: + branches-ignore: + - main workflow_dispatch: inputs: pr_number: - description: GitHub PR number to mirror + description: Upstream GitHub PR number to mirror required: true type: string permissions: contents: read - pull-requests: read concurrency: - group: gitcode-mirror-${{ github.event.pull_request.number || inputs.pr_number }} + group: gitcode-mirror-${{ github.repository }}-${{ inputs.pr_number || github.ref_name }} cancel-in-progress: true env: GH_TOKEN: ${{ github.token }} + GITHUB_UPSTREAM_REPOSITORY: hw-native-sys/PTOAS + GITHUB_UPSTREAM_BRANCH: main GITCODE_API: https://gitcode.com/api/v5 GITCODE_UPSTREAM: cann/pto-as + GITCODE_TARGET_BRANCH: master + GITCODE_SOURCE_PREFIX: mirror/github-pr- GITCODE_FORK_OWNER: ${{ vars.GITCODE_FORK_OWNER }} GITCODE_FORK_REPOSITORY: ${{ vars.GITCODE_FORK_REPOSITORY || 'pto-as' }} GITCODE_COMMIT_EMAIL: ${{ vars.GITCODE_COMMIT_EMAIL }} - GITCODE_TARGET_BRANCH: master - GITCODE_SOURCE_PREFIX: mirror/github-pr- jobs: mirror: runs-on: ubuntu-22.04 steps: - - name: Read pull request metadata + - name: Resolve upstream pull request env: - EVENT_PR_NUMBER: ${{ github.event.pull_request.number }} INPUT_PR_NUMBER: ${{ inputs.pr_number }} + EVENT_BRANCH: ${{ github.ref_name }} shell: bash run: | set -euo pipefail - pr_number="${EVENT_PR_NUMBER:-${INPUT_PR_NUMBER:-}}" + if [[ -n "$INPUT_PR_NUMBER" ]]; then + pr_number="$INPUT_PR_NUMBER" + else + pr_number="" + fi if [[ ! "$pr_number" =~ ^[0-9]+$ ]]; then - echo "Invalid pull request number." >&2 - exit 1 + source_branch="$EVENT_BRANCH" + if [[ -z "$source_branch" || "$source_branch" =~ (^|/)(\.|\.\.)($|/) || + "$source_branch" == -* || "$source_branch" == */ || "$source_branch" == *' ' * || + ! "$source_branch" =~ ^[A-Za-z0-9._/-]+$ ]]; then + echo "The GitHub source branch is invalid." >&2 + exit 1 + fi + + encoded_head="${GITHUB_REPOSITORY_OWNER}:${source_branch}" + prs="$(gh api --method GET \ + "repos/${GITHUB_UPSTREAM_REPOSITORY}/pulls" \ + -f state=open -f base="$GITHUB_UPSTREAM_BRANCH" \ + -f head="$encoded_head" -f per_page=100)" + count="$(jq 'length' <<< "$prs")" + if [[ "$count" != "1" ]]; then + echo "Expected exactly one open upstream PR for ${GITHUB_REPOSITORY}:${source_branch}; found ${count}." >&2 + exit 1 + fi + pr_number="$(jq -r '.[0].number' <<< "$prs")" fi - metadata="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}")" + metadata="$(gh api "repos/${GITHUB_UPSTREAM_REPOSITORY}/pulls/${pr_number}")" base_ref="$(jq -r '.base.ref' <<< "$metadata")" head_sha="$(jq -r '.head.sha' <<< "$metadata")" - author_login="$(jq -r '.user.login' <<< "$metadata")" - author_id="$(jq -r '.user.id' <<< "$metadata")" source_repo="$(jq -r '.head.repo.full_name // empty' <<< "$metadata")" + source_branch="$(jq -r '.head.ref // empty' <<< "$metadata")" + pr_state="$(jq -r '.state' <<< "$metadata")" - if [[ "$base_ref" != "main" || ! "$head_sha" =~ ^[0-9a-f]{40}$ || - ! "$author_login" =~ ^[A-Za-z0-9-]+$ || ! "$author_id" =~ ^[0-9]+$ ]]; then - echo "Unsupported pull request metadata." >&2 - exit 1 - fi - if [[ -z "$source_repo" || ! "$source_repo" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then - echo "The pull request has no valid source repository." >&2 + if [[ "$pr_state" != "open" || "$base_ref" != "$GITHUB_UPSTREAM_BRANCH" || + ! "$head_sha" =~ ^[0-9a-f]{40}$ || "$source_repo" != "$GITHUB_REPOSITORY" || + -z "$source_branch" || ! "$source_branch" =~ ^[A-Za-z0-9._/-]+$ ]]; then + echo "The upstream PR does not belong to this GitHub fork or target main." >&2 exit 1 fi { echo "PR_NUMBER=$pr_number" - echo "GITHUB_BASE_REF=$base_ref" echo "GITHUB_HEAD_SHA=$head_sha" - echo "PR_AUTHOR_LOGIN=$author_login" - echo "PR_AUTHOR_ID=$author_id" - echo "GITHUB_SOURCE_REPOSITORY=$source_repo" + echo "GITHUB_SOURCE_BRANCH=$source_branch" echo "MIRROR_BRANCH=${GITCODE_SOURCE_PREFIX}${pr_number}" } >> "$GITHUB_ENV" @@ -89,22 +106,44 @@ jobs: github_dir="${RUNNER_TEMP}/github" mkdir -p "$github_dir" git -C "$github_dir" init -q - git -C "$github_dir" remote add origin "https://github.com/${GITHUB_REPOSITORY}.git" - git -C "$github_dir" fetch --no-tags --filter=blob:none origin \ - "+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/base" \ - "+refs/pull/${PR_NUMBER}/head:refs/remotes/origin/pr-head" - - actual_head="$(git -C "$github_dir" rev-parse refs/remotes/origin/pr-head)" + git -C "$github_dir" remote add upstream "https://github.com/${GITHUB_UPSTREAM_REPOSITORY}.git" + git -C "$github_dir" remote add fork "https://github.com/${GITHUB_REPOSITORY}.git" + git -C "$github_dir" -c http.extraHeader="Authorization: Bearer ${GH_TOKEN}" fetch \ + --no-tags --filter=blob:none upstream \ + "+refs/heads/${GITHUB_UPSTREAM_BRANCH}:refs/remotes/upstream/base" + git -C "$github_dir" -c http.extraHeader="Authorization: Bearer ${GH_TOKEN}" fetch \ + --no-tags --filter=blob:none fork \ + "+refs/heads/${GITHUB_SOURCE_BRANCH}:refs/remotes/fork/pr-head" + + actual_head="$(git -C "$github_dir" rev-parse refs/remotes/fork/pr-head)" if [[ "$actual_head" != "$GITHUB_HEAD_SHA" ]]; then - echo "The pull request changed while it was being read." >&2 + echo "The upstream PR changed while it was being read." >&2 exit 1 fi merge_base="$(git -C "$github_dir" merge-base \ - refs/remotes/origin/base refs/remotes/origin/pr-head)" + refs/remotes/upstream/base refs/remotes/fork/pr-head)" git -C "$github_dir" -c diff.renames=false diff \ --no-ext-diff --no-textconv --binary --full-index \ - "$merge_base" refs/remotes/origin/pr-head > "${RUNNER_TEMP}/github-pr.patch" + "$merge_base" refs/remotes/fork/pr-head > "${RUNNER_TEMP}/github-pr.patch" + + - name: Validate personal GitCode configuration + shell: bash + run: | + set -euo pipefail + if [[ ! "$GITCODE_FORK_OWNER" =~ ^[A-Za-z0-9_.-]+$ || + ! "$GITCODE_FORK_REPOSITORY" =~ ^[A-Za-z0-9_.-]+$ || + ! "$GITCODE_COMMIT_EMAIL" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$ ]]; then + echo "Configure GITCODE_FORK_OWNER, GITCODE_FORK_REPOSITORY, and GITCODE_COMMIT_EMAIL in this fork." >&2 + exit 1 + fi + if [[ -z "${{ secrets.GITCODE_MIRROR_SSH_KEY }}" || + -z "${{ secrets.GITCODE_KNOWN_HOSTS }}" || + -z "${{ secrets.GITCODE_MIRROR_TOKEN }}" ]]; then + echo "Configure the GitCode mirror secrets in this fork." >&2 + exit 1 + fi + echo "GITCODE_FORK=${GITCODE_FORK_OWNER}/${GITCODE_FORK_REPOSITORY}" >> "$GITHUB_ENV" - name: Configure GitCode SSH env: @@ -113,20 +152,13 @@ jobs: shell: bash run: | set -euo pipefail - if [[ -z "$GITCODE_SSH_KEY" || -z "$GITCODE_KNOWN_HOSTS" ]]; then - echo "GitCode SSH secrets are not configured." >&2 - exit 1 - fi - install -d -m 700 "$HOME/.ssh" printf '%s\n' "$GITCODE_SSH_KEY" > "$HOME/.ssh/gitcode_mirror" printf '%s\n' "$GITCODE_KNOWN_HOSTS" > "$HOME/.ssh/known_hosts" chmod 600 "$HOME/.ssh/gitcode_mirror" "$HOME/.ssh/known_hosts" - { - echo "GIT_SSH_COMMAND=ssh -i $HOME/.ssh/gitcode_mirror -o IdentitiesOnly=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts" - } >> "$GITHUB_ENV" + echo "GIT_SSH_COMMAND=ssh -i $HOME/.ssh/gitcode_mirror -o IdentitiesOnly=yes -o UserKnownHostsFile=$HOME/.ssh/known_hosts" >> "$GITHUB_ENV" - - name: Update GitCode fork branch + - name: Update personal GitCode fork branch env: GIT_AUTHOR_NAME: ${{ vars.GITCODE_FORK_OWNER }} GIT_AUTHOR_EMAIL: ${{ vars.GITCODE_COMMIT_EMAIL }} @@ -136,29 +168,20 @@ jobs: run: | set -euo pipefail - if [[ ! "$GITCODE_FORK_OWNER" =~ ^[A-Za-z0-9_.-]+$ || - ! "$GITCODE_FORK_REPOSITORY" =~ ^[A-Za-z0-9_.-]+$ || - ! "$GITCODE_COMMIT_EMAIL" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$ ]]; then - echo "GitCode fork owner, repository, or commit email is not configured." >&2 - exit 1 - fi - export GITCODE_FORK="${GITCODE_FORK_OWNER}/${GITCODE_FORK_REPOSITORY}" - echo "GITCODE_FORK=$GITCODE_FORK" >> "$GITHUB_ENV" - gitcode_dir="${RUNNER_TEMP}/gitcode" git clone -q --single-branch --branch "$GITCODE_TARGET_BRANCH" \ "git@gitcode.com:${GITCODE_FORK}.git" "$gitcode_dir" - git -C "$gitcode_dir" checkout -q -B "$MIRROR_BRANCH" "origin/$GITCODE_TARGET_BRANCH" - old_remote="$(git ls-remote origin "refs/heads/${MIRROR_BRANCH}" | cut -f1)" + old_remote="$(git -C "$gitcode_dir" ls-remote origin "refs/heads/${MIRROR_BRANCH}" | cut -f1)" git -C "$gitcode_dir" apply --check --binary "${RUNNER_TEMP}/github-pr.patch" git -C "$gitcode_dir" apply --index --binary "${RUNNER_TEMP}/github-pr.patch" if ! git -C "$gitcode_dir" diff --cached --quiet; then + pr_title="$(gh api "repos/${GITHUB_UPSTREAM_REPOSITORY}/pulls/${PR_NUMBER}" --jq .title)" git -C "$gitcode_dir" commit -q \ - -m "$(gh pr view "$PR_NUMBER" --repo "$GITHUB_REPOSITORY" --json title --jq .title)" \ - -m "Mirrored from https://github.com/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}" \ + -m "$pr_title" \ + -m "Mirrored from https://github.com/${GITHUB_UPSTREAM_REPOSITORY}/pull/${PR_NUMBER}" \ -m "GitHub-Head: ${GITHUB_HEAD_SHA}" fi @@ -169,21 +192,15 @@ jobs: - name: Create GitCode pull request if needed env: GITCODE_TOKEN: ${{ secrets.GITCODE_MIRROR_TOKEN }} - PR_TITLE: ${{ github.event.pull_request.title }} shell: bash run: | set -euo pipefail - if [[ -z "$GITCODE_TOKEN" ]]; then - echo "GITCODE_MIRROR_TOKEN is not configured." >&2 - exit 1 - fi pulls_file="${RUNNER_TEMP}/gitcode-pulls.json" curl --fail-with-body --silent --show-error --retry 3 \ -H "Authorization: Bearer ${GITCODE_TOKEN}" \ -H 'Accept: application/json' \ - "${GITCODE_API}/repos/${GITCODE_UPSTREAM}/pulls?state=all&per_page=100" \ - > "$pulls_file" + "${GITCODE_API}/repos/${GITCODE_UPSTREAM}/pulls?state=all&per_page=100" > "$pulls_file" existing_url="$(jq -r --arg branch "$MIRROR_BRANCH" --arg fork "$GITCODE_FORK" \ '.[] | select(.head.ref == $branch and .head.repo.full_name == $fork and .state == "open") | .html_url' \ @@ -193,9 +210,10 @@ jobs: exit 0 fi - body="Mirrored from: https://github.com/${GITHUB_REPOSITORY}/pull/${PR_NUMBER}\n\nGitHub head SHA: ${GITHUB_HEAD_SHA}\n\nThis branch is maintained from the GitHub PR and should not be edited directly." + pr_title="$(gh api "repos/${GITHUB_UPSTREAM_REPOSITORY}/pulls/${PR_NUMBER}" --jq .title)" + body="Mirrored from: https://github.com/${GITHUB_UPSTREAM_REPOSITORY}/pull/${PR_NUMBER}\n\nGitHub head SHA: ${GITHUB_HEAD_SHA}\n\nThis branch is maintained from the GitHub PR and should not be edited directly." payload="$(jq -n \ - --arg title "[GitHub #${PR_NUMBER}] ${PR_TITLE:-GitHub pull request}" \ + --arg title "[GitHub #${PR_NUMBER}] ${pr_title}" \ --arg body "$body" \ --arg head "${GITCODE_FORK_OWNER}:${MIRROR_BRANCH}" \ --arg base "$GITCODE_TARGET_BRANCH" \ diff --git a/docs/gitcode-pr-mirror.md b/docs/gitcode-pr-mirror.md index c5d0690f20..9201e2a0ef 100644 --- a/docs/gitcode-pr-mirror.md +++ b/docs/gitcode-pr-mirror.md @@ -1,59 +1,75 @@ # GitCode PR mirror -This repository mirrors GitHub pull requests to the GitCode upstream project -`cann/pto-as`. GitHub `main` maps to GitCode `master`. +This repository-specific workflow is designed for personal GitHub and GitCode +Forks. It mirrors pull requests from a personal GitHub Fork into the GitCode +upstream project `cann/pto-as`; GitHub `main` maps to GitCode `master`. -## One-time administrator setup +## How it works -The workflow is `.github/workflows/mirror-pr-to-gitcode.yml`. It runs from the -trusted default branch with `pull_request_target`; it reads pull request Git -objects and never executes code from the pull request. - -Configure these GitHub Actions secrets: - -- `GITCODE_MIRROR_SSH_KEY`: a write-enabled key for the configured fork. It - must not be allowed to push `master`. -- `GITCODE_KNOWN_HOSTS`: the verified SSH host key for `gitcode.com`. -- `GITCODE_MIRROR_TOKEN`: a GitCode token owned by the configured fork owner, - with permission to read pull requests and create them in `cann/pto-as`. +```text +Personal GitHub Fork branch push + ↓ +Find the matching open PR in hw-native-sys/PTOAS + ↓ +Compute the PR diff against hw-native-sys/PTOAS:main + ↓ +Push mirror/github-pr- to the personal GitCode Fork + ↓ +Create or reuse a PR to cann/pto-as:master +``` -Configure these repository variables (Settings → Secrets and variables → -Actions → Variables): +The workflow synchronizes the final diff instead of cherry-picking commits, so +the two repositories do not need identical Git histories. Later pushes to the +same GitHub branch update the same GitCode mirror branch and PR. -- `GITCODE_FORK_OWNER`: the GitCode user or namespace that owns the fork. -- `GITCODE_FORK_REPOSITORY`: fork repository name; defaults to `pto-as`. -- `GITCODE_COMMIT_EMAIL`: a verified email for `GITCODE_FORK_OWNER`. +## Personal setup -The GitCode commit identity is taken from the configured fork owner and email: +Each contributor must first create both Forks: ```text - +GitHub: /PTOAS +GitCode: /pto-as ``` -Use the verified email configured for that account if it changes. +Copy this workflow into the personal GitHub Fork's default branch and enable +Actions. Configure these repository variables in that Fork: -## Operation +- `GITCODE_FORK_OWNER`: the GitCode user or namespace that owns the Fork. +- `GITCODE_FORK_REPOSITORY`: the Fork repository name; normally `pto-as`. +- `GITCODE_COMMIT_EMAIL`: a verified email for the GitCode account. -For each GitHub pull request targeting `main`, the workflow maintains this -branch in the GitCode fork: +Configure these repository secrets in the same GitHub Fork: + +- `GITCODE_MIRROR_SSH_KEY`: a write-enabled SSH key for the personal GitCode + Fork; it must not be allowed to push `master`. +- `GITCODE_KNOWN_HOSTS`: the verified SSH host key for `gitcode.com`. +- `GITCODE_MIRROR_TOKEN`: a GitCode token for reading and creating PRs in + `cann/pto-as`. + +The mirror commit author and committer are set to: ```text -mirror/github-pr- + ``` -It creates one GitCode pull request from that branch to -`cann/pto-as:master`. Later GitHub pushes update the same branch, so the -existing GitCode pull request is updated automatically. +## Daily usage + +1. Create a GitHub PR from the personal Fork to `hw-native-sys/PTOAS:main`. +2. Push to the PR branch in the personal GitHub Fork. +3. The `push` workflow finds the matching upstream PR and mirrors it. +4. Continue pushing to that GitHub branch; do not edit the GitCode mirror branch. -To mirror an already-open pull request, run the workflow manually and enter -its GitHub pull request number. +For an existing PR or a retry, open Actions in the personal GitHub Fork, run +`Mirror PR to GitCode`, and enter the upstream PR number. -If applying the patch fails, the workflow stops without pushing a new mirror -commit. Resolve the divergence in the GitHub pull request and run it again. +The matching upstream PR must be open, target `main`, and have its head repo +equal to the personal GitHub Fork. If no unique match is found, the workflow +stops without changing GitCode. ## Security requirements -Do not change this workflow to check out or execute the pull request head. A -fork pull request is untrusted input, while the workflow has access to the -GitCode credentials. Rotate any token that has been exposed and keep all -credentials in GitHub Actions secrets only. +This workflow does not check out or execute the pull request source code. It +only reads Git objects, computes a patch, and uses credentials from the +personal Fork's own Actions secrets. Keep all credentials in secrets, rotate +tokens that have been exposed, and never allow the mirror key to push the +GitCode protected branch. From d9904613128913939971611c5a2fc2364c4bae94 Mon Sep 17 00:00:00 2001 From: liuzidi Date: Wed, 2 Sep 2026 11:41:16 +0800 Subject: [PATCH 4/4] ci: test GitCode mirror on test branch --- .github/workflows/mirror-pr-to-gitcode.yml | 4 ++-- docs/gitcode-pr-mirror.md | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/mirror-pr-to-gitcode.yml b/.github/workflows/mirror-pr-to-gitcode.yml index fa226cab8e..64d1c67d3d 100644 --- a/.github/workflows/mirror-pr-to-gitcode.yml +++ b/.github/workflows/mirror-pr-to-gitcode.yml @@ -29,7 +29,7 @@ concurrency: env: GH_TOKEN: ${{ github.token }} GITHUB_UPSTREAM_REPOSITORY: hw-native-sys/PTOAS - GITHUB_UPSTREAM_BRANCH: main + GITHUB_UPSTREAM_BRANCH: ${{ vars.GITHUB_UPSTREAM_BRANCH || 'test' }} GITCODE_API: https://gitcode.com/api/v5 GITCODE_UPSTREAM: cann/pto-as GITCODE_TARGET_BRANCH: master @@ -87,7 +87,7 @@ jobs: if [[ "$pr_state" != "open" || "$base_ref" != "$GITHUB_UPSTREAM_BRANCH" || ! "$head_sha" =~ ^[0-9a-f]{40}$ || "$source_repo" != "$GITHUB_REPOSITORY" || -z "$source_branch" || ! "$source_branch" =~ ^[A-Za-z0-9._/-]+$ ]]; then - echo "The upstream PR does not belong to this GitHub fork or target main." >&2 + echo "The upstream PR does not belong to this GitHub fork or target branch." >&2 exit 1 fi diff --git a/docs/gitcode-pr-mirror.md b/docs/gitcode-pr-mirror.md index 9201e2a0ef..7da7b14d89 100644 --- a/docs/gitcode-pr-mirror.md +++ b/docs/gitcode-pr-mirror.md @@ -2,7 +2,9 @@ This repository-specific workflow is designed for personal GitHub and GitCode Forks. It mirrors pull requests from a personal GitHub Fork into the GitCode -upstream project `cann/pto-as`; GitHub `main` maps to GitCode `master`. +upstream project `cann/pto-as`; GitHub `test` maps to GitCode `master` by +default while this workflow is being validated. Set the repository variable +`GITHUB_UPSTREAM_BRANCH=main` when the workflow is ready for production use. ## How it works @@ -11,7 +13,7 @@ Personal GitHub Fork branch push ↓ Find the matching open PR in hw-native-sys/PTOAS ↓ -Compute the PR diff against hw-native-sys/PTOAS:main +Compute the PR diff against the configured upstream branch ↓ Push mirror/github-pr- to the personal GitCode Fork ↓ @@ -54,7 +56,8 @@ The mirror commit author and committer are set to: ## Daily usage -1. Create a GitHub PR from the personal Fork to `hw-native-sys/PTOAS:main`. +1. Create a GitHub PR from the personal Fork to the configured upstream branch + (`test` by default; set `GITHUB_UPSTREAM_BRANCH=main` for production). 2. Push to the PR branch in the personal GitHub Fork. 3. The `push` workflow finds the matching upstream PR and mirrors it. 4. Continue pushing to that GitHub branch; do not edit the GitCode mirror branch. @@ -62,9 +65,9 @@ The mirror commit author and committer are set to: For an existing PR or a retry, open Actions in the personal GitHub Fork, run `Mirror PR to GitCode`, and enter the upstream PR number. -The matching upstream PR must be open, target `main`, and have its head repo -equal to the personal GitHub Fork. If no unique match is found, the workflow -stops without changing GitCode. +The matching upstream PR must be open, target the configured upstream branch, +and have its head repo equal to the personal GitHub Fork. If no unique match is +found, the workflow stops without changing GitCode. ## Security requirements