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

GoalCommand
Download remote commitsgit fetch origin
Merge remote into current branchgit merge origin/main
Fetch + merge in one stepgit pull origin main
See branch tipsgit log --oneline --decorate --graph -n 10
Abort a conflicted mergegit merge --abort
Finish after resolving filesgit add <file> then git commit
Publish resolved historygit push
Undo a published commit safelygit revert <sha> then git push
Undo last local commit, keep stagedgit reset --soft HEAD~1
Undo last local commit, keep editsgit reset HEAD~1
Undo last local commit, discard editsgit reset --hard HEAD~1

Practice drills

Stay inside git-merge-lab (recreate the bare remote and clones if needed). Try these without peeking.

  1. Make Alice and Bob diverge on notes.txt again, push from Alice, and reproduce Bob’s rejected push.
  2. On Bob, fetch and merge origin/main, resolve markers so both lines remain, commit, and push.
  3. On Alice, pull the merge, then make a bad commit, push it, and undo it with git revert HEAD + push.
  4. On Bob, create a local-only commit and undo it with git reset --soft HEAD~1, confirming the changes are still staged.
  5. Start a conflicting merge on Bob, then abort it with git merge --abort and confirm git status is 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.