fix(file_tools): a trailing '.' still names the Delete/Move entry, not its target

This commit is contained in:
kshitijk4poor
2026-09-26 23:49:31 +05:30
committed by kshitij
parent 22facf4fdf
commit 97bacbbce5
3 changed files with 14 additions and 8 deletions

View File

@@ -152,11 +152,15 @@ def _overlaps(a: str, b: str) -> bool:
def split_entry(path: str) -> tuple[str, str]:
"""``os.path.split`` for a directory entry, trailing separators dropped first: an
empty leaf (``dir/link/``) would make entry checks degenerate to the link's target.
"""``os.path.split`` for a directory entry, trailing separators and ``.`` components
dropped first: an empty or ``.`` leaf (``dir/link/``, ``dir/link/.``) would make entry
checks degenerate to the link's target, while pathlib/rm still act on ``link``.
A bare root (``/``, ``C:\\``) is kept as is."""
drive, tail = os.path.splitdrive(path)
return os.path.split(drive + (tail.rstrip(os.sep + (os.altsep or "")) or tail[:1]))
parent, leaf = os.path.split(drive + (tail.rstrip(os.sep + (os.altsep or "")) or tail[:1]))
if leaf == "." and parent:
return split_entry(parent)
return parent, leaf
def is_protected_path(path: str, *, follow: bool = True) -> Optional[str]:

View File

@@ -43,13 +43,14 @@ def test_delete_file_refuses_directory(ops, tmp_path):
@pytest.mark.platforms("posix")
@pytest.mark.parametrize("op", ["delete", "delete_trailing_slash", "move"])
@pytest.mark.parametrize("op", ["delete", "delete_trailing_slash", "delete_dot", "move"])
@pytest.mark.parametrize("layout", ["beside_runtime_venv", "link_in_credential_dir"])
def test_delete_and_move_guard_the_entry_itself(ops, tmp_path, monkeypatch, op, layout):
"""Delete/Move vet the directory entry (parent resolved, leaf kept): a plain file whose
directory merely CONTAINS the runtime venv (``~/notes.txt``) stays deletable/movable,
while a link directly inside a credential dir is refused even though it points outside.
A trailing separator (``.ssh/link/``) must not empty the leaf and skip the entry check."""
A trailing separator or ``.`` (``.ssh/link/``, ``.ssh/link/.``) must not empty the leaf
and skip the entry check."""
home, outside = tmp_path / "home", tmp_path / "outside.txt"
outside.write_text("keep", encoding="utf-8")
monkeypatch.setenv("HOME", str(home))
@@ -67,7 +68,7 @@ def test_delete_and_move_guard_the_entry_itself(ops, tmp_path, monkeypatch, op,
if op == "move":
result = ops.move_file(str(entry), str(moved))
else:
result = ops.delete_file(str(entry) + ("/" if op == "delete_trailing_slash" else ""))
result = ops.delete_file(str(entry) + {"delete_trailing_slash": "/", "delete_dot": "/."}.get(op, ""))
if layout == "beside_runtime_venv":
assert result.error is None, result.error

View File

@@ -305,8 +305,9 @@ def test_v4a_patch_applies_to_resolved_workspace_not_backend_cwd(
@pytest.mark.platforms("posix")
@pytest.mark.parametrize("header,safe_root,content,dest", [
("*** Delete File: local.yaml", False, "shared: true\n", None),
# A trailing separator must still name the link, not fall back to its target.
# A trailing separator or "." must still name the link, not fall back to its target.
("*** Delete File: local.yaml/", False, "shared: true\n", None),
("*** Delete File: local.yaml/.", False, "shared: true\n", None),
("*** Move File: local.yaml -> old.yaml", False, "shared: true\n", "old.yaml"),
("*** Update File: local.yaml\n@@\n-shared: true\n+shared: false\n*** Move File: local.yaml -> old.yaml",
False, "shared: false\n", "old.yaml"),
@@ -341,7 +342,7 @@ def test_v4a_delete_and_move_act_on_a_symlink_not_its_target(
"patch", {"mode": "patch", "patch": f"*** Begin Patch\n{header}\n*** End Patch\n"}, task_id=task_id))
assert target.is_file() and not target.is_symlink()
assert target.read_text(encoding="utf-8") == content
assert target.read_text(encoding="utf-8-sig") == content
if safe_root:
assert not out.get("success") and "HERMES_WRITE_SAFE_ROOT" in json.dumps(out), out
assert link.is_symlink()