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 #

A working ignore file
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:

Checking what the rules will do
$ 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:

Untrack without deleting
$ 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 #

The attributes that prevent cross-platform pain
* text=auto eol=lf
*.png binary
*.min.js -diff
package-lock.json linguist-generated=true
AttributeEffect
eol=lfNormalize line endings — ends the CRLF wars
binaryNo diff, no merge attempts, no eol munging
-diff / diff=driverHide or customise diffs (lockfiles, minified code)
merge=oursKeep our version on merge (carefully)
linguist-generatedExclude 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:

.gitignore
node_modules/  dist/  .env  *.log  .DS_Store
.gitattributes
* 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.