From 530696020efc320cce8d826ceeaebaf4189c0592 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Sat, 1 Aug 2026 00:03:46 +0000 Subject: [PATCH] fix(action): accept from-version input on publish-ghcr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #40. The publish-ghcr mode re-derived the previous version from the registry independently of the generate-ghcr step that actually downloaded the source binary and ran bsdiff. If the registry state changed between the two steps (concurrent push, semver re-ordering with a cross-major nightly), the two values could disagree and `binpatch` consumers would silently hit SHA mismatch (the library only verifies the final output SHA, not the source binary identity — the annotation is a chain pointer, not a content hash). Add a new `from-version` input on publish-ghcr. When set, the annotation on the pushed patch manifest uses THIS value verbatim. When empty (back-compat), the old registry re-derive runs and prints a hint suggesting the caller thread the value from generate-ghcr. The consumer-side wiring change is trivial: ```yaml - uses: BYK/binpatch/action@vX.Y.Z id: gen with: { mode: generate-ghcr, ... } - uses: BYK/binpatch/action@vX.Y.Z with: mode: publish-ghcr from-version: ${{ steps.gen.outputs.from-version }} ``` Files: - action/action.yml: add `from-version` input, thread to push_patches via env, use input when set else re-derive - action/README.md: document the new input and the gen→publish wiring --- action/README.md | 30 ++++++++++++++++++++++++ action/action.yml | 58 +++++++++++++++++++++++++++++++++++++---------- 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/action/README.md b/action/README.md index 4436d14..0f12a0c 100644 --- a/action/README.md +++ b/action/README.md @@ -65,9 +65,39 @@ overridden ones: | `patches-dir` | `patches` | Where patches are written / read. | | `binary-glob` | `*` | Selects binaries to diff (e.g. `lore-*`). | | `max-ratio` | `50` | Size-gate percentage. | +| `from-version` | `""` | publish-ghcr: the previous version the patches diff against. Pass from `generate-ghcr.outputs.from-version` so the annotation matches the actual source. When empty, falls back to registry re-derive. | | `zig-bsdiff-version` / `zig-bsdiff-sha256` | pinned | Encoder, SHA-verified. | | `oras-version` / `oras-sha256` | pinned | OCI client, SHA-verified. | +## `generate-ghcr` → `publish-ghcr` wiring + +The patch manifest's `from-version` annotation must match the actual source +binary the bytes were generated from — `binpatch` verifies only the final +output SHA. Wire the value from `generate-ghcr` to `publish-ghcr`: + +```yaml +- uses: BYK/binpatch/action@vX.Y.Z + id: gen + with: + mode: generate-ghcr + version: ${{ steps.changes.outputs.nightly-version }} + repo: getsentry/cli + new-binaries-dir: ${{ steps.build.outputs.dir }} + new-gz-dir: ${{ steps.build.outputs.gz-dir }} + +- uses: BYK/binpatch/action@vX.Y.Z + if: steps.gen.outputs.has-patches == 'true' + with: + mode: publish-ghcr + version: ${{ steps.changes.outputs.nightly-version }} + repo: getsentry/cli + artifacts-dir: ${{ steps.build.outputs.gz-dir }} + binaries-dir: ${{ steps.build.outputs.dir }} + # CRITICAL: stamp the annotation with the value the generate step actually + # used, not a re-derivation of the registry state. See BYK/binpatch#40. + from-version: ${{ steps.gen.outputs.from-version }} +``` + ## Outputs | Output | Notes | diff --git a/action/action.yml b/action/action.yml index 0733416..b932982 100644 --- a/action/action.yml +++ b/action/action.yml @@ -128,6 +128,18 @@ inputs: published. Set 'true' to make patch-push failures fatal. required: false default: "false" + from-version: + description: >- + publish-ghcr: the previous version the patches were generated against. + When set, the annotation on the pushed patch manifest uses THIS value + verbatim instead of re-deriving it from the registry. Pass this from + generate-ghcr.outputs.from-version to guarantee the annotation matches + the actual patch source -- `binpatch` only verifies the final output + SHA, so a wrong annotation makes the patch invisible to any user whose + chain doesn't include the annotated source. Re-derivation is kept as + the back-compat fallback when this input is empty. + required: false + default: "" github-token: description: "Token for registry login and GitHub Release API access." required: false @@ -414,6 +426,7 @@ runs: PATCHES_DIR: ${{ inputs.patches-dir }} BINARIES_DIR: ${{ inputs.binaries-dir }} PATCH_PUSH_FATAL: ${{ inputs.patch-push-fatal }} + FROM_VERSION: ${{ inputs.from-version }} run: | set -uo pipefail FULL_REPO="${REGISTRY}/${REPO}" @@ -445,19 +458,40 @@ runs: return 0 fi - # Find from-version by listing versioned nightly tags. - local TAGS PREV_TAG="" tag PREV_VERSION - TAGS=$(oras repo tags "${FULL_REPO}" 2>/dev/null | grep "^${NIGHTLY_TAG_PREFIX}[0-9]" | sort -V || echo "") - for tag in $TAGS; do - if [ "$tag" = "${NIGHTLY_TAG_PREFIX}${VERSION}" ]; then break; fi - PREV_TAG="$tag" - done - - if [ -z "$PREV_TAG" ]; then - echo "No previous nightly tag found, skipping patch push" - return 0 + # Resolve from-version. Prefer the caller-supplied value (typically + # from-version from a sibling generate-ghcr invocation), since + # generate-ghcr is what actually downloaded the source binary and + # ran bsdiff — re-deriving from the registry here can disagree + # with the source the bytes were generated from if the registry + # state changed between the two steps (concurrent push, semver + # re-ordering, etc.). The `binpatch` library verifies only the + # final output SHA, so a wrong annotation makes the patch useless + # to any user whose chain doesn't include the annotated source. + # Falls back to registry re-derive when the input is empty for + # back-compat with workflows that haven't threaded the value. + local PREV_VERSION="" + if [ -n "${FROM_VERSION:-}" ]; then + PREV_VERSION="${FROM_VERSION}" + echo "Using caller-supplied from-version: ${PREV_VERSION}" + else + # Re-derive by listing versioned nightly tags. Same caveats as + # the consumer-side script -- the build's own tag may not exist + # yet (publish-ghcr runs before/around the versioned-tag push), + # so the loop break may not trigger and the last tag in sort-V + # order can leak through. + local TAGS PREV_TAG="" tag + TAGS=$(oras repo tags "${FULL_REPO}" 2>/dev/null | grep "^${NIGHTLY_TAG_PREFIX}[0-9]" | sort -V || echo "") + for tag in $TAGS; do + if [ "$tag" = "${NIGHTLY_TAG_PREFIX}${VERSION}" ]; then break; fi + PREV_TAG="$tag" + done + if [ -z "$PREV_TAG" ]; then + echo "No previous nightly tag found, skipping patch push" + return 0 + fi + PREV_VERSION="${PREV_TAG#"${NIGHTLY_TAG_PREFIX}"}" + echo "Re-derived from-version: ${PREV_VERSION} (consider passing from-version input from generate-ghcr)" fi - PREV_VERSION="${PREV_TAG#"${NIGHTLY_TAG_PREFIX}"}" cd "${PATCHES_DIR}" || return 1 eval oras push "${FULL_REPO}:${PATCH_TAG_PREFIX}${VERSION}" \