Compute src/, tests/, and combined source-file hashes. Uses directory mtimes as a fast structural-change proxy so we can skip the expensive rglob walk when nothing was added or removed. The tests/ directory mtime is also returned so the caller can reuse it for the test_list_cache fa
(source_dir: Path, markers: MarkerPaths)
| 93 | |
| 94 | |
| 95 | def compute_source_hashes(source_dir: Path, markers: MarkerPaths) -> SourceHashes: |
| 96 | """Compute src/, tests/, and combined source-file hashes. |
| 97 | |
| 98 | Uses directory mtimes as a fast structural-change proxy so we can skip |
| 99 | the expensive rglob walk when nothing was added or removed. The tests/ |
| 100 | directory mtime is also returned so the caller can reuse it for the |
| 101 | test_list_cache fast-path. |
| 102 | """ |
| 103 | max_tests_dir_mtime = get_max_dir_mtime(source_dir / "tests") |
| 104 | current_src_hash = "" |
| 105 | current_test_hash = "" |
| 106 | current_source_hash = "" |
| 107 | current_source_files: list[str] = [] |
| 108 | |
| 109 | max_src_dir_mtime = get_max_dir_mtime(source_dir / "src") |
| 110 | if markers.src_files.exists(): |
| 111 | try: |
| 112 | marker_mtime = markers.src_files.stat().st_mtime |
| 113 | if max_src_dir_mtime <= marker_mtime: |
| 114 | cached = markers.src_files.read_text(encoding="utf-8").strip() |
| 115 | if cached: |
| 116 | current_src_hash = cached |
| 117 | except OSError: |
| 118 | pass |
| 119 | |
| 120 | if markers.test_files.exists(): |
| 121 | try: |
| 122 | marker_mtime = markers.test_files.stat().st_mtime |
| 123 | if max_tests_dir_mtime <= marker_mtime: |
| 124 | cached = markers.test_files.read_text(encoding="utf-8").strip() |
| 125 | if cached: |
| 126 | current_test_hash = cached |
| 127 | except OSError: |
| 128 | pass |
| 129 | |
| 130 | if not current_src_hash or not current_test_hash: |
| 131 | split = get_split_source_hashes(source_dir) |
| 132 | if not current_src_hash: |
| 133 | current_src_hash = split.src_hash |
| 134 | if not current_test_hash: |
| 135 | current_test_hash = split.tests_hash |
| 136 | current_source_files = sorted(split.src_files + split.test_files) |
| 137 | |
| 138 | if markers.source_files.exists(): |
| 139 | try: |
| 140 | marker_mtime = markers.source_files.stat().st_mtime |
| 141 | max_combined = max(max_src_dir_mtime, max_tests_dir_mtime) |
| 142 | if max_combined <= marker_mtime: |
| 143 | cached = markers.source_files.read_text(encoding="utf-8").strip() |
| 144 | if cached: |
| 145 | current_source_hash = cached |
| 146 | except OSError: |
| 147 | pass |
| 148 | if not current_source_hash: |
| 149 | current_source_hash, current_source_files = get_source_files_hash(source_dir) |
| 150 | |
| 151 | return SourceHashes( |
| 152 | src_hash=current_src_hash, |
no test coverage detected