error: Your local changes would be overwritten by merge - 10 Causes and Fixes

You ran git pull (or git merge) and Git refused with error: Your local changes would be overwritten by merge, followed by a list of files and the hint Please commit your changes or stash them before you merge. Nothing was merged, nothing was lost — Git stopped before touching your working tree because the incoming commits modify files you have already edited but not committed.

This is a pre-flight check, not a merge conflict. A merge conflict happens during a merge and leaves conflict markers in your files. This error happens before the merge starts, and your repository is left exactly as it was.

The fix depends on one question: do you want to keep your local edits? This page covers both answers, plus the less obvious cases — file mode changes, line-ending normalization, git stash failing on the same files, submodules, and CI checkouts that hit this on a clean-looking machine. Woman working on cybersecurity programming with laptops and multiple screens

A woman frustrated with her laptop while working remotely indoors, expressing stress.

Errorerror: Your local changes to the following files would be overwritten by merge: src/app.js Please commit your changes or stash them before you merge. Aborting
Where it happensGit - any version, any platform. Triggered by git pull, git merge, git checkout, git rebase, and git stash pop when the working tree has uncommitted changes.
What it meansThe commits you are pulling change a file that you have modified but not committed, and Git will not silently discard your edit.

The Fast Fix

If you want to keep your local edits, park them, merge, then put them back:

git stash push -m "wip before pull"
git pull
git stash pop

If you do not want your local edits — you were just poking at the file — throw them away and pull:

git checkout -- src/app.js   # discard one file, unrecoverable
git pull

git stash pop may itself report a conflict once the merge has landed. That is normal and recoverable: resolve the markers, git add, then git stash drop.

What Is Actually Causing It

1. Uncommitted edits to a file the incoming commits also change

Reproduce it

# origin/main has a new commit touching src/app.js
echo "console.log('local tweak');" >> src/app.js
git pull
# error: Your local changes to the following files would be overwritten by merge:
#	src/app.js
# Please commit your changes or stash them before you merge.
# Aborting

Why it happens — A merge writes the merged content of every changed file into your working tree. Git compares the incoming diff against your dirty files, sees an overlap, and aborts rather than overwrite work it has never recorded and could not restore.

The fix

git stash push -m "wip before pull" -- src/app.js
git pull
git stash pop

What changed: your edit now lives in a stash commit that Git can restore, so the merge is free to write src/app.js.

Confirm it workedgit status shows a clean tree before the pull, and after git stash pop your line is back in src/app.jsgrep "local tweak" src/app.js finds it.


2. You meant to commit the work, not stash it

Reproduce it

git status --short
# M  src/app.js
# M  src/api.js
git pull
# error: Your local changes to the following files would be overwritten by merge:
#	src/app.js

Why it happens — Git does not care whether your changes are staged or unstaged — both live in the working tree and both block the merge. Only a commit moves them somewhere the merge machinery can reason about.

The fix

git add -A
git commit -m "wip: local changes before merging origin/main"
git pull   # now a real merge; may produce conflicts, which is fine

What changed: the edits are a commit, so Git merges them instead of refusing. Real conflicts now show up as conflict markers you resolve normally.

Confirm it workedgit log --oneline -1 shows your wip commit, and git status reports a clean working tree before the pull runs.


3. Local changes you want to discard entirely

Reproduce it

git status --short
# M  package-lock.json
# M  src/generated/types.d.ts
git pull
# error: Your local changes to the following files would be overwritten by merge:
#	package-lock.json

Why it happens — Build artifacts and generated files get rewritten by local tooling and drift from HEAD without you touching them. They still count as working-tree modifications and still block the merge.

The fix

git restore package-lock.json src/generated/types.d.ts   # or: git checkout -- <files>
git pull

What changed: the files are reset to HEAD content. This is unrecoverable — Git never stored those edits.

Confirm it workedgit status --short prints nothing for those paths, and git pull completes with Fast-forward or a merge commit.


4. Untracked files that the incoming branch adds

Reproduce it

# origin/main adds src/config.ts; you already created your own
echo "export const config = {};" > src/config.ts
git pull
# error: The following untracked working tree files would be overwritten by merge:
#	src/config.ts
# Please move or remove them before you merge.

Why it happens — A near-identical message with a different noun: untracked, not local changes. Git has no HEAD version of the file to fall back on, so git stash (without -u) will not help — plain stash ignores untracked files.

The fix

git stash push -u -m "my untracked config"   # -u includes untracked files
git pull
git stash pop   # then reconcile your version with theirs

What changed: -u is what pulls untracked files into the stash. Alternatively mv src/config.ts src/config.ts.mine and diff afterwards.

Confirm it workedgit stash show --include-untracked --name-only stash@{0} lists src/config.ts before you pull.


5. Only the file mode changed (chmod on the executable bit)

Reproduce it

chmod +x scripts/deploy.sh
git diff --summary
# mode change 100644 => 100755 scripts/deploy.sh
git pull
# error: Your local changes to the following files would be overwritten by merge:
#	scripts/deploy.sh

Why it happens — Git tracks the executable bit as part of the tree. A permission change with identical content is still a modification, which is why git diff looks empty-ish while the merge still refuses. This bites hardest on WSL, Docker bind mounts, and network shares that report modes differently.

The fix

git config core.fileMode false   # tell this clone to ignore mode bits
git checkout -- scripts/deploy.sh
git pull

What changed: core.fileMode false makes Git stop comparing the executable bit in this repository. Do not set it if the executable bit is genuinely meaningful in your project.

Confirm it workedgit diff --summary prints nothing, and git status is clean even though the on-disk permissions are unchanged.


6. Line-ending or filter normalization rewrote every file

Reproduce it

# after adding a .gitattributes with * text=auto, or on Windows with core.autocrlf
git status --short
# M  src/a.ts
# M  src/b.ts
# ...hundreds of files
git pull
# error: Your local changes to the following files would be overwritten by merge:

Why it happenscore.autocrlf, .gitattributes text rules, and clean/smudge filters change what the working tree looks like relative to the index. Git compares bytes, so a CRLF-vs-LF difference across the tree marks everything as modified even though no one typed a character.

The fix

git add --renormalize .
git status --short   # if this is now empty, nothing real changed
git pull

What changed: --renormalize re-applies the current attribute rules to the index so the phantom diffs disappear. If files still show as modified, the changes are real content edits — handle them with stash or commit.

Confirm it workedgit status --short goes from hundreds of M lines to empty (or to only files you actually edited).


More causes (6 remaining)

7. Stale index timestamps make unmodified files look dirty

Reproduce it

touch src/*.ts   # or a build tool / restored CI cache rewrote every mtime
git pull
# error: Your local changes to the following files would be overwritten by merge:
#	src/app.ts

Why it happens — Git uses stat data (mtime, size, inode) as a fast path. When a tool rewrites files with identical content but new timestamps, the cache misses and Git re-reads them; combined with a stale or copied index this can leave entries marked dirty.

The fix

git status            # forces a full refresh of the index stat cache
git update-index --refresh
git pull

What changed: git update-index --refresh re-stats every entry and clears entries whose content actually matches HEAD.

Confirm it workedgit diff --stat reports no changed lines for those files after the refresh, and the pull proceeds.


8. `git stash pop` conflicts with the merge you just did

Reproduce it

git stash push -m wip
git pull
git stash pop
# error: Your local changes to the following files would be overwritten by merge:
#	src/app.js
# Please commit your changes or stash them before you merge.

Why it happensgit stash pop is itself implemented as a merge. If the pull left the working tree dirty — or a post-merge hook or build step rewrote files — pop refuses for the same reason pull did. The stash is not lost; a failed pop leaves the entry in place.

The fix

git stash list                  # confirm your stash is still there
git checkout -- .              # clean the tree of post-merge noise
git stash pop                  # now it applies

What changed: the tree is clean before pop runs. If you need to inspect first, git stash show -p stash@{0} prints the diff without applying it.

Confirm it workedgit stash list is empty after a successful pop, and git status shows your work restored as modifications.


9. A submodule's working tree is dirty, not the parent repo's

Reproduce it

git status
# modified:   vendor/sdk (modified content)
git pull
# error: Your local changes to the following files would be overwritten by merge:
#	vendor/sdk

Why it happens — To the parent repository a submodule is a single entry pointing at one commit. If the submodule's checkout is dirty or on a different commit, that entry reads as modified, and a pull that moves the submodule pointer would overwrite it.

The fix

git -C vendor/sdk stash push -m "submodule wip"   # or commit inside the submodule
git pull
git submodule update --init --recursive

What changed: the work is handled inside the submodule first; the parent then moves the pointer freely.

Confirm it workedgit status no longer lists vendor/sdk, and git submodule status shows the expected commit with no leading + or -.


10. CI or a script runs the pull on a tree a build step already modified

Reproduce it

# .github/workflows/ci.yml
- run: npm ci            # rewrites package-lock.json or generated files
- run: git pull --rebase origin main
# error: cannot pull with rebase: You have unstaged changes.
# error: Your local changes to the following files would be overwritten by merge:

Why it happens — The checkout looked pristine, but an install or codegen step wrote into tracked files before the pull ran. On a machine with no human editing anything, the working tree is still dirty by the time Git checks.

The fix

- run: npm ci
- run: git checkout -- .          # discard build-generated modifications
- run: git pull --rebase origin main

What changed: the pipeline explicitly resets the tree before pulling. Better still, add the generated paths to .gitignore so they are never tracked.

Confirm it worked — Add git status --porcelain before the pull step — it must print nothing. If it prints paths, those are your culprits.


11. You are on the wrong branch and pulling a wildly divergent one

Reproduce it

git branch --show-current
# feature/checkout-redesign
git pull origin main
# error: Your local changes to the following files would be overwritten by merge:
#	src/checkout/*

Why it happens — Merging a branch that diverged heavily touches far more files than you expect, so even a small local edit is likely to collide. The error is real, but the surprise is the sheer number of files listed.

The fix

git stash push -m "wip"
git fetch origin
git merge origin/main      # explicit about what you are merging into this branch
git stash pop

What changed: fetch + explicit merge separates "get the commits" from "integrate them", so you can inspect git log HEAD..origin/main before committing to the merge.

Confirm it workedgit log --oneline HEAD..origin/main | wc -l tells you how many commits are incoming before you merge anything.


12. `git checkout` or `git switch` to another branch hits the same wall

Reproduce it

echo "debug = true" >> src/settings.py
git switch release/2.0
# error: Your local changes to the following files would be overwritten by checkout:
#	src/settings.py
# Please commit your changes or stash them before you switch branches.

Why it happens — Same guard, different verb. Branch switching also rewrites the working tree, and Git carries uncommitted changes across branches only when the file is identical in both. When it differs, it stops.

The fix

git switch -c wip-settings   # take the changes onto a new branch, then commit
git add -A && git commit -m "wip settings"
git switch release/2.0

What changed: the edits get a home on their own branch instead of floating in the working tree.

Confirm it workedgit switch release/2.0 completes with Switched to branch 'release/2.0' and git status is clean.

Close-up of colorful programming code on a computer screen, showcasing digital technology. ## None of Those? Narrow It Down
  1. Run git status --short. If it prints nothing, the block is not ordinary edits — jump to the file-mode, line-ending, or submodule causes. If it prints paths, they are the exact files in conflict with the incoming commits.
  2. Read the error's own noun. "Your local changes" means tracked files with uncommitted edits; "The following untracked working tree files" means files Git has never seen and plain git stash will not move. They need different fixes.
  3. Run git diff --stat on the listed files. Zero changed lines with the file still shown as modified points at a mode change (git diff --summary will say mode change) or a line-ending/filter difference, not real edits.
  4. Run git diff origin/main -- <listed-file> after git fetch. This shows what the incoming side is about to do to that file — it tells you whether your edit is redundant (discard it) or unique (stash or commit it).
  5. Run git stash push -m probe then git status. If the tree is now clean, your problem was ordinary uncommitted work. If files are still listed, you are looking at untracked files, a dirty submodule, or ignored-but-tracked artifacts.
  6. Check git submodule status. A leading + means the submodule is at a different commit than the parent records; a dirty submodule blocks the parent's merge and must be cleaned inside the submodule directory.
  7. If hundreds of files are listed at once, run git add --renormalize . followed by git status. A tree that goes clean confirms a .gitattributes/core.autocrlf normalization, not real work.
  8. Still stuck? Take a safety copy of the whole directory (cp -r . ../repo-backup) before running anything with --hard or --force. Every destructive Git command becomes safe once a copy exists outside the repo.

Why This Error Exists At All

Git's core promise is that anything you have committed is recoverable. Every commit, every stash entry, every reflog line is an object in the database that some command can bring back. The working tree is the one place that promise does not hold: an uncommitted edit exists only as bytes on disk, with no object backing it and no reflog entry to find it by.

So before any operation that rewrites the working tree — merge, pull, checkout, switch, rebase, stash apply — Git computes which paths it is about to write and intersects that set with the paths you have modified. A non-empty intersection means the operation would destroy data Git cannot restore. Rather than ask, it aborts and changes nothing. This is why the error text ends in Aborting: the repository is in exactly the state it was in a millisecond earlier. There is no partial merge to clean up.

Once you see it this way, the whole family collapses into one rule: Git refuses to overwrite unrecorded work. "Your local changes would be overwritten by checkout", "...by merge", "cannot pull with rebase: You have unstaged changes", "The following untracked working tree files would be overwritten" — same check, different entry point. The fix is always to give the changes somewhere recoverable to live (commit, stash) or to explicitly declare them worthless (git restore, git checkout --, git reset --hard). The reason there is no --force you should reach for by default is that force here means "delete work with no undo", and Git makes you say that in a command whose name admits it. Two men analyzing code on computers in a modern office setting.

Stop It From Coming Back

  • Commit or stash before every git pull. Make it muscle memory, or alias it: git config --global alias.sync '!git stash push -m autosync && git pull && git stash pop'.
  • Add generated files to .gitignore and untrack the ones already committed with git rm --cached <path>. Lockfiles that must stay tracked should be regenerated in a dedicated commit, not incidentally by every npm ci.
  • Commit a .gitattributes with * text=auto and explicit eol rules for scripts. It kills the cross-platform CRLF churn that makes entire trees look modified on Windows checkouts.
  • Set git config --global pull.rebase true (or pull.ff only) so git pull has one predictable behaviour and you find out about divergence immediately instead of after an implicit merge commit.
  • Enable git config --global rerere.enabled true. Git records how you resolved a conflict and replays that resolution the next time the same one appears — a real time saver in stash-pull-pop cycles.
  • In CI, run git status --porcelain as an explicit step after install/build. If it emits anything, the pipeline is mutating tracked files and you should fix that rather than paper over it with checkout -- ..

Related Guides

Errors You Will Probably Hit Next

  • error: Your local changes to the following files would be overwritten by checkout: Please commit your changes or stash them before you switch branches.
  • error: cannot pull with rebase: You have unstaged changes. Please commit or stash them.
  • error: The following untracked working tree files would be overwritten by merge: Please move or remove them before you merge.
  • fatal: Not possible to fast-forward, aborting.

Read the noun in the error before you type a command: local changes means tracked-and-modified, untracked means Git has never seen the file, and the two need different fixes. Decide whether you want the edits — if yes, git stash or commit; if no, git restore. Git aborted before touching anything, so you have unlimited time to decide, and the only irreversible move is the one you make next.

댓글

이 블로그의 인기 게시물

TypeError: Cannot read properties of undefined (reading 'map') - 11 Causes and Fixes

Error: ERR_MODULE_NOT_FOUND: Cannot find package - 10 Causes and Fixes

npm ERR! code ERESOLVE unable to resolve dependency tree: 9 Causes and Fixes