| name | configure-gitattributes |
| description | Configure file merge/diff attributes. |
| user-invocable | true |
| allowed-tools | ["Bash","Read","Edit","Write"] |
configure-gitattributes — pick the right merge/diff behavior per file
.gitattributes fixes a class of recurring annoyances at the git-mechanics
level instead of by hand each time: a changelog conflict resolved by an actual
human weighing two entries when git could have just kept both; a generated
directory that shows up in git diff --stat and language-detection stats as
if it were hand-written source; a shell script that silently gets CRLF line
endings and breaks bash -n. Reach for this skill any time a pattern of
conflict or noise traces back to how git treats one class of file, not just
once to fix the immediate instance.
When this fires
- "configure gitattributes", "set up .gitattributes", "fix line endings"
- "stop CHANGELOG merge conflicts", "auto-resolve NEWS.md conflicts"
- "mark this as generated" / "exclude this from language stats"
- After resolving the same kind of merge conflict on the same file more than
once — that repetition is the signal this skill exists for.
Procedure
1. Read the existing file
cat .gitattributes 2>/dev/null || echo "no .gitattributes yet"
Append to it; don't overwrite existing lines you don't understand — ask if a
line's purpose isn't obvious from context (a repo-specific export-ignore, a
custom diff driver) rather than deleting it.
2. Match the situation to a pattern
| Situation | Line | Why |
|---|
A running-log file where two branches each append their own entry and both should survive a merge (CHANGELOG.md ## Unreleased section, NEWS.md) | CHANGELOG.md merge=union | Git ships a built-in union low-level merge driver — no merge.<name>.driver config needed. On conflict it takes all lines from both sides instead of conflict-marking, in original order — it does not de-duplicate, so if both sides independently add the exact same single line it appears twice in the result. Standard in tidyverse-style R packages (NEWS.md merge=union) and widely used for CHANGELOG.md. Caveat: union merge has no understanding of section headers or ordering — it can leave two entries under the wrong heading, or a heading duplicated, if the sides diverged enough. Skim the merged file before pushing; it beats a conflict marker but isn't infallible. |
A file made of independent, multi-line blocks rather than single lines, where two branches each add a distinct block (BibTeX references.bib, one entry per block) | references.bib merge=union | Same built-in union driver as above, but with a sharper, opposite-direction caveat: when two branches each insert a whole new block at the same point in the file, and those blocks share an identical boundary line (e.g. the closing } every BibTeX entry ends with), the underlying line-based diff can misalign the two insertions and silently drop one copy of that shared line instead of duplicating it — producing corrupted content (mismatched braces) with no conflict raised. This is the opposite failure mode from the single-line case above (loss, not duplication) and only triggers when both insertions land at the same point in the file; insertions at genuinely different locations merge cleanly. Confirmed by local merge simulation on d-morrison/rme#989 / ucdavis/epi204#363. A render/validity CI check is a recommended backstop, not a guarantee — without one, silent corruption can reach main undetected. |
Shell scripts that could pick up CRLF (e.g. edited on Windows, or a tool re-writes them) and then fail bash -n with a cryptic EOF error | *.sh text eol=lf | Forces LF on checkout/commit regardless of the committer's platform. See , section , for the failure mode this prevents. |
3. Apply and verify
For a merge-driver line, sanity-check it actually changed behavior rather than
trusting the .gitattributes edit alone:
git add .gitattributes && git commit -m "..."
git fetch origin main -q
git merge origin/main
If a merge that used to need manual resolution now completes with
Auto-merging <file> and no conflict markers, the driver is working.
If the PR's rationale for keeping a risky merge=union attribute leans on
"CI will catch a corrupted result," verify that claim against the actual
workflow file — don't just assert it. Check the claimed job's trigger
paths: filter (or lack of one) actually covers the attributed file, and
that the job runs before merge (on: pull_request), not only after
(on: push to main). A stated backstop that doesn't actually fire on
the file in question is worse than no backstop claim at all — it creates
false confidence. (d-morrison/rme#989: the .gitattributes comment
claimed a Quarto render CI job would catch a malformed references.bib
before merge, but preview.yml's paths: filter excluded .bib entirely
— caught by review, fixed by adding the path.)
4. Don't over-apply union merge
merge=union is a good default specifically for append-only log-style
files (changelogs, news files). It is a poor default for:
- Files where order or structure matters beyond "each entry is a line" (a
table, a numbered list where numbering must stay sequential).
- Files where a "conflict" usually indicates the two sides made incompatible
claims that a human needs to reconcile (most source code, config files,
test fixtures).
When in doubt, default to git's normal 3-way merge (i.e. don't add an
attribute) and let a real conflict surface.
Notes
- This skill edits
.gitattributes only. It does not touch .gitignore
(what's tracked at all) or CI config — those are separate concerns even
though they're often edited in the same session.
- Existing
.gitattributes precedent in this corpus: shared/vendored/ and
_extensions/ directories in consumer repos are natural
linguist-vendored candidates; check whether the target repo already
attributes them before adding a duplicate line.
- GitHub's own mergeable/conflict indicator doesn't evaluate a merge
driver configured here. The PR page's conflict banner and the
mergeable_state API field reflect GitHub's own merge check, which does
not invoke merge=<driver> declarations. A merge GitHub reports as
conflicting can auto-resolve cleanly under a real local git merge that
honors .gitattributes (or the reverse). See
ultracode-merge-conflicts
for when this matters enough to verify with a real local merge instead of
trusting the platform's flag.