diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 525d0e3..9692005 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -40,6 +40,15 @@ jobs: NEW_TAG="v${{ steps.bump.outputs.version }}" scripts/ci/generate-release-notes.sh "$NEW_TAG" "$PR_TITLE" "$PREV_TAG" + - name: Update CHANGELOG + if: steps.meta.outputs.bump != 'skip' + env: + PR_TITLE: ${{ steps.meta.outputs.pr_title }} + run: | + PREV_TAG=$(git describe --tags --abbrev=0) + VERSION="${{ steps.bump.outputs.version }}" + scripts/ci/update-changelog.sh "$VERSION" "$PR_TITLE" "$PREV_TAG" + - name: Commit, tag, and push if: steps.meta.outputs.bump != 'skip' env: @@ -48,7 +57,7 @@ jobs: run: | git config user.name "CI Bot" git config user.email "ci-bot@example.com" - git add pyproject.toml uv.lock + git add pyproject.toml uv.lock CHANGELOG.md git commit -m "chore: release v${VERSION} [skip ci]" git tag "v${VERSION}" git remote set-url origin "https://x-access-token:${CI_RUNNER_TOKEN}@${GITHUB_SERVER_URL#https://}/${GITHUB_REPOSITORY}.git" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0d10414 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,325 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [2.0.4] - 2026-07-10 + +### Summary + +Include optional dependencies in security audit + +### Changed + +- 4f7b672 chore: release v2.0.4 [skip ci] +- a27e9e5 Merge pull request '[fix] Include optional dependencies in security audit' (#50) from cursor/security-audit-all-extras into main +- fd0045d Include optional dependencies in security audit. +- 515f4bb Merge pull request 'revert 707f11f578f007c7152582ea524b66dacea50111' (#49) from brian-patch-1 into main +- ef31fb4 revert 707f11f578f007c7152582ea524b66dacea50111 +- 707f11f chore: release v2.0.4 [skip ci] +- b1d34f8 Merge pull request '[patch] Unify dev tooling via uv so pre-commit, CI, and the update bot stay aligned' (#48) from chore/align-dev-tooling-via-uv into main +- 565879d Unify dev tooling via uv so pre-commit, CI, and the update bot stay aligned. +- 0d0f430 Merge pull request 'Fix automated package publishing in release workflow' (#47) from fix/release-workflow-publish into main +- 5ac40b1 Publish packages from the release workflow so automated releases reach the registry. + +## [2.0.3] - 2026-07-10 + +### Summary + +Use structured fields for adapter debug and info logs + +### Changed + +- fa4ff1f chore: release v2.0.3 [skip ci] +- 0a39151 Merge pull request '[patch] Use structured fields for adapter debug and info logs' (#46) from cursor/structured-adapter-logging into main +- 82a702d Fix mypy error in Minio adapter log assertion. +- b886a7c Use structured fields for adapter debug and info logs. +- 4518a93 Merge pull request 'Migrate Python workflows to python-repositories-ci runner' (#41) from cursor/migrate-python-repositories-ci into main +- a8b7b59 Migrate Python workflows to python-repositories-ci runner label. +- 0e6941d Merge pull request 'Use lowercase CI image registry owner for Docker compatibility' (#45) from cursor/fix-ci-registry-owner-lowercase into main +- a6428cc Use lowercase CI image registry owner for Docker compatibility. +- 655fdc4 Merge pull request 'Fix CI image registry owner to match Gitea org slug' (#44) from cursor/fix-ci-registry-owner-case into main +- e031c54 Fix CI image registry owner to match Gitea org slug. +- 49b6238 Merge pull request 'Rename CI registry owner from brian to lille-vemmelund' (#43) from cursor/rename-ci-owner-to-lille-vemmelund into main +- 0549358 Keep python-utils package index under brian owner. +- 33efbd1 Update CI registry owner from brian to lille-vemmelund. +- 24126e4 Merge pull request 'Fix CI image build failures on runners with isolated DinD DNS' (#42) from fix/ci-image-build-dns into main +- f3b6cdb Fix CI image build failures on runners with isolated DinD DNS. +- fa2554e Merge pull request 'Add nightly CI base image and migrate workflows to python-repositories-ci' (#40) from cursor/ci-base-image into main +- b21fd24 Revert workflows to ubuntu-latest until CI image is seeded. +- 441a212 Add local CI image build script and document bootstrap flow. +- f2a8581 Read Python version from .python-version in CI image build. +- 3b05260 Add nightly CI base image and migrate workflows to python-repositories-ci. + +## [2.0.2] - 2026-07-09 + +### Summary + +Use dict[str, Any] for JSON repository document types + +### Changed + +- 369295b chore: release v2.0.2 [skip ci] +- 95c7859 Merge pull request '[patch] Use dict[str, Any] for JSON repository document types' (#38) from cursor/json-dict-str-any-typing into main +- 42e885e Use dict[str, Any] for JSON repository document types. + +## [2.0.1] - 2026-07-09 + +### Summary + +Align Redis and MinIO connect() idempotency + +### Changed + +- bd4794d chore: release v2.0.1 [skip ci] +- 3dc412c Merge pull request '[patch] Align Redis and MinIO connect() idempotency' (#37) from cursor/connect-idempotency-alignment into main +- b8d88e6 Cover injected-client connect when client was cleared by disconnect. +- 67bb88f Align Redis and MinIO connect() idempotency in ConnectionAwareAdapter. + +## [2.0.0] - 2026-07-09 + +### Summary + +Make MinIO bucket creation opt-in with production-safe defaults + +### Changed + +- c2e6a96 chore: release v2.0.0 [skip ci] +- 547a1b3 Merge pull request '[breaking] Make MinIO bucket creation opt-in with production-safe defaults' (#36) from cursor/minio-bucket-creation-opt-in into main +- 39baf9b Fix examples integration test by provisioning MinIO bucket in fixture. +- 4a5b3b6 Make MinIO bucket creation opt-in with production-safe defaults. +- 1431f45 Merge pull request 'Move Redis test container helpers into integration conftest' (#35) from cursor/move-redis-container-to-conftest into main +- eafc717 Move Redis test container helpers into integration conftest. + +## [1.1.0] - 2026-07-08 + +### Summary + +Add Redis scan_keys iterator API + +### Changed + +- 9a9b7b2 chore: release v1.1.0 [skip ci] +- 5b9573d Merge pull request '[minor] Add Redis scan_keys iterator API' (#34) from cursor/redis-scan-keys into main +- 50444af Add Redis scan_keys iterator API. + +## [1.0.0] - 2026-07-08 + +### Summary + +Clarify MinIO get() error semantics to match Redis behavior + +### Changed + +- 2f19fcc chore: release v1.0.0 [skip ci] +- 3bd6589 Merge pull request '[breaking] Clarify MinIO get() error semantics to match Redis behavior' (#33) from cursor/minio-error-semantics into main +- 5311d49 Clarify MinIO get() error semantics to match Redis behavior. + +## [0.5.1] - 2026-07-07 + +### Summary + +Fix MinIO get_object response leak in MinioAdapter.get() + +### Changed + +- 703bb95 chore: release v0.5.1 [skip ci] +- 8e47ebf Merge pull request '[patch] Fix MinIO get_object response leak in MinioAdapter.get()' (#32) from fix/minio-get-object-response-cleanup into main +- 9a800bf Fetch live PR title in pr-title-check CI workflow. +- 8c34534 Fix MinIO get_object response leak in MinioAdapter.get(). +- b1210bd Merge pull request 'Reach 100% combined coverage with targeted unit tests' (#31) from cursor/balanced-100-coverage into main +- 39b1043 Fix mypy unreachable-code warning in minio reconnect test. +- beb2b51 Reach 100% combined coverage with targeted unit tests. +- ce062be Merge pull request 'Enforce 90% combined coverage floor in CI' (#29) from cursor/enforce-coverage-floor into main +- a381d45 Lower combined coverage floor from 95% to 90%. +- 5c8cd84 Document where combined coverage floor is enforced in CI. +- 3463298 Skip coverage floor check in partial CI test jobs. +- 5393efc Enforce a 95% combined coverage floor in CI and local runs. +- e39fc96 Merge pull request 'Split CI into parallel unit, integration, and coverage report jobs' (#28) from cursor/split-ci-test-jobs into main +- c1f4826 Use christopherhx v4 artifact actions for parallel coverage merge. +- 0315a33 Avoid cross-job artifacts; combine coverage with --cov-append. +- 5d33ec6 Use artifact action v3 for Gitea Actions compatibility. +- 9538e4d Split CI into parallel unit, integration, and coverage report jobs. +- 14b8d27 Merge pull request 'Replace hardcoded version examples in release docs' (#27) from cursor/fix-version-doc-drift into main +- d3cbb65 Use version-agnostic examples in release docs. + +## [0.5.0] - 2026-07-06 + +### Summary + +Add config and client injection with test reorganization + +### Changed + +- 7dbb1fc chore: release v0.5.0 [skip ci] +- 918e5e6 Merge pull request '[minor] Add config and client injection with test reorganization' (#25) from cursor/config-client-injection into main +- 3aa9128 Load MINIO_SECURE from env and replace adapter asserts with explicit errors. +- 7991dab Add config and client injection with test reorganization. +- 366831a Merge pull request 'chore(deps): update dependencies' (#26) from renovate/auto-deps-update into main +- a98905d chore(deps): update dependencies [automated] +- 7ed1b34 Merge pull request 'Sync uv.lock on release to prevent CI failures' (#24) from cursor/sync-uv-lock-on-release into main +- 3236f27 manually updated package version +- ac487d9 Sync uv.lock on release to keep CI clean. + +## [0.4.1] - 2026-07-05 + +### Summary + +Extract shared connection lifecycle into ConnectionAwareAdapter + +### Changed + +- a049ce3 chore: release v0.4.1 [skip ci] +- 7b4cf84 Merge pull request '[patch] Extract shared connection lifecycle into ConnectionAwareAdapter' (#23) from cursor/extract-connection-aware-adapter into main +- b5a92e6 Sync uv.lock with pyproject.toml version 0.4.0. +- 7b950e0 Extract shared connection lifecycle into ConnectionAwareAdapter. + +## [0.4.0] - 2026-07-05 + +### Summary + +Strengthen is_connected with cached health probes + +### Changed + +- fe0c477 chore: release v0.4.0 [skip ci] +- e7e0fc8 Merge pull request '[minor] Strengthen is_connected with cached health probes' (#22) from cursor/strengthen-is-connected-health-checks into main +- 34fc046 Sync uv.lock with pyproject.toml version 0.3.2. +- 5149299 Strengthen is_connected with cached health probes. + +## [0.3.2] - 2026-06-30 + +### Summary + +Fix optional dependency handling for Redis and MinIO adapters + +### Changed + +- fef9552 chore: release v0.3.2 [skip ci] +- e9b2189 Merge pull request '[patch] Fix optional dependency handling for Redis and MinIO adapters' (#19) from cursor/fix-optional-dependencies into main +- f595390 Fix optional-deps import test for full-suite execution. +- 1f02195 Delegate top-level adapter imports to adapters package. +- f092ca2 Fix trailing whitespace in LICENSE and .gitignore. +- c6eed43 Fix optional dependency handling for Redis and MinIO adapters. +- 9e4b1e2 Merge pull request 'Avoid testcontainers Redis deprecation warning in integration tests.' (#18) from fix/redis-testcontainer-deprecation into main +- 0096df7 Avoid testcontainers Redis deprecation warning in integration tests. +- 3073bfb Merge pull request 'Fix PR coverage comment posting in CI.' (#17) from fix/coverage-pr-comment into main +- 30a1de3 Fix PR coverage comment posting in CI. +- 8465957 Merge pull request '[patch] update dependencies' (#16) from renovate/auto-deps-update into main +- 3ecb9c3 chore: retrigger CI +- 0f31170 Fix mypy errors from minio 7.2.20 type stubs. +- e3cab5e chore(deps): update dependencies [automated] +- f428a97 Merge pull request 'Fix dependency bot PR creation for Gitea.' (#15) from cursor/fix-dependency-bot-gitea-pr into main +- a3b9912 Fix dependency bot PR creation for Gitea. +- abaa078 Merge pull request 'Fix dependency update bot to use uv lock --upgrade.' (#14) from cursor/fix-dependency-update-bot-uv-lock into main +- 9864f0c Fix dependency update bot to use uv lock --upgrade. +- e39b467 Merge pull request 'Automate releases from PR titles and clean up CI workflows.' (#13) from cursor/pr-title-release-automation into main +- 85c1a64 Sync uv.lock with pyproject.toml version 0.3.1. +- a8491c0 Automate releases from PR titles and clean up CI workflows. + +## [0.3.1] - 2026-06-28 + +### Summary + +Add public repository interfaces and subclassable adapter CRUD API. + +### Changed + +- 2bb361e Merge pull request 'Add public repository interfaces and subclassable adapter CRUD API.' (#11) from cursor/public-repository-api into main +- c5169df Use pre-commit for Prettier in CI and document local hook setup. +- 497533f Fix mypy context manager typing for adapter subclasses. +- e98fd3b Add public repository interfaces and subclassable adapter CRUD API. +- f4280f1 Merge pull request 'Remove non-existent Mongo adapter from README.' (#10) from cleanup/remove-mongo-adapter-mentions into main +- d9b9fa4 updated package settings +- 9d25f7c updated package settings +- 1635815 updated package settings +- 933c4ee updated package settings +- 2b0d98f updated package settings +- 729fa48 Remove non-existent Mongo adapter from README. + +## [0.2.3] - 2025-09-18 + +### Summary + +added support for content type when putting object in minio + +### Changed + +- fa011be Merge pull request 'added support for content type when putting object in minio' (#9) from add-support-for-content-type-in-minio into main +- 2e93309 ruff format fix +- 3ab8da9 added support for content type when putting object in minio + +## [0.2.2] - 2025-09-17 + +### Summary + +moved import of optional packages into classes + +### Changed + +- 1dd604d Merge pull request 'moved import of optional packages into classes' (#6) from make-optional-parts-independent into main +- e5c4025 added graceful handling of missing optional packages +- 396cf83 code quality fixes +- 538b26b mypy fixed +- ee6acc6 moved import of optional packages into classes + +## [0.2.1] - 2025-09-15 + +### Summary + +exposed minio adapter + +### Changed + +- 0ca07c0 Merge pull request 'exposed minio adapter' (#5) from expose-minio-adapter into main +- 7446e88 added py.typed mypy config file +- d5f437f ruff format fix +- 7beca7c exposed minio adapter + +## [0.2.0] - 2025-09-15 + +### Summary + +add-minio-adapter + +### Changed + +- 65d8051 Merge pull request 'add-minio-adapter' (#4) from add-minio-adapter into main +- f5af303 auto install all extra packages +- 3260a3f added new optional package to ci +- c7bced4 ruff lint fixes +- 800d702 pyupgrade +- 5a72507 ruff format +- c45ba3c mypy fixes +- a3b5443 added minio adapter and tests +- cc99fe5 minor corrections +- 93fffe1 added optional group minio + +## [0.1.0] - 2025-09-14 + +### Summary + +Release v0.1.0 + +### Changed + +- 2eafd73 Merge pull request 'added redis adapter' (#1) from collect-code-from-other-repos into main +- 1cc047d removed unused bound ports for test +- c7cc2a3 added optional package redis +- b5d0297 pyupgrade fixed imports +- 2fa633a ruff fixed formatting +- 0e47db2 fuxed mypy issues +- 0948799 added integration tests +- 524bd0f fixed connection error handling +- 969c2c8 installed more pytest-cov +- 7caee87 added installing optional redis group +- 918535e added test coverage and upload step +- 5d7afe7 ruff format corrections +- 59a943e added redis types stub and fixed return type +- 1abcae4 added redis adapter +- c789e33 initial commit +- 56cad42 Initial commit diff --git a/README.md b/README.md index 6d66c0a..795e81f 100644 --- a/README.md +++ b/README.md @@ -194,11 +194,14 @@ uv run pre-commit run --all-files Releases are automated when a pull request is merged to `main`. CI reads the **merged PR title** to decide whether and how to bump the version. +- **Version history:** [`CHANGELOG.md`](CHANGELOG.md) +- **Gitea releases:** [releases page](https://gitea.lille-vemmelund.dk/LilleVemmelund/python-repositories/releases) + ### How it works 1. Open a PR targeting `main` (see [`.gitea/PULL_REQUEST_TEMPLATE.md`](.gitea/PULL_REQUEST_TEMPLATE.md)). 2. If the PR changes files under `python_repositories/`, the title **must** start with a version bump prefix (enforced by CI). -3. On merge, [`release.yml`](.gitea/workflows/release.yml) bumps [`pyproject.toml`](pyproject.toml) and syncs [`uv.lock`](uv.lock), commits, tags `vX.Y.Z`, creates a Gitea release with auto-generated notes, pushes the tag, and publishes the package to the Gitea Package Registry. +3. On merge, [`release.yml`](.gitea/workflows/release.yml) bumps [`pyproject.toml`](pyproject.toml) and syncs [`uv.lock`](uv.lock), updates [`CHANGELOG.md`](CHANGELOG.md), commits, tags `vX.Y.Z`, creates a Gitea release with auto-generated notes, pushes the tag, and publishes the package to the Gitea Package Registry. 4. [`publish.yml`](.gitea/workflows/publish.yml) handles manual `v*.*.*` tag pushes only (automated releases publish from `release.yml` because release commits use `[skip ci]`, which suppresses tag-push workflows). Docs-, CI-, and test-only PRs do not need a prefix and will not trigger a release. @@ -219,7 +222,7 @@ Example titles: ### Release notes -Release notes are generated from commits since the previous tag (see [`scripts/ci/generate-release-notes.sh`](scripts/ci/generate-release-notes.sh)). +Release notes are generated from commits since the previous tag (see [`scripts/ci/generate-release-notes.sh`](scripts/ci/generate-release-notes.sh)). The same content is appended to [`CHANGELOG.md`](CHANGELOG.md) on each release (see [`scripts/ci/update-changelog.sh`](scripts/ci/update-changelog.sh)). ### Manual release diff --git a/pyproject.toml b/pyproject.toml index da55b23..07ef3ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,6 +22,8 @@ dependencies = [ [project.urls] Repository = "https://gitea.lille-vemmelund.dk/LilleVemmelund/python-repositories" +Changelog = "https://gitea.lille-vemmelund.dk/LilleVemmelund/python-repositories/src/branch/main/CHANGELOG.md" +Releases = "https://gitea.lille-vemmelund.dk/LilleVemmelund/python-repositories/releases" [project.optional-dependencies] redis = [ diff --git a/scripts/ci/backfill-changelog.sh b/scripts/ci/backfill-changelog.sh new file mode 100755 index 0000000..4cb837c --- /dev/null +++ b/scripts/ci/backfill-changelog.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=common.sh +source "${SCRIPT_DIR}/common.sh" + +CHANGELOG="CHANGELOG.md" + +# Best-effort summary for historical releases without stored PR titles. +summary_for_range() { + local from_ref="$1" + local to_ref="$2" + local subject + + while IFS= read -r subject; do + [[ -z "$subject" ]] && continue + if echo "$subject" | grep -qE '^chore: release v'; then + continue + fi + if echo "$subject" | grep -qE "^Merge pull request '"; then + local title + title=$(extract_pr_title_from_merge_commit "$subject") + if [[ -n "$title" ]]; then + strip_bump_prefix "$title" + return 0 + fi + fi + echo "$subject" + return 0 + done < <(git log "${from_ref}..${to_ref}" --pretty=format:'%s' 2>/dev/null || true) + + echo "Release ${to_ref}" +} + +TAGS=() +while IFS= read -r tag; do + TAGS+=("$tag") +done < <(git tag -l 'v*' --sort=version:refname) +if [[ ${#TAGS[@]} -eq 0 ]]; then + echo "No version tags found." >&2 + exit 1 +fi + +changelog_header > "$CHANGELOG" + +for ((i = ${#TAGS[@]} - 1; i >= 0; i--)); do + TAG="${TAGS[$i]}" + VERSION="${TAG#v}" + DATE=$(git log -1 --format=%cs "$TAG" 2>/dev/null || date -u +%Y-%m-%d) + + if [[ $i -eq 0 ]]; then + PREV_TAG="" + else + PREV_TAG="${TAGS[$((i - 1))]}" + fi + + SUMMARY=$(summary_for_range "$PREV_TAG" "$TAG") + COMMITS=$(commits_between "$PREV_TAG" "$TAG") + SECTION=$(changelog_section "$VERSION" "$DATE" "$SUMMARY" "$COMMITS") + printf '\n%s\n' "$SECTION" >> "$CHANGELOG" +done + +echo "Generated ${CHANGELOG} from ${#TAGS[@]} tags." >&2 diff --git a/scripts/ci/common.sh b/scripts/ci/common.sh index 140a19d..cdf3a3b 100755 --- a/scripts/ci/common.sh +++ b/scripts/ci/common.sh @@ -61,6 +61,59 @@ set_pyproject_version() { rm -f pyproject.toml.bak } +# Returns markdown bullet list of commits between two refs (exclusive..inclusive). +# Optional third argument limits entries when from_ref is empty (release notes only). +commits_between() { + local from_ref="$1" + local to_ref="${2:-HEAD}" + local limit="${3:-}" + + if [[ -z "$from_ref" ]]; then + if [[ -n "$limit" ]]; then + git log "${to_ref}" --pretty=format:'- %h %s' -"${limit}" 2>/dev/null || true + else + git log "${to_ref}" --pretty=format:'- %h %s' 2>/dev/null || true + fi + else + git log "${from_ref}..${to_ref}" --pretty=format:'- %h %s' 2>/dev/null || true + fi +} + +commits_since() { + commits_between "$1" HEAD +} + +changelog_header() { + cat <<'EOF' +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +EOF +} + +changelog_section() { + local version="$1" + local date="$2" + local summary="$3" + local commits="$4" + + cat < [date]" >&2 + exit 1 +fi + +if [[ -f "$CHANGELOG" ]] && grep -qF "## [${VERSION}]" "$CHANGELOG"; then + echo "CHANGELOG already contains version ${VERSION}; skipping." >&2 + exit 0 +fi + +if [[ -n "$SUMMARY" ]]; then + SUMMARY_TEXT=$(strip_bump_prefix "$SUMMARY") +else + SUMMARY_TEXT="Automated release v${VERSION}." +fi + +COMMITS=$(commits_since "$PREV_TAG") +SECTION=$(changelog_section "$VERSION" "$DATE" "$SUMMARY_TEXT" "$COMMITS") + +if [[ ! -f "$CHANGELOG" ]]; then + changelog_header > "$CHANGELOG" + printf '\n%s\n' "$SECTION" >> "$CHANGELOG" +else + UNRELEASED_MARKER='## [Unreleased]' + if ! grep -qF "$UNRELEASED_MARKER" "$CHANGELOG"; then + echo "CHANGELOG.md exists but is missing ${UNRELEASED_MARKER}" >&2 + exit 1 + fi + + TMP=$(mktemp) + SECTION_FILE=$(mktemp) + printf '%s\n' "$SECTION" > "$SECTION_FILE" + + awk -v section_file="$SECTION_FILE" ' + /^## \[Unreleased\]/ { + print + print "" + while ((getline line < section_file) > 0) { + print line + } + close(section_file) + next + } + { print } + ' "$CHANGELOG" > "$TMP" + rm -f "$SECTION_FILE" + mv "$TMP" "$CHANGELOG" +fi + +echo "Updated ${CHANGELOG} with version ${VERSION}." >&2