Unverified Commit 65c0f773 authored by Austin Horstman's avatar Austin Horstman
Browse files

nixpkgs-plugin-update: prefer recent GitHub release tags

Use GitHub's recent-tag GraphQL query as an ordered source of candidate tags
and pick the first acceptable release tag from that list.

This keeps the generic git fallback unchanged while avoiding the older "largest
version wins" behavior on repositories whose newest maintained release has a
lower semantic version than an older historical tag.
parent ef2ef8eb
Loading
Loading
Loading
Loading
+16 −78
Original line number Diff line number Diff line
@@ -125,6 +125,14 @@ def select_latest_tag(
    return None


def first_release_tag(tags: list[str]) -> str | None:
    for tag in tags:
        if normalize_release_version(tag) is not None:
            return tag

    return None


class Repo:
    def __init__(self, uri: str, branch: str) -> None:
        self.uri = uri
@@ -318,17 +326,6 @@ class RepoGitHub(Repo):
        with urllib.request.urlopen(req, timeout=10) as response:
            return json.load(response)

    def _extract_commit_date(self, target: dict) -> datetime | None:
        commit_date_str = None
        if "committedDate" in target:
            commit_date_str = target["committedDate"]
        elif "target" in target and "committedDate" in target["target"]:
            commit_date_str = target["target"]["committedDate"]

        if commit_date_str:
            return datetime.fromisoformat(commit_date_str.replace("Z", "+00:00"))
        return None

    @retry(urllib.error.URLError, tries=4, delay=3, backoff=2)
    def get_latest_tag(self) -> str | None:
        try:
@@ -341,23 +338,11 @@ class RepoGitHub(Repo):
                return super().get_latest_tag()

            query = """
            query GetLatestVersionInfo($owner: String!, $name: String!) {
            query GetRecentTags($owner: String!, $name: String!) {
              repository(owner: $owner, name: $name) {
                refs(refPrefix: "refs/tags/", first: 5, orderBy: {field: TAG_COMMIT_DATE, direction: DESC}) {
                refs(refPrefix: "refs/tags/", first: 20, orderBy: {field: TAG_COMMIT_DATE, direction: DESC}) {
                  nodes {
                    name
                    target {
                      ... on Commit {
                        committedDate
                      }
                      ... on Tag {
                        target {
                          ... on Commit {
                            committedDate
                          }
                        }
                      }
                    }
                  }
                }
              }
@@ -377,64 +362,17 @@ class RepoGitHub(Repo):
                )
                return None

            if "data" not in data or not data["data"]:
                log.warning(
                    "No data in GraphQL response for %s/%s", self.owner, self.repo
                )
                return None

            repo = data["data"]["repository"]
            repo = data.get("data", {}).get("repository")
            if not repo:
                log.debug(
                    "Repository %s/%s not found or inaccessible", self.owner, self.repo
                )
                return None

            valid_versions = []
            invalid_tags = []
            for ref_node in repo["refs"]["nodes"]:
                tag_name = ref_node["name"]
                commit_date = self._extract_commit_date(ref_node["target"])
                if not commit_date:
                    continue

                try:
                    version = parse_version(tag_name)
                    valid_versions.append((tag_name, version, commit_date))
                except InvalidVersion:
                    invalid_tags.append((tag_name, None, commit_date))

            def get_version(tag_tuple):
                _, version, _ = tag_tuple
                return version

            def get_date(tag_tuple):
                _, _, date = tag_tuple
                return date or datetime.min

            def get_max_versions(versions, sort_key):
                return max(versions, key=sort_key, default=(None, None, None))

            max_valid_tag, _, max_valid_date = get_max_versions(
                valid_versions, get_version
            )
            max_invalid_tag, _, max_invalid_date = get_max_versions(
                invalid_tags, get_date
            )
            if max_valid_tag and max_invalid_tag:
                return (
                    max_invalid_tag
                    if (max_invalid_date or datetime.min)
                    > (max_valid_date or datetime.min)
                    else max_valid_tag
                )
            elif max_valid_tag:
                return max_valid_tag
            elif max_invalid_tag:
                return max_invalid_tag
            else:
            recent_tags = [node["name"] for node in repo["refs"]["nodes"]]
            if not recent_tags:
                return None

            latest_tag = first_release_tag(recent_tags)
            return latest_tag if latest_tag is not None else recent_tags[0]

        except Exception as e:
            log.warning(
                "Error fetching version info for %s/%s: %s",