fix(file_tools): a trailing '.' still names the Delete/Move entry, not its target
This commit is contained in:
@@ -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]:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user