Git .gitattributes and Line Ending Normalization

Use .gitattributes for LF and CRLF policies, inspect indexed bytes, renormalize tracked text carefully and distinguish attributes from ignore rules.

Lesson 23 of 30Intermediate10 min read

A pull request that changes every line of a file can be hiding a small edit behind a line-ending conversion. Windows tools often write carriage-return plus line-feed endings, while many Unix tools use line feed alone. Git can normalize tracked text and check it out under an agreed policy. The .gitattributes file makes that policy part of the repository rather than an undocumented setting on one laptop. This guide explains which bytes belong in the index, which belong in the working tree, and how to introduce a policy without mixing unrelated changes into a normalization commit.

Keep attributes separate from ignore rules

A .gitignore entry controls whether an otherwise untracked path is normally offered for adding. It does not define how an already tracked file's content should be stored. A .gitattributes entry assigns attributes to matching paths, including text handling, diff behavior and filters. Committing either file makes its rules available to collaborators, but their jobs are different. A binary asset is not automatically ignored because you mark it as binary. Likewise, an ignored filename does not become LF-normalized merely because it appears in an ignore pattern. Choose the appropriate mechanism for the actual requirement.

Understand normalization and checkout endings

With text normalization enabled, Git stores normalized text with LF line endings in the index and commits. The eol attribute determines the intended working-tree ending at checkout for matching text paths. An eol=lf rule requests LF; eol=crlf requests CRLF. Your current working file may not immediately change just because an attribute file was edited or staged. Inspect the state instead of assuming a policy declaration already rewrote every byte on disk. Global and local configuration also interact with unspecified attributes, which is why a deliberate repository rule helps reduce machine-dependent surprises.

Use path-specific rules for text and binary files

A practical policy might start with automatic text detection and then declare known script or data formats explicitly. Shell scripts often need LF endings; Windows command files can have a CRLF policy. Mark binary formats so text conversion is not applied to their content. Automatic detection is convenient but should not replace your knowledge of a format, especially for files with unusual byte patterns. Attribute patterns are path rules with their own documented matching behavior. Test representative files from each category and keep exceptions visible rather than depending on every contributor's editor guessing the same policy.

Inspect index and working-tree evidence

Git ls-files --eol displays ending information for tracked index entries and working files, together with effective text attributes. The exact spacing is a display detail; the important distinction is i/lf versus w/crlf and the attribute rule. Git show :path reads staged content, which can differ from the working file after normalization. When reviewing a suspicious diff, inspect both the content change and the file's ending state. A warning that CRLF will be replaced is not necessarily corruption, but it should agree with an intentional policy instead of being dismissed without understanding why it appeared.

Introduce normalization as a controlled change

Before running git add --renormalize, start from a understood working state, review the new attributes and account for any existing edits. The command reapplies relevant clean handling to tracked content; it is not a substitute for reading the resulting cached diff. Make normalization a focused commit so reviewers can distinguish byte-policy changes from behavior changes. Avoid combining a broad conversion with a feature refactor. Check your build and representative scripts afterward, because format-specific requirements still matter. Untracked files require their own staging decision and should not be assumed to have joined the normalization commit automatically.

Coordinate editors and tooling with the repository

Editor settings can make a repository policy easier to follow, but Git attributes and an editor's file-writing preferences remain separate mechanisms. Align the formatter, generated-file process and editor configuration with the committed rules. If a generator rewrites a text file every build, inspect whether its ending convention matches the policy before blaming Git for noisy diffs. Consider file encoding too: line endings do not determine whether a file is UTF-8 or another representation. A clear contribution note should identify the intended format and a small verification command rather than requiring contributors to guess which warning to suppress.

Sources and prerequisites

Git .gitattributes and Line Ending Normalization Summary

  • Attributes and ignore rules solve different problems.
  • Inspect staged and working-tree ending state independently.
  • Keep normalization separate from unrelated behavior changes.

Git .gitattributes and Line Ending Normalization Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Stage CRLF text under an LF rule

Run the Bash lab in a new directory. Disabling automatic conversion is a local setting for this demonstration, allowing the explicit attribute rule to show its effect. The text file is created with CRLF bytes. Staging it under text eol=lf normalizes the index, while the current working copy remains CRLF. Other configuration and checkout operations can change working-tree results in a different repository.

Git's column spacing may vary. The meaningful result is an LF index entry and a CRLF working file governed by the LF checkout rule. The content inspection shows two text lines without turning an ending convention into an extra business edit. Reading a file in a terminal cannot by itself prove its exact byte representation, so use the ending inspection and byte-aware tooling when diagnosing a real issue.

Terminal
mkdir attributes-lab
cd attributes-lab
git init -b main
git config core.autocrlf false
printf '*.txt text eol=lf\n' > .gitattributes
printf 'first\r\nsecond\r\n' > notes.txt
git add .gitattributes notes.txt
git ls-files --eol notes.txt
git show :notes.txt
Output
i/lf    w/crlf  attr/text eol=lf  notes.txt
first
second

Common Git .gitattributes and Line Ending Normalization Mistakes

Combining normalization with a feature edit

If the same commit rewrites thousands of line endings and changes application logic, reviewers must separate two unrelated causes of difference. That makes errors easier to overlook and a later revert harder to reason about. Introduce the policy, inspect normalization and verify the existing behavior first. Then make the feature change in its own commit. A smaller diff is useful evidence only when it represents a coherent change, not when important conversion effects have been hidden.

Git .gitattributes and Line Ending Normalization in Real Projects

Add line-ending rules for scripts and images

Add explicit rules for shell scripts, Windows command files and PNG images in the same lab. Then ask Git for the effective attributes on sample paths; the sample files do not need to exist for this rule lookup. Explain why a PNG attribute policy differs from ignoring PNG files entirely. These choices are illustrative, and a project's actual generator or distribution requirements should determine its committed exceptions.

Terminal
printf '*.txt text eol=lf\n*.sh text eol=lf\n*.cmd text eol=crlf\n*.png -text\n' > .gitattributes
git check-attr text eol -- script.sh deploy.cmd logo.png
Output
script.sh: text: set
script.sh: eol: lf
deploy.cmd: text: set
deploy.cmd: eol: crlf
logo.png: text: unset
logo.png: eol: unspecified

The lookup exposes the effective rules directly. PNG content will not undergo text normalization under this rule, but the path can still be tracked. The shell and command-file rules specify different checkout endings deliberately. Review and stage the attribute file before sharing the policy, and apply any needed tracked-file renormalization as a separate understood change rather than assuming the lookup modified existing files.

Git .gitattributes and Line Ending Normalization FAQ

Does .gitattributes replace .gitignore?

No. Attributes define how matching paths are handled, while ignore rules affect the normal addition of untracked paths. A file with -text can remain a tracked binary asset. A filename in .gitignore may still be tracked if it was already committed. Keeping these roles separate helps when a policy appears to do nothing: identify whether your problem is unwanted tracking, content conversion, diff presentation or a filter rather than trying the same pattern in every configuration file.

Why can the index be LF while my working file is CRLF?

The index and working tree are different representations under text conversion rules. Adding the file can normalize its staged content without immediately rewriting the existing working file. A later checkout applies relevant checkout behavior, subject to the effective configuration and attributes. Inspect both ending columns to understand the current state. The difference can be intentional and healthy, but it should follow the team's declared policy rather than accidental conflicting settings on two machines.

Does git add --renormalize automatically include new files?

It reapplies relevant clean handling to tracked files under the command's documented behavior. Do not assume an untracked file has been added merely because you ran renormalization. Inspect status and the cached diff, stage intended new paths explicitly, and check the final proposed snapshot. Also account for ordinary tracked edits already present: staging a broad path set can include changes beyond ending normalization, which is why a clean, understood starting state is useful.

Should every repository force all files to LF?

Choose rules by format and tooling rather than applying a blanket preference without checking. Many source files and shell scripts use LF comfortably, while some Windows-specific files may have a CRLF expectation. Binary formats should not be treated as ordinary text. Generated assets and exported data can impose their own requirements. Document intentional exceptions, align generators and editors, and test representative files so the policy improves reproducibility without quietly changing a format's meaning.

Git .gitattributes and Line Ending Normalization Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choicemedium

Under the practice rules, which ending is requested for deploy.cmd at checkout?

Git .gitattributes and Line Ending Normalization Quiz

2 questions to check what you learned.

  1. Which file assigns versioned path rules for text normalization?

    • .gitignore
    • .gitattributes
    • .git/HEAD
  2. What does i/lf with w/crlf report for notes.txt?

    • An index conflict that deleted the working file
    • A file excluded from the next commit
    • LF in the index and CRLF in the working tree