Bucket the paths from `git status --porcelain` by directory/role. A path may contribute to multiple buckets (e.g. tests/meson.build sets tests, build, and meson). Anything outside src/, tests/, examples/, or a recognized build file goes into `other` (ci/, agents/, docs/, .github/, ...).
(porcelain: str)
| 138 | |
| 139 | |
| 140 | def classify_changes(porcelain: str) -> ChangeBuckets: |
| 141 | """Bucket the paths from `git status --porcelain` by directory/role. |
| 142 | |
| 143 | A path may contribute to multiple buckets (e.g. tests/meson.build sets |
| 144 | tests, build, and meson). Anything outside src/, tests/, examples/, or a |
| 145 | recognized build file goes into `other` (ci/, agents/, docs/, .github/, ...). |
| 146 | """ |
| 147 | buckets = ChangeBuckets() |
| 148 | for path in _parse_porcelain_paths(porcelain): |
| 149 | buckets.files.append(path) |
| 150 | |
| 151 | # Normalize Windows-style backslashes that may appear in `git status` |
| 152 | # output on some configurations. |
| 153 | norm = path.replace("\\", "/") |
| 154 | base = norm.rsplit("/", 1)[-1] |
| 155 | |
| 156 | is_src = norm.startswith("src/") |
| 157 | is_tests = norm.startswith("tests/") |
| 158 | is_examples = norm.startswith("examples/") |
| 159 | is_meson_file = base == "meson.build" or base == "meson_options.txt" |
| 160 | is_cmake_file = base == "CMakeLists.txt" |
| 161 | # `is_build` only flips for TOP-LEVEL build files. A nested |
| 162 | # `docs/meson.build` (or similar) must NOT force the expensive |
| 163 | # C++ test phase to run. We keep `is_meson_file` broad so the |
| 164 | # cheaper meson-lint stage still runs whenever any meson file |
| 165 | # changes, but C++ tests are gated on root-only build files. |
| 166 | is_top_level = "/" not in norm |
| 167 | is_build = is_top_level and (is_meson_file or is_cmake_file) |
| 168 | |
| 169 | if is_src: |
| 170 | buckets.src = True |
| 171 | if is_tests: |
| 172 | buckets.tests = True |
| 173 | if is_examples: |
| 174 | buckets.examples = True |
| 175 | if is_build: |
| 176 | buckets.build = True |
| 177 | if is_meson_file: |
| 178 | buckets.meson = True |
| 179 | if not (is_src or is_tests or is_examples or is_build): |
| 180 | buckets.other = True |
| 181 | return buckets |
| 182 | |
| 183 | |
| 184 | class GitPorcelainError(RuntimeError): |
no test coverage detected