fix(tools): grep fallback follows a symlinked search root on the content lane
`_search_with_grep_pruned` built `find <root> -type f -exec grep …`; find's `-type f` tests the link itself, so a symlinked root (file or directory) handed grep nothing and search_files answered total_count 0 with no error and no warning — byte-identical to "no match" on every platform. `find -H` follows the operand and only the operand, so links met inside the tree keep their traversal semantics, and the follow happens in the command on the host that owns the link (SSH/container backends included). Same shape as the files-lane fix in the previous commit. The plain `grep -r` lane is unchanged: GNU grep already follows a command-line symlink (measured); BSD grep skips it, which needs a macOS-side follow-up. Based on the analysis in #116271 by @liuhao1024, whose local `os.path.islink` resolution would not see a link that exists only on a remote execution host. Co-authored-by: liuhao1024 <liuhao1024@users.noreply.github.com> Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
This commit is contained in:
@@ -329,6 +329,34 @@ class TestSymlinkedRootOnTheFilesLane:
|
||||
assert nothing.error is None and nothing.total_count == 0, (
|
||||
f"an empty root ({root}) answered {nothing.total_count} file(s): {nothing.files!r}")
|
||||
|
||||
def test_symlinked_content_root_under_a_dot_dir_matches_on_the_grep_lane(self, tmp_path, monkeypatch):
|
||||
"""The pruned grep lane (root under a dot-dir) must search through a symlinked
|
||||
root instead of answering a silent zero (#116270).
|
||||
|
||||
``find <link> -type f`` returned no files on every platform, so the pipeline was
|
||||
byte-identical to "no match"; ``find -H`` follows the operand.
|
||||
"""
|
||||
from tools.file_tools import _get_file_ops
|
||||
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
|
||||
hidden = tmp_path / ".dot"
|
||||
(hidden / "real").mkdir(parents=True)
|
||||
(hidden / "real" / "f.md").write_text("NEEDLE\n")
|
||||
(hidden / "link.md").symlink_to(hidden / "real" / "f.md")
|
||||
(hidden / "dirlink").symlink_to(hidden / "real")
|
||||
|
||||
ops = _get_file_ops(task_id="t-symlink-content-grep")
|
||||
if not ops._has_command("grep"):
|
||||
pytest.skip("grep not installed")
|
||||
self._pin_engine(monkeypatch, ops, "grep")
|
||||
for root in ("link.md", "dirlink"):
|
||||
r = ops.search("NEEDLE", path=str(hidden / root), target="content")
|
||||
assert r.error is None, r.error
|
||||
assert r.total_count == 1, (
|
||||
f"symlinked root {root} answered total_count={r.total_count} on the grep lane")
|
||||
miss = ops.search("ABSENT_TOKEN", path=str(hidden / "dirlink"), target="content")
|
||||
assert miss.error is None and miss.total_count == 0
|
||||
|
||||
def test_a_symlinked_root_pointing_at_home_is_still_refused(self, tmp_path, monkeypatch):
|
||||
"""The no-rg breadth guard must classify the link's target (#116270)."""
|
||||
import tools.file_operations as file_operations
|
||||
|
||||
@@ -952,7 +952,10 @@ class SearchMixin:
|
||||
grep's exit code, so a hard grep error surfaces as an empty result."""
|
||||
grep_parts = self._grep_cmd(["grep", "-nHE"], pattern, output_mode, context)
|
||||
q_root = self._escape_shell_arg(path or ".")
|
||||
find_parts = ["find", q_root]
|
||||
# ``-H``: follow a symlink handed in as the OPERAND (and only the operand). Without
|
||||
# it ``find <link> -type f`` tests the link itself and hands grep nothing, so a
|
||||
# symlinked root answered a confident ``total_count: 0`` on every platform (#116270).
|
||||
find_parts = ["find", "-H", q_root]
|
||||
if protected_paths:
|
||||
find_parts.extend([self._prune_expr(protected_paths), "-o"])
|
||||
find_parts.extend([self._hidden_prune_expr([q_root]), "-o", "-type f"])
|
||||
|
||||
Reference in New Issue
Block a user