The day Git taught me the difference between merge and rebase
A deep dive into accidental merges, silent deletions, and why Git’s three-way merge can ‘correctly’ make your files disappear.
KMS ITC
There are Git errors that shout at you.
And there are Git mistakes that stay completely silent.
The silent ones are the dangerous ones.
This is the story of one of them — and the mental model shift that finally made it predictable.

Act I — the accident
I had a feature branch:
job-portal
From it, I branched:
job-portal-2
Meanwhile, an unfinished version of job-portal was accidentally merged into master.
Production wasn’t supposed to include that feature.
By the time I realized it, other commits had already landed on master. Reverting the merge cleanly would have been messy (and high-risk).
So I made a practical decision:
- Manually delete the unintended files
- Commit the deletions
- Push to
master - Restore production to its correct state
At that point:
masterwas correct- but the Git history had changed in a subtle way
And that subtlety would matter.
Act II — the mystery of the missing files
Later, I wanted job-portal-2 to contain:
- latest
master - plus the full
job-portalfeature
So I merged master into job-portal-2.
No conflicts.
Everything looked fine.
Except something wasn’t fine.
The job-portal files were gone. Completely.
No errors. No warnings. Just… absent.
I then tried:
git merge job-portal
Still nothing came back.
At this point, it felt like Git was ignoring me.
It wasn’t.
Git was being perfectly logical.
Act III — the merge algorithm (and why deletion “wins”)
Here is the critical principle:
Git does not compare branches by current file state.
Git compares changes since the common ancestor.
That’s three-way merge logic.
Let’s simplify the history.
Before the accident
job-portaladds filesjob-portal-2branches offjob-portal
After the accidental merge + manual cleanup
mastercontains a merge commit that introduced the feature- then a later commit deletes those feature files
Now the important part:
When I merged master into job-portal-2, Git looked for:
common_ancestor(master, job-portal-2)
That ancestor was before the deletion.
So Git compared:
- ancestor →
master: files deleted - ancestor →
job-portal-2: no changes to those files
Git concluded:
- the deletion happened
- the feature branch didn’t modify those files after the fork
- therefore, deletion wins
This is correct three-way merge behavior.
Why merging job-portal didn’t resurrect the files
Because from Git’s perspective:
- the files already existed at the fork point
- they were deleted later in
master - the feature branch never modified them after that point
So Git saw no “new change” to apply.
There is no concept of “restore what’s different right now.”
Git reasons in terms of change, not state.
Once you accept that, the behavior stops being mysterious.
Act IV — why rebase works when merge doesn’t
Merge compares histories.
Rebase rewrites history.
That’s the key distinction.
When I ran:
git checkout job-portal
git rebase master
Git performed these steps:
- found the fork point
- temporarily removed feature commits
- moved the branch pointer to the latest
master - replayed feature commits one by one
Visually:
Before rebase
master: … → (delete feature files)job-portal: feature commits that originally added those files
After rebase
job-portalcommits are now applied after the deletion
That subtle change is everything.
Commits that originally “added a file” are now being replayed onto a branch where that file does not exist — so Git treats them as fresh additions.
The files came back.
Not because I forced them.
But because I replayed intent.
Merge vs rebase — a systems view
Here’s the operational mental model that actually holds up under pressure:
- Merge asks: “What changed in each branch since the fork?”
- Rebase asks: “What if this feature was written today on top of that branch?”
So:
- Merge preserves historical truth (including deletions).
- Rebase preserves developmental intent (by replaying commits on a new base).
In production recovery scenarios, intent often matters more — but it comes with one big cost: you’re rewriting history.
Practical guidance (when to use what)
Use merge when you want:
- maximum auditability (no rewritten history)
- shared branches with many collaborators
- to preserve “what really happened” in the DAG
Use rebase when you want:
- a feature branch that behaves as if it was written on top of the latest
main - to resolve confusing ancestry (like silent deletion outcomes)
- a clean, linear review story (especially before opening a PR)
Act V — the hidden commit (interactive rebase as hygiene)
After the rebase succeeded, I inspected the branch history and noticed something odd:
- an unrelated “MFA implementation” commit
It belonged to another unfinished branch.
Rebase had faithfully replayed everything in my branch — including what I didn’t want.
So I cleaned it up with interactive rebase:
git rebase -i master
In the editor, I changed that unrelated commit from pick to drop.
Then Git replayed only the commits that actually belonged.
This is feature branch hygiene at its best:
- not cosmetic
- not “nice to have”
- an architectural asset for preventing future confusion
Act VI — the force push discipline
One final subtlety:
Rebase rewrites commit hashes.
That means your local branch no longer matches the remote.
So clicking “Sync” in an IDE can be dangerous.
Use:
git push --force-with-lease
Not plain --force.
And avoid blind pull/sync after rewriting history.
Rebase demands deliberate pushes.
Engineering lessons extracted
This wasn’t just a Git problem. It revealed deeper discipline:
-
Production is a source of truth
- If
masterchanges, feature branches must adapt.
- If
-
Merge is conservative
- It respects historical deletion.
-
Rebase is declarative
- It says: “Apply my feature as if written now.”
-
Silent success is not correctness
- If Git does nothing, it may be doing exactly what you told it.
-
Clean history is an architectural asset
- Interactive rebase prevents future ambiguity.
The deeper reflection
Git is not about files.
It is about change graphs.
Once you understand that Git reasons over commit DAGs — not directories — everything becomes predictable.
The real shift happened when I stopped asking:
“Why doesn’t Git see my files?”
And started asking:
“What does Git think changed?”
That question changed everything.
If you’d like, reach out via /contact and we’ll share a lightweight “Git operating model” we use for production teams: branching rules, rebase/merge policy, and the minimum checks that prevent silent failure modes like this one.