close
Skip to content

Commit 415a64a

Browse files
authored
Merge pull request #1263 from astandrik/fix/index-mode-capability-rebuild
fix: rebuild index when mode adds capabilities
2 parents 89f0cd4 + d024f41 commit 415a64a

4 files changed

Lines changed: 365 additions & 69 deletions

File tree

‎src/pipeline/pipeline.c‎

Lines changed: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ struct cbm_pipeline {
151151
char *project_name;
152152
cbm_git_context_t git_ctx;
153153
char *branch_qn;
154+
cbm_index_mode_t requested_mode;
154155
cbm_index_mode_t mode;
155156
atomic_int cancelled_storage;
156157
atomic_int *cancelled;
@@ -268,6 +269,7 @@ cbm_pipeline_t *cbm_pipeline_new(const char *repo_path, const char *db_path,
268269
p->project_name = cbm_project_name_from_path(repo_path);
269270
(void)cbm_git_context_resolve(repo_path, &p->git_ctx);
270271
p->branch_qn = cbm_git_context_branch_qn(p->project_name, &p->git_ctx);
272+
p->requested_mode = mode;
271273
p->mode = mode;
272274
p->persistence = false;
273275
p->committed_nodes = -1;
@@ -1371,7 +1373,7 @@ static int capture_existing_adr(cbm_pipeline_t *p, const char *db_path) {
13711373
* metadata write has succeeded. */
13721374
static int try_incremental_or_delete_db(cbm_pipeline_t *p, cbm_file_info_t *files, int file_count,
13731375
const cbm_file_hash_t *baseline_manifest,
1374-
int baseline_count) {
1376+
int baseline_count, bool force_full_on_mismatch) {
13751377
char *db_path = resolve_db_path(p);
13761378
if (!db_path) {
13771379
return CBM_PIPELINE_FORCE_FULL_REINDEX;
@@ -1393,7 +1395,7 @@ static int try_incremental_or_delete_db(cbm_pipeline_t *p, cbm_file_info_t *file
13931395
}
13941396
cbm_log_info("pipeline.route", "path", "incremental_manifest");
13951397
int rc = cbm_pipeline_run_incremental(p, db_path, files, file_count, baseline_manifest,
1396-
baseline_count);
1398+
baseline_count, force_full_on_mismatch);
13971399
/* Delete the existing generation ONLY when we are about to rebuild it.
13981400
* On main this was guarded by an early `return rc` for the incremental
13991401
* path; this function has no such early return, so the delete must be
@@ -1442,19 +1444,20 @@ static int pipeline_mode_coverage_rank(cbm_index_mode_t mode) {
14421444
* must never erase files that the cheaper discovery intentionally skips. The
14431445
* exact-manifest pipeline therefore keeps the most comprehensive successfully
14441446
* published mode and performs any changed rebuild at that coverage level. */
1445-
static void promote_mode_to_existing_coverage(cbm_pipeline_t *p) {
1447+
static bool promote_mode_to_existing_coverage(cbm_pipeline_t *p) {
14461448
if (!p || !p->project_name) {
1447-
return;
1449+
return false;
14481450
}
14491451
char *db_path = resolve_db_path(p);
14501452
if (!db_path) {
1451-
return;
1453+
return false;
14521454
}
14531455
cbm_store_t *store = cbm_store_open_path_query(db_path);
14541456
free(db_path);
14551457
if (!store) {
1456-
return;
1458+
return false;
14571459
}
1460+
bool promoted = false;
14581461
cbm_coverage_meta_t meta = {0};
14591462
if (cbm_store_coverage_meta_get(store, p->project_name, &meta) == CBM_STORE_OK &&
14601463
meta.index_mode) {
@@ -1470,31 +1473,12 @@ static void promote_mode_to_existing_coverage(cbm_pipeline_t *p) {
14701473
cbm_log_info("pipeline.mode", "requested", pipeline_mode_name(p->mode), "effective",
14711474
pipeline_mode_name(stored_mode), "reason", "preserve_existing_coverage");
14721475
p->mode = stored_mode;
1476+
promoted = true;
14731477
}
14741478
}
14751479
cbm_store_coverage_meta_clear(&meta);
14761480
cbm_store_close(store);
1477-
}
1478-
1479-
int cbm_pipeline_refresh_artifact(cbm_pipeline_t *p, const char *db_path) {
1480-
if (!p || !db_path || !p->repo_path || !p->project_name) {
1481-
return 0;
1482-
}
1483-
bool existing = cbm_artifact_exists(p->repo_path);
1484-
if (!p->persistence && !existing) {
1485-
return 0;
1486-
}
1487-
int quality = p->persistence ? CBM_ARTIFACT_BEST : CBM_ARTIFACT_FAST;
1488-
int rc = cbm_artifact_export(db_path, p->repo_path, p->project_name, quality);
1489-
if (rc != 0) {
1490-
const char *err = cbm_artifact_export_last_error();
1491-
if (p->persistence) {
1492-
cbm_log_error("pipeline.err", "phase", "artifact_export", "err", err ? err : "unknown");
1493-
return rc;
1494-
}
1495-
cbm_log_warn("artifact.refresh_failed", "err", err ? err : "unknown");
1496-
}
1497-
return 0;
1481+
return promoted;
14981482
}
14991483

15001484
/* Defined below, next to the other publication helpers. */
@@ -2014,8 +1998,8 @@ static int dump_and_persist_hashes(cbm_pipeline_t *p, const cbm_file_hash_t *bas
20141998
cbm_pipeline_free_semantic_manifest(manifest, manifest_count);
20151999
if (rc != 0) {
20162000
/* db_path is this function's strdup (resolve_db_path); every return
2017-
* must release it -- refresh_artifact below only borrows it. LSan on
2018-
* the Linux leg caught exactly this pair of exits leaking. */
2001+
* must release it. LSan on the Linux leg caught exactly this pair of
2002+
* exits leaking. */
20192003
free(db_path);
20202004
return rc;
20212005
}
@@ -2028,12 +2012,8 @@ static int dump_and_persist_hashes(cbm_pipeline_t *p, const cbm_file_hash_t *bas
20282012
free(p->saved_adr);
20292013
p->saved_adr = NULL;
20302014

2031-
/* The SQLite generation is the commit point. Automatic refresh of an
2032-
* existing artifact is best-effort, but an explicitly requested artifact
2033-
* is caller-visible and must report an export failure. */
2034-
int artifact_rc = cbm_pipeline_refresh_artifact(p, db_path);
20352015
free(db_path);
2036-
return artifact_rc;
2016+
return 0;
20372017
}
20382018

20392019
/* Run githistory pass. */
@@ -2157,20 +2137,26 @@ static int run_extraction_phase(cbm_pipeline_t *p, cbm_pipeline_ctx_t *ctx,
21572137
return rc;
21582138
}
21592139

2160-
static int cbm_pipeline_run_staged(cbm_pipeline_t *p, bool *was_incremental) {
2140+
static int cbm_pipeline_run_staged(cbm_pipeline_t *p) {
21612141
if (!p) {
21622142
return CBM_NOT_FOUND;
21632143
}
2164-
*was_incremental = false;
21652144

21662145
CBM_PROF_START(t_pipeline_total);
21672146
struct timespec t0;
21682147
cbm_clock_gettime(CLOCK_MONOTONIC, &t0);
21692148
cbm_path_alias_collection_t *path_aliases = NULL;
21702149
cbm_file_hash_t *baseline_manifest = NULL;
21712150
int baseline_count = 0;
2151+
char **requested_excluded_dirs = NULL;
2152+
int requested_excluded_count = 0;
2153+
cbm_ignored_file_t *requested_ignored_files = NULL;
2154+
int requested_ignored_count = 0;
2155+
int requested_ignored_total = 0;
2156+
bool restore_requested_discovery = false;
21722157

2173-
promote_mode_to_existing_coverage(p);
2158+
p->mode = p->requested_mode;
2159+
bool mode_promoted = promote_mode_to_existing_coverage(p);
21742160

21752161
/* cbm_pipeline_new() may precede the actual run by an arbitrary interval.
21762162
* Refresh once here, then use this exact snapshot for both Branch graph
@@ -2193,7 +2179,7 @@ static int cbm_pipeline_run_staged(cbm_pipeline_t *p, bool *was_incremental) {
21932179
/* Phase 1: Discover files */
21942180
CBM_PROF_START(t_discover);
21952181
cbm_discover_opts_t opts = {
2196-
.mode = p->mode,
2182+
.mode = p->requested_mode,
21972183
.ignore_file = NULL,
21982184
.max_file_size = 0,
21992185
};
@@ -2227,26 +2213,62 @@ static int cbm_pipeline_run_staged(cbm_pipeline_t *p, bool *was_incremental) {
22272213
/* Snapshot every semantic input once before routing/extraction. The same
22282214
* bytes drive exact no-op comparison and are checked against a fresh
22292215
* rediscovery immediately before any replacement is published. */
2230-
rc = cbm_pipeline_build_semantic_manifest(p->project_name, p->repo_path, files, file_count,
2231-
p->excluded_dirs, p->excluded_count, &p->git_ctx,
2232-
p->userconfig, &baseline_manifest, &baseline_count);
2216+
rc = mode_promoted
2217+
? cbm_pipeline_build_fresh_semantic_manifest(p->project_name, p->repo_path, p->mode,
2218+
&baseline_manifest, &baseline_count)
2219+
: cbm_pipeline_build_semantic_manifest(p->project_name, p->repo_path, files,
2220+
file_count, p->excluded_dirs, p->excluded_count,
2221+
&p->git_ctx, p->userconfig, &baseline_manifest,
2222+
&baseline_count);
22332223
if (rc != 0) {
22342224
rc = CBM_PIPELINE_ABORT_PRESERVE_DB;
22352225
goto cleanup;
22362226
}
22372227

22382228
/* Check for existing DB → try incremental or delete for reindex */
2239-
rc = try_incremental_or_delete_db(p, files, file_count, baseline_manifest, baseline_count);
2229+
rc = try_incremental_or_delete_db(p, files, file_count, baseline_manifest, baseline_count,
2230+
mode_promoted);
22402231
if (rc == CBM_PIPELINE_ABORT_PRESERVE_DB || rc == CBM_PIPELINE_PERSIST_FAILED) {
22412232
goto cleanup;
22422233
}
22432234
if (rc >= 0) {
2244-
*was_incremental = true;
22452235
goto cleanup;
22462236
}
22472237
if (rc != CBM_PIPELINE_FORCE_FULL_REINDEX) {
22482238
goto cleanup;
22492239
}
2240+
2241+
/* A changed downgrade rebuilds the complete graph at the stored effective
2242+
* mode. Keep the requested discovery lists to report the caller's scope. */
2243+
if (mode_promoted) {
2244+
cbm_discover_free(files, file_count);
2245+
files = NULL;
2246+
file_count = 0;
2247+
2248+
requested_excluded_dirs = p->excluded_dirs;
2249+
requested_excluded_count = p->excluded_count;
2250+
requested_ignored_files = p->ignored_files;
2251+
requested_ignored_count = p->ignored_count;
2252+
requested_ignored_total = p->ignored_total;
2253+
restore_requested_discovery = true;
2254+
2255+
p->excluded_dirs = NULL;
2256+
p->excluded_count = 0;
2257+
p->ignored_files = NULL;
2258+
p->ignored_count = 0;
2259+
p->ignored_total = 0;
2260+
2261+
opts.mode = p->mode;
2262+
rc = cbm_discover_ex2(p->repo_path, &opts, &files, &file_count, &p->excluded_dirs,
2263+
&p->excluded_count, &p->ignored_files, &p->ignored_count,
2264+
&p->ignored_total);
2265+
cbm_log_info("pipeline.rediscover", "requested_mode", pipeline_mode_name(p->requested_mode),
2266+
"effective_mode", pipeline_mode_name(p->mode), "files", itoa_buf(file_count));
2267+
if (rc != 0 || check_cancel(p)) {
2268+
rc = CBM_NOT_FOUND;
2269+
goto cleanup;
2270+
}
2271+
}
22502272
cbm_log_info("pipeline.route", "path", "full");
22512273

22522274
/* Phase 2: Create graph buffer and registry */
@@ -2296,6 +2318,15 @@ static int cbm_pipeline_run_staged(cbm_pipeline_t *p, bool *was_incremental) {
22962318
cbm_registry_free(p->registry);
22972319
p->registry = NULL;
22982320
cbm_path_alias_collection_free(path_aliases);
2321+
if (restore_requested_discovery) {
2322+
cbm_discover_free_excluded(p->excluded_dirs, p->excluded_count);
2323+
cbm_discover_free_ignored(p->ignored_files, p->ignored_count);
2324+
p->excluded_dirs = requested_excluded_dirs;
2325+
p->excluded_count = requested_excluded_count;
2326+
p->ignored_files = requested_ignored_files;
2327+
p->ignored_count = requested_ignored_count;
2328+
p->ignored_total = requested_ignored_total;
2329+
}
22992330
/* Clear and free user extension config */
23002331
cbm_set_user_lang_config(NULL);
23012332
cbm_userconfig_free(p->userconfig);
@@ -2458,7 +2489,7 @@ static int seal_staging_db(const char *staging_path) {
24582489
return rc;
24592490
}
24602491

2461-
static int export_after_publish(cbm_pipeline_t *p, const char *final_path, bool was_incremental) {
2492+
static int export_after_publish(cbm_pipeline_t *p, const char *final_path) {
24622493
if (p->persistence) {
24632494
CBM_PROF_START(t_art);
24642495
int rc = cbm_artifact_export(final_path, p->repo_path, p->project_name, CBM_ARTIFACT_BEST);
@@ -2469,7 +2500,7 @@ static int export_after_publish(cbm_pipeline_t *p, const char *final_path, bool
24692500
}
24702501
return rc;
24712502
}
2472-
if (was_incremental && p->repo_path && cbm_artifact_exists(p->repo_path)) {
2503+
if (p->repo_path && cbm_artifact_exists(p->repo_path)) {
24732504
(void)cbm_artifact_export(final_path, p->repo_path, p->project_name, CBM_ARTIFACT_FAST);
24742505
}
24752506
return 0;
@@ -2511,8 +2542,7 @@ int cbm_pipeline_run(cbm_pipeline_t *p) {
25112542
free(final_path);
25122543
return CBM_NOT_FOUND;
25132544
}
2514-
bool was_incremental = false;
2515-
int rc = cbm_pipeline_run_staged(p, &was_incremental);
2545+
int rc = cbm_pipeline_run_staged(p);
25162546
free(p->db_path);
25172547
p->db_path = configured_db_path;
25182548

@@ -2582,7 +2612,7 @@ int cbm_pipeline_run(cbm_pipeline_t *p) {
25822612
return CBM_PIPELINE_PERSIST_FAILED;
25832613
}
25842614

2585-
rc = export_after_publish(p, final_path, was_incremental);
2615+
rc = export_after_publish(p, final_path);
25862616
free(staging_path);
25872617
free(final_path);
25882618
return rc;

‎src/pipeline/pipeline_incremental.c‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ enum { INCR_RING_BUF = 4, INCR_RING_MASK = 3, INCR_TS_BUF = 24 };
1515
#include "pipeline/pipeline.h"
1616
#include <stdio.h>
1717
#include <time.h>
18-
#include "pipeline/artifact.h"
1918
#include "pipeline/lsp_surface.h"
2019
#include "pipeline/pass_lsp_cross.h"
2120
#include "sqlite3.h"
@@ -1468,7 +1467,7 @@ static int run_postpasses(cbm_pipeline_ctx_t *ctx, cbm_file_info_t *changed_file
14681467
* generation boundary as full indexing. */
14691468
static int dump_and_persist(cbm_gbuf_t *gbuf, const char *db_path, const char *project,
14701469
atomic_int *cancelled, const cbm_file_hash_t *manifest,
1471-
int manifest_count, const char *adr_content, const char *repo_path,
1470+
int manifest_count, const char *adr_content,
14721471
const cbm_coverage_row_t *cov, int cov_count,
14731472
const cbm_coverage_meta_t *meta_template,
14741473
const cbm_lsp_surface_row_t *surface_rows, int surface_row_count) {
@@ -1494,11 +1493,6 @@ static int dump_and_persist(cbm_gbuf_t *gbuf, const char *db_path, const char *p
14941493
if (rc != 0) {
14951494
return rc;
14961495
}
1497-
1498-
/* Auto-update artifact if one already exists (persistence was enabled previously) */
1499-
if (repo_path && cbm_artifact_exists(repo_path)) {
1500-
cbm_artifact_export(db_path, repo_path, project, CBM_ARTIFACT_FAST);
1501-
}
15021496
return 0;
15031497
}
15041498

@@ -2364,7 +2358,7 @@ static int run_closure_delta(cbm_pipeline_t *p, const char *db_path, const char
23642358

23652359
int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_file_info_t *files,
23662360
int file_count, const cbm_file_hash_t *baseline_manifest,
2367-
int baseline_count) {
2361+
int baseline_count, bool force_full_on_mismatch) {
23682362
struct timespec t0;
23692363
cbm_clock_gettime(CLOCK_MONOTONIC, &t0);
23702364
closure_plan_t closure_plan = {0};
@@ -2425,7 +2419,16 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
24252419
incr_test_set_last_route(CBM_INCREMENTAL_ROUTE_NOOP);
24262420
#endif
24272421
cbm_log_info("incremental.noop", "reason", "semantic_manifest_equal");
2428-
return cbm_pipeline_refresh_artifact(p, db_path);
2422+
return 0;
2423+
}
2424+
if (force_full_on_mismatch) {
2425+
cbm_store_free_file_hashes(stored, stored_count);
2426+
cbm_store_close(store);
2427+
#if defined(CBM_INCREMENTAL_TEST_API) && CBM_INCREMENTAL_TEST_API
2428+
incr_test_set_last_route(CBM_INCREMENTAL_ROUTE_FORCED_FULL);
2429+
#endif
2430+
cbm_log_info("incremental.force_full", "reason", "mode_downgrade_changed");
2431+
return CBM_PIPELINE_FORCE_FULL_REINDEX;
24292432
}
24302433
/* Manifest delta. Closure repair recomputes exactly the changed
24312434
* files plus the recorded consumers of any changed SURFACE; every
@@ -2850,9 +2853,9 @@ int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_fil
28502853
* re-parsed files have no codec output, and publishing a stale row
28512854
* would satisfy a future closure plan with yesterday's surface; an
28522855
* empty table just routes the next incremental to a full rebuild. */
2853-
int persist_rc = dump_and_persist(
2854-
existing, db_path, project, cbm_pipeline_cancelled_ptr(p), manifest, manifest_count,
2855-
saved_adr, cbm_pipeline_repo_path(p), cov, cov_n, &coverage_meta, NULL, 0);
2856+
int persist_rc =
2857+
dump_and_persist(existing, db_path, project, cbm_pipeline_cancelled_ptr(p), manifest,
2858+
manifest_count, saved_adr, cov, cov_n, &coverage_meta, NULL, 0);
28562859
cbm_pipeline_free_semantic_manifest(manifest, manifest_count);
28572860
free(saved_adr);
28582861
free(cov);

‎src/pipeline/pipeline_internal.h‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,7 @@ int cbm_scan_project_env_urls_excluded(const char *root_path, cbm_env_binding_t
657657
* files, merges into disk DB. Returns 0 on success. */
658658
int cbm_pipeline_run_incremental(cbm_pipeline_t *p, const char *db_path, cbm_file_info_t *files,
659659
int file_count, const cbm_file_hash_t *baseline_manifest,
660-
int baseline_count);
660+
int baseline_count, bool force_full_on_mismatch);
661661

662662
/* Exact semantic inputs for no-op/forced-full routing. The manifest contains
663663
* every discovered source plus repository controls actually consumed by
@@ -752,11 +752,6 @@ int cbm_delta_patch(cbm_store_t *store, const char *project, cbm_gbuf_t *gbuf, i
752752
const cbm_delta_saved_edge_t *snapshot, int snapshot_count);
753753
/* discard helper shared with the delta executor (unlink stage + sidecars). */
754754
void cbm_pipeline_discard_stage(const char *stage_path);
755-
/* The SQLite generation is authoritative. An explicitly requested artifact is
756-
* part of the caller-visible operation and its export error is returned;
757-
* automatic refresh of an already-existing artifact remains best-effort. */
758-
int cbm_pipeline_refresh_artifact(cbm_pipeline_t *p, const char *db_path);
759-
760755
/* Hand the pipeline the per-file LSP-surface rows serialized at the
761756
* collect_all_defs seam (the only moment the result cache is alive).
762757
* Takes ownership; dump_and_persist_hashes writes them into the staging

0 commit comments

Comments
 (0)