| 1180 | } |
| 1181 | |
| 1182 | static cbm_discover_status_t discover_impl(const char *repo_path, const cbm_discover_opts_t *opts, |
| 1183 | cbm_file_info_t **out, int *count, char ***excluded_out, |
| 1184 | int *excluded_count_out, |
| 1185 | cbm_ignored_file_t **ignored_out, int *ignored_count_out, |
| 1186 | int *ignored_total_out, bool count_only, int max_files, |
| 1187 | uint64_t deadline_ms) { |
| 1188 | if (excluded_out) { |
| 1189 | *excluded_out = NULL; |
| 1190 | } |
| 1191 | if (excluded_count_out) { |
| 1192 | *excluded_count_out = 0; |
| 1193 | } |
| 1194 | if (ignored_out) { |
| 1195 | *ignored_out = NULL; |
| 1196 | } |
| 1197 | if (ignored_count_out) { |
| 1198 | *ignored_count_out = 0; |
| 1199 | } |
| 1200 | if (ignored_total_out) { |
| 1201 | *ignored_total_out = 0; |
| 1202 | } |
| 1203 | if (!repo_path || !out || !count || (count_only && max_files < 0)) { |
| 1204 | return CBM_DISCOVER_ERROR; |
| 1205 | } |
| 1206 | |
| 1207 | *out = NULL; |
| 1208 | *count = 0; |
| 1209 | |
| 1210 | /* Verify directory exists */ |
| 1211 | struct stat st; |
| 1212 | if (wide_stat(repo_path, &st) != 0 || !S_ISDIR(st.st_mode)) { |
| 1213 | return CBM_DISCOVER_ERROR; |
| 1214 | } |
| 1215 | |
| 1216 | /* Load gitignore sources for ordinary repos AND linked worktrees. |
| 1217 | * Sources merged in order (later patterns win on conflict): |
| 1218 | * 1. <repo>/.gitignore — committed exclusions |
| 1219 | * 2. <common>/info/exclude — per-clone exclusions, not committed |
| 1220 | * <common> is the git common dir, resolved via resolve_git_common_dir() so a |
| 1221 | * worktree (where .git is a gitlink file) reads the shared info/exclude/config |
| 1222 | * just like a normal checkout. Both are folded into a single matcher so all |
| 1223 | * downstream call paths remain unchanged. Fixes issue #489: OOM on repos whose |
| 1224 | * worktrees are excluded only via .git/info/exclude (e.g. Sandcastle). */ |
| 1225 | cbm_gitignore_t *gitignore = NULL; |
| 1226 | char gi_path[CBM_SZ_4K]; |
| 1227 | struct stat gi_stat; |
| 1228 | /* Resolve the git common dir, transparently following a worktree gitlink so the |
| 1229 | * .git/info/exclude and core.excludesfile sources are honoured inside linked |
| 1230 | * worktrees too (where .git is a file pointing at the shared dir, not a directory). */ |
| 1231 | char git_common_dir[CBM_SZ_4K]; |
| 1232 | bool is_git_repo = resolve_git_common_dir(repo_path, git_common_dir, sizeof(git_common_dir)); |
| 1233 | bool has_git_config = false; |
| 1234 | /* Always honour the .gitignore at the indexed-directory root, even when the |
| 1235 | * directory is not a git repo root (e.g. indexing a sub-package directly). |
| 1236 | * Fixes issue #510: a root .gitignore was silently ignored without .git/. */ |
| 1237 | snprintf(gi_path, sizeof(gi_path), "%s/.gitignore", repo_path); |
| 1238 | gitignore = cbm_gitignore_load(gi_path); |
| 1239 | if (is_git_repo) { |
no test coverage detected