diff --git a/src/serena/project.py b/src/serena/project.py index 0673f05e..966fc8c7 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -170,18 +170,22 @@ class Project: abs_path = Path(self.project_root) / relative_path return abs_path.exists() - def validate_relative_path(self, relative_path: str) -> None: + def validate_relative_path(self, relative_path: str, require_not_ignored: bool = False) -> None: """ Validates that the given relative path to an existing file/dir is safe to read or edit, - meaning it's inside the project directory and is not ignored by git. + meaning it's inside the project directory. Passing a path to a non-existing file will lead to a `FileNotFoundError`. + + :param relative_path: the path to validate, relative to the project root + :param require_not_ignored: if True, the path must not be ignored according to the project's ignore settings """ if not self.is_path_in_project(relative_path): raise ValueError(f"{relative_path=} points to path outside of the repository root; cannot access for safety reasons") - if self.is_ignored_path(relative_path): - raise ValueError(f"Path {relative_path} is ignored; cannot access for safety reasons") + if require_not_ignored: + if self.is_ignored_path(relative_path): + raise ValueError(f"Path {relative_path} is ignored; cannot access for safety reasons") def gather_source_files(self, relative_path: str = "") -> list[str]: """Retrieves relative paths of all source files, optionally limited to the given path diff --git a/src/serena/tools/file_tools.py b/src/serena/tools/file_tools.py index f3db634e..d7ec5a41 100644 --- a/src/serena/tools/file_tools.py +++ b/src/serena/tools/file_tools.py @@ -36,7 +36,7 @@ class ReadFileTool(Tool): required for the task. :return: the full text of the file at the given relative path """ - self.project.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path, require_not_ignored=True) result = self.project.read_file(relative_path) result_lines = result.splitlines() @@ -67,7 +67,7 @@ class CreateTextFileTool(Tool, ToolMarkerCanEdit): will_overwrite_existing = abs_path.exists() if will_overwrite_existing: - self.project.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path, require_not_ignored=True) else: assert abs_path.is_relative_to( self.get_project_root() @@ -88,7 +88,7 @@ class ListDirTool(Tool): def apply(self, relative_path: str, recursive: bool, skip_ignored_files: bool = False, max_answer_chars: int = -1) -> str: """ - Lists all non-gitignored files and directories in the given directory (optionally with recursion). + Lists files and directories in the given directory (optionally with recursion). :param relative_path: the relative path to the directory to list; pass "." to scan the project root :param recursive: whether to scan subdirectories recursively @@ -107,7 +107,7 @@ class ListDirTool(Tool): } return json.dumps(error_info) - self.project.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path, require_not_ignored=skip_ignored_files) dirs, files = scan_directory( os.path.join(self.get_project_root(), relative_path), @@ -134,7 +134,7 @@ class FindFileTool(Tool): :param relative_path: the relative path to the directory to search in; pass "." to scan the project root :return: a JSON object with the list of matching files """ - self.project.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path, require_not_ignored=True) dir_to_scan = os.path.join(self.get_project_root(), relative_path) @@ -192,7 +192,7 @@ class ReplaceRegexTool(Tool, ToolMarkerCanEdit): If this is set to False and the regex matches multiple occurrences, an error will be returned (and you may retry with a revised, more specific regex). """ - self.project.validate_relative_path(relative_path) + self.project.validate_relative_path(relative_path, require_not_ignored=True) with EditedFileContext(relative_path, self.agent) as context: original_content = context.get_original_content() updated_content, n = re.subn(regex, repl, original_content, flags=re.DOTALL | re.MULTILINE)