Conflicts aren’t chaos — just a chance to fetch, rebase, resolve, and push.
It’s 7:15 p.m. You’ve just finished tweaking a deployment script after a long day of staging issues, server restarts, and Slack pings. You run your final test, everything looks perfect, and you confidently type:
|
1 2 |
git push origin main |
…and Git slaps you with a wall of red text:
|
1 2 3 |
! [rejected] main -> main (fetch first) error: failed to push some refs... |
Your heart sinks. The day was so close to being over. But no — someone else pushed changes to main while you were working. Now your local repo and the remote branch have diverged, and Git refuses to combine them automatically.
Welcome to one of the most common rites of passage in software development: resolving merge conflicts.
Why This Happens
In Git’s world, the remote branch (origin/main) is the “source of truth.”
If another teammate (or an automated deployment bot) pushes commits before you do, your local main becomes out of date.
When you try to push, Git sees that your local history doesn’t include those remote commits — and it refuses the operation to prevent overwriting someone else’s work.
Step 1: Stay Calm and Fetch
Start by syncing your local view of the repo:
|
1 2 |
git fetch origin --prune |
This pulls the latest info from GitHub (or wherever your repo lives) without merging anything yet.
You can confirm what you’re missing:
|
1 2 |
git status |
If you see something like:
|
1 2 |
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded. |
Then yes — someone beat you to the push.
Step 2: Rebase to Catch Up (The Clean Way)
To neatly stack your local changes on top of what’s already on origin/main, use:
|
1 2 |
git pull --rebase origin main |
This tells Git:
“Pretend I made my commits after the latest ones from the remote branch.”
If there are no conflicts, Git will quickly replay your commits on top. Done.
But if there are conflicting edits (e.g., you and a teammate both touched the same line), Git pauses the rebase and marks the files with conflict markers like:
|
1 2 3 4 5 6 |
<<<<<<< HEAD your local code ======= their remote code >>>>>>> origin/main |
At this point:
- Open the file in your editor.
- Decide which code should stay (yours, theirs, or a mix).
- Save and stage the resolved file:
|
1 2 |
git add <file> |
Then continue the rebase:
|
1 2 |
git rebase --continue |
Repeat until Git finishes replaying your commits.
💡 Tip: If things get messy, you can always reset the rebase safely:
12 git rebase --abort
Step 3: Push With Confidence
Once your rebase is complete, your local branch history matches the remote.
Now it’s time to push:
|
1 2 |
git push origin main |
This time, Git smiles and accepts your changes.
What If You Don’t Want to Rebase?
Some teams prefer merge commits to keep a visible record of when branches were combined.
If that’s your team’s policy, you can use:
|
1 2 |
git pull --no-rebase origin main |
Then resolve conflicts as usual and commit the merge.
But for most modern workflows — especially on projects where multiple developers push frequently — rebasing keeps history tidy and readable.
Out of Scope (But Good to Know)
There are more complex scenarios, like:
- Rebasing public branches with shared commits
- Handling long-running feature branches that span multiple PRs
- Recovering from a bad rebase or force-push
Those deserve their own deep dive — we’ll save that for another post.
Final Thoughts
Resolving merge conflicts isn’t a failure — it’s a normal part of teamwork. Once you get comfortable with rebasing, conflict resolution, and Git’s guardrails, you’ll start to see the elegance in how it protects your codebase from chaos.
So next time Git makes a fuss,
Don’t let the warning make you blush.
Just fetch, rebase, resolve, and push —
Git’s your friend, not out to crush!




