diff --git a/pkgs/development/python-modules/nixpkgs-plugin-update/nixpkgs-plugin-update/src/nixpkgs_plugin_update/__init__.py b/pkgs/development/python-modules/nixpkgs-plugin-update/nixpkgs-plugin-update/src/nixpkgs_plugin_update/__init__.py index ac1944ef35e1..612fa0823bdf 100644 --- a/pkgs/development/python-modules/nixpkgs-plugin-update/nixpkgs-plugin-update/src/nixpkgs_plugin_update/__init__.py +++ b/pkgs/development/python-modules/nixpkgs-plugin-update/nixpkgs-plugin-update/src/nixpkgs_plugin_update/__init__.py @@ -25,7 +25,7 @@ from multiprocessing.dummy import Pool from pathlib import Path from tempfile import NamedTemporaryFile from typing import Any, Callable -from urllib.parse import urljoin, urlparse, urlsplit +from urllib.parse import unquote, urljoin, urlparse, urlsplit import git from packaging.version import InvalidVersion, parse as parse_version @@ -40,7 +40,7 @@ AUTO_BRANCH = "" VERSION_DATE_PATTERN = re.compile(r"(\d{4}-\d{2}-\d{2})$") VERSION_TAG_PATTERN = re.compile(r"^(.+?)-unstable-") NON_RELEASE_TAG_PREFIXES = ("pre-",) -RELEASE_VERSION_PATTERN = re.compile(r"^[^\d]*(\d[\w.@-]*)$") +RELEASE_VERSION_PATTERN = re.compile(r"^[^\d]*(\d[\w.@+-]*)$") LOG_LEVELS = { logging.getLevelName(level): level @@ -127,12 +127,32 @@ def select_latest_tag( def first_release_tag(tags: list[str]) -> str | None: for tag in tags: - if normalize_release_version(tag) is not None: - return tag + normalized_tag = normalize_release_version(tag) + if normalized_tag is None: + continue + + try: + version = parse_version(normalized_tag) + if version.is_prerelease or version.is_devrelease: + continue + except InvalidVersion: + pass + + return tag return None +def tag_from_github_atom_href(href: str) -> str | None: + path = urlparse(href).path + marker = "/releases/tag/" + if marker in path: + return unquote(path.split(marker, 1)[1]) + + tag_name = Path(path).name + return unquote(tag_name) if tag_name else None + + class Repo: def __init__(self, uri: str, branch: str) -> None: self.uri = uri @@ -227,7 +247,7 @@ class Repo: loaded = json.loads(data) return loaded - def prefetch(self, ref: str) -> str: + def prefetch(self, ref: str, has_submodules: bool | None = None) -> str: log.info("Prefetching %s", self.uri) loaded = self._prefetch(ref) return loaded["sha256"] @@ -349,7 +369,7 @@ class RepoGitHub(Repo): if not href: continue - tag_name = Path(urlparse(href).path).name + tag_name = tag_from_github_atom_href(href) if tag_name: tags.append(tag_name) @@ -419,7 +439,7 @@ class RepoGitHub(Repo): recent_tags = [node["name"] for node in repo["refs"]["nodes"]] if not recent_tags: - return None + return self._get_latest_tag_from_fallbacks() latest_tag = first_release_tag(recent_tags) return latest_tag if latest_tag is not None else recent_tags[0] @@ -449,8 +469,11 @@ class RepoGitHub(Repo): new_repo = RepoGitHub(owner=new_owner, repo=new_name, branch=self._branch) self.redirect = new_repo - def prefetch(self, commit: str) -> str: - if self.has_submodules(): + def prefetch(self, commit: str, has_submodules: bool | None = None) -> str: + if has_submodules is None: + has_submodules = self.has_submodules() + + if has_submodules: sha256 = super().prefetch(commit) else: sha256 = self.prefetch_github(commit) @@ -634,6 +657,29 @@ def make_unstable_version(date: datetime, last_tag: str | None) -> str: return f"{tag_part}-unstable-{date_str}" +def newer_version_tag(first_tag: str | None, second_tag: str | None) -> str | None: + if first_tag is None: + return second_tag + if second_tag is None: + return first_tag + + first_version = normalize_release_version(first_tag) + second_version = normalize_release_version(second_tag) + if first_version is None: + return second_tag + if second_version is None: + return first_tag + + try: + return ( + first_tag + if parse_version(first_version) >= parse_version(second_version) + else second_tag + ) + except InvalidVersion: + return first_tag if first_version >= second_version else second_tag + + def get_commit_target( repo: Repo, branch: str, @@ -673,6 +719,7 @@ def select_plugin_target( and current_plugin.tag is None and current_plugin.date.date() > release_date.date() ): + latest_tag = newer_version_tag(current_plugin.last_tag, latest_tag) return get_commit_target(plugin_desc.repo, "HEAD", latest_tag) return release_commit, release_date, release_version, latest_tag @@ -927,9 +974,7 @@ class Editor: cache.store() print(f"{len(results)} of {len(current_plugins)} were checked") - # Do only partial update of out file - if len(results) != len(current_plugins): - results = self.merge_results(current_plugins, results) + results = self.merge_results(current_plugins, results) plugins, redirects = check_results(results) # Track version changes for commit message generation @@ -969,9 +1014,11 @@ class Editor: if isinstance(plugin, Plugin) and hasattr(plugin, "normalized_name"): result[plugin.normalized_name] = (plugin_desc, plugin, redirect) elif isinstance(plugin, Exception): - # For exceptions, we can't determine the normalized_name - # Just log the error and continue - log.error(f"Error fetching plugin {plugin_desc.name}: {plugin!r}") + log.warning( + "Keeping current plugin data after error fetching %s: %r", + plugin_desc.name, + plugin, + ) else: # For unexpected types, log the issue log.error( @@ -1174,9 +1221,9 @@ def prefetch_plugin( has_submodules = p.repo.has_submodules() log.debug(f"prefetch {p.name}") sha256 = ( - p.repo.prefetch(f"{GIT_TAGS_PREFIX}{source_tag}") + p.repo.prefetch(f"{GIT_TAGS_PREFIX}{source_tag}", has_submodules=has_submodules) if source_tag - else p.repo.prefetch(commit) + else p.repo.prefetch(commit, has_submodules=has_submodules) ) license_spdx_id = ( current_plugin.license