mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-03-12 10:31:50 +01:00
fix: compare updates via release tag (#745)
## ℹ️ Description - Link to the related issue(s): Issue #N/A - Describe the motivation and context for this change. Ensure update-check compares against release tags instead of moving branch tips and keep tests/translations in sync. ## 📋 Changes Summary - compare release commit via tag name first and fall back only when missing - update update-checker tests for commit-ish resolution and tag-based release data - refresh German translations for update-checker log strings ### ⚙️ Type of Change Select the type(s) of change(s) included in this pull request: - [x] 🐞 Bug fix (non-breaking change which fixes an issue) ## ✅ Checklist Before requesting a review, confirm the following: - [x] I have reviewed my changes to ensure they meet the project's standards. - [x] I have tested my changes and ensured that all tests pass (`pdm run test`). - [x] I have formatted the code (`pdm run format`). - [x] I have verified that linting passes (`pdm run lint`). - [x] I have updated documentation where necessary. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * More reliable update checks by resolving commits from tags, branches or hashes and robustly comparing short vs full hashes. * Improved prerelease handling to avoid inappropriate preview updates and better handling of missing release data. * **Localization & UX** * Error and prerelease messages now use localized strings; commit dates shown consistently in UTC and short-hash form. * **Tests** * Updated tests to cover the new resolution flow, error cases, and logging behavior. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -6,6 +6,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from gettext import gettext as _
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -66,51 +67,33 @@ class UpdateChecker:
|
||||
return version.split("+")[1]
|
||||
return None
|
||||
|
||||
def _get_release_commit(self, tag_name:str) -> str | None:
|
||||
"""Get the commit hash for a release tag.
|
||||
def _resolve_commitish(self, commitish:str) -> tuple[str | None, datetime | None]:
|
||||
"""Resolve a commit-ish to a full commit hash and date.
|
||||
|
||||
Args:
|
||||
tag_name: The release tag name (e.g. 'latest').
|
||||
commitish: The commit hash, tag, or branch.
|
||||
|
||||
Returns:
|
||||
The commit hash, or None if it cannot be determined.
|
||||
Tuple of (full commit hash, commit date), or (None, None) if it cannot be determined.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.github.com/repos/Second-Hand-Friends/kleinanzeigen-bot/releases/tags/{tag_name}",
|
||||
f"https://api.github.com/repos/Second-Hand-Friends/kleinanzeigen-bot/commits/{commitish}",
|
||||
timeout = self._request_timeout()
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if isinstance(data, dict) and "target_commitish" in data:
|
||||
return str(data["target_commitish"])
|
||||
return None
|
||||
if not isinstance(data, dict):
|
||||
return None, None
|
||||
commit_date = None
|
||||
if "commit" in data and "author" in data["commit"] and "date" in data["commit"]["author"]:
|
||||
commit_date = datetime.fromisoformat(data["commit"]["author"]["date"].replace("Z", "+00:00"))
|
||||
sha = data.get("sha")
|
||||
commit_hash = str(sha) if sha else None
|
||||
return commit_hash, commit_date
|
||||
except Exception as e:
|
||||
logger.warning("Could not get release commit: %s", e)
|
||||
return None
|
||||
|
||||
def _get_commit_date(self, commit:str) -> datetime | None:
|
||||
"""Get the commit date for a commit hash.
|
||||
|
||||
Args:
|
||||
commit: The commit hash.
|
||||
|
||||
Returns:
|
||||
The commit date, or None if it cannot be determined.
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"https://api.github.com/repos/Second-Hand-Friends/kleinanzeigen-bot/commits/{commit}",
|
||||
timeout = self._request_timeout()
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
if isinstance(data, dict) and "commit" in data and "author" in data["commit"] and "date" in data["commit"]["author"]:
|
||||
return datetime.fromisoformat(data["commit"]["author"]["date"].replace("Z", "+00:00"))
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("Could not get commit date: %s", e)
|
||||
return None
|
||||
logger.warning(_("Could not resolve commit '%s': %s"), commitish, e)
|
||||
return None, None
|
||||
|
||||
def _get_short_commit_hash(self, commit:str) -> str:
|
||||
"""Get the short version of a commit hash.
|
||||
@@ -123,6 +106,19 @@ class UpdateChecker:
|
||||
"""
|
||||
return commit[:7]
|
||||
|
||||
def _commits_match(self, local_commit:str, release_commit:str) -> bool:
|
||||
"""Determine whether two commits refer to the same hash.
|
||||
|
||||
This accounts for short vs. full hashes (e.g. 7 chars vs. 40 chars).
|
||||
"""
|
||||
local_commit = local_commit.strip()
|
||||
release_commit = release_commit.strip()
|
||||
if local_commit == release_commit:
|
||||
return True
|
||||
if len(local_commit) < len(release_commit) and release_commit.startswith(local_commit):
|
||||
return True
|
||||
return len(release_commit) < len(local_commit) and local_commit.startswith(release_commit)
|
||||
|
||||
def check_for_updates(self, *, skip_interval_check:bool = False) -> None:
|
||||
"""Check for updates to the bot.
|
||||
|
||||
@@ -138,12 +134,12 @@ class UpdateChecker:
|
||||
|
||||
local_version = self.get_local_version()
|
||||
if not local_version:
|
||||
logger.warning("Could not determine local version.")
|
||||
logger.warning(_("Could not determine local version."))
|
||||
return
|
||||
|
||||
local_commit = self._get_commit_hash(local_version)
|
||||
if not local_commit:
|
||||
logger.warning("Could not determine local commit hash.")
|
||||
local_commitish = self._get_commit_hash(local_version)
|
||||
if not local_commitish:
|
||||
logger.warning(_("Could not determine local commit hash."))
|
||||
return
|
||||
|
||||
# --- Fetch release info from GitHub using correct endpoint per channel ---
|
||||
@@ -158,7 +154,9 @@ class UpdateChecker:
|
||||
release = response.json()
|
||||
# Defensive: ensure it's not a prerelease
|
||||
if release.get("prerelease", False):
|
||||
logger.warning("Latest release from GitHub is a prerelease, but 'latest' channel expects a stable release.")
|
||||
logger.warning(
|
||||
_("Latest release from GitHub is a prerelease, but 'latest' channel expects a stable release.")
|
||||
)
|
||||
return
|
||||
elif self.config.update_check.channel == "preview":
|
||||
# Use /releases endpoint and select the most recent prerelease
|
||||
@@ -169,51 +167,47 @@ class UpdateChecker:
|
||||
response.raise_for_status()
|
||||
releases = response.json()
|
||||
# Find the most recent prerelease
|
||||
release = next((r for r in releases if r.get("prerelease", False)), None)
|
||||
release = next((r for r in releases if r.get("prerelease", False) and not r.get("draft", False)), None)
|
||||
if not release:
|
||||
logger.warning("No prerelease found for 'preview' channel.")
|
||||
logger.warning(_("No prerelease found for 'preview' channel."))
|
||||
return
|
||||
else:
|
||||
logger.warning("Unknown update channel: %s", self.config.update_check.channel)
|
||||
logger.warning(_("Unknown update channel: %s"), self.config.update_check.channel)
|
||||
return
|
||||
except Exception as e:
|
||||
logger.warning("Could not get releases: %s", e)
|
||||
logger.warning(_("Could not get releases: %s"), e)
|
||||
return
|
||||
|
||||
# Get release commit
|
||||
try:
|
||||
release_commit = self._get_release_commit(release["tag_name"])
|
||||
except Exception as e:
|
||||
logger.warning("Failed to get release commit: %s", e)
|
||||
return
|
||||
if not release_commit:
|
||||
logger.warning("Could not determine release commit hash.")
|
||||
# Get release commit-ish (use tag name to avoid branch tip drift)
|
||||
release_commitish = release.get("tag_name")
|
||||
if not release_commitish:
|
||||
release_commitish = release.get("target_commitish")
|
||||
if not release_commitish:
|
||||
logger.warning(_("Could not determine release commit hash."))
|
||||
return
|
||||
|
||||
# Get commit dates
|
||||
try:
|
||||
local_commit_date = self._get_commit_date(local_commit)
|
||||
release_commit_date = self._get_commit_date(release_commit)
|
||||
except Exception as e:
|
||||
logger.warning("Failed to get commit dates: %s", e)
|
||||
return
|
||||
if not local_commit_date or not release_commit_date:
|
||||
logger.warning("Could not determine commit dates for comparison.")
|
||||
# Resolve commit hashes and dates for comparison
|
||||
local_commit, local_commit_date = self._resolve_commitish(local_commitish)
|
||||
release_commit, release_commit_date = self._resolve_commitish(str(release_commitish))
|
||||
if not local_commit or not release_commit or not local_commit_date or not release_commit_date:
|
||||
logger.warning(_("Could not determine commit dates for comparison."))
|
||||
return
|
||||
|
||||
if local_commit == release_commit:
|
||||
if self._commits_match(local_commit, release_commit):
|
||||
# If the commit hashes are identical, we are on the latest version. Do not proceed to other checks.
|
||||
logger.info(
|
||||
"You are on the latest version: %s (compared to %s in channel %s)",
|
||||
_("You are on the latest version: %s (compared to %s in channel %s)"),
|
||||
local_version,
|
||||
self._get_short_commit_hash(release_commit),
|
||||
self.config.update_check.channel
|
||||
)
|
||||
self.state.update_last_check()
|
||||
self.state.save(self.state_file)
|
||||
return
|
||||
# All commit dates are in UTC; append ' UTC' to timestamps in logs for clarity.
|
||||
if local_commit_date < release_commit_date:
|
||||
logger.warning(
|
||||
"A new version is available: %s from %s UTC (current: %s from %s UTC, channel: %s)",
|
||||
_("A new version is available: %s from %s UTC (current: %s from %s UTC, channel: %s)"),
|
||||
self._get_short_commit_hash(release_commit),
|
||||
release_commit_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
local_version,
|
||||
@@ -221,11 +215,13 @@ class UpdateChecker:
|
||||
self.config.update_check.channel
|
||||
)
|
||||
if release.get("body"):
|
||||
logger.info("Release notes:\n%s", release["body"])
|
||||
logger.info(_("Release notes:\n%s"), release["body"])
|
||||
else:
|
||||
logger.info(
|
||||
"You are on a different commit than the release for channel '%s' (tag: %s). This may mean you are ahead, behind, or on a different branch. "
|
||||
"Local commit: %s (%s UTC), Release commit: %s (%s UTC)",
|
||||
_(
|
||||
"You are on a different commit than the release for channel '%s' (tag: %s). This may mean you are ahead, behind, or on a different branch. "
|
||||
"Local commit: %s (%s UTC), Release commit: %s (%s UTC)"
|
||||
),
|
||||
self.config.update_check.channel,
|
||||
release.get("tag_name", "unknown"),
|
||||
self._get_short_commit_hash(local_commit),
|
||||
|
||||
Reference in New Issue
Block a user