Skip to main content

🔄 Git & GitHub Sync Issues: Understanding and Resolving Mismatches

Sooner or later, every developer types git push and gets a wall of red text back: "Updates were rejected." It feels like the tool is fighting you. It isn't — Git is protecting you from silently overwriting work. This reference decodes what "out of sync" really means and gives you a calm, repeatable playbook for putting local and remote back in agreement.

Reference & Extra Tutorials · Resources · Git Survival Kit

🎯 What This Covers

By the end of this reference, you will be able to:

  • Explain the three places your code lives — working directory, local repo, and remote — and how they drift apart
  • Read git status and tell whether you are ahead, behind, or diverged from the remote
  • Follow the golden rule — pull before you push — and know why it prevents most rejections
  • Resolve a merge conflict from the conflict markers all the way to a clean commit
  • Use git push --force-with-lease safely after rewriting history — and know when never to touch it
  • Adopt daily habits that keep sync problems from ever appearing

Estimated Time: 40 minutes

Practice: Simulate a two-machine conflict and resolve it end to end.

In This Reference

The Three-Place Mental Model

Most sync confusion evaporates once you realize your code lives in three separate places, and Git only moves it between them when you tell it to. Think of it like writing an essay: there's the paragraph you're typing right now, the saved draft on your laptop, and the copy you've emailed to your editor. They are not automatically the same.

PlaceWhat it isHow you move things into it
Working directoryThe files you edit right now, on diskSave in your editor
Staging areaThe changes marked for the next commitgit add
Local repositoryYour committed history on this machinegit commit
Remote (GitHub)The shared history everyone pulls fromgit push / git pull
graph LR A["Working Directory"] -->|git add| B["Staging Area"] B -->|git commit| C["Local Repository"] C -->|git push| D["Remote on GitHub"] D -->|git pull| C

A "sync issue" is simply the last hop — between your local repository and the remote — being out of step. Everything below is about diagnosing and closing that gap without losing anyone's work.

💡 Git is distributed, not live

Unlike a shared Google Doc, Git does not update in real time. Your local history is a full, independent copy. It only learns about the remote when you fetch or pull, and the remote only learns about you when you push. That "snapshot" nature is exactly why drift happens.

Why Repos Drift Out of Sync

Picture GitHub as a shared group chat and your local repo as your private notes. If you jot things down without reading the chat, you fall behind. If you talk in the chat from another device, your notes fall behind. Drift comes from one of three everyday situations:

  • You are ahead. You committed locally but haven't pushed. The remote is missing your work.
  • You are behind. Someone else pushed (or you did, from another machine). Your local copy is missing their work.
  • You have diverged. Both sides gained new commits independently — the dangerous, interesting case that a plain push will reject.
graph TD A["git push rejected"] --> B{"Why?"} B -->|Only you committed| C["You are AHEAD
just push"] B -->|Only they committed| D["You are BEHIND
pull first"] B -->|Both committed| E["DIVERGED
pull, resolve, then push"]

The rejection message you dread — "failed to push some refs" — almost always means diverged. Git is refusing to fast-forward because doing so would drop the commits already on the remote. That refusal is a feature, not a bug.

Reading Ahead, Behind & Diverged

Before you change anything, ask Git where you stand. Two commands do almost all the diagnosis.

Start with git status

git status is you asking, "What's up with my repo right now?" It compares your working directory, staging area, and the remote-tracking branch Git last knew about.

git status

Output — you are ahead

On branch main
Your branch is ahead of 'origin/main' by 2 commits.
  (use "git push" to publish your local commits)

nothing to commit, working tree clean

"Ahead by 2 commits" means the safe, happy case: just push. But that status only reflects what Git knew at your last fetch — the remote may have moved since. So refresh first.

Refresh your knowledge with git fetch

git fetch downloads the remote's latest commits without touching your working files. It's the "read the group chat before replying" step — pure information, zero risk.

git fetch origin
git status

Output — you have diverged

On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 3 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

Now you know the truth: 1 local commit they don't have, 3 remote commits you don't have. This is the diverged case, and the next section is your recipe.

📖 fetch vs pull

git fetch only downloads and updates the remote-tracking branch (origin/main) — your files stay put. git pull is fetch plus an immediate merge into your current branch. When you want to look before you leap, fetch; when you're ready to integrate, pull.

The Standard Fix: Pull, Resolve, Push

For the overwhelming majority of sync problems, one ordered routine solves it. Memorize this rhythm.

sequenceDiagram participant You as Your Machine participant GH as GitHub You->>You: Commit your local work first You->>GH: Fetch and check status GH-->>You: Send the latest remote commits You->>You: Merge remote changes and resolve conflicts You->>GH: Push the reconciled history GH-->>You: Confirm both sides now match

Step 1 — Commit or stash your own work

Never pull on top of uncommitted changes you care about. Commit them, or set them aside with git stash.

git add .
git commit -m "Add contact form validation"

Step 2 — Pull the remote in

Always pull before you push. This brings the remote's commits into your branch so your eventual push is a clean fast-forward. It avoids the coding equivalent of two people showing up to a party in the same outfit.

git pull origin main

If the histories merge cleanly, Git either fast-forwards or creates a small merge commit, and you're done — skip to Step 4. If they conflict, Git pauses and hands the wheel to you (Step 3).

Step 3 — Resolve any conflicts

Covered in full in the next section. Edit the marked files, stage them, and commit.

Step 4 — Push the reconciled history

git push origin main

Output

Enumerating objects: 8, done.
To github.com:yourname/todo-app.git
   a1b2c3d..e4f5g6h  main -> main

Both sides now point at the same commit. You are in sync.

⚠️ Prefer a rebase pull for a tidy history

Plain git pull creates a merge commit each time. For a linear, easier-to-read history on a shared branch, many teams pull with a rebase instead: git pull --rebase origin main. It replays your local commits on top of the remote ones. Just don't rebase commits you've already pushed and shared.

Resolving Merge Conflicts

A merge conflict happens when both sides changed the same lines of the same file, and Git honestly can't decide which version wins. Rather than guess, it stops and asks you. That message looks scary but is really Git saying, "You know the intent — you choose."

Auto-merging src/app.js
CONFLICT (content): Merge conflict in src/app.js
Automatic merge failed; fix conflicts and then commit the result.

Read the conflict markers

Open the file. Git inserts three markers around the disputed region:

<<<<<<< HEAD
const greeting = "Hello there";   // your local version
=======
const greeting = "Hi, welcome!";  // the version from origin/main
>>>>>>> origin/main
  • Between <<<<<<< HEAD and ======= is your change.
  • Between ======= and >>>>>>> origin/main is their change.

Edit to the version you actually want

Delete all three marker lines and leave the final, correct code. You can keep yours, keep theirs, or blend both — whatever the code should truly be:

const greeting = "Hi, welcome!";  // decided: keep the friendlier copy

Stage the resolved file and commit

git add src/app.js
git commit                # opens an editor with a prepared merge message
# or supply your own:
git commit -m "Merge origin/main: keep friendlier greeting copy"

✅ Tools make conflicts painless

You rarely edit markers by hand in practice. Run git mergetool, or use VS Code's built-in three-way merge editor with its "Accept Current / Accept Incoming / Accept Both" buttons. And if you ever want to bail out and start over, git merge --abort returns you to exactly where you were before the pull.

graph TD A["Conflict reported"] --> B["Open the marked file"] B --> C["Choose or combine both sides"] C --> D["Delete the conflict markers"] D --> E["git add the file"] E --> F["git commit"] F --> G["git push"]

Force Push: The Emergency Parachute

Sometimes you deliberately rewrite local history — you rebased, amended a commit, or squashed several into one. Now your local branch and the remote have genuinely different histories, and a normal push is rejected because Git won't discard remote commits. The only honest fix is to overwrite the remote:

# Safer force: refuse if the remote moved since you last fetched
git push origin main --force-with-lease

⚠️ Force push overwrites shared history

A force push replaces the remote branch with yours. If a teammate had commits there that you didn't have, you just erased them. Treat it like replacing a page in a shared notebook — only safe when you're certain no one else wrote on it.

Always prefer --force-with-lease over the blunt --force. The "with lease" variant checks that the remote is still where you last saw it and aborts if someone pushed in the meantime — a built-in safety catch that plain --force skips entirely.

SituationSafe to force-push?
Your own private feature branch, no one else on it✅ Yes, with --force-with-lease
A shared branch others are actively using❌ No — coordinate or open a new branch
The main branch of a team repo❌ Almost never — usually protected on purpose

A Real Team Scenario

You and a teammate are building a to-do list app. Walk through exactly what happens and what you type:

  1. You finish a feature and commit locally: git commit -m "Add due-date field".
  2. Meanwhile your teammate pushed a fix straight to GitHub. The remote now has a commit you lack.
  3. You try git pushrejected. You've diverged.
  4. You run git pull origin main. Git reports a conflict in app.js because you both edited the same function.
  5. You open app.js, read the markers, keep the correct blend of both changes, remove the markers.
  6. You stage and commit: git add app.js && git commit.
  7. You push again — accepted. You're both on the same page.

💡 The takeaway

Nothing here required force pushing or fancy commands. Ninety percent of "Git broke my repo" moments are just this loop: commit → pull → resolve → push. Run it calmly and the red text goes away.

Practice & Quiz

🏋️ Exercise 1: Manufacture and fix a divergence

Goal: Deliberately create an "ahead and behind" situation in a safe test repo, then reconcile it. This is the fastest way to make the concept stick.

💡 Hint

Create a repo on GitHub, clone it, then edit the same file in the GitHub web UI (that's your "teammate"). Back in your clone, commit a different change to that file and try to push. Then run git pull and resolve the conflict.

✅ Solution
# 1. In your local clone, make and commit a change
echo "local line" >> notes.txt
git add notes.txt
git commit -m "Local edit to notes"

# 2. (On github.com) edit notes.txt in the browser and commit — the "teammate"

# 3. Back locally, this push is rejected (diverged)
git push origin main        # ! [rejected] ... fetch first

# 4. Pull, resolve the conflict, commit
git pull origin main        # CONFLICT in notes.txt
#   ...edit notes.txt, remove the <<<</====/>>>> markers...
git add notes.txt
git commit -m "Merge remote notes edit"

# 5. Now the push succeeds
git push origin main

🏋️ Exercise 2: Diagnose before acting

Goal: Given a fresh clone that may be behind, determine your exact position relative to the remote without changing any files.

✅ Solution
git fetch origin          # download remote state, touch nothing
git status                # "ahead", "behind", or "diverged"
git log --oneline --graph --all -n 15   # see both histories visually

Only after reading this do you decide whether to push, pull, or pull-then-resolve.

🎯 Quick Quiz

Question 1: Your push is rejected with "failed to push some refs." What should you almost always do next?

Question 2: What is the difference between git fetch and git pull?

Question 3: When rewriting shared history, which push is the safer choice?

Best Practices & Pitfalls

✅ Do

  • Run git fetch and git status before you push, so surprises never reach the remote
  • Always pull before you push on a shared branch
  • Commit or stash your work before pulling, so you never merge on top of a dirty tree
  • Do new work on a feature branch and merge via a Pull Request — it isolates conflicts
  • Commit small and push often; tiny changes conflict far less than huge ones

❌ Don't

  • Reach for --force as a first response to a rejected push — pull instead
  • Force push to main or any branch teammates share
  • Blindly accept "yours" or "theirs" in a conflict without reading both sides
  • Delete and re-clone the repo to "fix" sync problems — you'll lose unpushed local commits
  • Ignore the status message; Git usually tells you the exact command to run next

⚠️ "Detached HEAD" is not a sync error

If you ever see You are in 'detached HEAD' state, you've checked out a specific commit rather than a branch. It's unrelated to remote sync. Get back on track with git switch main (or git checkout main), then continue as normal.

Summary

🎉 Key Takeaways

  • Your code lives in three places — working directory, local repo, and remote — and Git only moves it on command
  • Drift is always one of ahead, behind, or diverged; git fetch then git status tells you which
  • The universal fix is the loop commit → pull → resolve → push
  • Merge conflicts are Git asking you to choose; edit the marked region, remove the markers, add, commit
  • Reserve force pushing for rewritten history on your own branch, and prefer --force-with-lease

📚 Additional Resources

🚀 What's Next?

Now that you can keep a repo in sync, browse the rest of the reference library. Up next is the HTML Files Index — a complete, categorized map of every extra tutorial and cheat sheet in this collection.

🎉 The red text no longer scares you

Sync problems are just Git protecting your team's work. Read the status, pull before you push, and resolve with intent — that's the whole game.