mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-12 09:47:20 +08:00
perf(mcp): resolve list_projects branches with one Git child
Previous behavior: src/mcp/mcp.c:3895 called cbm_git_context_resolve for the single branch field. Attached repositories could execute seven Git commands per project, and a rejected status-based replacement took 3,380.410 ms for the isolated 50-project page. Add src/git/git_context.c:115 resolve_current_branch as the single attached/detached/unborn implementation shared by cbm_git_context_resolve and cbm_git_current_branch. cbm_git_run_first_line_buf drains stdout and reaps symbolic-ref once; cbm_pclose_exit_code normalizes POSIX wait status and Windows GetExitCodeProcess results. Exit 0 returns attached/unborn names, exit 1 returns DETACHED, and non-Git errors remain absent. Route CBM_ONLY_SUITE=git_context in tests/test_main.c:386 so focused runs execute four tests instead of traversing high-memory fixtures. tests/test_git_context.c:252 covers attached, detached, unborn, non-Git, full-context parity, heap cleanup, and portable command setup; tests/test_security.c:621 covers normalized exit 7. Verification: Git context ASAN/UBSan 4/4; normalized-exit canary 1/1; MCP ASAN/UBSan 224/224; scripts/check-source-safety.sh passed; staged diff check passed. The -O2 binary returned the isolated default page in 773.848 ms and explicit full inventory in 5,023.220 ms, preserved 41/53 branch fields including the unborn main branch, and reaped both server processes and all reader threads. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
This commit is contained in:
@@ -110,6 +110,47 @@ int cbm_git_capture_first_line(const char *repo_path, const char *git_args, char
|
||||
return *out ? 0 : CBM_NOT_FOUND;
|
||||
}
|
||||
|
||||
int cbm_git_run_first_line_buf(const char *repo_path, const char *git_args,
|
||||
char *out, size_t out_size, int *out_exit_code) {
|
||||
if (!out || out_size == 0 || !out_exit_code) {
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
out[0] = '\0';
|
||||
*out_exit_code = CBM_NOT_FOUND;
|
||||
char cmd[CBM_GIT_CMD_BUFSZ];
|
||||
if (!cbm_git_format_command(cmd, sizeof(cmd), repo_path, git_args)) {
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
FILE *fp = cbm_popen(cmd, "r");
|
||||
if (!fp) {
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
|
||||
char line[CBM_GIT_OUTPUT_BUFSZ];
|
||||
bool got_line = fgets(line, (int)sizeof(line), fp) != NULL;
|
||||
size_t line_len = got_line ? strlen(line) : 0;
|
||||
bool truncated = got_line && line_len > 0 && line[line_len - 1] != '\n' && !feof(fp);
|
||||
bool output_fits = true;
|
||||
if (got_line) {
|
||||
git_trim_newlines(line);
|
||||
size_t value_len = strlen(line);
|
||||
if (value_len >= out_size) {
|
||||
output_fits = false;
|
||||
} else {
|
||||
memcpy(out, line, value_len + 1);
|
||||
}
|
||||
}
|
||||
char drain[CBM_SZ_128];
|
||||
while (fgets(drain, (int)sizeof(drain), fp)) {
|
||||
}
|
||||
*out_exit_code = cbm_pclose_exit_code(fp);
|
||||
if (truncated || !output_fits) {
|
||||
out[0] = '\0';
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cbm_git_drain_command(const char *repo_path, const char *git_args) {
|
||||
char cmd[CBM_GIT_CMD_BUFSZ];
|
||||
if (!cbm_git_format_command(cmd, sizeof(cmd), repo_path, git_args)) {
|
||||
|
||||
@@ -20,6 +20,8 @@ bool cbm_git_format_status_command(char *cmd, size_t cmd_size, const char *repo_
|
||||
int cbm_git_capture_first_line_buf(const char *repo_path, const char *git_args,
|
||||
char *out, size_t out_size);
|
||||
int cbm_git_capture_first_line(const char *repo_path, const char *git_args, char **out);
|
||||
int cbm_git_run_first_line_buf(const char *repo_path, const char *git_args,
|
||||
char *out, size_t out_size, int *out_exit_code);
|
||||
int cbm_git_drain_command(const char *repo_path, const char *git_args);
|
||||
|
||||
#endif
|
||||
|
||||
+30
-3
@@ -112,6 +112,28 @@ static char *derive_canonical_root(const char *input_path, const char *worktree_
|
||||
return root;
|
||||
}
|
||||
|
||||
static int resolve_current_branch(const char *path, char **out_branch) {
|
||||
if (!out_branch) {
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
*out_branch = NULL;
|
||||
if (!path || !path[0]) {
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
char branch[CBM_GIT_OUTPUT_BUFSZ];
|
||||
int exit_code = CBM_NOT_FOUND;
|
||||
if (cbm_git_run_first_line_buf(path, "symbolic-ref --quiet --short HEAD", branch,
|
||||
sizeof(branch), &exit_code) != 0) {
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
const char *resolved = exit_code == 0 && branch[0] ? branch : exit_code == 1 ? "DETACHED" : NULL;
|
||||
if (!resolved) {
|
||||
return CBM_NOT_FOUND;
|
||||
}
|
||||
*out_branch = cbm_strdup(resolved);
|
||||
return *out_branch ? 0 : CBM_NOT_FOUND;
|
||||
}
|
||||
|
||||
static char *slug_from_branch(const char *branch, bool detached) {
|
||||
const char *fallback = detached ? "detached" : "working-tree";
|
||||
const char *src = detached ? fallback : (branch && branch[0] ? branch : fallback);
|
||||
@@ -203,9 +225,10 @@ int cbm_git_context_resolve(const char *path, cbm_git_context_t *out) {
|
||||
out->head_sha = cbm_strdup("");
|
||||
}
|
||||
|
||||
if (cbm_git_capture_first_line(path, "symbolic-ref --quiet --short HEAD", &out->branch) !=
|
||||
0) {
|
||||
out->branch = cbm_strdup("DETACHED");
|
||||
if (resolve_current_branch(path, &out->branch) != 0) {
|
||||
out->branch = NULL;
|
||||
}
|
||||
if (out->branch && strcmp(out->branch, "DETACHED") == 0) {
|
||||
out->is_detached = true;
|
||||
}
|
||||
|
||||
@@ -233,6 +256,10 @@ int cbm_git_context_resolve(const char *path, cbm_git_context_t *out) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cbm_git_current_branch(const char *path, char **out_branch) {
|
||||
return resolve_current_branch(path, out_branch);
|
||||
}
|
||||
|
||||
char *cbm_git_context_branch_qn(const char *project_name, const cbm_git_context_t *ctx) {
|
||||
const char *project = project_name && project_name[0] ? project_name : "project";
|
||||
const char *slug = "working-tree";
|
||||
|
||||
@@ -21,6 +21,9 @@ typedef struct {
|
||||
|
||||
int cbm_git_context_resolve(const char *path, cbm_git_context_t *out);
|
||||
void cbm_git_context_free(cbm_git_context_t *ctx);
|
||||
/* Resolve only the current branch for branch-only callers. Returns a heap
|
||||
* string (including "DETACHED") through out_branch on success. */
|
||||
int cbm_git_current_branch(const char *path, char **out_branch);
|
||||
char *cbm_git_context_branch_qn(const char *project_name, const cbm_git_context_t *ctx);
|
||||
int cbm_git_context_props_json(const cbm_git_context_t *ctx, char *buf, int buf_size);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user