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. */
| 1098 | * busy-skips and failed runs retry instead of silently losing the change |
| 1099 | * (#937). Observations are staged in the pending_* fields. */ |
| 1100 | static bool check_changes(cbm_watcher_t *w, project_state_t *s, bool *changed_out) { |
| 1101 | if (!changed_out) { |
| 1102 | return false; |
| 1103 | } |
| 1104 | *changed_out = false; |
| 1105 | if (!s->is_git) { |
| 1106 | return true; |
| 1107 | } |
| 1108 | |
| 1109 | bool changed = false; |
| 1110 | |
| 1111 | /* Check HEAD movement (commit, checkout, pull) */ |
| 1112 | s->pending_head[0] = '\0'; |
| 1113 | char head[CBM_SZ_128] = {0}; |
| 1114 | watcher_git_status_t head_status = git_head(w, s, head, sizeof(head)); |
| 1115 | if (head_status == WATCHER_GIT_OK) { |
| 1116 | if (s->last_head[0] == '\0') { |
| 1117 | /* First observed HEAD: adopt as baseline, not a change. */ |
| 1118 | snprintf(s->last_head, sizeof(s->last_head), "%s", head); |
| 1119 | } else if (strcmp(head, s->last_head) != 0) { |
| 1120 | changed = true; |
| 1121 | } |
| 1122 | snprintf(s->pending_head, sizeof(s->pending_head), "%s", head); |
| 1123 | } else if (head_status != WATCHER_GIT_COMMAND_FAILED) { |
| 1124 | return false; |
| 1125 | } |
| 1126 | |
| 1127 | /* Working tree: reindex only when the DIRTY STATE ITSELF changed — |
| 1128 | * a persistently dirty tree polled while idle must not re-trigger |
| 1129 | * full reindex/write cycles (#937 write amplification). */ |
| 1130 | uint64_t sig = 0; |
| 1131 | watcher_git_status_t signature_status = git_dirty_signature(w, s, &sig); |
| 1132 | if (signature_status == WATCHER_GIT_COMMAND_FAILED) { |
| 1133 | /* The repository may have been removed between baseline and poll. |
| 1134 | * Preserve committed observations and wait for a later clean probe. */ |
| 1135 | return true; |
| 1136 | } |
| 1137 | if (signature_status != WATCHER_GIT_OK) { |
| 1138 | return false; |
| 1139 | } |
| 1140 | if (sig != s->last_dirty_sig) { |
| 1141 | changed = true; |
| 1142 | } |
| 1143 | s->pending_dirty_sig = sig; |
| 1144 | |
| 1145 | *changed_out = changed; |
| 1146 | return true; |
| 1147 | } |
| 1148 | |
| 1149 | /* Context for poll_once foreach callback */ |
| 1150 | typedef struct { |
no test coverage detected