[LTP] [PATCH] ci: apply Patchwork series via REST API
Andrea Cervesato
andrea.cervesato@suse.de
Mon Sep 7 14:27:06 CEST 2026
From: Andrea Cervesato <andrea.cervesato@suse.com>
Patchwork on kernel.org requires authentication to download mbox files
from the web interface, causing anonymous curl downloads in CI to fail
with HTTP 302 redirects to the login page.
Add an 'apply' command to ci/tools/patchwork.sh that retrieves patches
via the public REST API and applies them using git am. Update workflows
to use this command instead of curling the mbox URL directly.
Signed-off-by: Andrea Cervesato <andrea.cervesato@suse.com>
---
Patchwork instances on kernel.org require authentication to download mbox
files from the web interface, returning HTTP 302 redirects to the login
page for anonymous requests. This breaks the CI workflows when trying to
download and apply patch series using the web mbox URL.
This series adds an 'apply' subcommand to ci/tools/patchwork.sh that
retrieves the individual patches using the public REST API and applies
them via git am, and updates the CI workflows to use this command.
---
.github/workflows/ci-docker-build.yml | 6 +--
.github/workflows/ci-sphinx-doc.yml | 6 +--
ci/tools/patchwork.sh | 69 ++++++++++++++++++++++++++++++++++-
3 files changed, 74 insertions(+), 7 deletions(-)
diff --git a/.github/workflows/ci-docker-build.yml b/.github/workflows/ci-docker-build.yml
index ec002d316..b117547d8 100644
--- a/.github/workflows/ci-docker-build.yml
+++ b/.github/workflows/ci-docker-build.yml
@@ -146,7 +146,7 @@ jobs:
run: $CC --version
- name: Apply Patchwork series
- if: inputs.SERIES_ID != '' && inputs.SERIES_MBOX != ''
+ if: inputs.SERIES_ID != ''
env:
PATCHWORK_TOKEN: ${{ secrets.PATCHWORK_TOKEN }}
run: |
@@ -155,7 +155,7 @@ jobs:
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git checkout -b review_patch_series_"${{ inputs.SERIES_ID }}"
- curl -k -L --retry 3 --max-redirs 1 --location-trusted "${{ inputs.SERIES_MBOX }}" | git am
+ ./ci/tools/patchwork.sh apply "${{ inputs.SERIES_ID }}"
./ci/tools/patchwork.sh state "${{ inputs.SERIES_ID }}" "needs-ack"
@@ -199,7 +199,7 @@ jobs:
./build.sh -r install -o ${TREE:-in} $INSTALL_OPT
- name: Send results to Patchwork
- if: always() && inputs.SERIES_ID != '' && inputs.SERIES_MBOX != ''
+ if: always() && inputs.SERIES_ID != ''
env:
PATCHWORK_TOKEN: ${{ secrets.PATCHWORK_TOKEN }}
run: |
diff --git a/.github/workflows/ci-sphinx-doc.yml b/.github/workflows/ci-sphinx-doc.yml
index beb378392..ffa5cb926 100644
--- a/.github/workflows/ci-sphinx-doc.yml
+++ b/.github/workflows/ci-sphinx-doc.yml
@@ -35,7 +35,7 @@ jobs:
sudo apt install autoconf make python3-virtualenv
- name: Apply Patchwork series
- if: inputs.SERIES_ID != '' && inputs.SERIES_MBOX != ''
+ if: inputs.SERIES_ID != ''
env:
PATCHWORK_TOKEN: ${{ secrets.PATCHWORK_TOKEN }}
run: |
@@ -44,7 +44,7 @@ jobs:
git config --global --add safe.directory "$GITHUB_WORKSPACE"
git checkout -b review_patch_series_"${{ inputs.SERIES_ID }}"
- curl -k -L --retry 3 --max-redirs 1 --location-trusted "${{ inputs.SERIES_MBOX }}" | git am
+ ./ci/tools/patchwork.sh apply "${{ inputs.SERIES_ID }}"
./ci/tools/patchwork.sh state "${{ inputs.SERIES_ID }}" "needs-ack"
@@ -53,7 +53,7 @@ jobs:
make autotools && ./configure
- name: Send results to Patchwork
- if: always() && inputs.SERIES_ID != '' && inputs.SERIES_MBOX != ''
+ if: always() && inputs.SERIES_ID != ''
env:
PATCHWORK_TOKEN: ${{ secrets.PATCHWORK_TOKEN }}
run: |
diff --git a/ci/tools/patchwork.sh b/ci/tools/patchwork.sh
index 4e61eb7f3..8d930007b 100755
--- a/ci/tools/patchwork.sh
+++ b/ci/tools/patchwork.sh
@@ -202,7 +202,74 @@ send_results() {
done
}
+apply_series() {
+ if [ $# -ne 1 ]; then
+ echo "'apply' command expects 1 parameter ($#)" >&2
+ exit 1
+ fi
+
+ local series_id="$1"
+ local stdout
+ local patch_ids
+
+ stdout="$(curl -f -k -s --retry 3 "$PATCHWORK_URL/api/1.2/series/$series_id/")"
+ if [ $? -ne 0 ] || [ -z "$stdout" ]; then
+ echo "Failed to fetch series $series_id from $PATCHWORK_URL" >&2
+ exit 1
+ fi
+
+ patch_ids="$(echo "$stdout" | jq -r '.patches[].id')"
+ if [ -z "$patch_ids" ]; then
+ echo "No patches found for series $series_id" >&2
+ exit 1
+ fi
+
+ local tmp_dir
+ tmp_dir="$(mktemp -d)"
+ trap 'rm -rf "$tmp_dir"' EXIT
+
+ local count=0
+ for patch_id in $patch_ids; do
+ local patch_json
+ patch_json="$(curl -f -k -s --retry 3 "$PATCHWORK_URL/api/1.2/patches/$patch_id/")"
+ if [ $? -ne 0 ] || [ -z "$patch_json" ]; then
+ echo "Failed to fetch patch $patch_id from $PATCHWORK_URL" >&2
+ exit 1
+ fi
+
+ count=$((count + 1))
+ local patch_file
+ patch_file="$(printf "%s/%04d-%s.patch" "$tmp_dir" "$count" "$patch_id")"
+
+ echo "$patch_json" | jq -r '
+ "From " + (.headers["Message-Id"] // .msgid // "patchwork") + " Mon Sep 07 00:00:00 2026",
+ "From: " + (.headers.From // ((.submitter.name // "Unknown") + " <" + (.submitter.email // "unknown@example.com") + ">")),
+ "Date: " + (.headers.Date // .date // ""),
+ "Subject: " + (.headers.Subject // .name // "No subject"),
+ "Message-Id: " + (.headers["Message-Id"] // .msgid // ""),
+ "MIME-Version: 1.0",
+ "Content-Type: text/plain; charset=UTF-8",
+ "Content-Transfer-Encoding: 8bit",
+ "",
+ .content,
+ "",
+ .diff,
+ ""
+ ' > "$patch_file"
+ done
+
+ git am --3way "$tmp_dir"/*.patch
+ local ret=$?
+ if [ $ret -ne 0 ]; then
+ git am --abort 2>/dev/null || true
+ exit $ret
+ fi
+}
+
case "$1" in
+apply)
+ apply_series "$2"
+ ;;
state)
set_series_state "$2" "$3"
;;
@@ -213,7 +280,7 @@ verify)
verify_new_patches
;;
*)
- echo "Available commands: state, check, verify" >&2
+ echo "Available commands: apply, state, check, verify" >&2
exit 1
;;
esac
---
base-commit: 463b33ad464b6840d0a1df5d41939e50d55d542c
change-id: 20260907-ci_patchwork_apply-90de69203f86
Best regards,
--
Andrea Cervesato <andrea.cervesato@suse.com>
More information about the ltp
mailing list