Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-lastversion for openSUSE:Factory checked in at 2026-07-06 12:34:44 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-lastversion (Old) and /work/SRC/openSUSE:Factory/.python-lastversion.new.1982 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-lastversion" Mon Jul 6 12:34:44 2026 rev:2 rq:1363964 version:3.6.12 Changes: -------- --- /work/SRC/openSUSE:Factory/python-lastversion/python-lastversion.changes 2026-03-31 16:21:23.164511636 +0200 +++ /work/SRC/openSUSE:Factory/.python-lastversion.new.1982/python-lastversion.changes 2026-07-06 12:37:27.162081978 +0200 @@ -1,0 +2,20 @@ +Mon Jul 6 06:26:38 UTC 2026 - Dirk Müller <[email protected]> + +- update to 3.6.12: + * Fixed Docker image publish on release + * Moved the `NOSONAR` suppression for the missing `USER` + directive off the `FROM` line; BuildKit's frontend rejects + trailing inline comments on `FROM`, breaking the `Publish + Docker image` workflow in 3.6.11 + * Fixed `Version` object JSON serialization error when caching + release data with dict output format + * The `version` field is now serialized to string at the cache + layer for both file and Redis backends + * This fixes the "Object of type 'Version' is not JSON + serializable" warning when running `lastversion --changelog + *.spec` + * Companion to the 3.6.8 datetime serialization fix; the dict- + format path kept `version` as a `Version` instance and + tripped the same cache encoder + +------------------------------------------------------------------- Old: ---- lastversion-3.6.10.tar.gz New: ---- lastversion-3.6.12.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-lastversion.spec ++++++ --- /var/tmp/diff_new_pack.AYNJ7m/_old 2026-07-06 12:37:28.478127706 +0200 +++ /var/tmp/diff_new_pack.AYNJ7m/_new 2026-07-06 12:37:28.486127984 +0200 @@ -17,7 +17,7 @@ Name: python-lastversion -Version: 3.6.10 +Version: 3.6.12 Release: 0 Summary: Find the latest stable release version of an arbitrary project License: BSD-2-Clause ++++++ lastversion-3.6.10.tar.gz -> lastversion-3.6.12.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/.github/workflows/dockerpublish.yml new/lastversion-3.6.12/.github/workflows/dockerpublish.yml --- old/lastversion-3.6.10/.github/workflows/dockerpublish.yml 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/.github/workflows/dockerpublish.yml 2026-05-15 15:42:18.000000000 +0200 @@ -25,13 +25,13 @@ uses: actions/checkout@v3 - name: Log in to Docker Hub - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} - name: Log in to the Container registry - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 with: registry: ghcr.io username: ${{ github.actor }} @@ -39,14 +39,14 @@ - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf with: images: | ${{ github.repository }} ghcr.io/${{ github.repository }} - name: Build and push Docker images - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f with: context: . push: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/.github/workflows/pythonpackage.yml new/lastversion-3.6.12/.github/workflows/pythonpackage.yml --- old/lastversion-3.6.10/.github/workflows/pythonpackage.yml 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/.github/workflows/pythonpackage.yml 2026-05-15 15:42:18.000000000 +0200 @@ -36,7 +36,7 @@ echo "%packager Dick Solomon <[email protected]>" >> ~/.rpmmacros cd tests lastversion brotli.spec - lastversion nginx-module-immutable.spec + lastversion nginx-module-security-headers.spec if lastversion libmozjpeg.spec; then exit 1 fi @@ -45,15 +45,15 @@ # Use custom token if available, otherwise fall back to GITHUB_TOKEN (for Dependabot PRs) LASTVERSION_GITHUB_API_TOKEN: ${{ secrets.LASTVERSION_GITHUB_API_TOKEN || github.token }} - name: 'Upload brotli.spec artifact' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: brotli.spec path: tests/brotli.spec - - name: 'Upload nginx-module-immutable.spec artifact' - uses: actions/upload-artifact@v4 + - name: 'Upload nginx-module-security-headers.spec artifact' + uses: actions/upload-artifact@v7 with: - name: nginx-module-immutable.spec - path: tests/nginx-module-immutable.spec + name: nginx-module-security-headers.spec + path: tests/nginx-module-security-headers.spec build: runs-on: ubuntu-latest @@ -98,7 +98,7 @@ LASTVERSION_GITHUB_API_TOKEN: ${{ secrets.LASTVERSION_GITHUB_API_TOKEN || github.token }} - name: Upload pytest test results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: pytest-results-${{ matrix.python-version }} path: junit/test-results-${{ matrix.python-version }}.xml @@ -118,7 +118,7 @@ container: getpagespeed/rpmbuilder:centos-8 steps: - name: Download brotli.spec artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: name: brotli.spec - name: Build updated spec @@ -126,15 +126,15 @@ mkdir -p /github/home/rpmbuild/{SOURCES,SPECS}/ mv *.spec /sources/ build - build-nginx-module-immutable-rpm: + build-nginx-module-security-headers-rpm: needs: py36 runs-on: ubuntu-latest container: getpagespeed/rpmbuilder:centos-8 steps: - - name: Download nginx-module-immutable.spec artifact - uses: actions/download-artifact@v4 + - name: Download nginx-module-security-headers.spec artifact + uses: actions/download-artifact@v8 with: - name: nginx-module-immutable.spec + name: nginx-module-security-headers.spec - name: Build updated spec run: | mkdir -p /github/home/rpmbuild/{SOURCES,SPECS}/ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/.github/workflows/rpmbuild.yml new/lastversion-3.6.12/.github/workflows/rpmbuild.yml --- old/lastversion-3.6.10/.github/workflows/rpmbuild.yml 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/.github/workflows/rpmbuild.yml 2026-05-15 15:42:18.000000000 +0200 @@ -9,9 +9,11 @@ runs-on: ubuntu-latest steps: - name: Install SSH key - uses: shimataro/ssh-key-action@v2 + uses: shimataro/ssh-key-action@87a8f067114a8ce263df83e9ed5c849953548bc3 # v2.8.1 with: key: ${{ secrets.BUILDER_SSH_KEY }} known_hosts: ${{ secrets.BUILDER_SSH_KNOWN_HOSTS }} - name: Trigger RPM build - run: ${{ secrets.RPM_BUILD_CMD }} + run: eval "$RPM_BUILD_CMD" + env: + RPM_BUILD_CMD: ${{ secrets.RPM_BUILD_CMD }} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/CHANGELOG.md new/lastversion-3.6.12/CHANGELOG.md --- old/lastversion-3.6.10/CHANGELOG.md 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/CHANGELOG.md 2026-05-15 15:42:18.000000000 +0200 @@ -1,6 +1,18 @@ # Changelog All notable changes to this project will be documented in this file. +## [3.6.12] - 2026-05-15 +### Fixed +* Fixed Docker image publish on release + - Moved the `NOSONAR` suppression for the missing `USER` directive off the `FROM` line; BuildKit's frontend rejects trailing inline comments on `FROM`, breaking the `Publish Docker image` workflow in 3.6.11 + +## [3.6.11] - 2026-05-15 +### Fixed +* Fixed `Version` object JSON serialization error when caching release data with dict output format + - The `version` field is now serialized to string at the cache layer for both file and Redis backends + - This fixes the "Object of type 'Version' is not JSON serializable" warning when running `lastversion --changelog *.spec` + - Companion to the 3.6.8 datetime serialization fix; the dict-format path kept `version` as a `Version` instance and tripped the same cache encoder + ## [3.6.9] - 2026-03-16 ### Fixed * Fixed `lastversion wordpress` returning wrong version (6.3.8 instead of latest) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/CLAUDE.md new/lastversion-3.6.12/CLAUDE.md --- old/lastversion-3.6.10/CLAUDE.md 1970-01-01 01:00:00.000000000 +0100 +++ new/lastversion-3.6.12/CLAUDE.md 2026-05-15 15:42:18.000000000 +0200 @@ -0,0 +1,90 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project + +**lastversion** — CLI tool and Python library that finds the latest stable release version of software projects across multiple hosting platforms (GitHub, GitLab, PyPI, Bitbucket, SourceForge, WordPress, etc.). Used heavily in RPM build automation and CI/CD pipelines. + +## Development Setup + +- **Virtualenv:** `~/.virtualenvs/lastversion/` +- **Secrets:** Source `~/.secrets` before running tests (contains `GITHUB_API_TOKEN` and other API tokens) + +## Common Commands + +```bash +# Run all tests (sources secrets, activates venv, parallel execution with 10min timeout) +make test + +# Run a single test +source ~/.secrets && ~/.virtualenvs/lastversion/bin/python -m pytest tests/test_github.py::test_function_name -v + +# Lint +~/.virtualenvs/lastversion/bin/python -m flake8 src tests +~/.virtualenvs/lastversion/bin/python -m pylint src tests + +# Security scan +~/.virtualenvs/lastversion/bin/python -m bandit -c pyproject.toml -r src/lastversion + +# Release (CI auto-publishes to PyPI on GitHub release creation) +# 1. Bump __version__ in src/lastversion/__about__.py +# 2. Commit: git commit -m "chore(release): X.Y.Z" +# 3. Tag: git tag -s vX.Y.Z -m "vX.Y.Z" +# 4. Push: git push origin master && git push origin vX.Y.Z +# 5. Create release: gh release create vX.Y.Z --title "vX.Y.Z" --notes "..." +# CI workflow pythonpublish.yml handles PyPI upload automatically. +# Do NOT use `make publish` or manual `twine upload`. +make publish # backup only, prefer CI + +# Build single binary +make one-file +``` + +## Architecture + +### Repository Holder Pattern + +Core abstraction: `BaseProjectHolder` (in `src/lastversion/repo_holders/base.py`) is the abstract base class for all hosting platform adapters. Each adapter lives in `src/lastversion/repo_holders/` and implements release discovery for its platform. + +`HolderFactory` (`holder_factory.py`) maintains an ordered registry of adapters and selects the appropriate one based on the input URL/name. Detection order matters — adapters are tried in registration order. + +### Key Modules + +- **`lastversion.py`** — Core logic: `latest()`, `has_update()`, `check_version()` entry points +- **`cli.py`** — CLI argument parsing, all subcommands (get, download, extract, install, update-spec, etc.) +- **`version.py`** — `Version` class extending `packaging.Version` with normalization for messy real-world version strings (dashed versions, Java-style, pre-release variants) +- **`cache.py`** — Dual-level caching: HTTP-level (CacheControl/ETag) + release-data-level (File or Redis backend) with PID-based file locking +- **`config.py`** — Platform-aware config singleton (`~/.config/lastversion/lastversion.yml` on Linux, `~/Library/Application Support/lastversion/lastversion.yml` on macOS) + +### GitHub Adapter (`repo_holders/github.py`) + +Most complex adapter. Uses GraphQL API with REST fallback. Handles rate limiting, API tokens (`GITHUB_API_TOKEN`/`GITHUB_TOKEN` env vars), commit-based version extraction, and release note parsing. + +### RPM Spec Integration + +`update-spec` command parses `.spec` files for `%{upstream_github}`, `%global lastversion_repo`, and `%global lastversion_*` directives to determine repo and version constraints. + +## Testing + +- **Framework:** pytest with pytest-xdist (parallel: `-n auto`) +- **CI matrix:** Python 3.6, 3.9, 3.13 +- Tests include live API calls — require valid API tokens +- Test files mirror the module structure: `test_github.py`, `test_gitlab.py`, `test_pypi.py`, etc. +- Helper utilities in `tests/helpers.py` + +## Linting + +- **flake8:** max-line-length=120, select C,E,F,W,B,B950, ignore E203/E501/E704 (configured in `setup.cfg`) +- **bandit:** security scanning configured in `pyproject.toml` +- **pre-commit:** ruff, flake8, black, isort, bandit — all at line-length 120 + +## Code Style + +- **Docstrings:** Google-style on all new/modified functions. First line: one-sentence imperative summary ending with a period. Include Args, Returns, Raises sections with type info even if type hints exist. Document coroutine behavior where relevant. +- **Interpreter:** Always use `~/.virtualenvs/lastversion/bin/python` — never bare `python` or `pytest`. +- **PRs:** Must include docstrings for new/modified functions. Must update `.cursor/rules/` if adding new modules or architectural changes. + +## Python Compatibility + +Supports Python 3.6+. Conditional dependencies exist for `cachecontrol` and `urllib3` based on Python version (see `setup.py`). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/Dockerfile new/lastversion-3.6.12/Dockerfile --- old/lastversion-3.6.10/Dockerfile 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/Dockerfile 2026-05-15 15:42:18.000000000 +0200 @@ -1,4 +1,6 @@ # Use a lightweight base image with Python and pip installed +# Root required for GitHub Actions workspace compatibility. +# NOSONAR FROM python:3.13-alpine # Using "lastversion" user as provided by some linter was a mistake and causes issues with GitHub actions being ran as "runner" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/Makefile new/lastversion-3.6.12/Makefile --- old/lastversion-3.6.10/Makefile 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/Makefile 2026-05-15 15:42:18.000000000 +0200 @@ -14,7 +14,7 @@ publish: clean python setup.py sdist bdist_wheel - twine upload -s dist/* + twine upload dist/* clean: rm -rf *.egg-info *.egg dist build .pytest_cache diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/docs/requirements.txt new/lastversion-3.6.12/docs/requirements.txt --- old/lastversion-3.6.10/docs/requirements.txt 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/docs/requirements.txt 2026-05-15 15:42:18.000000000 +0200 @@ -4,3 +4,4 @@ mkdocstrings[crystal,python] markdown-include pymdown-extensions +markdown>=3.8.1 # not directly required, pinned by Snyk to avoid a vulnerability diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/scripts/check_docs_frontmatter.py new/lastversion-3.6.12/scripts/check_docs_frontmatter.py --- old/lastversion-3.6.10/scripts/check_docs_frontmatter.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/scripts/check_docs_frontmatter.py 2026-05-15 15:42:18.000000000 +0200 @@ -27,7 +27,7 @@ REQUIRED_FIELDS = ["title", "description"] # Regex to extract YAML front-matter -FRONTMATTER_REGEX = re.compile(r"^---\s*\n(.*?)\n---\s*\n", re.DOTALL) +FRONTMATTER_REGEX = re.compile(r"^---[ \t]*\n(.*?)\n---[ \t]*\n", re.DOTALL) def is_excluded(filepath: Path) -> bool: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/src/lastversion/__about__.py new/lastversion-3.6.12/src/lastversion/__about__.py --- old/lastversion-3.6.10/src/lastversion/__about__.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/src/lastversion/__about__.py 2026-05-15 15:42:18.000000000 +0200 @@ -1,4 +1,4 @@ """Package metadata""" -__version__ = "3.6.10" +__version__ = "3.6.12" __self__ = "dvershinin/lastversion" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/src/lastversion/cache.py new/lastversion-3.6.12/src/lastversion/cache.py --- old/lastversion-3.6.10/src/lastversion/cache.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/src/lastversion/cache.py 2026-05-15 15:42:18.000000000 +0200 @@ -13,10 +13,30 @@ from typing import Any, Dict, Optional from lastversion.config import get_config +from lastversion.version import Version log = logging.getLogger(__name__) +def _json_default(obj: Any) -> Any: + """Serialize types that the stdlib JSON encoder does not handle natively. + + Args: + obj: Object to serialize. Currently handles :class:`lastversion.version.Version`. + + Returns: + A JSON-serializable representation of ``obj``. + + Raises: + TypeError: If ``obj`` is of a type this handler does not know about. + Unknown types are surfaced explicitly rather than silently coerced + via ``str()`` — a leaked unexpected type is a bug, not a cache entry. + """ + if isinstance(obj, Version): + return str(obj) + raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable") + + class CacheBackend(ABC): """Abstract base class for cache backends.""" @@ -228,7 +248,7 @@ try: with open(cache_path, "w", encoding="utf-8") as f: - json.dump(data, f) + json.dump(data, f, default=_json_default) log.debug("Cached value for key: %s (TTL: %d)", key, ttl) except (IOError, TypeError) as e: log.warning("Error writing cache for key %s: %s", key, e) @@ -493,7 +513,7 @@ ttl = self.default_ttl try: - data = json.dumps(value) + data = json.dumps(value, default=_json_default) if ttl > 0: self._client.setex(redis_key, ttl, data) else: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/src/lastversion/cli.py new/lastversion-3.6.12/src/lastversion/cli.py --- old/lastversion-3.6.10/src/lastversion/cli.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/src/lastversion/cli.py 2026-05-15 15:42:18.000000000 +0200 @@ -494,8 +494,8 @@ args.repo = __self__ # "expand" repo:1.2 as repo --branch 1.2 - # noinspection HttpUrlsUsage - if ":" in args.repo and not (args.repo.startswith(("https://", "http://")) and args.repo.count(":") == 1): + _url_prefixes = ("https://", "http://") # NOSONAR - URL parsing, not HTTP request + if ":" in args.repo and not (args.repo.startswith(_url_prefixes) and args.repo.count(":") == 1): # right split ':' once only to preserve it in protocol of URLs # https://github.com/repo/owner:2.1 repo_args = args.repo.rsplit(":", 1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/src/lastversion/lastversion.py new/lastversion-3.6.12/src/lastversion/lastversion.py --- old/lastversion-3.6.10/src/lastversion/lastversion.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/src/lastversion/lastversion.py 2026-05-15 15:42:18.000000000 +0200 @@ -99,7 +99,7 @@ elif line.startswith("Source0:"): source0 = line.split("Source0:")[1].strip() # noinspection HttpUrlsUsage - if source0.startswith("https://") or source0.startswith("http://"): + if source0.startswith("https://") or source0.startswith("http://"): # NOSONAR spec_urls.append(source0) elif line.startswith("%global upstream_version "): current_version = shlex.split(line)[2].strip() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/src/lastversion/repo_holders/alpine.py new/lastversion-3.6.12/src/lastversion/repo_holders/alpine.py --- old/lastversion-3.6.10/src/lastversion/repo_holders/alpine.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/src/lastversion/repo_holders/alpine.py 2026-05-15 15:42:18.000000000 +0200 @@ -101,7 +101,7 @@ # APKINDEX is a tar.gz containing an APKINDEX file tar_bytes = io.BytesIO(response.content) - with tarfile.open(fileobj=tar_bytes, mode="r:gz") as tar: + with tarfile.open(fileobj=tar_bytes, mode="r:gz") as tar: # NOSONAR for member in tar.getmembers(): if member.name == "APKINDEX": file_obj = tar.extractfile(member) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/src/lastversion/repo_holders/base.py new/lastversion-3.6.12/src/lastversion/repo_holders/base.py --- old/lastversion-3.6.10/src/lastversion/repo_holders/base.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/src/lastversion/repo_holders/base.py 2026-05-15 15:42:18.000000000 +0200 @@ -68,7 +68,7 @@ return False try: # On Unix, signal 0 doesn't kill but checks if process exists - os.kill(pid, 0) + os.kill(pid, 0) # NOSONAR return True except OSError as err: # ESRCH = No such process, EPERM = Permission denied (process exists) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/src/lastversion/utils.py new/lastversion-3.6.12/src/lastversion/utils.py --- old/lastversion-3.6.10/src/lastversion/utils.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/src/lastversion/utils.py 2026-05-15 15:42:18.000000000 +0200 @@ -363,7 +363,7 @@ """ archive_file: tarfile.TarFile - with tarfile.open(fileobj=buffer, mode="r") as archive_file: + with tarfile.open(fileobj=buffer, mode="r") as archive_file: # NOSONAR if not check_if_tar_safe(archive_file): raise TarPathTraversalException("Attempted Path Traversal in Tar File") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/tests/nginx-module-security-headers.spec new/lastversion-3.6.12/tests/nginx-module-security-headers.spec --- old/lastversion-3.6.10/tests/nginx-module-security-headers.spec 1970-01-01 01:00:00.000000000 +0100 +++ new/lastversion-3.6.12/tests/nginx-module-security-headers.spec 2026-05-15 15:42:18.000000000 +0200 @@ -0,0 +1,138 @@ +############################################# +%global upstream_github GetPageSpeed +%global upstream_name ngx_security_headers +############################################# +%global lastversion_tag x +%global lastversion_dir x +%global upstream_version x +############################################ +%if 0%{?plesk} +%define name_prefix sw- +%define nginx_modules_dir %{_datadir}/nginx/modules +%define extradist .pl%{plesk} +%define nginx_load_module load_module nginx/modules +%else +%define name_prefix %{nil} +%define nginx_modules_dir %{_libdir}/nginx/modules +%define extradist %{nil} +%define nginx_load_module load_module modules +%endif + +# +%define nginx_user nginx +%define nginx_group nginx + +%global main_version %{nginx_version} +%define main_release 1%{?dist}.ngx + +%if 0%{?rhel} || 0%{?amzn} +%define _group System Environment/Daemons +BuildRequires: openssl-devel +%endif + +%if 0%{?suse_version} == 1315 +%define _group Productivity/Networking/Web/Servers +BuildRequires: libopenssl-devel +%define _debugsource_template %{nil} +%endif + +%if 0%{?rhel} == 7 +%define epoch 1 +Epoch: %{epoch} +%define dist .el7 +BuildRequires: gcc-c++ +BuildRequires: binutils +BuildRequires: make +%define _group System Environment/Daemons +%endif + +%if 0%{?rhel} == 8 +%define epoch 1 +Epoch: %{epoch} +%define _debugsource_template %{nil} +%define dist .el8 +BuildRequires: gcc-c++ +BuildRequires: binutils +BuildRequires: make +%define _group System Environment/Daemons +%endif + +%define bdir %{_builddir}/%{name}-%{main_version} + +Summary: NGINX module for adding security headers +Name: %{name_prefix}nginx-module-security-headers +Version: %{main_version}+%{upstream_version} +Release: 2%{?dist}%{extradist}.gps +Vendor: GetPageSpeed, Inc. +URL: https://github.com/GetPageSpeed/ngx_security_headers +Group: %{_group} + +Source0: http://nginx.org/download/nginx-%{main_version}.tar.gz +Source100: %{url}/archive/%{lastversion_tag}/%{upstream_name}-%{lastversion_tag}.tar.gz + +License: BSD + +BuildRoot: %{_tmppath}/%{name}-%{main_version}-%{main_release}-root + +BuildRequires: zlib-devel +BuildRequires: pcre-devel +Requires: %{name_prefix}nginx-r%{main_version} + +Provides: %{name}-r%{main_version} + +%description +This NGINX module adds security headers such as X-Content-Type-Options, +X-Frame-Options, and other important security-related HTTP headers. + +%define WITH_CC_OPT $(echo %{optflags} $(pcre-config --cflags)) +%define WITH_LD_OPT -Wl,-z,relro -Wl,-z,now + +%define BASE_CONFIGURE_ARGS $(echo "--prefix=%{_sysconfdir}/nginx --sbin-path=%{_sbindir}/nginx --modules-path=%{_libdir}/nginx/modules --conf-path=%{_sysconfdir}/nginx/nginx.conf --error-log-path=%{_localstatedir}/log/nginx/error.log --http-log-path=%{_localstatedir}/log/nginx/access.log --pid-path=%{_localstatedir}/run/nginx.pid --lock-path=%{_localstatedir}/run/nginx.lock --http-client-body-temp-path=%{_localstatedir}/cache/nginx/client_temp --http-proxy-temp-path=%{_localstatedir}/cache/nginx/proxy_temp --http-fastcgi-temp-path=%{_localstatedir}/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=%{_localstatedir}/cache/nginx/uwsgi_temp --http-scgi-temp-path=%{_localstatedir}/cache/nginx/scgi_temp --user=%{nginx_user} --group=%{nginx_group} --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_ran dom_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module") +%define MODULE_CONFIGURE_ARGS $(echo "--add-dynamic-module=%{lastversion_dir}") + +%prep +%setup -qcTn %{name}-%{main_version} +tar --strip-components=1 -zxf %{SOURCE0} +tar xzf %SOURCE100 + +%build +cd %{bdir} +./configure %{BASE_CONFIGURE_ARGS} %{MODULE_CONFIGURE_ARGS} \ + --with-cc-opt="%{WITH_CC_OPT}" \ + --with-ld-opt="%{WITH_LD_OPT}" +make %{?_smp_mflags} modules + +%install +cd %{bdir} +%{__rm} -rf $RPM_BUILD_ROOT +%{__mkdir} -p $RPM_BUILD_ROOT%{_datadir}/doc/%{name} +%{__install} -m 644 -p %{lastversion_dir}/LICENSE \ + $RPM_BUILD_ROOT%{_datadir}/doc/%{name}/ +%{__install} -m 644 -p %{lastversion_dir}/README.md \ + $RPM_BUILD_ROOT%{_datadir}/doc/%{name}/ +%{__mkdir} -p $RPM_BUILD_ROOT%{nginx_modules_dir} +for so in `find %{bdir}/objs/ -maxdepth 1 -type f -name "*.so"`; do +%{__install} -m755 $so \ + $RPM_BUILD_ROOT%{nginx_modules_dir} +done + +%check +%{__rm} -rf $RPM_BUILD_ROOT/usr/src +cd %{bdir} +grep -v 'usr/src' debugfiles.list > debugfiles.list.new && mv debugfiles.list.new debugfiles.list +cat /dev/null > debugsources.list +%if 0%{?suse_version} >= 1500 +cat /dev/null > debugsourcefiles.list +%endif + +%clean +%{__rm} -rf $RPM_BUILD_ROOT + +%files +%defattr(-,root,root) +%{nginx_modules_dir}/* +%dir %{_datadir}/doc/%{name} +%{_datadir}/doc/%{name}/* + +%changelog +# no changelog diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/tests/test_cache.py new/lastversion-3.6.12/tests/test_cache.py --- old/lastversion-3.6.10/tests/test_cache.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/tests/test_cache.py 2026-05-15 15:42:18.000000000 +0200 @@ -326,6 +326,30 @@ result = cache.get("test/repo") assert result == data + def test_release_cache_version_roundtrip(self, caplog): + """Regression: ReleaseDataCache must accept a dict with a Version field. + + Mirrors the production flow at ``lastversion.lastversion:410`` where the + release dict for ``output_format == "dict"`` still holds a + :class:`Version` instance. + """ + from lastversion.version import Version + + backend = FileCacheBackend(cache_dir=self.temp_dir) + cache = ReleaseDataCache(backend=backend, enabled=True, ttl=3600) + + data = {"version": Version("0.2.42"), "tag_name": "v0.2.42"} + + with caplog.at_level("WARNING", logger="lastversion.cache"): + cache.set("dvershinin/gixy", data, pre_ok=False, even=False, formal=False) + + assert "not JSON serializable" not in caplog.text + + result = cache.get("dvershinin/gixy", pre_ok=False, even=False, formal=False) + assert result is not None + assert result["version"] == "0.2.42" + assert result["tag_name"] == "v0.2.42" + def test_cache_key_params(self): """Test that different parameters create different cache keys.""" backend = FileCacheBackend(cache_dir=self.temp_dir) @@ -393,6 +417,52 @@ assert result == data assert result["tag_date"] == "2024-01-15 10:30:00" + def test_cache_version_serialization(self, caplog): + """Regression: Version objects in cache data must serialize cleanly. + + Reproduces the production warning seen with `lastversion --changelog *.spec`: + ``Object of type 'Version' is not JSON serializable``. + """ + from lastversion.version import Version + + cache = FileCacheBackend(cache_dir=self.temp_dir, default_ttl=3600) + + data = { + "version": Version("1.2.3"), + "tag_name": "v1.2.3", + } + + with caplog.at_level("WARNING", logger="lastversion.cache"): + cache.set("test-repo-version", data) + + assert "not JSON serializable" not in caplog.text + + result = cache.get("test-repo-version") + assert result is not None + # Cache returns the version as a string; callers (lastversion._return_from_cache) + # re-wrap with Version() for dict output. + assert result["version"] == "1.2.3" + assert result["tag_name"] == "v1.2.3" + + def test_cache_rejects_unknown_type(self, caplog): + """Unknown non-serializable types must still surface as a warning. + + Guards against a regression where someone swaps the explicit + ``_json_default`` for a blanket ``default=str``, which would silently + cache garbage instead of flagging the bug. + """ + + class Unserializable: + pass + + cache = FileCacheBackend(cache_dir=self.temp_dir, default_ttl=3600) + + with caplog.at_level("WARNING", logger="lastversion.cache"): + cache.set("test-repo-unknown", {"weird": Unserializable()}) + + assert "not JSON serializable" in caplog.text + assert cache.get("test-repo-unknown") is None + class TestCacheFactory: """Tests for cache factory function.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/lastversion-3.6.10/tests/test_github.py new/lastversion-3.6.12/tests/test_github.py --- old/lastversion-3.6.10/tests/test_github.py 2026-03-24 18:47:40.000000000 +0100 +++ new/lastversion-3.6.12/tests/test_github.py 2026-05-15 15:42:18.000000000 +0200 @@ -143,7 +143,7 @@ # 3.5.4 is more specific than 3.5 assert GitHubRepoSession.is_version_more_specific(Version("3.5.4"), Version("3.5")) # 3.5.4.1 is more specific than 3.5.4 - assert GitHubRepoSession.is_version_more_specific(Version("3.5.4.1"), Version("3.5.4")) + assert GitHubRepoSession.is_version_more_specific(Version("3.5.4.1"), Version("3.5.4")) # NOSONAR # 3.5.4 is more specific than 3 assert GitHubRepoSession.is_version_more_specific(Version("3.5.4"), Version("3")) # 3.5 is NOT more specific than 3.5.4
