Fix: Git "Your local changes would be overwritten by merge"
Part of: Docker, DevOps & Infrastructure
Quick Answer
How to fix Git error 'Your local changes to the following files would be overwritten by merge' using git stash, commit, checkout, and pull strategies.
Git Refusing to Drop Your Work
Personally, I think this is one of Git’s friendliest errors. It is telling you “I am about to lose your work; do you want me to?” The fix depends on whether you want to keep, discard, or save the changes; never on the command’s surface syntax. I learned to pick the intent first and the command second. You run git pull or git merge and get:
error: Your local changes to the following files would be overwritten by merge:
src/config.js
src/utils/helpers.ts
Please commit your changes or stash them before you merge.
AbortingOr during git checkout:
error: Your local changes to the following files would be overwritten by checkout:
src/app.js
Please commit your changes or stash them before you switch branches.
AbortingOr during git rebase:
error: cannot rebase: You have unstaged changes.
Please commit or stash them.Git is protecting your work. You have uncommitted changes in files that the incoming merge, checkout, or rebase would also modify. Git refuses to proceed because it would overwrite your local edits.
Quick Reference Before You Dive In
If you arrived here from Google with a fresh error, the five facts that resolve roughly 90 percent of cases:
- The fastest safe fix is
git stash && git pull && git stash pop. It saves your changes, pulls cleanly, then reapplies them. Thegit stashdocumentation and thegit pullreference are the canonical sources. git stash -uALSO saves untracked files. Without-u, new files you have not yet staged stay in your working directory and can cause issues during the pull.git restore <file>(Git 2.23+) discards local changes to a specific file. Use it instead of the oldergit checkout -- <file>syntax.git reset --hardis the nuclear option and is irreversible for UNCOMMITTED changes. The reflog only recovers committed work; uncommitted edits are gone forever.- If
.DS_Store,package-lock.json, or build output keep causing this, they need to be in.gitignore. Generated files should not be tracked.
The rest of this article walks through each cause in detail, plus the failure modes most other guides skip.
Why Git Refuses Mid-Operation
Git needs to modify files in your working directory to complete the operation (merge, pull, checkout, or rebase). If those files have uncommitted changes, Git has a conflict: your local edits versus the incoming changes. Rather than silently losing your work, Git aborts the operation.
The check happens before any merging starts, by comparing the index against both HEAD and the target tree. If any file is dirty (different in your working tree from the index) and the operation would change that file in the target, Git refuses. This is why the error can show up even when your edit and the incoming change touch entirely different lines of the same file: Git is conservative, not smart. It does not attempt a three-way merge against an uncommitted state, because doing so could silently lose your work if the merge algorithm chose the wrong side.
This happens in these scenarios:
- You edited files and then ran
git pullbefore committing. - You are switching branches that have different versions of files you modified.
- You are rebasing onto a branch that touches the same files.
- An IDE or build tool modified files automatically (like
package-lock.json,.DS_Store, or generated configs). - A line-ending or filemode change has marked files as modified even though the visible content is identical.
The fix depends on whether you want to keep your changes, discard them, or save them temporarily. Pick the action first, then pick the command; most of the time you want option one, and stashing is the safest path.
In Production: Incident Lens
This is primarily a developer-side error rather than a production runtime failure, but it shows up in production in one specific scenario: hotfix deploys. The flow is: an on-call engineer SSHes (or shells into a build agent) to deploy a hotfix, runs git pull origin main on the deploy host, sees this error because the build agent had local changes from a previous deploy script that wrote a config file, and either force-discards the change (losing important state) or panics and types git reset --hard from muscle memory. Either way, the hotfix can land on top of state that nobody expected.
The blast radius is “the hotfix did not actually get deployed correctly.” If the engineer did git stash and forgot to git stash pop after merging, the local fix is silently dropped. If they did git reset --hard, any deploy-time config (build IDs, release tags written into version files, generated assets) is gone. The monitoring signal is the smoke test that runs immediately after every deploy: it should hit a known endpoint and verify the hotfix-specific behavior. If your smoke test only checks “200 OK on /health,” you will not catch a hotfix that silently regressed.
Recovery is git reflog and git cherry-pick. The reflog keeps a 90-day history of where HEAD has pointed, so you can find the lost commit hash and cherry-pick it back. Postmortem preventives are branch protection rules (require PR review and CI green before main can change), a deploy script that runs against a clean checkout every time (no in-place pulls on a long-lived working tree), and a smoke test per deploy that verifies the specific change, not just process liveness. Some teams also forbid running git commands on production hosts entirely: deploys come from immutable artifacts built in CI, which sidesteps this class of error completely.
When to Use Which Fix
The next eight sections cover the fixes in detail. The table below maps your intent to the recommended fix.
| Your intent | Recommended fix | Why |
|---|---|---|
| Keep changes, want to pull cleanly | Fix 1: git stash, pull, git stash pop | Safest, reversible |
| Changes are done, ready to commit | Fix 2: commit first, then pull | Clean history |
| Don’t need changes, want remote version | Fix 3: git restore to discard | Discard cleanly |
| Some changes worth keeping, others not | Fix 4: git stash push specific files | Per-file control |
| Prefer linear history | Fix 5: git pull --rebase | No merge commits |
| Generated files keep conflicting | Fix 6: .gitignore + git rm --cached | Stop tracking artifacts |
package-lock.json conflicts | Fix 7: npm install after pull regenerates | Special handling |
| Just need to switch branches NOW | Fix 8: git checkout -f or reset --hard | Nuclear option |
If multiple rows apply, pick the topmost one.
Fix 1: Stash Your Changes, Then Pull
The most common fix. Git stash temporarily saves your changes, lets you pull, then reapplies them.
git stash
git pull
git stash popStep by step:
git stashsaves your uncommitted changes and reverts the working directory to the last commit.git pullpulls the latest changes from the remote (now succeeds because no local changes conflict).git stash popreapplies your stashed changes on top of the pulled code.
If git stash pop causes merge conflicts, Git tells you which files conflict. Resolve them manually, then:
git add <resolved-files>
git stash drop # Remove the stash entry after resolvingFor detailed conflict resolution during stash pop, see Fix: git stash pop conflicts.
A habit I have built: always use git stash -u instead of plain git stash. The -u flag also stashes untracked files (new files you have not yet added with git add). Without it, those files stay in your working directory and can sometimes interfere with the merge or pull. There is no downside to using -u by default; I aliased it years ago and never looked back.
Fix 2: Commit Your Changes First
If your changes are ready to keep, commit them before pulling:
git add -A
git commit -m "WIP: save current work before pulling"
git pullIf the pull introduces conflicts with your committed changes, Git starts a merge. Resolve the conflicts:
# Edit the conflicting files
git add <resolved-files>
git commit -m "Merge remote changes"This approach creates a clean history where your changes and the remote changes are both preserved. If you prefer a linear history, use git pull --rebase instead (see Fix 5).
Fix 3: Discard Your Local Changes
If you don’t need your local changes and just want the remote version, discard them:
Discard changes to specific files:
git checkout -- src/config.js src/utils/helpers.tsOr with newer Git (2.23+):
git restore src/config.js src/utils/helpers.tsDiscard all uncommitted changes:
git checkout -- .Or:
git restore .Then pull:
git pullWarning: Discarded changes are gone forever. Git does not keep a backup of uncommitted changes. Make sure you truly don’t need them before discarding. If you are unsure, use stash (Fix 1) instead; you can always drop the stash later.
Fix 4: Stash Specific Files
If you want to keep some changes and discard others, stash only specific files:
git stash push -m "save config changes" src/config.jsThis stashes only src/config.js. Other modified files remain in your working directory.
Then resolve the remaining files:
git checkout -- src/utils/helpers.ts # Discard this one
git pull
git stash pop # Restore the stashed config changesYou can also use git stash push with --patch to selectively stash individual hunks within files:
git stash push -pGit interactively asks which changes to stash and which to keep.
Fix 5: Use git pull —rebase
If you prefer a linear commit history without merge commits:
git stash
git pull --rebase
git stash popOr commit first, then rebase:
git add -A
git commit -m "WIP: my changes"
git pull --rebase--rebase replays your commits on top of the remote changes instead of creating a merge commit. This keeps the history cleaner.
If conflicts arise during rebase, resolve them and continue:
# Edit conflicts
git add <resolved-files>
git rebase --continueFor more complex rebase scenarios, see Fix: git rebase conflicts.
To make rebase the default for git pull:
git config --global pull.rebase trueFix 6: Add Generated Files to .gitignore
If the conflicting files are generated artifacts (build output, lock files, IDE configs) they probably should not be tracked by Git in the first place.
Common culprits:
.DS_Store(macOS)Thumbs.db(Windows)*.pyc,__pycache__/(Python).idea/,.vscode/settings.json(IDE settings)dist/,build/,.next/(build output)*.log(log files)
Add them to .gitignore:
.DS_Store
Thumbs.db
*.pyc
__pycache__/
.idea/
dist/
build/
*.logThen remove them from tracking (without deleting the files):
git rm --cached .DS_Store
git rm --cached -r .idea/
git commit -m "Stop tracking generated files"After this, Git no longer tracks these files, and they won’t cause merge conflicts.
A specific mistake I have shipped before: adding .DS_Store to .gitignore after the file was already tracked. The .gitignore file ONLY affects untracked files; already-tracked files keep being tracked until you git rm --cached <file>. The two-step rule is universal: .gitignore plus git rm --cached if the file was ever committed.
Fix 7: Handle package-lock.json Conflicts
package-lock.json is a frequent source of this error. It changes whenever you npm install, and pulling someone else’s changes often conflicts.
Option 1: Accept theirs and regenerate:
git checkout --theirs package-lock.json
npm install
git add package-lock.jsonOption 2: Stash, pull, reinstall:
git stash
git pull
npm install # Regenerates lock file with merged dependencies
git add package-lock.json
git commit -m "Update package-lock.json after merge"
git stash popFix 8: Force Checkout (Nuclear Option)
If you want to switch branches and don’t care about any local changes:
git checkout -f <branch-name>Or:
git reset --hard
git checkout <branch-name>Warning: git reset --hard permanently discards all uncommitted changes, both staged and unstaged. There is no recovery. Only use this when you are certain you don’t need any local changes.
If you end up in a detached HEAD state after a forced checkout, see Fix: git detached HEAD.
Stranger Causes I Have Tracked Down
If the error persists after trying the fixes above:
Check for case-sensitivity conflicts. On macOS/Windows, Config.js and config.js are the same file. On Linux, they are different. This can cause phantom “changes” that Git reports as modified even though you haven’t edited them.
Check line ending settings. If core.autocrlf is converting line endings, Git may report files as modified even though the content is the same:
git config core.autocrlfSet it consistently:
git config --global core.autocrlf input # Linux/macOS
git config --global core.autocrlf true # WindowsCheck for file permission changes. On some systems, Git tracks file permission changes. If a file’s executable bit changed, Git reports it as modified:
git config core.fileMode false # Ignore permission changesCheck for git hooks or IDE auto-formatting. Pre-commit hooks or IDE format-on-save can modify files right after you stage them, causing unexpected changes. Disable auto-format temporarily to test.
Use git diff to see what changed:
git diff src/config.jsThis shows exactly what is different between your local file and the last commit. If the diff looks unexpected (whitespace changes, line endings), the issue is likely in your editor or Git configuration, not in your code.
Check the reflog if you already destroyed something. If you ran git reset --hard or git stash drop and lost a change you actually needed, every commit Git has seen in the last 90 days is still in .git/objects and reachable via reflog:
git reflog
# Find the commit hash where your work existed
git checkout <hash> # Inspect it
git cherry-pick <hash> # Or pull it back onto your current branchThis only recovers committed work. Uncommitted edits that were discarded by git checkout -- or git restore are gone; Git never wrote them to the object database. Once you have learned this lesson, commit early and often, even with WIP: messages, so the reflog has something to recover from. See Fix: git reset —hard undo for a deeper walkthrough.
Check for a partially-applied previous merge. If a previous git pull failed halfway through (network drop, Ctrl+C), Git can leave the index in an inconsistent state where files appear modified but git diff is empty. Look for .git/MERGE_HEAD or .git/index.lock:
ls .git/MERGE_HEAD .git/index.lock 2>/dev/null
git merge --abort # if MERGE_HEAD exists
rm .git/index.lock # if a stale lock exists from a crashed git processCheck sparse-checkout patterns. If you use sparse checkout (git sparse-checkout), files outside your sparse pattern are not in your working tree but are still tracked. A pull that updates one of those files can trigger this error in unexpected ways. List the active patterns with git sparse-checkout list and either widen the pattern or use git sparse-checkout reapply.
If none of this helps and you have to force push after resolving, be cautious; force push overwrites remote history and any teammate who already pulled the rewritten branch will see the same “local changes” error in reverse.
What Other Tutorials Get Wrong About This Error
Most Git tutorials list the same fixes but frame them in ways that produce subtle bugs.
They jump to git reset --hard for everything. It is the nuclear option; it discards work permanently and silently. git stash should be the default; reset --hard is the last resort.
They omit git stash -u. Without it, untracked files stay in the working directory and can cause issues. Articles that show plain git stash train readers on the incomplete pattern.
They miss .gitignore + git rm --cached. Adding to .gitignore alone does nothing if the file is already tracked. The two steps are necessary together; articles that show only .gitignore send readers in circles.
They confuse git checkout -- file with git restore file. Both work, but git restore (2.23+) is the modern form. Articles that recommend the older form train readers on the legacy syntax.
They omit git reflog. When a reset --hard discards committed work, the reflog can recover it for up to 90 days. Articles that present reset --hard as “no recovery” leave readers panicking when recovery is actually possible.
They miss package-lock.json as a special case. Lock files conflict on every npm operation. Articles that show generic merge fixes miss that the right answer is usually npm install after pull, not manual conflict resolution.
Frequently Asked Questions
What is the difference between git stash and git stash -u?
git stash saves only TRACKED files (files Git already knows about). git stash -u ALSO saves untracked files (new files you have not staged with git add). The -u form is safer because nothing in your working directory gets left behind during the pull.
Can I recover changes I lost to git reset --hard?
Only if they were committed. The git reflog keeps a 90-day history of where HEAD has pointed, so any commit you made (including WIP commits) can be recovered with git checkout <hash> or git cherry-pick <hash>. Uncommitted edits are NOT in the reflog; they are lost forever.
Why does git pull fail when my changes don’t even touch the same lines?
Git is conservative. The check is at the FILE level, not the line level. If your edit and the incoming change touch any of the same files, Git refuses to proceed even if the edits would not actually conflict line-by-line. This is intentional: Git would rather make you decide than risk silently losing your work.
What’s the difference between git pull and git pull --rebase?
git pull creates a merge commit when your local commits and remote commits diverge. git pull --rebase replays your local commits on top of the remote, producing a linear history without a merge commit. Both achieve the same end state; rebase is cleaner but rewrites your local commit hashes.
Should I commit before pulling, or stash?
If the changes are ready (a logical unit, properly tested), commit them. If they are mid-work (a half-implemented feature), stash. Commits live in your history forever; stashes are temporary. Both are safer than reset --hard.
Why does my IDE keep modifying files I haven’t touched?
Format-on-save, line-ending conversion, or auto-import features can modify files when you open them. The IDE writes the changes; Git sees them as modifications. Either configure the IDE to respect the project’s formatting rules, or commit the formatting baseline so subsequent saves match. The .editorconfig file standardizes this across IDEs.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Git Worktree Not Working — Branch Already Checked Out, Prune, Submodules, and Locked Worktrees
How to fix git worktree errors — fatal: 'branch' is already checked out at, worktree prune removing valid trees, detached HEAD on add, submodules not initialized, moving/locking worktrees, and ignoring per-worktree files.
Fix: Undo git reset --hard and Recover Lost Commits
How to undo git reset --hard and recover lost commits using git reflog — step-by-step recovery for accidentally reset branches, lost work, and dropped stashes.
Fix: .gitignore Not Working (Files Still Being Tracked)
How to fix .gitignore not working — files still showing in git status after being added to .gitignore, caused by already-tracked files, wrong syntax, nested gitignore rules, and cache issues.
Fix: git fatal: A branch named 'x' already exists
How to fix 'git fatal: A branch named already exists' when creating or renaming branches — including local conflicts, remote tracking branches, and worktree issues.