You committed. You push. Git refuses: the remote already has commits you do not. Often the same file changed on both sides. That is not a broken repo — it is divergent history. This page shows how to merge remote work into yours, resolve conflicts, and undo mistakes with revert or a careful local rollback.
If you still need init, stage, commit, and first push, start with Git Without the Panic. Here we assume that loop already feels familiar.
Warm-up: a two-clone lab
You need a fake “remote” and two working copies — you and a teammate — so conflict demos stay local. Create a bare repository (server-shaped, no working tree) and two clones:
mkdir -p git-merge-lab
cd git-merge-lab
git init --bare remote.git
git clone remote.git alice
git clone remote.git bob
Seed a shared file from Alice’s clone, push it, and update Bob so both start from the same commit:
cd alice
echo 'Shared project notes' > notes.txt
git add notes.txt
git commit -m "Add shared notes"
git branch -M main
git push -u origin main
cd ../bob
git pull origin main
Use alice/ and bob/ for the scenarios below. Delete git-merge-lab when you are done.
Note: A bare repo is only for this lab. On GitHub or GitLab the remote already exists; origin points there the same way.
Mental model: why push fails
Git will not overwrite remote commits you have never seen. If origin/main moved ahead (or diverged) while you committed locally, git push is rejected until your branch includes that remote work.
Two outcomes after you bring remote commits in:
- Fast-forward — your branch had no unique commits; Git just slides the pointer forward. No merge commit.
- Merge commit — both sides have unique commits; Git combines histories. If the same lines changed, you get a conflict and must choose the final text.
git fetch downloads remote commits without changing your files. git merge origin/main (or git pull, which fetch+merges) applies them. Prefer seeing fetch and merge as separate steps while you learn — conflicts are easier to reason about that way.
Hero use case: stuck after a conflicting push
This is the everyday trap: you and someone else edited the same file. They pushed first. Your push fails.
Divergent edits
In Alice’s clone, change the shared file and push:
cd alice
echo 'Alice: deploy checklist' >> notes.txt
git add notes.txt
git commit -m "Add Alice deploy note"
git push
In Bob’s clone, edit the same file without pulling first, then commit:
cd ../bob
echo 'Bob: rollback plan' >> notes.txt
git add notes.txt
git commit -m "Add Bob rollback note"
Bob tries to publish:
git push
Git rejects the push — remote main has Alice’s commit Bob does not have. Do not reach for —force to “win.” Fetch and merge instead.
Fetch, see the fork, merge
git fetch origin
git status
git log --oneline --decorate --graph -n 10
You should see your local commit and origin/main on different tips. Merge the remote branch into Bob’s main:
git merge origin/main
(git pull origin main does fetch + merge in one step. Same conflict appears if the file overlaps.)
Read and fix conflict markers
Git pauses with notes.txt unmerged. Open the file. Conflict sections look like this:
<<<<<<< HEAD
Bob: rollback plan
=======
Alice: deploy checklist
>>>>>>> origin/main
HEAD is your side (Bob). The other side is what came from origin/main (Alice). Edit the file to the result you want — keep both notes, drop one, or rewrite the paragraph — and delete the marker lines (<<<<<<<, =======, >>>>>>>).
Example of a finished file:
Shared project notes
Alice: deploy checklist
Bob: rollback plan
Stage, commit the merge, push
git add notes.txt
git status
git commit -m "Merge origin/main; keep Alice and Bob notes"
git push
Local and remote main now agree. Alice can git pull to receive the merge commit.
Note: Mid-conflict and want to bail? git merge --abort returns you to the pre-merge state. Your local commit remains; only the in-progress merge is canceled.
Note: Force-pushing over teammates’ commits rewrites shared history and causes more pain. Resolve the merge; do not erase their work.
Merge on purpose
Conflicts are just merges where Git needs help. Once you have a feature branch (see Stay Off Main), merge it into main the same way:
git switch main
git pull origin main
git merge feature/login
git push origin main
If Git reports conflicts, fix markers, git add the files, and finish with git commit (Git often opens a default merge message — keep it or replace with -m).
Revert a bad commit (safe on shared branches)
You pushed a commit that broke something, and others may already have pulled it. Revert adds a new commit that undoes that change. History stays honest; nobody’s clone is rewritten.
Find the bad commit, then revert it:
git log --oneline -n 5
git revert HEAD
Or revert a specific hash:
git revert a1b2c3d
Git opens a message (or accept the default). Push the revert:
git push
Note: git revert can itself conflict if later commits touched the same lines. Resolve markers the same way as a merge, then git add and git revert --continue. To cancel an in-progress revert: git revert --abort.
Rollback local commits (reset)
Use reset when the commit is still only on your machine — or you accept rewriting a branch nobody else depends on. Pick the mode by what you want to keep:
Undo the last commit but leave changes staged:
git reset --soft HEAD~1
Undo the last commit; keep changes in the working tree (unstaged) — this is the default mixed reset:
git reset HEAD~1
Undo the last commit and discard those file changes:
git reset --hard HEAD~1
Note: —hard permanently drops uncommitted work in tracked files. Prefer --soft or mixed until you are sure. Once a commit is on the remote and teammates may have it, prefer git revert over git reset + force-push.
Note: If you reset too far by mistake, git reflog can show recent HEAD positions so you can recover — a rescue tool, not everyday workflow.
Quick reference card
| Goal | Command |
|---|---|
| Download remote commits | git fetch origin |
| Merge remote into current branch | git merge origin/main |
| Fetch + merge in one step | git pull origin main |
| See branch tips | git log --oneline --decorate --graph -n 10 |
| Abort a conflicted merge | git merge --abort |
| Finish after resolving files | git add <file> then git commit |
| Publish resolved history | git push |
| Undo a published commit safely | git revert <sha> then git push |
| Undo last local commit, keep staged | git reset --soft HEAD~1 |
| Undo last local commit, keep edits | git reset HEAD~1 |
| Undo last local commit, discard edits | git reset --hard HEAD~1 |
Practice drills
Stay inside git-merge-lab (recreate the bare remote and clones if needed). Try these without peeking.
- Make Alice and Bob diverge on
notes.txtagain, push from Alice, and reproduce Bob’s rejected push. - On Bob, fetch and merge
origin/main, resolve markers so both lines remain, commit, and push. - On Alice, pull the merge, then make a bad commit, push it, and undo it with
git revert HEAD+ push. - On Bob, create a local-only commit and undo it with
git reset --soft HEAD~1, confirming the changes are still staged. - Start a conflicting merge on Bob, then abort it with
git merge --abortand confirmgit statusis clean of merge state.
Solid answers — clear and portable, not the only ones:
# 1–2 (from bob/, after alice pushed a conflicting line)
git fetch origin
git merge origin/main
# edit notes.txt: remove markers, keep both notes
git add notes.txt
git commit -m "Merge origin/main; keep both notes"
git push
# 3 (from alice/)
git pull origin main
echo 'oops' >> notes.txt
git add notes.txt && git commit -m "Bad note" && git push
git revert HEAD && git push
# 4 (from bob/)
echo 'local only' >> notes.txt
git add notes.txt && git commit -m "Local experiment"
git reset --soft HEAD~1
git status
# 5
git fetch origin
# create a conflict again, then:
git merge origin/main
git merge --abort
git status
If you can work through those five, you cover the recovery loop most teams need: sync with fetch/merge, resolve conflicts without force-push, undo published mistakes with revert, and roll back local commits with reset. Day-one snapshot skills live in Git Without the Panic. To create and publish a feature branch before you merge it, continue with Stay Off Main: Git Branches Without the Fear.