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.
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:
# 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:
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:
git add --renormalize .
git status
Git will stage only files whose repository representation changes under the new attributes. Review the staged change before committing:
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 indexw/— line-ending information in the working treeattr/— the attribute rule currently applied
For example:
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.