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(); }