| 1109 | } |
| 1110 | |
| 1111 | static cbm_discover_status_t discover_impl(const char *repo_path, const cbm_discover_opts_t *opts, |
| 1112 | cbm_file_info_t **out, int *count, char ***excluded_out, |
| 1113 | int *excluded_count_out, |
| 1114 | cbm_ignored_file_t **ignored_out, int *ignored_count_out, |
| 1115 | int *ignored_total_out, bool count_only, int max_files, |
| 1116 | uint64_t deadline_ms) { |
| 1117 | if (excluded_out) { |
| 1118 | *excluded_out = NULL; |
| 1119 | } |
| 1120 | if (excluded_count_out) { |
| 1121 | *excluded_count_out = 0; |
| 1122 | } |
| 1123 | if (ignored_out) { |
| 1124 | *ignored_out = NULL; |
| 1125 | } |
| 1126 | if (ignored_count_out) { |
| 1127 | *ignored_count_out = 0; |
| 1128 | } |
| 1129 | if (ignored_total_out) { |
| 1130 | *ignored_total_out = 0; |
| 1131 | } |
| 1132 | if (!repo_path || !out || !count || (count_only && max_files < 0)) { |
| 1133 | return CBM_DISCOVER_ERROR; |
| 1134 | } |
| 1135 | |
| 1136 | *out = NULL; |
| 1137 | *count = 0; |
| 1138 | |
| 1139 | /* Verify directory exists */ |
| 1140 | struct stat st; |
| 1141 | if (wide_stat(repo_path, &st) != 0 || !S_ISDIR(st.st_mode)) { |
| 1142 | return CBM_DISCOVER_ERROR; |
| 1143 | } |
| 1144 | |
| 1145 | /* Load gitignore sources for ordinary repos AND linked worktrees. |
| 1146 | * Sources merged in order (later patterns win on conflict): |
| 1147 | * 1. <repo>/.gitignore — committed exclusions |
| 1148 | * 2. <common>/info/exclude — per-clone exclusions, not committed |
| 1149 | * <common> is the git common dir, resolved via resolve_git_common_dir() so a |
| 1150 | * worktree (where .git is a gitlink file) reads the shared info/exclude/config |
| 1151 | * just like a normal checkout. Both are folded into a single matcher so all |
| 1152 | * downstream call paths remain unchanged. Fixes issue #489: OOM on repos whose |
| 1153 | * worktrees are excluded only via .git/info/exclude (e.g. Sandcastle). */ |
| 1154 | cbm_gitignore_t *gitignore = NULL; |
| 1155 | char gi_path[CBM_SZ_4K]; |
| 1156 | struct stat gi_stat; |
| 1157 | /* Resolve the git common dir, transparently following a worktree gitlink so the |
| 1158 | * .git/info/exclude and core.excludesfile sources are honoured inside linked |
| 1159 | * worktrees too (where .git is a file pointing at the shared dir, not a directory). */ |
| 1160 | char git_common_dir[CBM_SZ_4K]; |
| 1161 | bool is_git_repo = resolve_git_common_dir(repo_path, git_common_dir, sizeof(git_common_dir)); |
| 1162 | bool has_git_config = false; |
| 1163 | /* Always honour the .gitignore at the indexed-directory root, even when the |
| 1164 | * directory is not a git repo root (e.g. indexing a sub-package directly). |
| 1165 | * Fixes issue #510: a root .gitignore was silently ignored without .git/. */ |
| 1166 | snprintf(gi_path, sizeof(gi_path), "%s/.gitignore", repo_path); |
| 1167 | gitignore = cbm_gitignore_load(gi_path); |
| 1168 | if (is_git_repo) { |
no test coverage detected