W3docs

.gitattributes

Learn the .gitattributes file to control line endings, diffs, merge strategies, and export behavior per file path. Includes examples.

What .gitattributes does

A .gitattributes file tells Git how to treat specific files based on their path. Where .gitignore decides whether Git tracks a file, .gitattributes decides how Git handles the files it does track — how to normalize line endings, how to diff them, how to merge them, and what to do at export time. It lives in your repository and is committed, so every collaborator gets the same behavior, regardless of their personal Git config.

This page covers the file format, the most common attributes (line-ending normalization, marking binaries, custom diff/merge drivers, and export-ignore), where Git looks for the file, and how conflicting rules are resolved.

File format

Each line pairs a file pattern with one or more attributes:

# pattern        attributes
*.txt            text
*.png            binary
*.sh             text eol=lf

Patterns follow the same glob rules as .gitignore: * matches anything except /, ** matches across directories, a leading / anchors to the directory of the .gitattributes file, and lines starting with # are comments. Each attribute after the pattern takes one of four forms:

  • Settext turns the attribute on.
  • Unset-text turns it off (the leading dash).
  • Valueeol=lf sets a specific value.
  • Unspecified!text clears any earlier setting, leaving Git's default.

Where Git looks for it

Most projects keep a single .gitattributes at the repository root. But Git checks several locations, and a rule in a deeper directory overrides one higher up:

  • A .gitattributes in any directory applies to files in that directory and below.
  • $GIT_DIR/info/attributes holds rules that are not committed (local to your clone).
  • core.attributesFile (often ~/.config/git/attributes) sets per-user defaults.

When two rules could match the same file, the more specific path wins, and within one file the last matching line wins. You can inspect the result for any path with git check-attr:

git check-attr -a README.md
# README.md: text: auto

Normalizing line endings

The most common use of .gitattributes is ending the "every line changed" mess that happens when Windows and Unix developers share a repo. Marking files as text lets Git normalize line endings to LF in the repository and convert them on checkout:

* text=auto
*.sh text eol=lf
*.bat text eol=crlf

text=auto lets Git decide which files are text and store them with LF in the repository; the explicit eol settings then force a specific ending on checkout for files that need one (shell scripts must stay LF, Windows batch files must stay CRLF). Because the rules are committed, this is more reliable than relying on each developer's core.autocrlf setting, which differs from machine to machine.

If you add * text=auto to an existing repo, files already committed with CRLF won't be renormalized automatically. Run a one-time cleanup so the next commit fixes them:

git add --renormalize .
git commit -m "Normalize line endings"

Marking files as binary

Telling Git a file is binary stops it from trying to show a textual diff or merge it line by line:

*.pdf binary
*.png binary

The binary attribute is a built-in macro that expands to -text -diff, which disables line-ending conversion and textual diffing. This keeps Git from corrupting a file with line-ending rewrites and stops git diff from dumping unreadable byte soup into the terminal.

For large binaries such as videos, datasets, or design files, marking them binary isn't enough — they still bloat the repository's history. Store those with Git LFS instead, which .gitattributes is also used to configure.

Custom diff and merge behavior

.gitattributes can route certain files through custom diff or merge drivers, but a custom driver must be defined in your Git config first — the attribute only references it by name.

A common case is a generated lock file: during a merge conflict you want to keep your branch's version wholesale instead of merging line by line. Register an ours driver once, then point the path at it:

git config merge.ours.driver true
# .gitattributes
package-lock.json merge=ours

Setting driver to true means "the merge always succeeds and the result is the current branch's version." (This per-file merge=ours attribute is independent of the -s ours merge strategy, which applies to a whole merge.)

A custom diff driver works the same way and is handy for non-text formats. Git also ships built-in diff drivers that produce meaningful hunk headers for common languages, so a diff shows which function changed:

*.c diff=cpp
*.py diff=python

See git diff for how these drivers shape the output.

Export-ignore

When someone downloads a release archive via git archive, you often want to leave out development files. The export-ignore attribute does exactly that:

/tests       export-ignore
/.github     export-ignore
.gitattributes export-ignore

This keeps test suites, CI config, and editor files out of the tarball that git archive produces, so consumers download only what they need. A related attribute, export-subst, expands placeholders like $Format:%H$ inside exported files so an archive can record the commit it was built from.

Common attributes

AttributeEffect
textNormalize line endings to LF in the repo.
eol=lf / eol=crlfForce a specific line ending on checkout.
binaryTreat the file as binary — no diff, no line-ending conversion.
merge=<driver>Use a custom merge strategy for the file.
diff=<driver>Use a custom diff driver.
export-ignoreExclude the path from git archive exports.
export-substExpand $Format:…$ placeholders in archived files.

When to use it

Reach for .gitattributes whenever Git's per-file behavior should be the same for everyone on the team rather than left to local settings. The everyday wins are normalizing line endings on a mixed Windows/Unix team, silencing useless diffs on binaries, and trimming release archives. The advanced wins — custom merge/diff drivers and Git LFS — solve specific pain points once a project hits them. Start with * text=auto and add rules as concrete problems appear.

Practice

Practice
What does the '.gitattributes' file control?
What does the '.gitattributes' file control?
Was this page helpful?