Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/pipeline/artifact.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 14 additions & 0 deletions tests/test_artifact.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Loading