Two invisible files do a disproportionate share of repository hygiene: .gitignore decides what git never tracks, and .gitattributes decides how git treats what it does — line endings, diffs, merge strategies, and language statistics. One keeps noise out; the other makes the remaining content behave.
.gitignore: the rules #
node_modules/
dist/
*.log
.env
!*.env.example
coverage/
.DS_Store
Patterns are shell globs: *.log anywhere, dist/ directories only, a leading / pins to the repo root, ! negates. The negation matters — committing an example env while ignoring real ones is the standard pattern:
$ git check-ignore -v .env build/tmp.log
.gitignore:4:.env .env
.gitignore:3:*.log build/tmp.log
check-ignore -v names the rule and line that matched — the debugger when "why isn't my file ignored (or tracked)?" strikes.
The one gotcha: already-tracked files #
Ignore rules apply only to untracked files. A file committed before the rule existed stays tracked forever, silently:
$ git rm --cached .env
$ git commit -m "stop tracking .env"
That is the fix for the eternal "I added it to .gitignore but it still shows in status". Secrets in this category are worse — history keeps them after untracking, which is a rotation problem, not a git one.
.gitattributes: the treatment #
* text=auto eol=lf
*.png binary
*.min.js -diff
package-lock.json linguist-generated=true
| Attribute | Effect |
|---|---|
eol=lf | Normalize line endings — ends the CRLF wars |
binary | No diff, no merge attempts, no eol munging |
-diff / diff=driver | Hide or customise diffs (lockfiles, minified code) |
merge=ours | Keep our version on merge (carefully) |
linguist-generated | Exclude from language stats |
text=auto eol=lf at the top is the single most valuable line for teams mixing Linux and Windows: the repository stores LF, working copies normalize, and diffs stop being one-file-changed-actually-every-file.
The practical baseline #
Every repository deserves both files on day one — the first commit is the natural home. A minimal starting pair:
node_modules/ dist/ .env *.log .DS_Store
* text=auto eol=lf
Global personal ignores (git config --global core.excludesfile) keep editor droppings out without polluting the project's file. The Git series continues.
