GET /api/repo-info?project=NAME → { root_path, branch, remote_url, web_base, * blob_base }. blob_base is " /blob/ " ready for the frontend to * append "/ #L -L ". remote_url is credential-stripped; * fields are empty strings when unknown (e.g. no git remote). */
| 331 | * append "/<file_path>#L<start>-L<end>". remote_url is credential-stripped; |
| 332 | * fields are empty strings when unknown (e.g. no git remote). */ |
| 333 | static void handle_repo_info(cbm_http_conn_t *c, const cbm_http_req_t *req) { |
| 334 | char project[256] = {0}; |
| 335 | if (!cbm_http_query_param(req->query, "project", project, (int)sizeof(project)) || |
| 336 | project[0] == '\0') { |
| 337 | cbm_http_replyf(c, 400, g_cors_json, "{\"error\":\"missing project parameter\"}"); |
| 338 | return; |
| 339 | } |
| 340 | |
| 341 | char db_path[1024]; |
| 342 | db_path_for_project(project, db_path, sizeof(db_path)); |
| 343 | if (db_path[0] == '\0' || !cbm_file_exists(db_path)) { |
| 344 | cbm_http_replyf(c, 404, g_cors_json, "{\"error\":\"project not found\"}"); |
| 345 | return; |
| 346 | } |
| 347 | cbm_store_t *store = cbm_store_open_path_query(db_path); |
| 348 | if (!store) { |
| 349 | cbm_http_replyf(c, 500, g_cors_json, "{\"error\":\"cannot open store\"}"); |
| 350 | return; |
| 351 | } |
| 352 | |
| 353 | char root_path[1024] = {0}; |
| 354 | cbm_project_t proj; |
| 355 | memset(&proj, 0, sizeof(proj)); |
| 356 | if (cbm_store_get_project(store, project, &proj) == CBM_STORE_OK && proj.root_path) { |
| 357 | snprintf(root_path, sizeof(root_path), "%s", proj.root_path); |
| 358 | } |
| 359 | cbm_project_free_fields(&proj); |
| 360 | cbm_store_close(store); |
| 361 | |
| 362 | char branch[256] = {0}; |
| 363 | if (root_path[0]) { |
| 364 | cbm_git_context_t gctx; |
| 365 | memset(&gctx, 0, sizeof(gctx)); |
| 366 | if (cbm_git_context_resolve(root_path, &gctx) == 0 && gctx.branch) { |
| 367 | snprintf(branch, sizeof(branch), "%s", gctx.branch); |
| 368 | } |
| 369 | cbm_git_context_free(&gctx); |
| 370 | } |
| 371 | |
| 372 | char *remote = root_path[0] ? git_origin_remote_url(root_path) : NULL; |
| 373 | char *remote_safe = cbm_ui_git_strip_credentials(remote); /* never echo secrets */ |
| 374 | char *web_base = cbm_ui_git_web_base(remote); |
| 375 | |
| 376 | char blob_base[1152] = {0}; |
| 377 | if (web_base && web_base[0] && branch[0]) { |
| 378 | snprintf(blob_base, sizeof(blob_base), "%s/blob/%s", web_base, branch); |
| 379 | } |
| 380 | |
| 381 | /* JSON-escape the free-form fields. */ |
| 382 | char esc_root[2048], esc_branch[512], esc_remote[2048], esc_web[2048], esc_blob[2304]; |
| 383 | cbm_json_escape(esc_root, (int)sizeof(esc_root), root_path); |
| 384 | cbm_json_escape(esc_branch, (int)sizeof(esc_branch), branch); |
| 385 | cbm_json_escape(esc_remote, (int)sizeof(esc_remote), remote_safe ? remote_safe : ""); |
| 386 | cbm_json_escape(esc_web, (int)sizeof(esc_web), web_base ? web_base : ""); |
| 387 | cbm_json_escape(esc_blob, (int)sizeof(esc_blob), blob_base); |
| 388 | |
| 389 | cbm_http_replyf(c, 200, g_cors_json, |
| 390 | "{\"root_path\":\"%s\",\"branch\":\"%s\",\"remote_url\":\"%s\"," |
no test coverage detected