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.