Skip to content

Apply reviewed patch #9

Apply reviewed patch

Apply reviewed patch #9

Workflow file for this run

name: Apply reviewed patch
on:
workflow_dispatch:
inputs:
patch_base64:
description: "Base64 of a git diff patch, optionally gzip-compressed"
required: false
type: string
restore_from:
description: "Restore mode: commit SHA (7-40 hexadecimal characters)"
required: false
type: string
restore_path:
description: "Restore mode: one ordinary file in src/engine/**, tests/** or docs/**"
required: false
type: string
commit_message:
description: "Commit message"
required: true
type: string
permissions:
contents: write
concurrency:
group: openads-apply-patch-main
cancel-in-progress: false
jobs:
prepare:
runs-on: ubuntu-24.04
permissions:
contents: read
outputs:
base_sha: ${{ steps.base.outputs.sha }}
steps:
- name: Require dispatch from main
env:
DISPATCH_REF: ${{ github.ref }}
run: test "$DISPATCH_REF" = refs/heads/main
- uses: actions/checkout@v5
with:
ref: main
fetch-tags: true
fetch-depth: 0
persist-credentials: false
- name: Record exact base
id: base
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- name: Prepare restore or patch and enforce allowlist
env:
PATCH_BASE64: ${{ inputs.patch_base64 }}
RESTORE_FROM: ${{ inputs.restore_from }}
RESTORE_PATH: ${{ inputs.restore_path }}
COMMIT_MESSAGE: ${{ inputs.commit_message }}
shell: bash
run: |
set -euo pipefail
python3 - <<'PYTHON'
import base64, gzip, os, pathlib, re, subprocess
if not os.environ["COMMIT_MESSAGE"].strip():
raise SystemExit("Commit message must not be empty")
encoded = "".join(os.environ["PATCH_BASE64"].split())
restore_from = os.environ["RESTORE_FROM"].strip()
restore_path = os.environ["RESTORE_PATH"]
artifact = pathlib.Path(os.environ["RUNNER_TEMP"]) / "reviewed-patch"
artifact.mkdir()
patch_file = artifact / "patch.diff"
if restore_from or restore_path:
if encoded or not restore_from or not restore_path:
raise SystemExit("Use either patch_base64 OR both restore_from and restore_path")
if not re.fullmatch(r"[0-9a-fA-F]{7,40}", restore_from):
raise SystemExit("restore_from must be a commit SHA")
parts = restore_path.split("/")
allowed = restore_path.startswith(("src/engine/", "tests/", "docs/"))
if (not allowed or any(p in ("", ".", "..") for p in parts)
or any(p.lower() == ".git" for p in parts)
or "\\" in restore_path):
raise SystemExit("Disallowed restore_path")
commit = subprocess.check_output([
"git", "rev-parse", "--verify", restore_from + "^{commit}"
]).decode().strip()
entry = subprocess.check_output([
"git", "--literal-pathspecs", "ls-tree", "-z", commit, "--", restore_path
]).split(b"\0")
entry = [e for e in entry if e]
if len(entry) != 1:
raise SystemExit("restore_path must name exactly one existing file")
metadata, actual_path = entry[0].split(b"\t", 1)
mode, kind, _ = metadata.split()
if actual_path != os.fsencode(restore_path) or kind != b"blob" or mode not in (b"100644", b"100755"):
raise SystemExit("restore_path must name one ordinary file, not a directory or symlink")
subprocess.run([
"git", "--literal-pathspecs", "checkout", commit, "--", restore_path
], check=True)
# Normalize the one-file restore into the same exact patch used by all CI jobs.
patch = subprocess.check_output([
"git", "diff", "--cached", "--binary", "--full-index", "--no-ext-diff", "--no-renames"
])
subprocess.run(["git", "reset", "--hard", "HEAD"], check=True)
else:
patch = base64.b64decode(encoded, validate=True)
if patch.startswith(b"\x1f\x8b"):
patch = gzip.decompress(patch)
if not patch:
raise SystemExit("Input makes no changes")
patch_file.write_bytes(patch)
subprocess.run(["git", "apply", "--check", "--index", str(patch_file)], check=True)
subprocess.run(["git", "apply", "--index", str(patch_file)], check=True)
paths = subprocess.check_output([
"git", "diff", "--cached", "--name-only", "--no-renames", "-z"
]).split(b"\0")
paths = [p for p in paths if p]
if not paths:
raise SystemExit("Patch makes no changes")
if restore_path and paths != [os.fsencode(restore_path)]:
raise SystemExit("Restore must change exactly the requested file")
for path in paths:
parts = path.split(b"/")
allowed = (path.startswith(b"src/engine/") or
path.startswith(b"tests/") or path.startswith(b"docs/") or
path.startswith(b"src/network/") or
path.startswith(b"tools/serverd/") or
path.startswith(b"src/abi/") or
path in (b"roadmap.txt", b"src/mgmt/mg_collector.cpp", b"openads.ini.sample"))
if (not allowed or any(p in (b"", b".", b"..") for p in parts)
or any(p.lower() == b".git" for p in parts)):
raise SystemExit("Disallowed path: " + repr(path))
# Reject symlinks and submodules, including type changes.
raw = subprocess.check_output([
"git", "diff", "--cached", "--raw", "--no-renames", "-z"
]).split(b"\0")
for metadata in raw[::2]:
if not metadata:
continue
old_mode, new_mode = metadata.split()[:2]
if old_mode[1:] not in (b"000000", b"100644", b"100755") or new_mode not in (b"000000", b"100644", b"100755"):
raise SystemExit("Only ordinary files may be patched")
(artifact / "base.sha").write_bytes(subprocess.check_output(["git", "rev-parse", "HEAD"]))
PYTHON
- name: Share validated patch with checks
uses: actions/upload-artifact@v4
with:
name: reviewed-patch
path: ${{ runner.temp }}/reviewed-patch/
retention-days: 1
if-no-files-found: error
build-and-test:
needs: prepare
permissions:
contents: read
name: ${{ matrix.os }} / ${{ matrix.preset }}${{ matrix.tls && ' / TLS' || '' }}
runs-on: ${{ matrix.os }}
# macOS is out of the matrix for now: its unit tests stall and a timeout
# cancels the whole run. Re-add the macos-14 entry once that is fixed.
timeout-minutes: ${{ matrix.os == 'macos-14' && 90 || 30 }}
continue-on-error: ${{ matrix.os == 'macos-14' }}
strategy:
fail-fast: false
matrix:
include:
- os: windows-2022
preset: msvc-x64
tls: false
# 32-bit MSVC: many deployments still ship x86. CI must build it
# so x86-only breakage (bitness-dependent narrowing C4244/C2220,
# SQLLEN*/SQLPOINTER signatures C2733, /WX C4100) can't slip past.
- os: windows-2022
preset: msvc-x86
tls: false
- os: ubuntu-24.04
preset: ninja-clang
tls: false
# M12.12 — verify the OPENADS_WITH_TLS path (vendored
# mbedtls 3.6) on at least one runner per release.
- os: ubuntu-24.04
preset: ninja-clang
tls: true
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.base_sha }}
persist-credentials: false
# Version string comes from `git describe --tags`: without
# tag refs a main-branch checkout reports a bare SHA (no
# dots) and network_version_test fails everywhere. Releases
# check out the tag itself so they never hit this.
# fetch-tags alone stays shallow (default fetch-depth: 1), so
# describe still can't see v* tags - fetch the full history.
fetch-tags: true
fetch-depth: 0
- name: Download validated patch
uses: actions/download-artifact@v4
with:
name: reviewed-patch
path: ${{ runner.temp }}/reviewed-patch
- name: Apply validated patch
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$(cat "$RUNNER_TEMP/reviewed-patch/base.sha")"
git apply --check --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
git apply --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
- name: Install Ninja (Linux / macOS)
if: runner.os != 'Windows'
uses: seanmiddleditch/gha-setup-ninja@v6
- name: Configure
run: |
cmake --preset ${{ matrix.preset }} \
${{ matrix.tls && '-DOPENADS_WITH_TLS=ON' || '' }}
shell: bash
- name: Build
run: cmake --build build/${{ matrix.preset }} --config Release --target dbf_cdx_bench openads_unit_tests
- name: Test
run: ctest --test-dir build/${{ matrix.preset }} --output-on-failure -C Release
- name: Bench smoke (CDX multi-tag create)
# Small run to smoke the optimized INDEX ON / bulk create path (#128)
run: |
BENCH="build/${{ matrix.preset }}/tests/Release/dbf_cdx_bench${{ runner.os == 'Windows' && '.exe' || '' }}"
if [ -f "$BENCH" ]; then
"$BENCH" 1000 || true
fi
shell: bash
php-binding:
needs: prepare
permissions:
contents: read
name: PHP binding
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.base_sha }}
persist-credentials: false
- name: Download validated patch
uses: actions/download-artifact@v4
with:
name: reviewed-patch
path: ${{ runner.temp }}/reviewed-patch
- name: Apply validated patch
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$(cat "$RUNNER_TEMP/reviewed-patch/base.sha")"
git apply --check --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
git apply --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v6
- name: Build OpenADS ACE library
run: |
cmake --preset ninja-clang
cmake --build build/ninja-clang --config Release --target openads_ace
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: ffi
ini-values: ffi.enable=1
- name: Install PHP dependencies
working-directory: bindings/php
run: composer install --no-interaction
- name: Run PHP binding tests
working-directory: bindings/php
env:
OPENADS_ACE_LIB: ${{ github.workspace }}/build/ninja-clang/src/libace64.so
run: composer test
sql-uri-smoke:
needs: prepare
permissions:
contents: read
name: SQL URI smoke
runs-on: ubuntu-24.04
timeout-minutes: 15
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.base_sha }}
persist-credentials: false
- name: Download validated patch
uses: actions/download-artifact@v4
with:
name: reviewed-patch
path: ${{ runner.temp }}/reviewed-patch
- name: Apply validated patch
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$(cat "$RUNNER_TEMP/reviewed-patch/base.sha")"
git apply --check --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
git apply --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v6
- name: Configure
run: cmake --preset ninja-clang
shell: bash
- name: Build unit tests
run: cmake --build build/ninja-clang --config Release --target openads_unit_tests
shell: bash
- name: Run SQL URI smoke tests
run: |
ctest --test-dir build/ninja-clang -C Release --output-on-failure \
-R "sql_uri_smoke|sqlite_filter_pushdown|sqlite_seek_smoke"
shell: bash
sql-live-pg-maria:
needs: prepare
permissions:
contents: read
name: SQL live PG + Maria
runs-on: ubuntu-24.04
timeout-minutes: 20
services:
postgres:
image: postgres:16
env:
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
mariadb:
image: mariadb:11
env:
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: "yes"
MARIADB_DATABASE: test
ports:
- 3306:3306
options: >-
--health-cmd "healthcheck.sh --connect --innodb_initialized"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.base_sha }}
persist-credentials: false
- name: Download validated patch
uses: actions/download-artifact@v4
with:
name: reviewed-patch
path: ${{ runner.temp }}/reviewed-patch
- name: Apply validated patch
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$(cat "$RUNNER_TEMP/reviewed-patch/base.sha")"
git apply --check --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
git apply --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y ninja-build libpq-dev libmariadb-dev
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v6
- name: Configure (PostgreSQL + MariaDB)
run: |
cmake --preset ninja-clang \
-DOPENADS_WITH_POSTGRESQL=ON \
-DOPENADS_WITH_MARIADB=ON
shell: bash
- name: Build unit tests
run: cmake --build build/ninja-clang --config Release --target openads_unit_tests
shell: bash
- name: Run live PG + Maria smoke tests
env:
OPENADS_TEST_PG_URI: postgresql://postgres@127.0.0.1:5432/postgres
OPENADS_TEST_MARIADB_URI: mariadb://root@127.0.0.1:3306/test
run: |
ctest --test-dir build/ninja-clang -C Release --output-on-failure \
-R "pg_live_smoke|maria_live_smoke"
shell: bash
sql-live-mssql:
needs: prepare
permissions:
contents: read
name: SQL live MSSQL
runs-on: ubuntu-24.04
timeout-minutes: 25
services:
mssql:
image: mcr.microsoft.com/mssql/server:2022-latest
env:
ACCEPT_EULA: Y
MSSQL_SA_PASSWORD: OpenADS_CI_Passw0rd!
ports:
- 1433:1433
options: >-
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P OpenADS_CI_Passw0rd! -Q 'SELECT 1' || exit 1"
--health-interval 10s
--health-timeout 5s
--health-retries 10
--health-start-period 30s
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.base_sha }}
persist-credentials: false
- name: Download validated patch
uses: actions/download-artifact@v4
with:
name: reviewed-patch
path: ${{ runner.temp }}/reviewed-patch
- name: Apply validated patch
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$(cat "$RUNNER_TEMP/reviewed-patch/base.sha")"
git apply --check --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
git apply --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y ninja-build
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v6
- name: Configure (MSSQL)
run: cmake --preset ninja-clang -DOPENADS_WITH_MSSQL=ON -DOPENADS_WITH_TLS=ON
shell: bash
- name: Build unit tests
run: cmake --build build/ninja-clang --config Release --target openads_unit_tests
shell: bash
- name: Run live MSSQL smoke test
env:
OPENADS_TEST_MSSQL_CONNSTR: mssql://sa:OpenADS_CI_Passw0rd!@127.0.0.1:1433/master
run: |
ctest --test-dir build/ninja-clang -C Release --output-on-failure \
-R mssql_live_smoke
shell: bash
sql-live-firebird:
needs: prepare
permissions:
contents: read
name: SQL live Firebird
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.base_sha }}
persist-credentials: false
- name: Download validated patch
uses: actions/download-artifact@v4
with:
name: reviewed-patch
path: ${{ runner.temp }}/reviewed-patch
- name: Apply validated patch
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$(cat "$RUNNER_TEMP/reviewed-patch/base.sha")"
git apply --check --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
git apply --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
- name: Install build deps
run: |
sudo apt-get update
sudo apt-get install -y ninja-build firebird-dev firebird3.0-utils
- name: Install Ninja
uses: seanmiddleditch/gha-setup-ninja@v6
- name: Seed embedded Firebird fixture
run: |
mkdir -p /tmp/openads-fb
cat > /tmp/openads-fb/seed.sql <<'EOF'
CREATE DATABASE '/tmp/openads-fb/live.fdb' USER 'SYSDBA' DEFAULT CHARACTER SET UTF8;
EXIT;
EOF
isql-fb -user SYSDBA -i /tmp/openads-fb/seed.sql
- name: Configure (Firebird)
run: cmake --preset ninja-clang -DOPENADS_WITH_FIREBIRD=ON
shell: bash
- name: Build unit tests
run: cmake --build build/ninja-clang --config Release --target openads_unit_tests
shell: bash
- name: Run live Firebird smoke test
env:
OPENADS_TEST_FIREBIRD_DB: /tmp/openads-fb/live.fdb
run: |
ctest --test-dir build/ninja-clang -C Release --output-on-failure \
-R firebird_live_smoke
shell: bash
harbour-smoke:
needs: prepare
permissions:
contents: read
name: Harbour smoke (Windows)
runs-on: windows-2022
timeout-minutes: 120
steps:
- uses: actions/checkout@v5
with:
ref: ${{ needs.prepare.outputs.base_sha }}
persist-credentials: false
- name: Download validated patch
uses: actions/download-artifact@v4
with:
name: reviewed-patch
path: ${{ runner.temp }}/reviewed-patch
- name: Apply validated patch
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$(cat "$RUNNER_TEMP/reviewed-patch/base.sha")"
git apply --check --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
git apply --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
- name: Cache Harbour toolchain
uses: actions/cache@v4
id: harbour-cache
with:
path: ${{ runner.temp }}/harbour-ci
key: harbour-core-master-msvc64-v5-${{ hashFiles('tools/scripts/bootstrap_harbour_ci.ps1') }}
- name: Configure OpenADS
run: cmake --preset msvc-x64
shell: bash
- name: Build OpenADS + make_cdx
run: cmake --build build/msvc-x64 --config Release --target openads_ace make_cdx
shell: bash
- name: Bootstrap Harbour
run: pwsh -File tools/scripts/bootstrap_harbour_ci.ps1
env:
HARBOUR_CI_ROOT: ${{ runner.temp }}/harbour-ci
OPENADS_ROOT: ${{ github.workspace }}
OPENADS_BUILD: ${{ github.workspace }}/build/msvc-x64
GIT_TERMINAL_PROMPT: "0"
- name: Run Harbour smoke
run: pwsh -File tools/scripts/run_harbour_smoke.ps1
env:
OPENADS_BUILD: ${{ github.workspace }}/build/msvc-x64
HARBOUR_ROOT: ${{ runner.temp }}/harbour-ci
commit:
needs:
- prepare
- build-and-test
- php-binding
- sql-uri-smoke
- sql-live-pg-maria
- sql-live-mssql
- sql-live-firebird
- harbour-smoke
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v5
with:
ref: main
fetch-tags: true
fetch-depth: 0
- name: Download validated patch
uses: actions/download-artifact@v4
with:
name: reviewed-patch
path: ${{ runner.temp }}/reviewed-patch
- name: Commit and push only the tested patch
env:
COMMIT_MESSAGE: ${{ inputs.commit_message }}
EXPECTED_BASE: ${{ needs.prepare.outputs.base_sha }}
shell: bash
run: |
set -euo pipefail
test "$(cat "$RUNNER_TEMP/reviewed-patch/base.sha")" = "$EXPECTED_BASE"
git fetch origin main
test "$(git rev-parse origin/main)" = "$EXPECTED_BASE" || {
echo "main changed while checks ran. Dispatch again against the new base."
exit 1
}
test "$(git rev-parse HEAD)" = "$EXPECTED_BASE"
git apply --check --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
git apply --index "$RUNNER_TEMP/reviewed-patch/patch.diff"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
printf '%s\n' "$COMMIT_MESSAGE" > "$RUNNER_TEMP/patch-commit-message.txt"
git -c core.hooksPath=/dev/null commit -F "$RUNNER_TEMP/patch-commit-message.txt"
git push origin HEAD:refs/heads/main