nixos-rebuild-ng: fix repl behaving differently on git flakes than build commands
Since we use builtins.getFlake we have behavior differences between normal nix build and the nix repl because builtins.getFlake won't pick up local flakes as git+file but assumes path:// flakes instead. This can have surprising effects such as beeing able to access untracked files that would lead to build failures otherwise or copying large files to the nix store.
This commit is contained in:
@@ -61,6 +61,25 @@ class BuildAttr:
|
||||
return cls(Path(file or "default.nix"), attr)
|
||||
|
||||
|
||||
def discover_git(location: Path) -> str | None:
|
||||
current = location.resolve()
|
||||
previous = None
|
||||
|
||||
while current.is_dir() and current != previous:
|
||||
dotgit = current / ".git"
|
||||
if dotgit.is_dir():
|
||||
return str(current)
|
||||
elif dotgit.is_file(): # this is a worktree
|
||||
with dotgit.open() as f:
|
||||
dotgit_content = f.read().strip()
|
||||
if dotgit_content.startswith("gitdir: "):
|
||||
return dotgit_content.split("gitdir: ")[1]
|
||||
previous = current
|
||||
current = current.parent
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Flake:
|
||||
path: Path | str
|
||||
@@ -84,11 +103,15 @@ class Flake:
|
||||
assert m is not None, f"got no matches for {flake_str}"
|
||||
attr = m.group("attr")
|
||||
nixos_attr = f"nixosConfigurations.{attr or hostname_fn() or 'default'}"
|
||||
path = m.group("path")
|
||||
if ":" in path:
|
||||
return cls(path, nixos_attr)
|
||||
path_str = m.group("path")
|
||||
if ":" in path_str:
|
||||
return cls(path_str, nixos_attr)
|
||||
else:
|
||||
return cls(Path(path), nixos_attr)
|
||||
path = Path(path_str)
|
||||
git_repo = discover_git(path)
|
||||
if git_repo is not None:
|
||||
return cls(f"git+file://{git_repo}", nixos_attr)
|
||||
return cls(path, nixos_attr)
|
||||
|
||||
@classmethod
|
||||
def from_arg(cls, flake_arg: Any, target_host: Remote | None) -> Self | None:
|
||||
|
||||
@@ -4,6 +4,8 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
import nixos_rebuild.models as m
|
||||
|
||||
from .helpers import get_qualified_name
|
||||
@@ -30,7 +32,7 @@ def test_build_attr_to_attr() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_flake_parse() -> None:
|
||||
def test_flake_parse(tmpdir: Path, monkeypatch: MonkeyPatch) -> None:
|
||||
assert m.Flake.parse("/path/to/flake#attr") == m.Flake(
|
||||
Path("/path/to/flake"), "nixosConfigurations.attr"
|
||||
)
|
||||
@@ -40,15 +42,31 @@ def test_flake_parse() -> None:
|
||||
assert m.Flake.parse("/path/to/flake", lambda: "hostname") == m.Flake(
|
||||
Path("/path/to/flake"), "nixosConfigurations.hostname"
|
||||
)
|
||||
assert m.Flake.parse(".#attr") == m.Flake(Path("."), "nixosConfigurations.attr")
|
||||
assert m.Flake.parse("#attr") == m.Flake(Path("."), "nixosConfigurations.attr")
|
||||
assert m.Flake.parse(".") == m.Flake(Path("."), "nixosConfigurations.default")
|
||||
# change directory to tmpdir
|
||||
with monkeypatch.context() as patch_context:
|
||||
patch_context.chdir(tmpdir)
|
||||
assert m.Flake.parse(".#attr") == m.Flake(Path("."), "nixosConfigurations.attr")
|
||||
assert m.Flake.parse("#attr") == m.Flake(Path("."), "nixosConfigurations.attr")
|
||||
assert m.Flake.parse(".") == m.Flake(Path("."), "nixosConfigurations.default")
|
||||
assert m.Flake.parse("path:/to/flake#attr") == m.Flake(
|
||||
"path:/to/flake", "nixosConfigurations.attr"
|
||||
)
|
||||
assert m.Flake.parse("github:user/repo/branch") == m.Flake(
|
||||
"github:user/repo/branch", "nixosConfigurations.default"
|
||||
)
|
||||
git_root = tmpdir / "git_root"
|
||||
git_root.mkdir()
|
||||
(git_root / ".git").mkdir()
|
||||
assert m.Flake.parse(str(git_root)) == m.Flake(
|
||||
f"git+file://{git_root}", "nixosConfigurations.default"
|
||||
)
|
||||
|
||||
work_tree = tmpdir / "work_tree"
|
||||
work_tree.mkdir()
|
||||
(work_tree / ".git").write_text("gitdir: /path/to/git", "utf-8")
|
||||
assert m.Flake.parse(str(work_tree)) == m.Flake(
|
||||
"git+file:///path/to/git", "nixosConfigurations.default"
|
||||
)
|
||||
|
||||
|
||||
def test_flake_to_attr() -> None:
|
||||
@@ -61,7 +79,7 @@ def test_flake_to_attr() -> None:
|
||||
|
||||
|
||||
@patch(get_qualified_name(platform.node), autospec=True)
|
||||
def test_flake_from_arg(mock_node: Any) -> None:
|
||||
def test_flake_from_arg(mock_node: Any, monkeypatch: MonkeyPatch, tmpdir: Path) -> None:
|
||||
mock_node.return_value = "hostname"
|
||||
|
||||
# Flake string
|
||||
@@ -73,9 +91,11 @@ def test_flake_from_arg(mock_node: Any) -> None:
|
||||
assert m.Flake.from_arg(False, None) is None
|
||||
|
||||
# True
|
||||
assert m.Flake.from_arg(True, None) == m.Flake(
|
||||
Path("."), "nixosConfigurations.hostname"
|
||||
)
|
||||
with monkeypatch.context() as patch_context:
|
||||
patch_context.chdir(tmpdir)
|
||||
assert m.Flake.from_arg(True, None) == m.Flake(
|
||||
Path("."), "nixosConfigurations.hostname"
|
||||
)
|
||||
|
||||
# None when we do not have /etc/nixos/flake.nix
|
||||
with patch(
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any
|
||||
from unittest.mock import ANY, call, patch
|
||||
|
||||
import pytest
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
import nixos_rebuild.models as m
|
||||
import nixos_rebuild.nix as n
|
||||
@@ -51,7 +52,8 @@ def test_build(mock_run: Any, monkeypatch: Any) -> None:
|
||||
autospec=True,
|
||||
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
|
||||
)
|
||||
def test_build_flake(mock_run: Any) -> None:
|
||||
def test_build_flake(mock_run: Any, monkeypatch: MonkeyPatch, tmpdir: Path) -> None:
|
||||
monkeypatch.chdir(tmpdir)
|
||||
flake = m.Flake.parse(".#hostname")
|
||||
|
||||
assert n.build_flake(
|
||||
@@ -165,7 +167,10 @@ def test_build_remote(mock_uuid4: Any, mock_run: Any, monkeypatch: Any) -> None:
|
||||
autospec=True,
|
||||
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
|
||||
)
|
||||
def test_build_remote_flake(mock_run: Any, monkeypatch: Any) -> None:
|
||||
def test_build_remote_flake(
|
||||
mock_run: Any, monkeypatch: MonkeyPatch, tmpdir: Path
|
||||
) -> None:
|
||||
monkeypatch.chdir(tmpdir)
|
||||
flake = m.Flake.parse(".#hostname")
|
||||
build_host = m.Remote("user@host", [], None)
|
||||
monkeypatch.setenv("NIX_SSHOPTS", "--ssh opts")
|
||||
@@ -287,7 +292,7 @@ def test_copy_closure(monkeypatch: Any) -> None:
|
||||
@patch(get_qualified_name(n.run_wrapper, n), autospec=True)
|
||||
def test_edit(mock_run: Any, monkeypatch: Any, tmpdir: Any) -> None:
|
||||
# Flake
|
||||
flake = m.Flake.parse(".#attr")
|
||||
flake = m.Flake.parse(f"{tmpdir}#attr")
|
||||
n.edit(flake, {"commit_lock_file": True})
|
||||
mock_run.assert_called_with(
|
||||
[
|
||||
@@ -297,7 +302,7 @@ def test_edit(mock_run: Any, monkeypatch: Any, tmpdir: Any) -> None:
|
||||
"edit",
|
||||
"--commit-lock-file",
|
||||
"--",
|
||||
".#nixosConfigurations.attr",
|
||||
f"{tmpdir}#nixosConfigurations.attr",
|
||||
],
|
||||
check=False,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user