A Windows Git project can look perfectly innocent until one checkout turns a modest code change into a 2,000-line diff. The culprit is often not a rebellious editor—it is the quiet disagreement between Windows-style CRLF line endings and Unix-style LF endings.

The durable fix is to put the project’s policy in a tracked .gitattributes file. That makes line-ending behavior part of the repository rather than a personal setting buried in each contributor’s Git for Windows installation. Git’s documentation confirms that text attributes normalize text files to LF in the repository index, while eol=crlf or eol=lf controls the form written into a working tree.

This guide uses Git Bash, but the commands work in PowerShell and Command Prompt as well.

Dual monitors display a code editor and a diagram illustrating LF-to-CRLF line-ending conversion at a desk. Understand the two layers: Git configuration and project rules​

Git has two related controls:

  • Local or global configuration, such as core.autocrlf, affects an individual developer’s environment.
  • .gitattributes, committed at the repository root, defines rules for everyone who clones the project.

For a shared Windows 10 or Windows 11 project, .gitattributes should be the authority. It travels with the code, applies in CI environments, and prevents one developer’s global Git setting from becoming everyone else’s mystery diff.

A practical Windows baseline is:

Code:
# Let Git identify ordinary text files and store normalized LF in the repository.
* text=auto

# Windows and Visual Studio project files that commonly require CRLF when checked out.
*.sln  text eol=crlf
*.vcxproj text eol=crlf
*.vcproj text eol=crlf
*.bat  text eol=crlf
*.cmd  text eol=crlf
*.ps1  text eol=crlf

# Scripts commonly executed in Unix-like shells, containers, or CI.
*.sh   text eol=lf
*.py   text eol=lf
*.yml  text eol=lf
*.yaml text eol=lf

# Do not subject known binary formats to text conversion.
*.png  -text
*.jpg  -text
*.jpeg -text
*.gif  -text
*.ico  -text
*.pdf  -text
*.zip  -text
*.dll  -text
*.exe  -text

This is a starting policy, not sacred scripture. For example, a Windows-only PowerShell estate may deliberately use CRLF for .ps1; a cross-platform PowerShell project may instead standardize on LF. The important part is making a conscious, repository-level decision.

Set a sensible Git for Windows baseline​

First, inspect your current setting:

git config --global --get core.autocrlf

For a typical Windows workstation, this is a reasonable personal default:

git config --global core.autocrlf true

That tells Git to check normalized text out as CRLF for Windows tools while converting text to LF when it is added to the repository.

However, do not mistake this for a replacement for .gitattributes. GitHub’s documentation notes that project attributes override a contributor’s core.autocrlf preference for matched files. In other words: use the global setting for broad local comfort; use .gitattributes to establish the team contract.

If a repository already has a clear .gitattributes policy, avoid changing global settings merely to “fix” that one project. Global toggles are a blunt instrument—rather like repairing a desk lamp with a leaf blower.

Create and commit .gitattributes​

From the root of the repository, create the file:

touch .gitattributes

Open it in your preferred editor, add the rules appropriate to the project, then check what Git sees:

Code:
git status
git diff -- .gitattributes

Stage the policy file:

git add .gitattributes

Do not immediately commit a repository-wide normalization alongside unrelated feature work. A line-ending-only commit is far easier to review, merge, and revert.

Normalize existing tracked files safely​

Adding .gitattributes governs future adds and checkouts, but it does not automatically rewrite every tracked file already in the index. That is what git add --renormalize . is for.

Before running it, make sure the working tree is clean:

git status

If you have unfinished edits, commit them to a temporary branch or stash them first. Normalization intentionally touches many files, and mixing it with functional edits creates noisy reviews and needless merge pain.

Then run:

Code:
git add --renormalize .
git status

Git will stage only files whose repository representation changes under the new attributes. Review the staged change before committing:

Code:
git diff --cached --stat
git diff --cached

Expect many files to appear changed even though their visible text is identical. That is normal for a normalization commit. What should not appear are binary assets, generated output, vendor packages, or files with deliberately unusual encodings. If they do, stop, adjust .gitattributes with -text or a more specific rule, and run the review again.

When the diff is limited to the intended line-ending cleanup:

git commit -m "Normalize line endings with .gitattributes"

Git’s own documentation recommends this renormalization approach after introducing attributes. It is safer and clearer than deleting the index or manually reopening hundreds of files in an editor.

Verify the result instead of trusting the green check mark​

Git includes a useful diagnostic for this exact job:

git ls-files --eol

The output reports:

  • i/ — line-ending information in Git’s index
  • w/ — line-ending information in the working tree
  • attr/ — the attribute rule currently applied

For example:

Code:
i/lf    w/crlf  attr/text eol=crlf    Solution.sln
i/lf    w/lf    attr/text eol=lf      build.sh
i/-text w/-text attr/-text            logo.png

That first line is not a conflict. It is the desired outcome for a Visual Studio solution configured with eol=crlf: Git stores normalized LF internally while the Windows working copy receives CRLF.

To test a specific file’s matching rule:

git check-attr text eol -- path/to/file

If Git reports text: unspecified where a rule should apply, check the pattern and the file location. Attribute patterns are not shell globs pasted into a different universe; their matching behavior matters. Keep the .gitattributes file in the repository root unless you intentionally want narrower directory-specific rules.

Common traps that cause line-ending churn​

Relying only on core.autocrlf​

A developer-level setting cannot guarantee what happens on another PC, a Linux build agent, or a teammate’s Mac. Use it as a local preference, not as the project constitution.

Marking everything as text​

Avoid a blanket * text unless you fully understand the repository contents. * text=auto is a safer general rule because Git attempts to detect text. Explicitly exclude binary formats that matter to the project.

Treating generated folders as source​

Folders such as build output, package caches, and dependency trees should usually be ignored rather than normalized. If they are intentionally committed, define careful attributes and keep generated artifacts separate from the source policy where possible.

Combining formatting cleanup with application changes​

A normalization commit can collide with active branches because every line may appear touched. Land it as an isolated commit, let active branches rebase or merge it deliberately, and then resume ordinary development.

For repositories that must merge histories created under different line-ending rules, Git also provides the merge.renormalize option. It can reduce artificial merge conflicts by applying virtual normalization during a three-way merge, but it is a mitigation—not a substitute for agreeing on .gitattributes before the branches drift apart.

The practical takeaway​

The goal is not to declare CRLF evil or LF morally superior. Both are valid line terminators. The problem is inconsistency: a repository without an explicit rule lets every operating system, editor, IDE, and Git configuration cast a vote.

Use .gitattributes to define the rules, normalize in one reviewable commit, and verify with git ls-files --eol before pushing. Once that is done, “every file changed” diffs can go back to being the rare emergency they deserve to be—not a recurring Windows development ritual.