diff --git a/nix_update/eval.nix b/nix_update/eval.nix index b2563eb..e426071 100644 --- a/nix_update/eval.nix +++ b/nix_update/eval.nix @@ -116,6 +116,8 @@ in rev = pkg.src.rev or null; tag = pkg.src.tag or null; hash = pkg.src.outputHash or null; + commit_sha = pkg.commitSha or null; + commit_date = pkg.commitDate or null; fod_subpackage = pkg.outputHash or null; go_modules = pkg.goModules.outputHash or null; go_modules_old = pkg.go-modules.outputHash or null; diff --git a/nix_update/eval.py b/nix_update/eval.py index ee96fdb..e00d37d 100644 --- a/nix_update/eval.py +++ b/nix_update/eval.py @@ -52,6 +52,8 @@ class Package: rev: str | None tag: str | None hash: str | None + commit_sha: str | None + commit_date: str | None fod_subpackage: str | None go_modules: str | None go_modules_old: str | None diff --git a/nix_update/update.py b/nix_update/update.py index da83826..c628e39 100644 --- a/nix_update/update.py +++ b/nix_update/update.py @@ -2,6 +2,8 @@ import fileinput from copy import deepcopy +from dataclasses import dataclass +from datetime import UTC from pathlib import Path from typing import TYPE_CHECKING @@ -18,6 +20,71 @@ from .options import Options +@dataclass +class ReplacementContext: + """Context for applying version replacements to a file.""" + + old_version: str + new_version: str + old_rev_tag: str | None + version_string_in_declaration: bool + + +def apply_line_replacements( + line: str, + line_number: int, + package: Package, + context: ReplacementContext, +) -> str: + """Apply version, rev, commitSha, and commitDate replacements to a line.""" + modified_line = line + # Update rev/tag if present + if ( + context.old_rev_tag is not None + and package.new_version + and package.new_version.rev + ): + modified_line = modified_line.replace( + context.old_rev_tag, + package.new_version.rev, + ) + # Update commitSha if present + if ( + package.commit_sha is not None + and package.new_version + and package.new_version.rev is not None + ): + modified_line = modified_line.replace( + f'"{package.commit_sha}"', + f'"{package.new_version.rev}"', + ) + # Update commitDate if present + if ( + package.commit_date is not None + and package.new_version + and package.new_version.commit_date is not None + ): + new_commit_date_str = ( + package.new_version.commit_date.astimezone(UTC) + .isoformat() + .replace("+00:00", "Z") + ) + modified_line = modified_line.replace( + f'"{package.commit_date}"', + f'"{new_commit_date_str}"', + ) + # Update version string + if not context.version_string_in_declaration or ( + package.version_position is not None + and package.version_position.line == line_number + ): + modified_line = modified_line.replace( + f'"{context.old_version}"', + f'"{context.new_version}"', + ) + return modified_line + + def replace_version(package: Package) -> bool: if package.new_version is None: msg = "Package new_version is None, cannot replace version" @@ -33,29 +100,27 @@ def replace_version(package: Package) -> bool: if changed: info(f"Update {old_version} -> {new_version} in {package.filename}") - version_string_in_version_declaration = False + version_string_in_declaration = False if package.version_position is not None: with Path(package.filename).open() as f: for i, line in enumerate(f, 1): if package.version_position.line == i: - version_string_in_version_declaration = old_version in line + version_string_in_declaration = old_version in line break + context = ReplacementContext( + old_version=old_version, + new_version=new_version, + old_rev_tag=old_rev_tag, + version_string_in_declaration=version_string_in_declaration, + ) with fileinput.FileInput(package.filename, inplace=True) as f: for i, original_line in enumerate(f, 1): - modified_line = original_line - if old_rev_tag is not None and package.new_version.rev: - modified_line = modified_line.replace( - old_rev_tag, - package.new_version.rev, - ) - if not version_string_in_version_declaration or ( - package.version_position is not None - and package.version_position.line == i - ): - modified_line = modified_line.replace( - f'"{old_version}"', - f'"{new_version}"', - ) + modified_line = apply_line_replacements( + line=original_line, + line_number=i, + package=package, + context=context, + ) print(modified_line, end="") else: info(f"Not updating version, already {old_version}") diff --git a/nix_update/version/bitbucket.py b/nix_update/version/bitbucket.py index c229f60..470a694 100644 --- a/nix_update/version/bitbucket.py +++ b/nix_update/version/bitbucket.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import datetime from typing import TYPE_CHECKING from .http import fetch_json @@ -32,5 +33,12 @@ def fetch_bitbucket_snapshots(url: ParseResult, branch: str) -> list[Version]: versions = fetch_bitbucket_versions(url) latest_version = versions[0].number if versions else "0" - date = ref["date"][:10] # to YYYY-MM-DD - return [Version(f"{latest_version}-unstable-{date}", rev=ref["hash"])] + date = ref["date"] + commit_date = datetime.fromisoformat(ref["date"]) + return [ + Version( + f"{latest_version}-unstable-{date[:10]}", + rev=ref["hash"], + commit_date=commit_date, + ), + ] diff --git a/nix_update/version/gitea.py b/nix_update/version/gitea.py index 4819c53..9529d36 100644 --- a/nix_update/version/gitea.py +++ b/nix_update/version/gitea.py @@ -1,6 +1,7 @@ from __future__ import annotations import re +from datetime import datetime from http import HTTPStatus from typing import TYPE_CHECKING from urllib import request @@ -64,5 +65,12 @@ def fetch_gitea_snapshots(url: ParseResult, branch: str) -> list[Version]: versions = fetch_gitea_versions(url) latest_version = versions[0].number if versions else "0" - date = commit["commit"]["committer"]["date"][:10] - return [Version(f"{latest_version}-unstable-{date}", rev=commit["sha"])] + date = commit["commit"]["committer"]["date"] + commit_date = datetime.fromisoformat(date) + return [ + Version( + f"{latest_version}-unstable-{date[:10]}", + rev=commit["sha"], + commit_date=commit_date, + ), + ] diff --git a/nix_update/version/github.py b/nix_update/version/github.py index 75c70a3..3103096 100644 --- a/nix_update/version/github.py +++ b/nix_update/version/github.py @@ -7,6 +7,7 @@ import re import urllib.request import xml.etree.ElementTree as ET +from datetime import datetime from http import HTTPStatus from typing import Any from urllib.parse import ParseResult, unquote, urlparse @@ -114,7 +115,16 @@ def fetch_github_versions_from_releases( except json.JSONDecodeError: info("unable to parse github response, ignoring") return [] - return [Version(r["tag_name"], r["prerelease"]) for r in releases] + return [ + Version( + number=r["tag_name"], + prerelease=r["prerelease"], + commit_date=datetime.fromisoformat(r["created_at"]) + if r.get("created_at") + else None, + ) + for r in releases + ] def fetch_github_versions_from_feed( @@ -195,8 +205,13 @@ def fetch_github_snapshots( url = urlparse(link.attrib["href"]) commit = url.path.rsplit("/", maxsplit=1)[-1] date = updated.text.split("T", maxsplit=1)[0] + commit_datetime = datetime.fromisoformat(updated.text) return [ - Version(f"{version}-unstable-{date}", rev=commit) + Version( + f"{version}-unstable-{date}", + rev=commit, + commit_date=commit_datetime, + ) for version in version_numbers ] diff --git a/nix_update/version/gitlab.py b/nix_update/version/gitlab.py index 69cd6a0..033986b 100644 --- a/nix_update/version/gitlab.py +++ b/nix_update/version/gitlab.py @@ -62,11 +62,13 @@ def fetch_gitlab_snapshots(url: ParseResult, branch: str) -> list[Version]: latest_version = versions[0].number if versions else "0" for commit in commits: - commit_date = datetime.strptime( - commit["committed_date"], - "%Y-%m-%dT%H:%M:%S.000%z", - ) - commit_date -= commit_date.utcoffset() # type: ignore[operator] + commit_date = datetime.fromisoformat(commit["committed_date"]) date = commit_date.strftime("%Y-%m-%d") - return [Version(f"{latest_version}-unstable-{date}", rev=commit["id"])] + return [ + Version( + f"{latest_version}-unstable-{date}", + rev=commit["id"], + commit_date=commit_date, + ), + ] return [] diff --git a/nix_update/version/sourcehut.py b/nix_update/version/sourcehut.py index 50d7bad..237b7cc 100644 --- a/nix_update/version/sourcehut.py +++ b/nix_update/version/sourcehut.py @@ -46,7 +46,11 @@ def snapshot_from_entry(entry: Element, url: ParseResult) -> Version: msg = f"No link found in atom feed {url}" raise VersionError(msg) rev = node.text.split("/")[-1] - return Version(f"{latest_version}-unstable-{date_str}", rev=rev) + return Version( + f"{latest_version}-unstable-{date_str}", + rev=rev, + commit_date=parsed, + ) def fetch_sourcehut_versions(url: ParseResult) -> list[Version]: diff --git a/nix_update/version/version.py b/nix_update/version/version.py index 54162f5..68aabfc 100644 --- a/nix_update/version/version.py +++ b/nix_update/version/version.py @@ -2,6 +2,10 @@ from dataclasses import dataclass from enum import StrEnum, auto +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from datetime import datetime @dataclass @@ -10,6 +14,7 @@ class Version: prerelease: bool | None = None rev: str | None = None tag: str | None = None + commit_date: datetime | None = None class VersionPreference(StrEnum):