This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/trafficserver-ci.git


The following commit(s) were added to refs/heads/main by this push:
     new 49af623  Add smart HTTP Git mirror service (#436)
49af623 is described below

commit 49af6232d2f7fcacaba610e5b8218467e2d754ce
Author: Brian Neradt <[email protected]>
AuthorDate: Wed Jun 10 18:25:49 2026 -0500

    Add smart HTTP Git mirror service (#436)
    
    Jenkins mirror clones are now served through a static HTTP export, which
    forces CI fanout jobs to download dumb HTTP packs and makes PR checkouts
    depend on broad ref fetches. That path can turn the controller into a
    bottleneck even when the local mirror is fresh.
    
    This adds a dedicated smart HTTP container and systemd unit that run
    git-http-backend on 127.0.0.1:9417 behind the existing /mirror/ URLs. The
    controller installer starts the service, mirror initialization disables
    receive-pack, and the runbook documents rollout, verification, and
    rollback.
    
    This narrows PR child job refspecs to the target branch and current PR
    head/merge refs with honorRefspec enabled. The mirror check scripts now
    verify PR merge refs and can compare the mirrored head against
    GITHUB_PR_HEAD_SHA.
---
 github-mirror/README.md                            | 316 ++++++++++++++++++---
 .../ats/mirror-smart-http-remap-snippet.config     |   7 +
 github-mirror/bin/check-docker-access.sh           |  27 +-
 github-mirror/bin/check-mirror.sh                  |  38 ++-
 github-mirror/bin/init-mirrors.sh                  |   2 +
 github-mirror/bin/install-controller.sh            |  13 +
 github-mirror/httpd/Dockerfile                     |  11 +
 github-mirror/httpd/docker-compose.yml             |  13 +
 github-mirror/httpd/mirror.conf                    |  28 +-
 .../systemd/github-mirror-smart-http.service       |  20 ++
 jenkins/github/autest.pipeline                     |   4 +-
 jenkins/github/centos.pipeline                     |   4 +-
 jenkins/github/clang-analyzer.pipeline             |   4 +-
 jenkins/github/debian.pipeline                     |   4 +-
 jenkins/github/docs.pipeline                       |   4 +-
 jenkins/github/fedora.pipeline                     |   4 +-
 jenkins/github/format.pipeline                     |   4 +-
 jenkins/github/freebsd.pipeline                    |   4 +-
 jenkins/github/osx.pipeline                        |   4 +-
 jenkins/github/rat.pipeline                        |   4 +-
 jenkins/github/rocky-asan.pipeline                 |   4 +-
 jenkins/github/rocky.pipeline                      |   4 +-
 jenkins/github/ubuntu.pipeline                     |   4 +-
 23 files changed, 457 insertions(+), 70 deletions(-)

diff --git a/github-mirror/README.md b/github-mirror/README.md
index ec09a39..e8acb61 100644
--- a/github-mirror/README.md
+++ b/github-mirror/README.md
@@ -32,12 +32,33 @@ https://ci.trafficserver.apache.org/github-mirror-webhook
   v
 /home/mirror/trafficserver.git
 /home/mirror/trafficserver-ci.git
+  ^
+  | read-only bind mount
   |
-  | httpd export under /mirror/ plus git-daemon on 9418
+127.0.0.1:9417/mirror/
+  |
+  | dedicated httpd container running git-http-backend
+  v
+https://ci.trafficserver.apache.org/mirror/
+  |
+  | ATS remap, cache disabled
   v
 Jenkins controller and docker agents
 ```
 
+The supported Jenkins serving path is smart Git HTTP behind ATS. The public
+URLs stay under `https://ci.trafficserver.apache.org/mirror/`, but ATS remaps
+that path to a dedicated controller-local httpd container on `127.0.0.1:9417`.
+The container runs `git-http-backend` and mounts `/home/mirror` read-only.
+
+Static dumb HTTP is not acceptable for the Jenkins fanout path. It cannot
+negotiate packs with the client, so many child jobs can repeatedly download
+large static pack files and probe missing loose objects. Smart HTTP uses
+`git-upload-pack` so each clone/fetch gets a negotiated pack.
+
+`git-daemon` on port 9418 is kept only as a diagnostic or emergency fallback.
+Do not use `git://` URLs as the normal Jenkins configuration.
+
 The webhook service only accepts signed GitHub payloads for:
 
 - `apache/trafficserver`
@@ -83,15 +104,16 @@ Repositories and events:
 Rationale:
   Our Jenkins jobs run on a fleet of docker hosts behind the controller. The
   jobs currently clone repeatedly from GitHub. We are moving those checkouts to
-  a local read-only mirror on the controller. The webhook keeps branch and pull
-  request refs current before Jenkins fans work out to the docker hosts.
+  a local read-only smart HTTP mirror on the controller. The webhook keeps
+  branch and pull request refs current before Jenkins fans work out to the
+  docker hosts.
 
 Thanks.
 ```
 
 ## Fresh Controller Install
 
-These steps assume Ubuntu and a controller that will serve
+These steps assume Ubuntu and a controller that serves
 `ci.trafficserver.apache.org` through ATS.
 
 1. Clone or copy `trafficserver-ci` onto the controller.
@@ -109,13 +131,17 @@ These steps assume Ubuntu and a controller that will serve
 
    The installer:
 
-   - installs `git`, `git-daemon-sysvinit`, `python3`, and `util-linux`;
+   - installs `git`, `git-daemon-sysvinit`, `docker.io`, `docker-compose`,
+     `python3`, and `util-linux`;
    - installs this package to `/opt/trafficserver-ci/github-mirror`;
    - creates/configures `/home/mirror`;
    - creates `/home/mirror/trafficserver.git`;
    - creates `/home/mirror/trafficserver-ci.git`;
+   - configures both bare repos with `http.uploadpack=true` and
+     `http.receivepack=false`;
    - installs systemd units;
    - installs `/etc/default/git-daemon`;
+   - enables the smart HTTP container service;
    - enables the fallback refresh timer.
 
 3. Install the GitHub webhook secret.
@@ -138,7 +164,7 @@ These steps assume Ubuntu and a controller that will serve
    sudo chmod 0600 /etc/trafficserver-github-mirror/github-mirror-webhook.env
    ```
 
-4. Configure the HTTPS webhook endpoint in ATS.
+4. Configure ATS remaps.
 
    Add `github-mirror/ats/remap-snippet.config` before the generic
    `ci.trafficserver.apache.org` Jenkins remap in:
@@ -147,38 +173,140 @@ These steps assume Ubuntu and a controller that will 
serve
    /opt/ats/etc/trafficserver/remap.config
    ```
 
+   Add or update the `/mirror/` remap with
+   `github-mirror/ats/mirror-smart-http-remap-snippet.config`. The important
+   change is:
+
+   ```text
+   https://ci.trafficserver.apache.org/mirror/ -> http://localhost:9417/mirror/
+   ```
+
+   Keep `proxy.config.http.cache.http=0`. Keep `hdr_rw_git.config` unless
+   testing proves it interferes with smart Git POSTs. Remove the mirror purge
+   plugin from this remap. Do not change the docs httpd/container remaps.
+
    Reload ATS:
 
    ```bash
    sudo /opt/ats/bin/traffic_ctl config reload
    ```
 
-5. Export `/home/mirror` as `/mirror/`.
+5. Verify the smart HTTP service.
 
-   If the controller already has httpd serving `/mirror/`, keep that setup.
-   For a fresh controller, use `github-mirror/httpd/mirror.conf` as the
-   reference config for the httpd instance behind ATS. The updater runs
-   `git update-server-info`, so a static HTTP export is sufficient.
+   ```bash
+   sudo systemctl status github-mirror-smart-http.service
+
+   cd /opt/trafficserver-ci/github-mirror/httpd
+   sudo docker-compose config
+   sudo docker exec github-mirror-smart-http httpd -t
+
+   git ls-remote http://127.0.0.1:9417/mirror/trafficserver.git 
refs/heads/master
+   git ls-remote http://127.0.0.1:9417/mirror/trafficserver-ci.git 
refs/heads/main
+   ```
 
-6. Start the webhook receiver.
+6. Start the webhook receiver after the secret is installed.
 
    ```bash
    sudo systemctl restart github-mirror-webhook.service
    sudo systemctl status github-mirror-webhook.service
    ```
 
-7. Confirm the timer and git-daemon are active.
+7. Confirm the timer and diagnostic git-daemon are active.
 
    ```bash
    systemctl list-timers github-mirror-fallback.timer
    sudo service git-daemon status
    ```
 
+8. Verify the public HTTPS mirror and at least one docker host.
+
+   ```bash
+   /opt/trafficserver-ci/github-mirror/bin/check-mirror.sh --pr 
<open-pr-number>
+
+   CONTROLLER=- \
+     /opt/trafficserver-ci/github-mirror/bin/check-docker-access.sh \
+       --pr <open-pr-number> docker12
+   ```
+
+   To verify the exact PR head Jenkins is about to build:
+
+   ```bash
+   GITHUB_PR_HEAD_SHA=<sha-from-jenkins-or-github> \
+     /opt/trafficserver-ci/github-mirror/bin/check-mirror.sh --pr 
<open-pr-number>
+   ```
+
+## Existing Controller Rollout
+
+1. Commit and merge the repo changes.
+
+2. Pull the merged branch into `/opt/trafficserver-ci` on `controller`.
+
+   ```bash
+   ssh controller
+   cd /opt/trafficserver-ci
+   sudo git pull --ff-only
+   ```
+
+3. Install or refresh the controller files.
+
+   ```bash
+   sudo github-mirror/bin/install-controller.sh
+   ```
+
+   If ASF webhooks are not ready yet, use the interim cron rollout below.
+
+4. Build/start the smart HTTP service and validate httpd.
+
+   ```bash
+   sudo systemctl enable --now github-mirror-smart-http.service
+   cd /opt/trafficserver-ci/github-mirror/httpd
+   sudo docker-compose config
+   sudo docker exec github-mirror-smart-http httpd -t
+   ```
+
+5. Update the ATS `/mirror/` remap to point at `http://localhost:9417/mirror/`.
+   Keep cache disabled and remove the mirror purge plugin from this remap.
+
+   ```bash
+   sudo /opt/ats/bin/traffic_ctl config reload
+   ```
+
+6. Verify from controller and at least one docker host.
+
+   ```bash
+   /opt/trafficserver-ci/github-mirror/bin/check-mirror.sh --pr 
<open-pr-number>
+
+   CONTROLLER=- \
+     /opt/trafficserver-ci/github-mirror/bin/check-docker-access.sh \
+       --pr <open-pr-number> docker12
+   ```
+
+7. Confirm the smart HTTP logs show Git requests instead of dumb HTTP object
+   probing.
+
+   ```bash
+   sudo tail -f /var/log/github-mirror-smart-http/access_log
+   ```
+
+   Healthy Jenkins clones should include `git-upload-pack` requests. They 
should
+   not produce thousands of loose-object 404s.
+
+8. Run a small PR fanout subset before broadening:
+
+   ```text
+   docs
+   rocky
+   one autest shard
+   ```
+
+   Also confirm existing docs URLs still work through the existing docs
+   container.
+
 ## Interim Cron Rollout
 
 Use this section while ASF Infra is still setting up the GitHub webhooks. The
-cron updater keeps the mirrors fresh enough for Jenkins by fetching heads, 
tags,
-and ATS pull request refs every five minutes.
+temporary cron updater fetches heads, tags, and ATS pull request refs every
+minute.
 
 1. Install the current `trafficserver-ci` branch on `controller`.
 
@@ -201,7 +329,9 @@ and ATS pull request refs every five minutes.
 
    This initializes `/home/mirror/trafficserver.git` and
    `/home/mirror/trafficserver-ci.git`, installs the scripts under
-   `/opt/trafficserver-ci/github-mirror`, and starts `git-daemon`.
+   `/opt/trafficserver-ci/github-mirror`, starts diagnostic `git-daemon`, and
+   starts the smart HTTP service. Set `START_SMART_HTTP=0` only if you are not
+   ready to change the ATS `/mirror/` remap yet.
 
 3. Install the temporary cron file.
 
@@ -212,6 +342,13 @@ and ATS pull request refs every five minutes.
    sudo systemctl restart cron
    ```
 
+   Check it is installed:
+
+   ```bash
+   sudo cat /etc/cron.d/github-mirror
+   grep github-mirror /var/log/syslog
+   ```
+
 4. Run one manual refresh and verify refs.
 
    ```bash
@@ -232,14 +369,15 @@ and ATS pull request refs every five minutes.
    From any checkout of this repo on a host that can SSH through `controller`:
 
    ```bash
-   github-mirror/bin/check-docker-access.sh docker12
+   github-mirror/bin/check-docker-access.sh --pr <pr-number> docker12
    ```
 
    From `controller` itself:
 
    ```bash
    CONTROLLER=- \
-     /opt/trafficserver-ci/github-mirror/bin/check-docker-access.sh docker12
+     /opt/trafficserver-ci/github-mirror/bin/check-docker-access.sh \
+       --pr <pr-number> docker12
    ```
 
 6. Update Jenkins job configuration so the PR and branch top-level jobs pass
@@ -255,21 +393,14 @@ and ATS pull request refs every five minutes.
    Then run a small PR job such as docs or RAT before starting the full build
    fanout.
 
-7. Watch the cron updater and Jenkins checkouts.
-
-   ```bash
-   grep github-mirror /var/log/syslog
-   git ls-remote https://ci.trafficserver.apache.org/mirror/trafficserver.git \
-     refs/heads/master
-   ```
-
-8. When ASF webhooks are available, install the secret, start the webhook, send
+7. When ASF webhooks are available, install the secret, start the webhook, send
    a GitHub ping delivery, then remove the temporary cron file.
 
    ```bash
    sudo systemctl restart github-mirror-webhook.service
    sudo rm -f /etc/cron.d/github-mirror
    sudo systemctl restart cron
+   sudo systemctl enable --now github-mirror-fallback.timer
    ```
 
 ## Mirror Operations
@@ -312,10 +443,19 @@ Check from docker agents:
 /opt/trafficserver-ci/github-mirror/bin/check-docker-access.sh --pr 12345 
docker1 docker12
 ```
 
-When running that command directly on the controller, use:
+Inspect smart HTTP:
 
 ```bash
-CONTROLLER=- /opt/trafficserver-ci/github-mirror/bin/check-docker-access.sh 
docker12
+sudo systemctl status github-mirror-smart-http.service
+cd /opt/trafficserver-ci/github-mirror/httpd
+sudo docker-compose logs --tail=100 github-mirror-smart-http
+sudo tail -n 100 /var/log/github-mirror-smart-http/access_log
+```
+
+Use `git-daemon` only as a diagnostic fallback:
+
+```bash
+git ls-remote git://ci.trafficserver.apache.org/trafficserver.git 
refs/heads/master
 ```
 
 ## Webhook Testing
@@ -329,8 +469,56 @@ View logs:
 journalctl -u github-mirror-webhook.service -f
 ```
 
+Local signed ping test:
+
+```bash
+secret=$(sudo awk -F= '/^GITHUB_WEBHOOK_SECRET=/ { print $2 }' \
+  /etc/trafficserver-github-mirror/github-mirror-webhook.env)
+body='{"repository":{"full_name":"apache/trafficserver"}}'
+sig=$(SECRET="$secret" BODY="$body" python3 - <<'PY'
+import hashlib
+import hmac
+import os
+
+print(
+    "sha256="
+    + hmac.new(
+        os.environ["SECRET"].encode(),
+        os.environ["BODY"].encode(),
+        hashlib.sha256,
+    ).hexdigest()
+)
+PY
+)
+
+curl -i \
+  -H "X-GitHub-Event: ping" \
+  -H "X-Hub-Signature-256: ${sig}" \
+  --data "${body}" \
+  http://127.0.0.1:9419/github-mirror-webhook
+```
+
 A bad secret or unsigned payload should return HTTP 401 and must not update any
-repository.
+repository:
+
+```bash
+curl -i \
+  -H "X-GitHub-Event: ping" \
+  -H "X-Hub-Signature-256: sha256=bad" \
+  --data "${body}" \
+  http://127.0.0.1:9419/github-mirror-webhook
+```
+
+Anonymous push attempts must fail:
+
+```bash
+GIT_TERMINAL_PROMPT=0 \
+  git push https://ci.trafficserver.apache.org/mirror/trafficserver.git \
+    HEAD:refs/heads/github-mirror-push-test
+```
+
+The expected result is rejection because the bare repositories have
+`http.receivepack=false` and the service does not allow receive-pack.
 
 ## Jenkins Integration
 
@@ -347,12 +535,22 @@ For GitHub PR jobs, configure the top-level job's 
`GITHUB_URL` parameter to:
 https://ci.trafficserver.apache.org/mirror/trafficserver.git
 ```
 
-The top-level pipeline passes that value to child jobs. During the temporary
-cron rollout, set the top-level PR job quiet period to at least 90 seconds. 
Once
-the webhook is live and verified, the quiet period can be removed or reduced.
+The repo-managed PR pipeline scripts fetch:
+
+- the target branch;
+- only the current PR's `refs/pull/<number>/head`;
+- only the current PR's `refs/pull/<number>/merge`.
+
+They also use `CloneOption(honorRefspec: true, timeout: 20)` so Jenkins does
+not fan out a wildcard PR ref fetch to every child job.
+
+During the temporary cron rollout, set the top-level PR job quiet period to at
+least 90 seconds. Once the webhook is live and verified, the quiet period can
+be removed or reduced.
 
 For branch jobs, configure the top-level branch jobs' `GITHUB_URL` parameter to
 the same ATS mirror URL. Child jobs will receive that value from the fanout 
job.
+Branch jobs continue using normal branch checkouts from the mirror URL.
 
 ## Migrating An Existing Controller
 
@@ -384,21 +582,42 @@ Do not delete the old scripts until the new path has run 
for a few days.
 
 ## Rollback
 
-1. Stop webhook updates.
+The Jenkins URLs do not need to change for a smart HTTP rollback because the
+public `/mirror/` URLs stay the same.
+
+1. Change the ATS `/mirror/` remap back to the previous static backend:
+
+   ```text
+   https://ci.trafficserver.apache.org/mirror/ -> http://localhost:8080/mirror/
+   ```
+
+2. Reload ATS.
 
    ```bash
-   sudo systemctl stop github-mirror-webhook.service
-   sudo systemctl stop github-mirror-fallback.timer
+   sudo /opt/ats/bin/traffic_ctl config reload
    ```
 
-2. Point Jenkins job parameters back at GitHub:
+3. Stop the smart HTTP service.
 
-   ```text
-   https://github.com/apache/trafficserver.git
-   https://github.com/apache/trafficserver-ci.git
+   ```bash
+   sudo systemctl disable --now github-mirror-smart-http.service
    ```
 
-3. If needed, re-enable the previous cron updater.
+4. If the mirror update path is also being rolled back, stop webhook updates
+   and re-enable the previous cron updater.
+
+   ```bash
+   sudo systemctl stop github-mirror-webhook.service
+   sudo systemctl stop github-mirror-fallback.timer
+   ```
+
+Full rollback to GitHub is still possible by pointing Jenkins job parameters
+back at:
+
+```text
+https://github.com/apache/trafficserver.git
+https://github.com/apache/trafficserver-ci.git
+```
 
 Rollback does not require deleting `/home/mirror`.
 
@@ -410,6 +629,7 @@ Missing PR ref:
 sudo -u gitdaemon \
   /opt/trafficserver-ci/github-mirror/bin/update-mirror.sh trafficserver --pr 
<number>
 git --git-dir=/home/mirror/trafficserver.git show-ref refs/pull/<number>/head
+git --git-dir=/home/mirror/trafficserver.git show-ref refs/pull/<number>/merge
 ```
 
 Webhook returns 401:
@@ -425,13 +645,25 @@ Webhook returns 401:
 Jenkins cannot clone from HTTPS:
 
 - Verify ATS remap order.
-- Verify the httpd `/mirror/` export.
+- Verify `/mirror/` points to `http://localhost:9417/mirror/`.
+- Verify the smart HTTP service is healthy.
 - Verify the public URL:
 
   ```bash
+  sudo systemctl status github-mirror-smart-http.service
+  sudo docker exec github-mirror-smart-http httpd -t
   git ls-remote https://ci.trafficserver.apache.org/mirror/trafficserver.git 
refs/heads/master
   ```
 
+Jenkins fetches look like dumb HTTP:
+
+- Confirm ATS is using the smart HTTP remap, not 
`http://localhost:8080/mirror/`.
+- Confirm logs include `git-upload-pack`:
+
+  ```bash
+  sudo tail -n 100 /var/log/github-mirror-smart-http/access_log
+  ```
+
 Docker hosts cannot reach the mirror:
 
 ```bash
diff --git a/github-mirror/ats/mirror-smart-http-remap-snippet.config 
b/github-mirror/ats/mirror-smart-http-remap-snippet.config
new file mode 100644
index 0000000..e4517be
--- /dev/null
+++ b/github-mirror/ats/mirror-smart-http-remap-snippet.config
@@ -0,0 +1,7 @@
+# Put this before the generic Jenkins/ci.trafficserver.apache.org catch-all 
remap.
+#
+# Smart Git HTTP traffic is served by github-mirror-smart-http on the
+# controller. Do not cache these responses in ATS.
+map https://ci.trafficserver.apache.org/mirror/ http://localhost:9417/mirror/ \
+  @plugin=conf_remap.so @pparam=proxy.config.http.cache.http=0 \
+  @plugin=header_rewrite.so @pparam=hdr_rw_git.config
diff --git a/github-mirror/bin/check-docker-access.sh 
b/github-mirror/bin/check-docker-access.sh
index 6156f03..01b351c 100755
--- a/github-mirror/bin/check-docker-access.sh
+++ b/github-mirror/bin/check-docker-access.sh
@@ -7,6 +7,7 @@ set -euo pipefail
 CONTROLLER=${CONTROLLER:-controller}
 PUBLIC_BASE=${PUBLIC_BASE:-https://ci.trafficserver.apache.org/mirror}
 PR_NUMBER=${PR_NUMBER:-}
+GITHUB_PR_HEAD_SHA=${GITHUB_PR_HEAD_SHA:-}
 
 usage() {
   cat <<'EOF'
@@ -20,6 +21,8 @@ Environment:
                Set to empty or "-" when running directly on the controller.
   PUBLIC_BASE  Public HTTPS mirror base URL. Default: 
https://ci.trafficserver.apache.org/mirror
   PR_NUMBER    Optional PR number to verify.
+  GITHUB_PR_HEAD_SHA
+               Optional expected PR head SHA to compare against --pr.
 EOF
 }
 
@@ -55,12 +58,28 @@ for docker_host in "$@"; do
     ssh_args+=(-J "${CONTROLLER}")
   fi
   ssh "${ssh_args[@]}" "${docker_host}" \
-    "PUBLIC_BASE=$(printf '%q' "${PUBLIC_BASE}") PR_NUMBER=$(printf '%q' 
"${PR_NUMBER}") bash -s" <<'REMOTE_CHECK'
+    "PUBLIC_BASE=$(printf '%q' "${PUBLIC_BASE}") PR_NUMBER=$(printf '%q' 
"${PR_NUMBER}") GITHUB_PR_HEAD_SHA=$(printf '%q' "${GITHUB_PR_HEAD_SHA}") bash 
-s" <<'REMOTE_CHECK'
 set -e
-git ls-remote "$PUBLIC_BASE/trafficserver.git" refs/heads/master >/dev/null
-git ls-remote "$PUBLIC_BASE/trafficserver-ci.git" refs/heads/main >/dev/null
+require_ref() {
+  repo=$1
+  ref=$2
+  output=$(git ls-remote "$PUBLIC_BASE/${repo}.git" "$ref")
+  if [ -z "$output" ]; then
+    echo "missing ${repo} ${ref}" >&2
+    exit 1
+  fi
+  printf '%s\n' "$output" | awk 'NR == 1 { print $1 }'
+}
+
+require_ref trafficserver refs/heads/master >/dev/null
+require_ref trafficserver-ci refs/heads/main >/dev/null
 if [ -n "$PR_NUMBER" ]; then
-  git ls-remote "$PUBLIC_BASE/trafficserver.git" "refs/pull/${PR_NUMBER}/head" 
>/dev/null
+  pr_head_sha=$(require_ref trafficserver "refs/pull/${PR_NUMBER}/head")
+  require_ref trafficserver "refs/pull/${PR_NUMBER}/merge" >/dev/null
+  if [ -n "$GITHUB_PR_HEAD_SHA" ] && [ "$pr_head_sha" != "$GITHUB_PR_HEAD_SHA" 
]; then
+    echo "PR head ${pr_head_sha} does not match 
GITHUB_PR_HEAD_SHA=${GITHUB_PR_HEAD_SHA}" >&2
+    exit 1
+  fi
 fi
 REMOTE_CHECK
 done
diff --git a/github-mirror/bin/check-mirror.sh 
b/github-mirror/bin/check-mirror.sh
index 2078689..a292f3c 100755
--- a/github-mirror/bin/check-mirror.sh
+++ b/github-mirror/bin/check-mirror.sh
@@ -8,6 +8,7 @@ MIRROR_ROOT=${MIRROR_ROOT:-/home/mirror}
 PUBLIC_BASE=${PUBLIC_BASE:-https://ci.trafficserver.apache.org/mirror}
 GIT=${GIT:-git}
 PR_NUMBER=${PR_NUMBER:-}
+GITHUB_PR_HEAD_SHA=${GITHUB_PR_HEAD_SHA:-}
 
 usage() {
   cat <<'EOF'
@@ -19,6 +20,8 @@ Environment:
   PUBLIC_BASE   Public HTTPS mirror base URL. Default: 
https://ci.trafficserver.apache.org/mirror
   GIT           Git executable. Default: git
   PR_NUMBER     Optional PR number to verify.
+  GITHUB_PR_HEAD_SHA
+                Optional expected PR head SHA to compare against --pr.
 EOF
 }
 
@@ -59,13 +62,28 @@ check_local_ref() {
   log "local ${repo} has ${ref}"
 }
 
-check_remote_ref() {
+local_ref_sha() {
+  local repo=$1
+  local ref=$2
+  local repo_dir="${MIRROR_ROOT}/${repo}.git"
+  "${GIT}" --git-dir="${repo_dir}" rev-parse "${ref}^{commit}"
+}
+
+remote_ref_sha() {
   local repo=$1
   local ref=$2
   local url="${PUBLIC_BASE}/${repo}.git"
   local output
   output=$("${GIT}" ls-remote "${url}" "${ref}")
   [ -n "${output}" ] || die "missing public ref ${ref} at ${url}"
+  printf '%s\n' "${output}" | awk 'NR == 1 { print $1 }'
+}
+
+check_remote_ref() {
+  local repo=$1
+  local ref=$2
+  local url="${PUBLIC_BASE}/${repo}.git"
+  remote_ref_sha "${repo}" "${ref}" >/dev/null
   log "public ${url} has ${ref}"
 }
 
@@ -76,8 +94,22 @@ check_remote_ref trafficserver-ci refs/heads/main
 
 if [ -n "${PR_NUMBER}" ]; then
   [[ "${PR_NUMBER}" =~ ^[0-9]+$ ]] || die "invalid PR number: ${PR_NUMBER}"
-  check_local_ref trafficserver "refs/pull/${PR_NUMBER}/head"
-  check_remote_ref trafficserver "refs/pull/${PR_NUMBER}/head"
+  pr_head_ref="refs/pull/${PR_NUMBER}/head"
+  pr_merge_ref="refs/pull/${PR_NUMBER}/merge"
+  check_local_ref trafficserver "${pr_head_ref}"
+  check_remote_ref trafficserver "${pr_head_ref}"
+  check_local_ref trafficserver "${pr_merge_ref}"
+  check_remote_ref trafficserver "${pr_merge_ref}"
+
+  if [ -n "${GITHUB_PR_HEAD_SHA}" ]; then
+    local_head_sha=$(local_ref_sha trafficserver "${pr_head_ref}")
+    public_head_sha=$(remote_ref_sha trafficserver "${pr_head_ref}")
+    [ "${local_head_sha}" = "${GITHUB_PR_HEAD_SHA}" ] ||
+      die "local PR head ${local_head_sha} does not match 
GITHUB_PR_HEAD_SHA=${GITHUB_PR_HEAD_SHA}"
+    [ "${public_head_sha}" = "${GITHUB_PR_HEAD_SHA}" ] ||
+      die "public PR head ${public_head_sha} does not match 
GITHUB_PR_HEAD_SHA=${GITHUB_PR_HEAD_SHA}"
+    log "PR head matches GITHUB_PR_HEAD_SHA=${GITHUB_PR_HEAD_SHA}"
+  fi
 fi
 
 log "mirror checks passed"
diff --git a/github-mirror/bin/init-mirrors.sh 
b/github-mirror/bin/init-mirrors.sh
index 85243d7..976295c 100755
--- a/github-mirror/bin/init-mirrors.sh
+++ b/github-mirror/bin/init-mirrors.sh
@@ -83,6 +83,8 @@ init_repo() {
   fi
 
   configure_remote "${repo_dir}" "${remote_url}" "$@"
+  "${GIT}" --git-dir="${repo_dir}" config http.uploadpack true
+  "${GIT}" --git-dir="${repo_dir}" config http.receivepack false
   touch "${repo_dir}/git-daemon-export-ok"
 
   if [ "$(id -u)" -eq 0 ]; then
diff --git a/github-mirror/bin/install-controller.sh 
b/github-mirror/bin/install-controller.sh
index 88be0ca..9bd0ab5 100755
--- a/github-mirror/bin/install-controller.sh
+++ b/github-mirror/bin/install-controller.sh
@@ -16,6 +16,7 @@ ENV_FILE=${ENV_FILE:-${ENV_DIR}/github-mirror-webhook.env}
 APT_INSTALL=${APT_INSTALL:-1}
 START_WEBHOOK=${START_WEBHOOK:-auto}
 START_FALLBACK_TIMER=${START_FALLBACK_TIMER:-1}
+START_SMART_HTTP=${START_SMART_HTTP:-1}
 INIT_MIRRORS=${INIT_MIRRORS:-1}
 
 usage() {
@@ -34,6 +35,8 @@ Environment:
   START_WEBHOOK  auto, 1, or 0. Default: auto
   START_FALLBACK_TIMER
                  Enable/start the systemd fallback timer when set to 1. 
Default: 1
+  START_SMART_HTTP
+                 Enable/start smart HTTP mirror service when set to 1. 
Default: 1
 EOF
 }
 
@@ -76,6 +79,8 @@ if [ "${APT_INSTALL}" = "1" ]; then
   DEBIAN_FRONTEND=noninteractive apt-get install -y \
     git \
     git-daemon-sysvinit \
+    docker.io \
+    docker-compose \
     python3 \
     util-linux
 fi
@@ -114,6 +119,8 @@ render_template 
"${INSTALL_ROOT}/systemd/github-mirror-fallback.service" "${tmp_
 install -o root -g root -m 0644 "${tmp_unit}" 
/etc/systemd/system/github-mirror-fallback.service
 render_template "${INSTALL_ROOT}/systemd/github-mirror-fallback.timer" 
"${tmp_unit}"
 install -o root -g root -m 0644 "${tmp_unit}" 
/etc/systemd/system/github-mirror-fallback.timer
+render_template "${INSTALL_ROOT}/systemd/github-mirror-smart-http.service" 
"${tmp_unit}"
+install -o root -g root -m 0644 "${tmp_unit}" 
/etc/systemd/system/github-mirror-smart-http.service
 rm -f "${tmp_unit}"
 
 systemctl daemon-reload
@@ -132,6 +139,12 @@ else
   systemctl disable --now github-mirror-fallback.timer >/dev/null 2>&1 || true
 fi
 
+if [ "${START_SMART_HTTP}" = "1" ]; then
+  systemctl enable --now github-mirror-smart-http.service
+else
+  systemctl disable --now github-mirror-smart-http.service >/dev/null 2>&1 || 
true
+fi
+
 if [ "${START_WEBHOOK}" = "1" ] ||
    { [ "${START_WEBHOOK}" = "auto" ] && grep -q '^GITHUB_WEBHOOK_SECRET=' 
"${ENV_FILE}" &&
      ! grep -q '^GITHUB_WEBHOOK_SECRET=CHANGE_ME' "${ENV_FILE}"; }; then
diff --git a/github-mirror/httpd/Dockerfile b/github-mirror/httpd/Dockerfile
new file mode 100644
index 0000000..c7d6b5c
--- /dev/null
+++ b/github-mirror/httpd/Dockerfile
@@ -0,0 +1,11 @@
+FROM httpd:2.4
+
+RUN apt-get update \
+  && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends 
\
+    ca-certificates \
+    git \
+  && rm -rf /var/lib/apt/lists/*
+
+COPY mirror.conf /usr/local/apache2/conf/extra/github-mirror.conf
+
+RUN printf '\nInclude conf/extra/github-mirror.conf\n' >> 
/usr/local/apache2/conf/httpd.conf
diff --git a/github-mirror/httpd/docker-compose.yml 
b/github-mirror/httpd/docker-compose.yml
new file mode 100644
index 0000000..b0ec2da
--- /dev/null
+++ b/github-mirror/httpd/docker-compose.yml
@@ -0,0 +1,13 @@
+version: '3'
+
+services:
+  github-mirror-smart-http:
+    build: .
+    image: trafficserver/github-mirror-smart-http:latest
+    container_name: github-mirror-smart-http
+    restart: unless-stopped
+    ports:
+      - "127.0.0.1:9417:80"
+    volumes:
+      - /home/mirror:/usr/local/apache2/mirror:ro
+      - /var/log/github-mirror-smart-http:/usr/local/apache2/logs
diff --git a/github-mirror/httpd/mirror.conf b/github-mirror/httpd/mirror.conf
index abfce7a..4849158 100644
--- a/github-mirror/httpd/mirror.conf
+++ b/github-mirror/httpd/mirror.conf
@@ -1,14 +1,26 @@
-# Apache httpd snippet for exporting /home/mirror as /mirror/.
+# Smart Git HTTP export for the ATS CI GitHub mirrors.
 #
-# This simple static export works with `git update-server-info`, which the
-# mirror updater runs after every fetch. If the controller already has an httpd
-# container or vhost serving /mirror/, keep that and use this as the rebuild
-# reference.
+# This is included by the dedicated github-mirror-smart-http container. ATS
+# terminates public HTTPS and remaps /mirror/ traffic to this container on
+# 127.0.0.1:9417.
 
-Alias /mirror/ /home/mirror/
+<IfModule !cgid_module>
+    LoadModule cgid_module modules/mod_cgid.so
+</IfModule>
 
-<Directory /home/mirror/>
-    Options Indexes FollowSymLinks
+SetEnv GIT_PROJECT_ROOT /usr/local/apache2/mirror
+SetEnv GIT_HTTP_EXPORT_ALL 1
+
+CustomLog "logs/access_log" combined
+
+ScriptAlias /mirror/ /usr/lib/git-core/git-http-backend/
+
+<Directory "/usr/lib/git-core">
+    Options +ExecCGI
     AllowOverride None
     Require all granted
 </Directory>
+
+<LocationMatch "^/mirror/.*/git-receive-pack$">
+    Require all denied
+</LocationMatch>
diff --git a/github-mirror/systemd/github-mirror-smart-http.service 
b/github-mirror/systemd/github-mirror-smart-http.service
new file mode 100644
index 0000000..e9c96f0
--- /dev/null
+++ b/github-mirror/systemd/github-mirror-smart-http.service
@@ -0,0 +1,20 @@
+[Unit]
+Description=ATS CI GitHub mirror smart HTTP service
+Documentation=file:@INSTALL_ROOT@/README.md
+After=network-online.target docker.service
+Wants=network-online.target
+Requires=docker.service
+
+[Service]
+Type=simple
+WorkingDirectory=@INSTALL_ROOT@/httpd
+ExecStartPre=/usr/bin/install -d -o root -g root -m 0755 
/var/log/github-mirror-smart-http
+ExecStartPre=/usr/bin/docker-compose build
+ExecStart=/usr/bin/docker-compose up --remove-orphans
+ExecStop=/usr/bin/docker-compose down
+Restart=on-failure
+RestartSec=5s
+TimeoutStartSec=10min
+
+[Install]
+WantedBy=multi-user.target
diff --git a/jenkins/github/autest.pipeline b/jenkins/github/autest.pipeline
index 7b2e939..c9e6f2d 100644
--- a/jenkins/github/autest.pipeline
+++ b/jenkins/github/autest.pipeline
@@ -45,6 +45,7 @@ pipeline {
           checkout([$class: 'GitSCM',
             branches: [[name: sha1]],
             extensions: [
+              [$class: 'CloneOption', honorRefspec: true, timeout: 20],
               // We have to set an idenity for the merge step because Git 
requires
               // the user.name and user.email to be set to do a merge.
               [$class: "UserIdentity",
@@ -60,7 +61,8 @@ pipeline {
                 ]
               ],
             ],
-            userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+            userRemoteConfigs: [[url: github_url,
+                refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
           sh '''#!/bin/bash
             set -x
 
diff --git a/jenkins/github/centos.pipeline b/jenkins/github/centos.pipeline
index 5d6b577..ccba76e 100644
--- a/jenkins/github/centos.pipeline
+++ b/jenkins/github/centos.pipeline
@@ -19,6 +19,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -34,7 +35,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/clang-analyzer.pipeline 
b/jenkins/github/clang-analyzer.pipeline
index e11ebf3..6c60359 100644
--- a/jenkins/github/clang-analyzer.pipeline
+++ b/jenkins/github/clang-analyzer.pipeline
@@ -15,6 +15,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -30,7 +31,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/debian.pipeline b/jenkins/github/debian.pipeline
index b1152d7..ac223c6 100644
--- a/jenkins/github/debian.pipeline
+++ b/jenkins/github/debian.pipeline
@@ -19,6 +19,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -34,7 +35,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/docs.pipeline b/jenkins/github/docs.pipeline
index 1ddf7cb..5b05109 100644
--- a/jenkins/github/docs.pipeline
+++ b/jenkins/github/docs.pipeline
@@ -16,6 +16,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -31,7 +32,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/fedora.pipeline b/jenkins/github/fedora.pipeline
index 23e6c07..7c738a8 100644
--- a/jenkins/github/fedora.pipeline
+++ b/jenkins/github/fedora.pipeline
@@ -19,6 +19,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -34,7 +35,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/format.pipeline b/jenkins/github/format.pipeline
index ab61f23..d991368 100644
--- a/jenkins/github/format.pipeline
+++ b/jenkins/github/format.pipeline
@@ -16,6 +16,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -31,7 +32,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/freebsd.pipeline b/jenkins/github/freebsd.pipeline
index 9eddde6..a3aba4d 100644
--- a/jenkins/github/freebsd.pipeline
+++ b/jenkins/github/freebsd.pipeline
@@ -8,6 +8,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -23,7 +24,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/osx.pipeline b/jenkins/github/osx.pipeline
index 75f8556..8186a18 100644
--- a/jenkins/github/osx.pipeline
+++ b/jenkins/github/osx.pipeline
@@ -8,6 +8,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -23,7 +24,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                         set -x
 
diff --git a/jenkins/github/rat.pipeline b/jenkins/github/rat.pipeline
index a8329f8..d271d5c 100644
--- a/jenkins/github/rat.pipeline
+++ b/jenkins/github/rat.pipeline
@@ -16,6 +16,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -31,7 +32,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/rocky-asan.pipeline 
b/jenkins/github/rocky-asan.pipeline
index f8103c5..b6a0037 100644
--- a/jenkins/github/rocky-asan.pipeline
+++ b/jenkins/github/rocky-asan.pipeline
@@ -19,6 +19,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -34,7 +35,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/rocky.pipeline b/jenkins/github/rocky.pipeline
index 15efdd3..ec6222a 100644
--- a/jenkins/github/rocky.pipeline
+++ b/jenkins/github/rocky.pipeline
@@ -19,6 +19,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -34,7 +35,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 
diff --git a/jenkins/github/ubuntu.pipeline b/jenkins/github/ubuntu.pipeline
index 55f2d3f..87b1b02 100644
--- a/jenkins/github/ubuntu.pipeline
+++ b/jenkins/github/ubuntu.pipeline
@@ -19,6 +19,7 @@ pipeline {
                     checkout([$class: 'GitSCM',
                         branches: [[name: sha1]],
                         extensions: [
+                            [$class: 'CloneOption', honorRefspec: true, 
timeout: 20],
                             // We have to set an idenity for the merge step 
because Git requires
                             // the user.name and user.email to be set to do a 
merge.
                             [$class: "UserIdentity",
@@ -34,7 +35,8 @@ pipeline {
                                 ]
                             ],
                         ],
-                        userRemoteConfigs: [[url: github_url, refspec: 
'+refs/pull/*:refs/remotes/origin/pr/*']]])
+                        userRemoteConfigs: [[url: github_url,
+                            refspec: 
"+refs/heads/${GITHUB_PR_TARGET_BRANCH}:refs/remotes/origin/${GITHUB_PR_TARGET_BRANCH}
 
+refs/pull/${GITHUB_PR_NUMBER}/head:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/head
 
+refs/pull/${GITHUB_PR_NUMBER}/merge:refs/remotes/origin/pr/${GITHUB_PR_NUMBER}/merge"]])
                     sh '''#!/bin/bash
                             set -x
 


Reply via email to