Also available in: Español · Português · Français · العربية
gitattributes Generator
Generate a .gitattributes file and check any path against it — which line wins, and what line endings git will store and hand to whoever clones your repository.
What is a .gitattributes file?
A .gitattributes file tells git how to treat particular paths. It lives in your repository, it is committed like anything else, and it decides which files count as text, which are binary, how they are diffed and merged, and — the part almost everybody comes here for — what happens to line endings on the way in and out.
The format is a pattern followed by attributes, one rule per line, exactly the shape of a .gitignore. That familiarity is most of why it goes wrong: the patterns really are gitignore's, but one piece of the syntax means something entirely different, and the attribute everyone reaches for does less than its name suggests.
This tool does both halves. You edit the file, then give it a path and it tells you which line wins, what state each attribute ends up in, and what git will actually store and check out — including for the setting on somebody else's machine that you do not control.
How to use it
- Start from the file shown. It is a reasonable default: normalise text, pin shell scripts to LF and batch files to CRLF, mark the usual binaries, and stop git diffing a lock file. Edit it directly.
- Type a path to check. Relative to the repository root. Every attribute that matches is listed with the pattern that supplied it, so you can see which line actually won rather than which one you meant.
- Change their core.autocrlf. Those three buttons stand for the setting on the machine of whoever clones your repository. Watch the line-ending panel as you press them — if it moves, your files depend on a setting you cannot see.
What line endings actually do, measured
This part was measured rather than read. The same CRLF file was committed in twenty-one throwaway repositories — every combination of three core.autocrlf settings and seven different attribute lines — and git was then asked what was in the object store, and, after the file was deleted and restored, what was in the working tree.
Without a .gitattributes, what your repository stores depends on who committed. The same file lands as CRLF under core.autocrlf=false and as LF under true or input. Nothing in your own checkout tells you which happened, which is why this shows up as a pull request full of whole-file changes rather than as an error. Add any text attribute and that variation vanishes: all three settings produced identical stored bytes.
The surprise is what text=auto does not do. It pins the repository and leaves the working tree alone — checked out again, the same file arrives as CRLF under two of the three settings and LF under the third. So a team that adds a single line of text=auto has made its history consistent and has not made its working trees agree. Only eol=lf or eol=crlf pins both ends.
And eol=crlf stores LF. It is an instruction about checkout, not about storage, so if you inspect the blob you will find LF and conclude the line did nothing. It did: it just did it at the other end.
The patterns are gitignore's, with one exception that fails silently
Matching behaves the way the familiar shape suggests. A pattern containing a slash is measured from the directory of its own .gitattributes; one without matches a name at any depth below it. A double star crosses directories and — measured, because it is easy to assume otherwise — also matches no directories at all, so sub/**/*.txt covers sub/a.txt as well as sub/deep/a.txt. Character classes work.
Precedence is by position, not by specificity: the last matching line wins, and a pattern being more precise counts for nothing. That is the same rule EditorConfig has, and the same one people get backwards in both. Across files, a .gitattributes deeper in the tree beats a shallower one whichever order git happens to read them in.
The exception is the exclamation mark. In a .gitignore, a leading ! negates a pattern. In a .gitattributes it is not supported at all — a line beginning with ! does nothing, and the earlier value stays in force. There is no error and no warning. To take an attribute away you put a dash before the attribute rather than the pattern, and to return it to unspecified you put the exclamation mark in front of the attribute instead.
One more thing worth knowing, because it is the commonest line in any real file: binary is not an attribute but a macro. It expands to unsetting text, diff and merge together, which is why marking a path binary correctly overrides an earlier text=auto rather than sitting alongside it.
Honest limitations
This models the rules, not your repository. Real projects can have several .gitattributes files at different depths, and git also reads .git/info/attributes and a global file outside the repository entirely, neither of which is committed and neither of which anyone else has. What is modelled here is the committed file and the rules within it, which is where the confusing part almost always lives.
The line-ending panel predicts what git does with a text file that currently has CRLF endings, since that is the case people arrive with. A file that is already LF only, or one with mixed endings, behaves differently in ways the panel does not attempt to draw.
Changing a .gitattributes does not change files already committed. Git applies the new rules the next time a file passes through it, so a repository that has been storing CRLF keeps storing CRLF until something rewrites those files. That is what the renormalize option exists for, and it produces exactly the enormous diff you would expect, which is why it is worth doing deliberately rather than discovering it later.
Why is it free?
It runs in your browser. Matching a path against a pattern costs nothing on your own machine, so there is no server to pay for and no account to create.
Nothing you type is uploaded, stored or logged. File names and directory layouts say a good deal about a private codebase, and the reliable way to keep that private is not to send it anywhere.