From 7344500e46b89093423bc443cad107be9d1a60bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 1 Dec 2020 09:30:17 +0100 Subject: [PATCH] get old version from git --- default.nix | 6 ++++- nix_update/__init__.py | 11 ++++++---- nix_update/eval.py | 25 ++++++++++++++++++--- nix_update/git.py | 50 ++++++++++++++++++++++++++++++++++++++++++ nix_update/update.py | 41 ++++++++++++++++++++++------------ tests/consul.patch | 21 ++++++++++++++++++ tests/test_git.py | 16 ++++++++++++++ 7 files changed, 148 insertions(+), 22 deletions(-) create mode 100644 nix_update/git.py create mode 100644 tests/consul.patch create mode 100644 tests/test_git.py diff --git a/default.nix b/default.nix index a4c9966..f10bd51 100644 --- a/default.nix +++ b/default.nix @@ -7,7 +7,11 @@ python3.pkgs.buildPythonApplication rec { src = ./.; buildInputs = [ makeWrapper ]; checkInputs = [ - mypy python3.pkgs.black python3.pkgs.flake8 glibcLocales + python3.pkgs.pytest + python3.pkgs.black + python3.pkgs.flake8 + glibcLocales + mypy # technically not a test input, but we need it for development in PATH nixFlakes ]; diff --git a/nix_update/__init__.py b/nix_update/__init__.py index 7703534..42c0218 100644 --- a/nix_update/__init__.py +++ b/nix_update/__init__.py @@ -62,7 +62,6 @@ def nix_shell(options: Options) -> None: def git_commit(git_dir: str, attribute: str, package: Package) -> None: - run(["git", "-C", git_dir, "add", package.filename], stdout=None) diff = run(["git", "-C", git_dir, "diff", "--staged"]) if len(diff.stdout) == 0: @@ -79,7 +78,7 @@ def git_commit(git_dir: str, attribute: str, package: Package) -> None: ) else: with tempfile.NamedTemporaryFile(mode="w") as f: - f.write(f"{attribute}:") + f.write(f"{attribute}: {package.old_version} -> {package.new_version}") f.flush() run( ["git", "-C", git_dir, "commit", "--verbose", "--template", f.name], @@ -120,7 +119,9 @@ def validate_git_dir(import_path: str) -> str: def nix_run(options: Options) -> None: cmd = ["nix", "shell", "--experimental-features", "nix-command"] run( - cmd + ["-f", options.import_path, options.attribute], stdout=None, check=False, + cmd + ["-f", options.import_path, options.attribute], + stdout=None, + check=False, ) @@ -135,7 +136,9 @@ def nix_build(options: Options) -> None: options.attribute, ] run( - cmd, stdout=None, check=False, + cmd, + stdout=None, + check=False, ) diff --git a/nix_update/eval.py b/nix_update/eval.py index e1a31d6..fa49868 100644 --- a/nix_update/eval.py +++ b/nix_update/eval.py @@ -1,12 +1,19 @@ import json -from dataclasses import dataclass -from typing import List, Optional +from dataclasses import dataclass, InitVar, field +from typing import List, Optional, Dict, Any from .errors import UpdateError from .options import Options from .utils import run +@dataclass +class Position: + file: str + line: int + column: int + + @dataclass class Package: attribute: str @@ -23,20 +30,32 @@ class Package: cargo_sha256: Optional[str] tests: Optional[List[str]] + raw_version_position: InitVar[Optional[Dict[str, Any]]] + new_version: Optional[str] = None + version_position: Optional[Position] = field(init=False) + + def __post_init__(self, raw_version_position: Optional[Dict[str, Any]]) -> None: + if raw_version_position is None: + self.version_position = None + else: + self.version_position = Position(**raw_version_position) def eval_expression(import_path: str, attr: str) -> str: return f"""(with import {import_path} {{}}; let pkg = {attr}; + raw_version_position = builtins.unsafeGetAttrPos "version" pkg; + position = if pkg ? isRubyGem then - builtins.unsafeGetAttrPos "version" pkg + raw_version_position else builtins.unsafeGetAttrPos "src" pkg; in {{ name = pkg.name; old_version = (builtins.parseDrvName pkg.name).version; + inherit raw_version_position; filename = position.file; line = position.line; urls = pkg.src.urls or null; diff --git a/nix_update/git.py b/nix_update/git.py new file mode 100644 index 0000000..2892ab3 --- /dev/null +++ b/nix_update/git.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 + +import subprocess +from typing import Optional +import re + + +def old_version_from_diff( + diff: str, linenumber: int, new_version: str +) -> Optional[str]: + current_line = 0 + old_str = None + new_str = None + regex = re.compile(r"^@@ -(\d+),(\d+) \+(\d+),(\d+) @@$") + for line in diff.split("\n"): + match = regex.match(line) + if match: + current_line = int(match.group(3)) + elif line.startswith("~"): + current_line += 1 + if current_line > linenumber: + return None + elif linenumber == current_line and line.startswith("-"): + old_str = line[1:] + elif linenumber == current_line and line.startswith("+"): + if new_version not in line: + old_str = None + else: + new_str = line[1:] + break + if not new_str or not old_str: + return None + idx = new_str.index(new_version) + prefix = new_str[:idx] + suffix = new_str[idx + len(new_version):] + return old_str.lstrip(prefix).rstrip(suffix) + + +def old_version_from_git( + filename: str, linenumber: int, new_version: str +) -> Optional[str]: + proc = subprocess.run( + ["git", "diff", "--color=never", "--word-diff=porcelain", "--", filename], + text=True, + stdout=subprocess.PIPE, + ) + assert proc.stdout is not None + if len(proc.stdout) == 0: + return None + return old_version_from_diff(proc.stdout, linenumber, new_version) diff --git a/nix_update/update.py b/nix_update/update.py index 504a072..52c0da3 100644 --- a/nix_update/update.py +++ b/nix_update/update.py @@ -8,9 +8,10 @@ from .eval import Package, eval_attr from .options import Options from .utils import info, run from .version import fetch_latest_version +from .git import old_version_from_git -def update_version(package: Package) -> None: +def replace_version(package: Package) -> None: old_version = package.old_version new_version = package.new_version assert new_version is not None @@ -90,23 +91,35 @@ def update_cargo_sha256_hash(opts: Options, filename: str, current_hash: str) -> replace_hash(filename, current_hash, target_hash) +def update_version(package: Package, version: str) -> None: + if version == "auto": + if not package.url: + if package.urls: + url = package.urls[0] + else: + raise UpdateError( + "Could not find a url in the derivations src attribute" + ) + new_version = fetch_latest_version(url) + else: + new_version = version + package.new_version = new_version + position = package.version_position + if new_version == package.old_version and position: + recovered_version = old_version_from_git( + position.file, position.line, new_version + ) + if recovered_version: + package.old_version = recovered_version + return + replace_version(package) + + def update(opts: Options) -> Package: package = eval_attr(opts) if opts.version != "skip": - if opts.version == "auto": - if not package.url: - if package.urls: - url = package.urls[0] - else: - raise UpdateError( - "Could not find a url in the derivations src attribute" - ) - new_version = fetch_latest_version(url) - else: - new_version = opts.version - package.new_version = new_version - update_version(package) + update_version(package, opts.version) update_src_hash(opts, package.filename, package.hash) diff --git a/tests/consul.patch b/tests/consul.patch new file mode 100644 index 0000000..ffd74f1 --- /dev/null +++ b/tests/consul.patch @@ -0,0 +1,21 @@ +diff --git a/pkgs/servers/consul/default.nix b/pkgs/servers/consul/default.nix +index eef9054ae70..334528e7673 100644 +--- a/pkgs/servers/consul/default.nix ++++ b/pkgs/servers/consul/default.nix +@@ -2,7 +2,7 @@ + +~ + buildGoModule rec { +~ + pname = "consul"; +~ + version = +-"1.8.6"; ++"1.9.0"; +~ + rev = "v${version}"; +~ + +~ + # Note: Currently only release tags are supported, because they have the Consul UI +~ diff --git a/tests/test_git.py b/tests/test_git.py new file mode 100644 index 0000000..a0a10e5 --- /dev/null +++ b/tests/test_git.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +from pathlib import Path +from unittest import TestCase +from nix_update.git import old_version_from_diff + + +TEST_ROOT = Path(__file__).parent.resolve() + + +class WordDiff(TestCase): + def test_worddiff(self) -> None: + with open(TEST_ROOT.joinpath("consul.patch")) as f: + diff = f.read() + s = old_version_from_diff(diff, 5, "1.9.0") + self.assertEqual(s, "1.8.6")