From 5f454dd33da8edb5b3e0fdfd254daacafacf0966 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Jul 2026 18:23:38 +0300 Subject: [PATCH] fix(artifact): put binary macro before merge=ours in generated .gitattributes gitattributes apply left to right and the binary macro expands to -diff -merge -text, so the generated line graph.db.zst merge=ours binary leaves the merge attribute UNSET (git check-attr merge reports "unset"), and concurrent artifact refreshes produce a binary merge conflict instead of auto-resolving -- the exact failure this file is generated to prevent. Reordering to graph.db.zst binary merge=ours makes check-attr report merge: ours while keeping -diff/-text intact. Verified on a real repository: before the reorder git check-attr merge -- .codebase-memory/graph.db.zst printed "unset"; after, "ours". Extends artifact_gitattributes_created to pin the ordering so the regression cannot return. Co-Authored-By: Claude Fable 5 Signed-off-by: Alex --- src/pipeline/artifact.c | 6 +++++- tests/test_artifact.c | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/pipeline/artifact.c b/src/pipeline/artifact.c index c7a8f451..2f1f1217 100644 --- a/src/pipeline/artifact.c +++ b/src/pipeline/artifact.c @@ -382,9 +382,13 @@ static void ensure_gitattributes(const char *repo_path) { } else { FILE *fp = fdopen(fd, "w"); if (fp) { + /* Order matters: attributes apply left to right and the `binary` + * macro expands to `-diff -merge -text`, so a trailing `binary` + * unsets `merge=ours` and the conflict prevention this file + * exists for never engages. The macro must come first. */ (void)fputs("# Auto-generated by codebase-memory-mcp\n" "# Prevent merge conflicts on compressed artifact\n" CBM_ARTIFACT_FILENAME - " merge=ours binary\n", + " binary merge=ours\n", fp); (void)fclose(fp); } else { diff --git a/tests/test_artifact.c b/tests/test_artifact.c index f87be801..ffe07e6a 100644 --- a/tests/test_artifact.c +++ b/tests/test_artifact.c @@ -242,6 +242,20 @@ TEST(artifact_gitattributes_created) { struct stat st; ASSERT_EQ(stat(ga, &st), 0); + /* Attribute ORDER is load-bearing: gitattributes apply left to right and + * the `binary` macro expands to `-diff -merge -text`, so a trailing + * `binary` unsets a preceding `merge=ours` (git check-attr merge reports + * "unset" and concurrent artifact refreshes produce binary conflicts + * instead of auto-resolving). The driver must come after the macro. */ + FILE *gaf = fopen(ga, "r"); + ASSERT_NOT_NULL(gaf); + char content[512] = {0}; + size_t rd = fread(content, 1, sizeof(content) - 1, gaf); + (void)fclose(gaf); + ASSERT_TRUE(rd > 0); + ASSERT_NOT_NULL(strstr(content, CBM_ARTIFACT_FILENAME " binary merge=ours")); + ASSERT_TRUE(strstr(content, "merge=ours binary") == NULL); + cleanup_dir(g_tmpdir); PASS(); }