| 2700 | } |
| 2701 | |
| 2702 | static int cov_rebuild_shadow_graph(cbm_store_t *s, const char *project) { |
| 2703 | char covproj[CBM_SZ_512]; |
| 2704 | cbm_store_coverage_shadow_project(covproj, sizeof(covproj), project); |
| 2705 | |
| 2706 | /* Fingerprint gate: rows first, so an unchanged failure set skips the |
| 2707 | * wipe entirely and the existing shadow stays byte-identical. */ |
| 2708 | cbm_coverage_row_t *fp_rows = NULL; |
| 2709 | int fp_count = 0; |
| 2710 | if (cbm_store_coverage_get(s, project, &fp_rows, &fp_count) != CBM_STORE_OK) { |
| 2711 | cbm_store_free_coverage(fp_rows, fp_count); |
| 2712 | return CBM_STORE_ERR; |
| 2713 | } |
| 2714 | char fp[CBM_SHA256_HEX_LEN + 1]; |
| 2715 | cov_failure_fingerprint(fp_rows, fp_count, fp); |
| 2716 | cbm_store_free_coverage(fp_rows, fp_count); |
| 2717 | char fp_key[CBM_SZ_512]; |
| 2718 | snprintf(fp_key, sizeof(fp_key), "coverage_shadow_fp:%s", project); |
| 2719 | char prev_fp[CBM_SHA256_HEX_LEN + 1] = {0}; |
| 2720 | { |
| 2721 | sqlite3_stmt *get = NULL; |
| 2722 | if (sqlite3_prepare_v2(s->db, "SELECT v FROM store_meta WHERE k = ?1;", CBM_NOT_FOUND, &get, |
| 2723 | NULL) == SQLITE_OK) { |
| 2724 | bind_text(get, SKIP_ONE, fp_key); |
| 2725 | if (sqlite3_step(get) == SQLITE_ROW) { |
| 2726 | const char *v = (const char *)sqlite3_column_text(get, 0); |
| 2727 | if (v) { |
| 2728 | snprintf(prev_fp, sizeof(prev_fp), "%s", v); |
| 2729 | } |
| 2730 | } |
| 2731 | sqlite3_finalize(get); |
| 2732 | } |
| 2733 | } |
| 2734 | if (prev_fp[0] && strcmp(prev_fp, fp) == 0) { |
| 2735 | return CBM_STORE_OK; |
| 2736 | } |
| 2737 | |
| 2738 | /* Wipe the previous view (edges first: no FK pragma guarantee). */ |
| 2739 | static const char *wipes[] = {"DELETE FROM edges WHERE project = ?1;", |
| 2740 | "DELETE FROM nodes WHERE project = ?1;"}; |
| 2741 | for (int w = 0; w < 2; w++) { |
| 2742 | sqlite3_stmt *del = NULL; |
| 2743 | if (sqlite3_prepare_v2(s->db, wipes[w], CBM_NOT_FOUND, &del, NULL) != SQLITE_OK) { |
| 2744 | store_set_error_sqlite(s, "coverage shadow wipe prepare"); |
| 2745 | return CBM_STORE_ERR; |
| 2746 | } |
| 2747 | bind_text(del, SKIP_ONE, covproj); |
| 2748 | int wipe_rc = sqlite3_step(del); |
| 2749 | sqlite3_finalize(del); |
| 2750 | if (wipe_rc != SQLITE_DONE) { |
| 2751 | store_set_error_sqlite(s, "coverage shadow wipe"); |
| 2752 | return CBM_STORE_ERR; |
| 2753 | } |
| 2754 | } |
| 2755 | |
| 2756 | cbm_coverage_row_t *rows = NULL; |
| 2757 | int count = 0; |
| 2758 | if (cbm_store_coverage_get(s, project, &rows, &count) != CBM_STORE_OK) { |
| 2759 | cbm_store_free_coverage(rows, count); |
no test coverage detected