| 364 | /* ── .gitattributes setup ────────────────────────────────────────── */ |
| 365 | |
| 366 | static void ensure_gitattributes(const char *repo_path) { |
| 367 | char ga_path[CBM_SZ_4K]; |
| 368 | artifact_path(ga_path, sizeof(ga_path), repo_path, ".gitattributes"); |
| 369 | |
| 370 | /* Atomic create-only-if-absent: O_EXCL closes the TOCTOU window |
| 371 | * between checking existence and writing. If the file exists, open |
| 372 | * fails with EEXIST and we leave it untouched. */ |
| 373 | int fd = open(ga_path, O_WRONLY | O_CREAT | O_EXCL, 0644); |
| 374 | if (fd < 0) { |
| 375 | if (errno != EEXIST) { |
| 376 | cbm_log_warn("artifact.gitattributes.open path=%s err=%s", ga_path, strerror(errno)); |
| 377 | } |
| 378 | /* fall through to merge driver setup either way */ |
| 379 | } else { |
| 380 | FILE *fp = fdopen(fd, "w"); |
| 381 | if (fp) { |
| 382 | /* Order matters: attributes apply left to right and the `binary` |
| 383 | * macro expands to `-diff -merge -text`, so a trailing `binary` |
| 384 | * unsets `merge=ours` and the conflict prevention this file |
| 385 | * exists for never engages. The macro must come first. */ |
| 386 | (void)fputs("# Auto-generated by codebase-memory-mcp\n" |
| 387 | "# Prevent merge conflicts on compressed artifact\n" CBM_ARTIFACT_FILENAME |
| 388 | " binary merge=ours\n", |
| 389 | fp); |
| 390 | (void)fclose(fp); |
| 391 | } else { |
| 392 | (void)close(fd); |
| 393 | } |
| 394 | } |
| 395 | |
| 396 | /* Best-effort: configure merge driver */ |
| 397 | if (!cbm_artifact_repo_path_is_shell_safe(repo_path)) { |
| 398 | return; |
| 399 | } |
| 400 | char cmd[CBM_SZ_1K]; |
| 401 | int n = snprintf(cmd, sizeof(cmd), |
| 402 | "git -C \"%s\" config merge.ours.driver true 2>" ARTIFACT_NULL_DEV, repo_path); |
| 403 | if (n < 0 || (size_t)n >= sizeof(cmd)) { |
| 404 | return; /* truncated command → skip (parity with git_context.c) */ |
| 405 | } |
| 406 | FILE *p = cbm_popen(cmd, "r"); |
| 407 | if (p) { |
| 408 | (void)cbm_pclose(p); |
| 409 | } |
| 410 | } |
| 411 | |
| 412 | /* ── Index stripping ─────────────────────────────────────────────── */ |
| 413 |
no test coverage detected