@@ -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. */
13721374static 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 ;
0 commit comments