Open a .db file briefly, collect node/edge counts and root_path, * then append a JSON entry to arr. */
| 2468 | /* Open a .db file briefly, collect node/edge counts and root_path, |
| 2469 | * then append a JSON entry to arr. */ |
| 2470 | static void build_project_json_entry(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *dir_path, |
| 2471 | const char *name, size_t name_len, int64_t size_bytes, |
| 2472 | bool metadata_only) { |
| 2473 | (void)name_len; |
| 2474 | |
| 2475 | char full_path[CBM_SZ_2K]; |
| 2476 | snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, name); |
| 2477 | |
| 2478 | /* #704: key on the db's INTERNAL project name, not its filename. Node/edge |
| 2479 | * rows are tagged with the internal name, so a drifted filename (copied or |
| 2480 | * renamed db, legacy '.'-vs-'-' username twin) would otherwise report 0 |
| 2481 | * nodes/edges and be unresolvable. Skip ghost/empty/corrupt dbs entirely so |
| 2482 | * they don't appear as resolvable projects. */ |
| 2483 | char project_name[CBM_SZ_1K]; |
| 2484 | cbm_store_t *pstore = NULL; |
| 2485 | if (!db_internal_project_name(full_path, project_name, sizeof(project_name), &pstore)) { |
| 2486 | return; /* ghost / unreadable — not a resolvable project */ |
| 2487 | } |
| 2488 | |
| 2489 | int nodes = 0; |
| 2490 | int edges = 0; |
| 2491 | if (!metadata_only) { |
| 2492 | nodes = cbm_store_count_nodes(pstore, project_name); |
| 2493 | edges = cbm_store_count_edges(pstore, project_name); |
| 2494 | } |
| 2495 | char root_path_buf[CBM_SZ_1K] = ""; |
| 2496 | cbm_project_t proj = {0}; |
| 2497 | if (cbm_store_get_project(pstore, project_name, &proj) == CBM_STORE_OK) { |
| 2498 | if (proj.root_path) { |
| 2499 | snprintf(root_path_buf, sizeof(root_path_buf), "%s", proj.root_path); |
| 2500 | } |
| 2501 | cbm_project_free_fields(&proj); |
| 2502 | } |
| 2503 | cbm_store_close(pstore); |
| 2504 | |
| 2505 | yyjson_mut_val *p = yyjson_mut_obj(doc); |
| 2506 | yyjson_mut_obj_add_strcpy(doc, p, "name", project_name); |
| 2507 | yyjson_mut_obj_add_strcpy(doc, p, "root_path", root_path_buf); |
| 2508 | /* Listing stays lean: only the branch (the one git fact that |
| 2509 | * disambiguates same-repo projects). The 12-field git block — mostly |
| 2510 | * null for non-git roots — cost ~10KB across a full cache and is one |
| 2511 | * index_status call away for the project you actually care about. */ |
| 2512 | if (!metadata_only && root_path_buf[0]) { |
| 2513 | cbm_git_context_t gctx = {0}; |
| 2514 | (void)cbm_git_context_resolve(root_path_buf, &gctx); |
| 2515 | if (gctx.is_git && gctx.branch) { |
| 2516 | yyjson_mut_obj_add_strcpy(doc, p, "branch", gctx.branch); |
| 2517 | } |
| 2518 | cbm_git_context_free(&gctx); |
| 2519 | } |
| 2520 | if (!metadata_only) { |
| 2521 | yyjson_mut_obj_add_int(doc, p, "nodes", nodes); |
| 2522 | yyjson_mut_obj_add_int(doc, p, "edges", edges); |
| 2523 | yyjson_mut_obj_add_int(doc, p, "size_bytes", size_bytes); |
| 2524 | } |
| 2525 | yyjson_mut_arr_add_val(arr, p); |
| 2526 | } |
| 2527 |
no test coverage detected