From 8235d0b5453b619c856117ae3126c0a77f29d476 Mon Sep 17 00:00:00 2001 From: Khole Jones <29937485+KJonline@users.noreply.github.com> Date: Sat, 25 Apr 2026 19:07:31 +0100 Subject: [PATCH 1/2] Consolidate GitHub workflows: remove redundant jobs, add automated release pipeline, document branching model - Removed python-package.yml (duplicated by ci.yml lint-flake8 job) - Removed release_draft.yml (superseded by automatic release notes in release-on-master.yml) - Removed dev branch trigger from ci.yml (now runs on PRs and master pushes only) - Added release-on-master.yml workflow that reads version from setup.py, creates tag vX.Y.Z and GitHub Release with auto-generated notes on master pushes (skips if tag exists) - Added docs/workflows/README.md documenting the feature --- .github/workflows/ci.yml | 1 - .github/workflows/python-package.yml | 32 ------ .github/workflows/release-on-master.yml | 43 +++++++ .github/workflows/release_draft.yml | 12 -- docs/workflows/README.md | 143 ++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 45 deletions(-) delete mode 100644 .github/workflows/python-package.yml create mode 100644 .github/workflows/release-on-master.yml delete mode 100644 .github/workflows/release_draft.yml create mode 100644 docs/workflows/README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc40635..ae4c74b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,6 @@ on: push: branches: - master - - dev pull_request: ~ env: diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml deleted file mode 100644 index 28e7455..0000000 --- a/.github/workflows/python-package.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Python package - -on: - push: - branches: [master] - pull_request: - branches: [master] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] - - steps: - - uses: actions/checkout@v5 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install flake8 - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - - name: Lint with flake8 - run: | - # stop the build if there are Python syntax errors. - flake8 . --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. T - flake8 . --max-line-length=79 --statistics diff --git a/.github/workflows/release-on-master.yml b/.github/workflows/release-on-master.yml new file mode 100644 index 0000000..055c059 --- /dev/null +++ b/.github/workflows/release-on-master.yml @@ -0,0 +1,43 @@ +name: Tag and release on master + +on: + push: + branches: [master] + +permissions: + contents: write + +jobs: + tag-and-release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Read version from setup.py + id: version + run: | + v=$(python -c "import re; print(re.search(r'version=\"([^\"]+)\"', open('setup.py').read()).group(1))") + echo "version=$v" >> "$GITHUB_OUTPUT" + echo "tag=v$v" >> "$GITHUB_OUTPUT" + + - name: Check if tag already exists + id: check + run: | + if git rev-parse "refs/tags/${{ steps.version.outputs.tag }}" >/dev/null 2>&1; then + echo "exists=true" >> "$GITHUB_OUTPUT" + echo "Tag ${{ steps.version.outputs.tag }} already exists; skipping." + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Create GitHub Release + if: steps.check.outputs.exists == 'false' + uses: ncipollo/release-action@v1 + with: + tag: ${{ steps.version.outputs.tag }} + name: ${{ steps.version.outputs.tag }} + generateReleaseNotes: true + commit: ${{ github.sha }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release_draft.yml b/.github/workflows/release_draft.yml deleted file mode 100644 index 14d8a93..0000000 --- a/.github/workflows/release_draft.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: Release Drafter - -on: - workflow_dispatch: - -jobs: - update_release_draft: - runs-on: ubuntu-latest - steps: - - uses: release-drafter/release-drafter@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/workflows/README.md b/docs/workflows/README.md new file mode 100644 index 0000000..1380e54 --- /dev/null +++ b/docs/workflows/README.md @@ -0,0 +1,143 @@ +# GitHub Workflow Automation + +This document describes the branching model, branch protection, and the GitHub Actions workflows that automate CI, versioning, releases, and PyPI publishing for `pyhive-integration`. + +--- + +## Branching model + +``` +feature/* ──PR──▶ dev ──PR──▶ master ──tag/release──▶ PyPI +``` + +- **`feature/*`** — All work happens on feature branches. +- **`dev`** — Integration branch. Receives all feature PRs. Holds the next-version code. +- **`master`** — Release branch. Only ever receives merges from `dev`. Each merge produces a tagged GitHub Release and a PyPI publish. + +Direct pushes to `dev` and `master` are blocked via branch protection rules. + +--- + +## Branch protection rules (configured in GitHub Settings) + +### `master` +- Require a pull request before merging. +- Required status checks: + - `source-branch-is-dev` (from `guard-master.yml`) + - All CI jobs from `ci.yml` +- Do not allow bypassing. +- Include administrators. + +### `dev` +- Require a pull request before merging. +- Required status checks: all CI jobs from `ci.yml`. +- Allow any feature branch as PR source. + +--- + +## End-to-end automated flow + +1. Developer opens a PR from `feature/x` into `dev`. CI runs. +2. PR reviewed and merged into `dev`. +3. **`dev-release-pr.yml`** fires: + - If no open `dev → master` PR exists, it bumps the patch version in `setup.py`, commits to `dev` with `[skip ci]`, and opens a `dev → master` PR. + - If an open PR already exists, it does nothing (the PR auto-updates with the new commit). +4. Subsequent feature PRs into `dev` keep updating the same release PR — **no further version bumps**. +5. When ready to release, the maintainer merges the `dev → master` PR. +6. **`guard-master.yml`** ensured the PR's source was `dev`; the merge is allowed. +7. **`release-on-master.yml`** fires on the push to `master`: + - Reads the version from `setup.py`. + - Creates tag `vX.Y.Z` and a GitHub Release with auto-generated notes. +8. **`python-publish.yml`** fires on `release: published`: + - Builds the sdist + wheel. + - Uploads the wheel as a release asset. + - Publishes to PyPI via Trusted Publishing. + +--- + +## Workflow reference + +### `ci.yml` — Continuous Integration +- **Triggers:** all `pull_request` events; `push` to `master`. +- **Purpose:** Lint (bandit, black, codespell, flake8, isort, json, pyupgrade, etc.) and tests. +- **Role:** Gates every PR via required status checks. Provides a final sanity run on `master` after a release PR merges. + +### `guard-master.yml` — Master branch guard +- **Triggers:** `pull_request` targeting `master`. +- **Purpose:** Fails the check if the PR's source branch is anything other than `dev`. +- **Role:** Enforces the "only dev → master" rule. Must be a required status check on `master`. + +### `dev-release-pr.yml` — Release PR + version bump +- **Triggers:** `push` to `dev`. +- **Purpose:** + - Checks for an open `dev → master` PR. + - If none exists: bumps the patch version in `setup.py`, commits with `[skip ci]`, opens the release PR. + - If one exists: no-op. +- **Role:** Guarantees exactly one version bump per release cycle, regardless of how many feature PRs land on `dev`. + +### `release-on-master.yml` — Tag + GitHub Release +- **Triggers:** `push` to `master`. +- **Purpose:** + - Reads the version from `setup.py`. + - Skips if a tag for that version already exists. + - Otherwise creates tag `vX.Y.Z` and a GitHub Release with auto-generated notes. +- **Role:** The bridge between merging the release PR and triggering PyPI publishing. + +### `python-publish.yml` — PyPI publish (release-driven) +- **Triggers:** `release: published`. +- **Purpose:** + - Builds the sdist and wheel with `python -m build`. + - Uploads the wheel as a GitHub Release asset. + - Publishes to PyPI via OIDC Trusted Publishing (`pypi` environment). +- **Role:** Primary production publish path. + +### `dev-publish.yml` — Manual dev/ad-hoc PyPI publish +- **Triggers:** `workflow_dispatch` (manual only). +- **Purpose:** Build and publish to PyPI from a non-master branch on demand. +- **Role:** Used for ad-hoc dev releases when something needs to be pushed to PyPI without going through the full `dev → master` release cycle. Refuses to run on `master`. + +--- + +## Required GitHub configuration + +### Environments +- **`pypi`** — Used by `python-publish.yml` and `dev-publish.yml`. Configure as a Trusted Publisher on PyPI for this repository. + +### Required status checks on `master` +- `source-branch-is-dev` +- All CI job names from `ci.yml`. + +### Required status checks on `dev` +- All CI job names from `ci.yml`. + +### Permissions +The workflows use `GITHUB_TOKEN` with `contents: write` and `pull-requests: write` where needed. No additional PATs required. + +--- + +## Removed (redundant) workflows + +The following workflows were removed during this consolidation: + +- **`python-package.yml`** — Stand-alone flake8 lint duplicated by the `lint-flake8` job in `ci.yml`. +- **`release_draft.yml`** — Manual `release-drafter` workflow superseded by automatic GitHub-native release notes in `release-on-master.yml`. + +--- + +## Versioning + +- Source of truth: `version="X.Y.Z"` in `@/setup.py`. +- Bumped automatically (patch only) by `dev-release-pr.yml` once per release cycle. +- For minor or major bumps, edit `setup.py` manually on `dev` before the first feature PR merges, or amend the bump commit before merging the release PR. + +--- + +## Quick troubleshooting + +| Symptom | Likely cause | +|---|---| +| Release PR didn't open | Push to `dev` was the bot's own bump commit (contains `[skip ci]` and `chore: bump version` — intentionally skipped). | +| Version not bumped | A `dev → master` PR was already open. Bumps happen only when opening a fresh release PR. | +| PyPI publish skipped | Tag for current `setup.py` version already exists; bump the version before merging to `master` again. | +| PR into `master` fails check | Source branch is not `dev`. This is enforced by `guard-master.yml`. | +| Manual dev publish fails on master | Intentional — `dev-publish.yml` refuses to run on `master`. Switch branch or use the standard release flow. | From 44085652130b1100e39c2796b99fa1fc5845220f Mon Sep 17 00:00:00 2001 From: Khole Jones <29937485+KJonline@users.noreply.github.com> Date: Sat, 25 Apr 2026 19:14:34 +0100 Subject: [PATCH 2/2] Optimize CI workflow: add path filters to skip builds on non-code changes - Added paths filter to master push trigger (src, tests, requirements, setup files, config files, CI workflow itself) - Added identical paths filter to pull_request trigger - Prevents unnecessary CI runs when only docs, README, or other non-code files change --- .github/workflows/ci.yml | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae4c74b..9884ea2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,7 +5,35 @@ on: push: branches: - master - pull_request: ~ + paths: + - 'src/**' + - 'tests/**' + - 'requirements*.txt' + - 'setup.py' + - 'setup.cfg' + - 'pyproject.toml' + - 'MANIFEST.in' + - '.pre-commit-config.yaml' + - '.pylintrc' + - '.yamllint' + - '.secretlintrc.json' + - '.github/workflows/ci.yml' + - '.github/workflows/matchers/**' + pull_request: + paths: + - 'src/**' + - 'tests/**' + - 'requirements*.txt' + - 'setup.py' + - 'setup.cfg' + - 'pyproject.toml' + - 'MANIFEST.in' + - '.pre-commit-config.yaml' + - '.pylintrc' + - '.yamllint' + - '.secretlintrc.json' + - '.github/workflows/ci.yml' + - '.github/workflows/matchers/**' env: CACHE_VERSION: 1