Is git merge=union safe? 11 merges, 10 silent, 5 wrong
We ran merge=union through 11 merges in plain git. 10 exited 0 with no warning, and 5 of those left a file that was wrong. Here is which shapes break.
Somebody on your team is tired of the changelog conflicting on every merge, and the fix in every answer is one line:
CHANGELOG.md merge=unionFFmpeg added the same line for its Changelog in March 2025. Git's own
manual is less relaxed. It says union "tends to leave the added lines in the
resulting file in random order" and to not use it "if you do not understand the
implications". It does not say what the implications are.
So we tested them. We built throwaway repositories with plain git 2.39.5, gave
two branches a change each, and merged them: 12 runs, 11 of them with
merge=union. The script and its full output are in this site's repository.
We ran it twice and got identical output.
10 of the 11 union merges exited 0, printed no warning and left no markers. 5 of those 10 left a file that was wrong. The command's output is the same in both groups. The only way to tell them apart is to read the file.
What union does
A normal merge stops when both sides changed the same lines. Union does the same three-way merge and, where git would have written conflict markers, it writes our lines followed by their lines. That is all of it. It does not know what a line means, it does not remove anything, and it never reports a conflict on a text file.
Everything below follows from that one rule.
Where it does what you want
Both sides append a different line (cases 1, 2 and 2r). Without the attribute this is the classic conflict: exit 1, markers. With union, exit 0 and both lines kept. That is the changelog case and it works.
One caveat, and it is the "random order" in the manual. Merge b into a and you
get two-a, two-b. Merge a into b and you get two-b, two-a. The order comes
from which branch you are on. Two clones that integrate in different directions
hold the same lines in a different order. For a changelog that is cosmetic. For
anything read top to bottom as a history, it is not.
Both sides append the identical line (cases 3 and 4). It is kept once. Git treats the same change on both sides as agreement, not as a conflict.
Rebase (case 9). The attribute applies to git rebase as well, exit 0, no
markers.
Binary files (case 10). Git 2.39.5 refused, as it should: "Cannot merge binary files", conflict, exit 1.
Where it is silently wrong
The same line at two positions is kept twice (case 4b). Side a appends
only-a, shared, side b appends shared, only-b. The result has shared
twice. Case 4 kept it once. The difference is only where the line sat inside
each append.
A field with two values becomes two fields (case 5). A task file, one side
sets status: Done, the other status: Blocked. The merged file holds both
lines. Nothing failed. The next reader of that file gets whichever line their
parser happens to take, and has no way to tell that two people disagreed.
A deleted line comes back (case 6). One side deletes remove-me, the other
appends a line right after it. Union keeps the other side's lines in that
region, and they include remove-me. The deletion is undone without a word.
Wikimedia described this exact failure in 2018, in the task where they decided
not to use union for release notes.
JSON stops being JSON (case 7). An array, one object appended on each side.
The merged file has both objects and no comma between them. json.load rejects
it. Exit 0.
JSONL keeps both versions of a record (case 8). Every line is a whole record,
which looks like union's ideal format. But edit the same record on both sides
and you get two lines with the same id, done and blocked. The file still
parses. That makes it worse than case 7, because no parser will warn you.
The hosting side
Union is honored locally, by your git. Whether a pull request on a host honors it is a separate question. We did not test any host, so this is what they report:
- GitLab: issue
gitlab-org/gitlab#18830states that GitLab readsunionandbinaryfrom.gitattributes. Custom drivers are not used in merge requests - Bitbucket Data Center:
BSERV-19161(February 2024) reports that with git above 2.34.0 the union driver fails to work and the pull request shows conflicts. It is closed as a duplicate of a request for merge-driver support
So a merge that is clean on your laptop can show a conflict on the server, and the reverse.
So, is it safe?
It is safe when all of these hold:
- the file is only ever appended to. No line is edited or deleted, on any branch
- every line is complete on its own, with no commas, brackets or indentation that depends on a neighbor
- order between branches does not matter, or is restored on read by sorting on something stored in the line, like a timestamp or an id
- a duplicated line is harmless, or the reader deduplicates
A changelog meets all four, mostly. A task file with a status: line meets none
of them. JSON fails the second. JSONL passes the second and fails the first the
moment a record is edited in place. If your format edits records in place, union
does not fix the conflict. It turns it from a loud failure into a quiet one.
If the file does qualify, read the merge result in the commit after it, which is what the manual is asking for. Union's exit code says nothing about whether the file is right.
What this does not cover
- One machine and one git version: 2.39.5, Apple's build, with the default
ortstrategy. We did not test others - One or two lines per branch. Real files are bigger. The shapes are the same, but we have not counted how often each one happens in a real history
- No hosted merge was run. The section on hosting is other people's reports
- We did not test
git merge-file --union, criss-cross merges, ordiff3conflict style
The script and both runs are in this site's repository under
docs/search-log/bench/. kadence writes one file per event and never
edits one, so it does not need union or any other driver. Why task files
conflict measures how often task files
like the one in case 5 conflict in real repositories.
Where the numbers come from
- Bench run 2026-10-09 with git 2.39.5 (Apple Git-154), run twice with identical output: 12 merges or rebases in throwaway repositories, 1 control without the attribute and 11 with `merge=union`. Script and full output in this site's repository: docs/search-log/bench/2026-10-09-union-merge.sh and .txt
- Bench, the 11 union cases: 10 exited 0 with no file left conflicted and no markers (cases 2, 2r, 3, 4, 4b, 5, 6, 7, 8, 9); 1 conflicted (case 10, a binary file). Of the 10 clean ones, 5 left a file that is wrong for its format (cases 4b, 5, 6, 7, 8)
- Bench case 1, the control: both branches append one different line at the end, no attribute; exit 1, CONFLICT (content), markers in the file
- Bench cases 2 and 2r: the same two appends with merge=union; merging b into a gives two-a then two-b, merging a into b gives two-b then two-a; both exit 0
- Bench cases 3, 4 and 4b: an identical appended line is kept once (3, 4); when the identical line sits at a different position in each side's append it is kept twice (4b, `grep -cx shared` prints 2)
- Bench case 5: one task file, status changed to Done on a and Blocked on b; exit 0, the merged file holds both `status: Done` and `status: Blocked`
- Bench case 6: a deletes the line `remove-me`, b keeps it and appends `added-b` after it; exit 0, `remove-me` is back in the merged file
- Bench case 7: a JSON array, one object appended on each side; exit 0, the merged file has no comma between the two new objects and Python's json.load rejects it
- Bench case 8: a JSONL file, the record T-1 edited to done on a and to blocked on b; exit 0, two lines with id T-1
- Bench case 9: `git rebase` of b onto a with merge=union exits 0 with both lines and no markers, so the attribute applies to rebase as well as merge
- Bench case 10: a binary file under merge=union; git 2.39.5 printed `warning: Cannot merge binary files` and CONFLICT (content), exit 1
- gitattributes(5) as installed with git 2.39.5, read with `man gitattributes` on 2026-10-09: union 'tends to leave the added lines in the resulting file in random order and the user should verify the result. Do not use this if you do not understand the implications.'
- Wikimedia Phabricator T201514, fetched 2026-10-09: created 2018-08-08, closed as a duplicate the same day; says union 'will re-add the removed line' and that conflicting changes that are not line-based 'will result in duplicate lines', 'without any merge conflict being reported'
- FFmpeg commit 'gitattributes: End merge conflicts in Changelog', Michael Niedermayer, 2025-03-14, ffmpeg-cvslog archive fetched 2026-10-09: adds the line `Changelog merge=union`
- Atlassian BSERV-19161, fetched 2026-10-09: created 2024-02-14, closed as a duplicate of BSERV-4566; reports that with git above 2.34.0 'the union merge driver fails to work in Bitbucket and the PR shows merge conflicts'
- gitlab-org/gitlab#18830, fetched 2026-10-09: states GitLab reads 'union' and 'binary' from .gitattributes, and that custom merge drivers are not used during merge requests
- Probe run 2026-10-09: `is git merge=union safe` returned pkg.go.dev, a Gitea compare page, two git mailing-list threads, two GitLab issues, a git commit mirror, a GitLab merge request and git's own t6026 test script; no page that tests the cases