Check if a project has changes. Returns true if reindex needed. * Must NOT mutate the committed baselines (last_head, last_dirty_sig): * poll_project commits them only after a SUCCESSFUL reindex so that * busy-skips and failed runs retry instead of silently losing the change * (#937). Observations are staged in the pending_* fields. */
| 1245 | * busy-skips and failed runs retry instead of silently losing the change |
| 1246 | * (#937). Observations are staged in the pending_* fields. */ |
| 1247 | static bool check_changes(cbm_watcher_t *w, project_state_t *s, bool *changed_out) { |
| 1248 | if (!changed_out) { |
| 1249 | return false; |
| 1250 | } |
| 1251 | *changed_out = false; |
| 1252 | if (!s->is_git) { |
| 1253 | return true; |
| 1254 | } |
| 1255 | |
| 1256 | bool changed = false; |
| 1257 | |
| 1258 | /* Check HEAD movement (commit, checkout, pull) */ |
| 1259 | s->pending_head[0] = '\0'; |
| 1260 | char head[CBM_SZ_128] = {0}; |
| 1261 | watcher_git_status_t head_status = git_head(w, s, head, sizeof(head)); |
| 1262 | if (head_status == WATCHER_GIT_OK) { |
| 1263 | if (s->last_head[0] == '\0') { |
| 1264 | /* First observed HEAD: adopt as baseline, not a change. */ |
| 1265 | snprintf(s->last_head, sizeof(s->last_head), "%s", head); |
| 1266 | } else if (strcmp(head, s->last_head) != 0) { |
| 1267 | changed = true; |
| 1268 | } |
| 1269 | snprintf(s->pending_head, sizeof(s->pending_head), "%s", head); |
| 1270 | } else if (head_status != WATCHER_GIT_COMMAND_FAILED) { |
| 1271 | return false; |
| 1272 | } |
| 1273 | |
| 1274 | /* Working tree: reindex only when the DIRTY STATE ITSELF changed — |
| 1275 | * a persistently dirty tree polled while idle must not re-trigger |
| 1276 | * full reindex/write cycles (#937 write amplification). */ |
| 1277 | uint64_t sig = 0; |
| 1278 | watcher_git_status_t signature_status = git_dirty_signature(w, s, &sig); |
| 1279 | if (signature_status == WATCHER_GIT_COMMAND_FAILED) { |
| 1280 | /* The repository may have been removed between baseline and poll. |
| 1281 | * Preserve committed observations and wait for a later clean probe. */ |
| 1282 | return true; |
| 1283 | } |
| 1284 | if (signature_status != WATCHER_GIT_OK) { |
| 1285 | return false; |
| 1286 | } |
| 1287 | if (sig != s->last_dirty_sig) { |
| 1288 | changed = true; |
| 1289 | } |
| 1290 | s->pending_dirty_sig = sig; |
| 1291 | |
| 1292 | *changed_out = changed; |
| 1293 | return true; |
| 1294 | } |
| 1295 | |
| 1296 | /* Context for poll_once foreach callback */ |
| 1297 | typedef struct { |
no test coverage detected