A generated CHANGELOG conflicts on every merge in Git. How can .gitattributes fix that?
answer
- Per-path merge behaviour, not a global strategy
- Three drivers ship built in
- One of them keeps both sides' lines
- Custom drivers are defined in unversioned config
basics
~20 sAssign the built-in union merge driver in Git with a line such as CHANGELOG.md merge=union. Git then keeps lines from both sides instead of writing conflict markers, which suits append-only, order-insensitive text and nothing else.
solid answer
~50 sGive the path a merge driver in `.gitattributes`. The quickest fix is `CHANGELOG.md merge=union`: `union` is one of Git's three built-in drivers, alongside `text` (the default three-way merge) and `binary`, and it runs the usual three-way merge but keeps the lines from **both** sides instead of emitting conflict markers. That is right only for append-only, line-oriented, order-insensitive content, because it will happily duplicate entries and can produce output that is syntactically invalid for structured formats such as JSON or YAML. For anything more complex, define your own driver in config with `merge.<name>.driver = <command> %O %A %B`, where `%O` is the merge base, `%A` is your version and the file the command must write the result into, and `%B` is the other side; a non-zero exit reports a conflict. Remember the driver command lives in unversioned config, so teammates without it silently fall back to the default text merge.
code
gitattributes · 3 linesCHANGELOG.md merge=union
AUTHORS merge=union
config/env.local merge=keepoursgo deeper
Know that Git can be told how to merge particular files through the attributes file, and that a built-in option exists which keeps both sides' lines rather than producing conflict markers.
Name the three built-in drivers and describe what union does to overlapping hunks. Be able to state the file shapes it suits, namely append-only line-oriented text, and why structured formats are excluded.
Show the operational tradeoffs: union succeeds silently so mistakes surface downstream, and a custom driver depends on per-machine config that teammates may not have. Know the placeholders a driver command receives and what its exit status means.
Own the question one level up: whether the artifact should be committed at all, or split into per-change fragments assembled at release time, so the repeated conflict disappears instead of being resolved automatically in a way nobody reviews.
## Why the file conflicts every time A changelog, a version manifest or a generated index conflicts constantly because every branch appends to the same region, usually the top of the file. The three-way merge sees two different insertions at the same position relative to the merge base and cannot decide the order, so it writes conflict markers. The content is not really in conflict; the format simply gives the merge algorithm no way to know that both additions are wanted. ## The built-in drivers Git provides three merge drivers you can name from an attribute without configuring anything: - **`text`** is the default line-level three-way merge, producing conflict markers when hunks overlap. - **`binary`** performs no content merge: it takes the current branch's version as tentative and declares a conflict. - **`union`** performs the three-way merge but, where the sides disagree, keeps the lines from both, in order, with no markers. `merge=union` is therefore a one-line fix for exactly the append-only case, and it needs no per-machine setup, which is what makes it safe to commit. ## Where union is wrong Union resolves by concatenation, and it does not understand the file. Consequences to be honest about in an interview: - Duplicates are kept when both sides added the same entry independently. - Ordering is whatever the merge produces, so a file whose semantics depend on order can end up subtly wrong. - Structured formats break. Two sides adding a key each can produce a document with a duplicated block or an unbalanced structure, and Git will report success. Never put `merge=union` on JSON, YAML or source code. - Because the merge succeeds, nobody is prompted to check the result; the error surfaces later, in a build or at runtime. Good candidates are flat append-only lists: changelog fragments, an authors file, a translation catalogue that is regenerated anyway. ## Custom drivers When the resolution needs domain knowledge, define your own driver. In config, name it under a `merge.<name>` section, give it a human-readable `name` and a `driver` command line. Git substitutes `%O` for a temporary file holding the merge-base version, `%A` for the current branch's version, `%B` for the other side, plus `%L` for the conflict marker size and `%P` for the path. The command writes the merged result into the file named by `%A` and exits zero for a clean merge or non-zero to report a conflict. Then assign it in `.gitattributes` with `<pattern> merge=<name>`. The classic trivial case is a keep-ours driver, configured with a `driver` of `true`: it leaves `%A` untouched and exits zero, so the current branch's version always wins for that path. That is useful for a per-environment file that must never take the other branch's content. ## The distribution problem `.gitattributes` is committed, so the assignment reaches everyone. The `merge.<name>.driver` command lives in a config file, which is not. A colleague who has not configured the driver does not get an error tuned to their situation; the merge proceeds with default behaviour, and the results differ from yours. Any custom driver therefore needs an onboarding step, a setup script, or a documented `git config` command, and it is worth preferring the built-ins when they suffice precisely because they need none of that. ## Better than any driver Step back before configuring anything: the strongest fix for a chronically conflicting generated file is to stop committing it, generating it at build or release time instead. Where it must be committed, splitting it into per-change fragment files that are combined at release time removes the shared append point altogether, and no merge driver is needed. Reach for `merge=union` when the file must stay whole and is genuinely append-only; reach for a custom driver only when a real domain rule justifies the setup cost.
- Why is merge=union dangerous on a JSON or YAML file in Git?Union resolves by keeping the lines from both sides, with no understanding of syntax. Two branches adding a key each can produce a duplicated block or an unbalanced document, and because the merge reports success, nobody is prompted to inspect it. The breakage then surfaces in a build or at runtime, far from the merge that caused it. Structured formats need either the default text merge or a format-aware custom driver.
- What does a custom merge driver command receive, and how does it signal a conflict?Git substitutes placeholders on the configured command line: `%O` for the merge-base version, `%A` for the current branch's version, `%B` for the other side, plus `%L` for marker size and `%P` for the path. The command writes the merged content into the file named by `%A`. Exiting zero means a clean merge; a non-zero exit tells Git the path is conflicted and leaves it for manual resolution.
Union merging is a photocopier that staples both submissions together. Perfect for a sign-up sheet, disastrous for a contract.
saying these in an interview costs you the question
- Applies union merging to JSON, YAML or source files
- Thinks merge=union is the same as a merge strategy flag
- Assumes the custom driver ships with the repository
- Expects union to deduplicate identical entries
- Never questions whether the file should be committed at all