Files
codebase-memory-mcp/src/git/git_context.c
T
Andrew Hundt 7a32d9a29f refactor(git): reuse portable existence checks
Replace existence-only stat() probes in git context resolution and watcher baseline initialization with cbm_file_exists().

This removes direct sys/stat dependencies from those files without changing behavior: both paths only needed to know whether the root path exists before attempting git snapshot/command handling.

Validation: make -f Makefile.cbm cbm; CBM_ONLY_SUITE=pipeline,tool_consolidation,mcp ./build/c/test-runner; scripts/check-source-safety.sh; git diff --check.
Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
2026-07-01 22:48:45 -04:00

292 lines
8.5 KiB
C

#include "git/git_context.h"
#include "git/git_command.h"
#include "foundation/compat.h"
#include "foundation/platform.h"
#include "foundation/str_util.h"
#include <ctype.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static bool path_is_absolute(const char *path) {
if (!path || !path[0]) {
return false;
}
if (path[0] == '/') {
return true;
}
#ifdef _WIN32
return isalpha((unsigned char)path[0]) && path[1] == ':';
#else
return false;
#endif
}
static char *join_root_relative(const char *root, const char *rel) {
if (!root || !root[0]) {
return cbm_strdup(rel);
}
int n = snprintf(NULL, 0, "%s/%s", root, rel);
if (n < 0) {
return NULL;
}
char *out = (char *)malloc((size_t)n + 1);
if (!out) {
return NULL;
}
snprintf(out, (size_t)n + 1, "%s/%s", root, rel);
return out;
}
static char *derive_canonical_root(const char *worktree_root, const char *git_common_dir) {
const char *src = git_common_dir && git_common_dir[0] ? git_common_dir : worktree_root;
if (!src) {
return cbm_strdup("");
}
char *root = path_is_absolute(src) ? cbm_strdup(src) : join_root_relative(worktree_root, src);
if (!root) {
return NULL;
}
size_t len = strlen(root);
while (len > 1 && (root[len - 1] == '/' || root[len - 1] == '\\')) {
root[--len] = '\0';
}
if (len >= 5 && strcmp(root + len - 5, "/.git") == 0) {
root[len - 5] = '\0';
}
#ifdef _WIN32
else if (len >= 5 && strcmp(root + len - 5, "\\.git") == 0) {
root[len - 5] = '\0';
}
#endif
return root;
}
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);
size_t len = strlen(src);
char *slug = (char *)malloc(len + 1);
if (!slug) {
return NULL;
}
size_t j = 0;
bool in_dash = false;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)src[i];
if (isalnum(c) || c == '-' || c == '_' || c == '.') {
if (j == 0 && c == '-') {
in_dash = true;
continue;
}
slug[j++] = (char)c;
in_dash = false;
} else if (j > 0 && !in_dash) {
slug[j++] = '-';
in_dash = true;
}
}
while (j > 0 && slug[j - 1] == '-') {
j--;
}
slug[j] = '\0';
if (slug[0] == '\0') {
free(slug);
return cbm_strdup(fallback);
}
return slug;
}
void cbm_git_context_free(cbm_git_context_t *ctx) {
if (!ctx) {
return;
}
free(ctx->input_path);
free(ctx->worktree_root);
free(ctx->git_dir);
free(ctx->git_common_dir);
free(ctx->canonical_root);
free(ctx->branch);
free(ctx->branch_slug);
free(ctx->head_sha);
free(ctx->base_sha);
memset(ctx, 0, sizeof(*ctx));
}
int cbm_git_context_resolve(const char *path, cbm_git_context_t *out) {
if (!out) {
return CBM_NOT_FOUND;
}
memset(out, 0, sizeof(*out));
if (!path || !path[0]) {
return CBM_NOT_FOUND;
}
out->input_path = cbm_strdup(path);
if (!out->input_path) {
return CBM_NOT_FOUND;
}
out->root_exists = cbm_file_exists(path);
if (!out->root_exists) {
return 0;
}
if (cbm_git_capture_first_line(path, "rev-parse --show-toplevel", &out->worktree_root) !=
0) {
out->is_git = false;
return 0;
}
out->is_git = true;
if (cbm_git_capture_first_line(path, "rev-parse --git-dir", &out->git_dir) != 0) {
out->git_dir = cbm_strdup("");
}
if (cbm_git_capture_first_line(path, "rev-parse --git-common-dir", &out->git_common_dir) !=
0) {
out->git_common_dir = cbm_strdup("");
}
if (cbm_git_capture_first_line(path, "rev-parse --verify HEAD", &out->head_sha) != 0) {
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");
out->is_detached = true;
}
out->is_worktree =
out->git_dir && out->git_common_dir && strcmp(out->git_dir, out->git_common_dir) != 0;
out->canonical_root = derive_canonical_root(out->worktree_root, out->git_common_dir);
out->branch_slug = slug_from_branch(out->branch, out->is_detached);
if (cbm_git_capture_first_line(path, "merge-base HEAD @{upstream}", &out->base_sha) != 0) {
out->base_sha = cbm_strdup("");
}
if (!out->git_dir || !out->git_common_dir || !out->head_sha || !out->branch ||
!out->canonical_root || !out->branch_slug || !out->base_sha) {
cbm_git_context_free(out);
return CBM_NOT_FOUND;
}
return 0;
}
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";
if (ctx) {
if (ctx->is_detached) {
slug = "detached";
} else if (ctx->is_git && ctx->branch_slug && ctx->branch_slug[0]) {
slug = ctx->branch_slug;
}
}
int n = snprintf(NULL, 0, "%s.__branch__.%s", project, slug);
if (n < 0) {
return NULL;
}
char *out = (char *)malloc((size_t)n + 1);
if (!out) {
return NULL;
}
snprintf(out, (size_t)n + 1, "%s.__branch__.%s", project, slug);
return out;
}
static bool append_fmt_checked(char *buf, int buf_size, int *off, const char *fmt, ...) {
if (!buf || !off || buf_size <= 0 || *off < 0 || *off >= buf_size) {
return false;
}
va_list ap;
va_start(ap, fmt);
int n = vsnprintf(buf + *off, (size_t)(buf_size - *off), fmt, ap);
va_end(ap);
if (n < 0 || n >= buf_size - *off) {
buf[buf_size - 1] = '\0';
return false;
}
*off += n;
return true;
}
static int json_escaped_len(const char *src) {
if (!src) {
return 0;
}
int len = 0;
for (int i = 0; src[i]; i++) {
unsigned char c = (unsigned char)src[i];
if (c == '"' || c == '\\' || c == '\n' || c == '\r' || c == '\t') {
len += 2;
} else if (c < 0x20) {
continue;
} else {
len++;
}
}
return len;
}
static bool json_append_bool(char *buf, int buf_size, int *off, const char *name, bool value,
bool comma) {
return append_fmt_checked(buf, buf_size, off, "\"%s\":%s%s", name, value ? "true" : "false",
comma ? "," : "");
}
static bool json_append_string(char *buf, int buf_size, int *off, const char *name,
const char *value, bool comma) {
int needed = json_escaped_len(value ? value : "");
char *escaped = malloc((size_t)needed + 1);
if (!escaped) {
return false;
}
int actual = cbm_json_escape(escaped, needed + 1, value ? value : "");
bool ok = actual == needed && append_fmt_checked(buf, buf_size, off, "\"%s\":\"%s\"%s", name,
escaped, comma ? "," : "");
free(escaped);
return ok;
}
int cbm_git_context_props_json(const cbm_git_context_t *ctx, char *buf, int buf_size) {
if (!ctx || !buf || buf_size <= 0) {
return 0;
}
int off = 0;
bool ok =
append_fmt_checked(buf, buf_size, &off, "{") &&
json_append_bool(buf, buf_size, &off, "is_git", ctx->is_git, true) &&
json_append_bool(buf, buf_size, &off, "is_worktree", ctx->is_worktree, true) &&
json_append_bool(buf, buf_size, &off, "is_detached", ctx->is_detached, true) &&
json_append_bool(buf, buf_size, &off, "root_exists", ctx->root_exists, true) &&
json_append_string(buf, buf_size, &off, "canonical_root", ctx->canonical_root, true) &&
json_append_string(buf, buf_size, &off, "worktree_root", ctx->worktree_root, true) &&
json_append_string(buf, buf_size, &off, "git_common_dir", ctx->git_common_dir, true) &&
json_append_string(buf, buf_size, &off, "branch", ctx->branch, true) &&
json_append_string(buf, buf_size, &off, "head_sha", ctx->head_sha, true) &&
json_append_string(buf, buf_size, &off, "base_sha", ctx->base_sha, false) &&
append_fmt_checked(buf, buf_size, &off, "}");
if (!ok) {
if (buf_size > 0) {
buf[0] = '\0';
}
return 0;
}
return off;
}