Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/argit/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,13 @@ def _git_commit(
for it in concrete_items:
if it.kind == "secret":
continue # secret is in secrets/, added below
if it.target:
# Stage only targets that were actually written. An item whose source
# is absent on this agent is skipped during copy (§4-7), so its target
# never exists; staging it anyway makes `git add` abort the whole backup
# with "pathspec '<target>' did not match any files". This happens
# whenever the live agent lacks a path the bundled manifest declares
# (e.g. a 2026.5.4 manifest against a 2026.5.7 Hermes build).
if it.target and (repo_root / it.target).exists():
paths_to_add.append(it.target)
for sf in manifest.sanitize:
paths_to_add.append(sf.target)
Expand Down
30 changes: 29 additions & 1 deletion tests/unit/test_backup_review_staging.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from unittest.mock import patch

from argit.backup import _git_commit
from argit.manifest import Manifest
from argit.manifest import Item, Manifest


def _empty_manifest() -> Manifest:
Expand Down Expand Up @@ -87,3 +87,31 @@ def test_git_commit_omits_review_path_when_none(capsys, tmp_path):
)
captured = capsys.readouterr()
assert ".argit/reviews/" not in captured.out


def test_git_commit_skips_items_with_missing_target(capsys, tmp_path):
"""An item whose source was absent is skipped during copy, so its target is
never written. _git_commit must NOT stage that target — otherwise `git add`
fails with "pathspec did not match any files" and aborts the whole backup.
(Reproduces a 2026.5.4 manifest declaring scripts/ against a Hermes build
that has no ~/.hermes/scripts/.)"""
repo = tmp_path / "repo"
(repo / "hermes" / "data").mkdir(parents=True)
# present: copied this run, target exists on disk
present_tgt = repo / "hermes" / "data" / "config.yaml"
present_tgt.write_text("model: x\n")
present = Item(kind="data", source="config.yaml", mode="0644",
target="hermes/data/config.yaml")
# missing: source absent → skipped during copy → target never written
missing = Item(kind="data", source="scripts/", mode="0755",
target="hermes/data/scripts/")

_git_commit(
repo, _empty_manifest(),
concrete_items=[present, missing], iso="2026-06-10T00:00:00Z",
dry=True, review_path=None,
)

out = capsys.readouterr().out
assert "hermes/data/config.yaml" in out
assert "hermes/data/scripts/" not in out
Loading