|
4 | 4 | from collections import Counter, defaultdict |
5 | 5 | from contextlib import contextmanager |
6 | 6 | from enum import IntEnum, auto, unique |
7 | | -from functools import lru_cache |
| 7 | +from functools import cached_property, lru_cache |
8 | 8 | from os import PathLike |
9 | 9 | from pathlib import Path |
10 | 10 | from typing import TYPE_CHECKING, Any |
@@ -215,23 +215,19 @@ def __init__( |
215 | 215 | def __repr__(self): |
216 | 216 | return self.__class__.__name__ |
217 | 217 |
|
218 | | - @property |
| 218 | + @cached_property |
219 | 219 | def _graph(self) -> PyDiGraph[Importable, Edge]: |
220 | 220 | if not self.__graph_ready: |
221 | 221 | logger.info("Lazily Computing Graph") |
222 | 222 | self.build_graph(self.projects[0].repo_operator) |
223 | 223 | return self.__graph |
224 | 224 |
|
225 | | - @_graph.setter |
226 | | - def _graph(self, value: PyDiGraph[Importable, Edge]) -> None: |
227 | | - self.__graph = value |
228 | | - |
229 | 225 | @stopwatch |
230 | 226 | @commiter |
231 | 227 | def build_graph(self, repo_operator: RepoOperator) -> None: |
232 | 228 | """Builds a codebase graph based on the current file state of the given repo operator""" |
233 | 229 | self.__graph_ready = True |
234 | | - self._graph.clear() |
| 230 | + self.__graph.clear() |
235 | 231 |
|
236 | 232 | # =====[ Add all files to the graph in parallel ]===== |
237 | 233 | syncs = defaultdict(lambda: []) |
@@ -492,22 +488,22 @@ def _process_diff_files(self, files_to_sync: Mapping[SyncType, list[Path]], incr |
492 | 488 | for file_path in files_to_sync[SyncType.REPARSE]: |
493 | 489 | file = self.get_file(file_path) |
494 | 490 | file.remove_internal_edges() |
495 | | - |
496 | | - task = self.progress.begin("Reparsing updated files", count=len(files_to_sync[SyncType.REPARSE])) |
497 | 491 | files_to_resolve = [] |
498 | | - # Step 4: Reparse updated files |
499 | | - for idx, file_path in enumerate(files_to_sync[SyncType.REPARSE]): |
500 | | - task.update(f"Reparsing {self.to_relative(file_path)}", count=idx) |
501 | | - file = self.get_file(file_path) |
502 | | - to_resolve.extend(file.unparse(reparse=True)) |
503 | | - to_resolve = list(filter(lambda node: self.has_node(node.node_id) and node is not None, to_resolve)) |
504 | | - file.sync_with_file_content() |
505 | | - files_to_resolve.append(file) |
506 | | - task.end() |
| 492 | + if len(files_to_sync[SyncType.REPARSE]) > 0: |
| 493 | + task = self.progress.begin("Reparsing updated files", count=len(files_to_sync[SyncType.REPARSE])) |
| 494 | + # Step 4: Reparse updated files |
| 495 | + for idx, file_path in enumerate(files_to_sync[SyncType.REPARSE]): |
| 496 | + task.update(f"Reparsing {self.to_relative(file_path)}", count=idx) |
| 497 | + file = self.get_file(file_path) |
| 498 | + to_resolve.extend(file.unparse(reparse=True)) |
| 499 | + to_resolve = list(filter(lambda node: self.has_node(node.node_id) and node is not None, to_resolve)) |
| 500 | + file.sync_with_file_content() |
| 501 | + files_to_resolve.append(file) |
| 502 | + task.end() |
507 | 503 | # Step 5: Add new files as nodes to graph (does not yet add edges) |
508 | | - task = self.progress.begin("Adding new files", count=len(files_to_sync[SyncType.ADD])) |
| 504 | + task = self.progress.begin("Parsing new files", count=len(files_to_sync[SyncType.ADD])) |
509 | 505 | for idx, filepath in enumerate(files_to_sync[SyncType.ADD]): |
510 | | - task.update(f"Adding {self.to_relative(filepath)}", count=idx) |
| 506 | + task.update(f"Parsing {self.to_relative(filepath)}", count=idx) |
511 | 507 | try: |
512 | 508 | content = self.io.read_text(filepath) |
513 | 509 | except UnicodeDecodeError as e: |
@@ -624,6 +620,10 @@ def get_edges(self) -> list[tuple[NodeId, NodeId, EdgeType, Usage | None]]: |
624 | 620 | return [(x[0], x[1], x[2].type, x[2].usage) for x in self._graph.weighted_edge_list()] |
625 | 621 |
|
626 | 622 | def get_file(self, file_path: os.PathLike, ignore_case: bool = False) -> SourceFile | None: |
| 623 | + # Performance hack: just use the relative path |
| 624 | + node_id = self.filepath_idx.get(str(file_path), None) |
| 625 | + if node_id is not None: |
| 626 | + return self.get_node(node_id) |
627 | 627 | # If not part of repo path, return None |
628 | 628 | absolute_path = self.to_absolute(file_path) |
629 | 629 | if not self.is_subdir(absolute_path) and not self.config.allow_external: |
@@ -752,6 +752,7 @@ def to_relative(self, filepath: PathLike | str) -> Path: |
752 | 752 | return path.relative_to(self.repo_path) |
753 | 753 | return path |
754 | 754 |
|
| 755 | + @lru_cache(maxsize=10000) |
755 | 756 | def is_subdir(self, path: PathLike | str) -> bool: |
756 | 757 | path = self.to_absolute(path) |
757 | 758 | return path == Path(self.repo_path) or path.is_relative_to(self.repo_path) or Path(self.repo_path) in path.parents |
|
0 commit comments