what does git rebase do — it takes a range of commits and replays them, one at a time, onto a different starting point, rewriting every replayed commit with a brand-new hash instead of combining histories the way a merge does. That's rebase meaning in one sentence; the rest of this git rebase tutorial covers how to use git rebase in practice — rebasing a branch onto main or master, git rebase --onto for stacked branches, git pull vs git pull --rebase, resolving conflicts during a rebase (which invert ours/theirs compared to a merge conflict, a detail most tutorials skip), interactive rebase at a glance, and undoing a rebase gone wrong with git reflog. If you already know rebase is what you want and just need to combine several commits into one, that narrower operation is interactive rebase's squash mode, covered in full in the dedicated guide to squashing commits.

The Fast Answer: What Git Rebase Does in One Sentence

Git rebase takes the commits unique to your branch and re-applies them, in order, on top of a different base commit — usually the current tip of main or master — so your branch ends up looking like it was built starting from where that branch is right now. Nothing about your code changes; what changes is which commit each of your commits claims as its parent, and because a commit's hash is computed partly from its parent's hash, every replayed commit gets a new identity.

# Standard case: rebase your current branch onto main
git checkout my-feature-branch
git fetch origin
git rebase origin/main

That's git rebase main (or git rebase master, or git rebase feature branch with master, depending on which branch you're standing on and which one you're rebasing onto) in three commands: fetch to make sure your view of the target is current, then rebase. If a conflict interrupts it, skip to resolving conflicts during a rebase. If you want the full definition before running anything, the next section covers exactly what "rebase" means as a word and as a Git operation — consider the rest of this guide a git rebase tutorial that starts from that definition and works outward to every practical case a rebasing branch actually hits.

Rebase Meaning: The Definition, Plainly

Define rebase: to change the base commit a set of commits builds on, by removing them from their current base and reapplying them onto a new one. That's the rebase definition outside Git too — "base" is the foundation something stands on, and "re-base" is standing it on a different foundation instead. Git rebase meaning follows the word directly, and rebasing meaning doesn't shift depending on which branch or how many commits are involved: your branch's commits currently sit on top of some ancestor commit; rebasing moves that ancestor to a newer one and rebuilds everything above it to match. Git leans on plain-English words for command names more than most tools do — you have to define cherry picking as an orchard idiom before git cherry-pick reads as anything but jargon, and rebase is the same kind of borrowing.

What does rebase mean in git, worked through an example: say main had three commits when your branch split off, and both main and your branch have since added more commits of their own. Rebasing here replays just your branch's new commits — not main's — on top of main's current tip, commit by commit, in their original order. Your branch afterward looks exactly as if you'd started it today, from main's latest state, instead of from three commits ago, and the loop is identical whether it's replaying 1 commit or 100. What does rebase do differently from a merge is the point worth sitting with before moving on — a merge keeps both histories and joins them with a new commit that has two parents; a rebase discards the old commits entirely and creates new ones with a single parent each, which is why the result reads as one straight line instead of two branches meeting. The official git-rebase reference documentation has git rebase explained at the plumbing level, flag by flag, for anyone who wants the primary source alongside this walkthrough.

Rebase: Detach From the Old Base, Replay Onto the New BEFORE — commits on old base B0 D E 9f8e7d6 d4e5f6a a1b2c3d D and E sit on old base B0 rebase onto B1 AFTER — commits replayed B1 D' E' 3c2b1a9 f4a9c21 8b7c6d5 Same diffs, brand-new hashes
Before, D and E sit on old base B0 with their original hashes; after rebasing onto B1, D' and E' carry identical diffs but every replayed commit gets a brand-new hash.

What is rebase used for, in practice: pulling in the latest main before opening a review so your diff applies cleanly against current code, and keeping a personal branch's history linear and easy to read instead of full of merge commits and out-of-order timestamps. What is rebasing not used for: bringing changes into a branch that other people are also actively committing to — that's what merge is for, and the decision matrix below spells out exactly where the line sits.

What Rebase Actually Does to Your Commits

Mechanically, git rebase <target> runs a loop: find every commit on your current branch that isn't on the target, then for each one — oldest first — check out the target (or the result of the previous replay), apply that commit's diff, and create a new commit with the applied diff, the original commit's message, author identity, and author date, but a new parent — and a fresh committer date, set to when the rebase actually ran, since the commit object itself is new. Repeat until every commit in the range has been replayed, then move your branch pointer to the last new commit.

Before:
  A---B---C  (main)
       \
        D---E---F  (my-feature-branch)

git checkout my-feature-branch
git rebase main

After:
  A---B---C  (main)
           \
            D'--E'--F'  (my-feature-branch)

D', E', and F' are new commit objects — new SHA-1 hashes — even though their diffs are identical to D, E, and F. That's the detail behind every "did my code change?" worry people have about rebasing: no, the content didn't change; the commit's identity did, because a commit's hash is a function of its tree, its message, its author/committer metadata, and critically, its parent's hash. Swap the parent and the hash changes even though nothing else about that commit is different. The original D, E, F aren't deleted immediately — they become unreachable from any branch or tag, which is exactly what makes reflog recovery possible for a while afterward.

This is the same new-hash mechanism that makes squashing commits a history rewrite too — rebase is squashing's parent operation, in the sense that git rebase -i is literally how most people squash. The difference here is scope: this article covers rebase as a way to move a whole branch's commits onto a new base, not as a way to combine several of them into fewer commits.

How to Rebase a Branch Onto Main or Master

The everyday case — how to use git rebase to bring your feature branch up to date with main before opening a pull request — is four commands, and the first two matter as much as the rebase itself. Rebase branch onto main, verify, done: this is the rebasing branch workflow most people are actually after, whichever of the phrasings above they arrived with.

# 1. Update your local view of the remote without touching any files
git fetch origin

# 2. Stand on the branch you want to rewrite
git checkout my-feature-branch

# 3. Replay your branch's commits onto the remote's current main
git rebase origin/main

# 4. If it completes cleanly, verify
git log --oneline --graph -10

git fetch first, same as before any merge, matters here for the same reason: git rebase main without a fresh fetch replays your commits onto whatever your local main last knew, not what's actually on the remote — you'd rebase onto stale ground and have to do it again once you fetch properly. Rebasing onto origin/main directly (the remote-tracking ref) instead of local main skips that problem entirely, the same logic covered in full in fetch vs pull.

If your default branch is named master instead — git rebase master, git rebase from master, git rebase master into branch, rebase branch onto master — nothing about the mechanics changes, only the ref name. Rebasing from master onto a feature branch is a different direction and rarely what you want; this section is specifically about rebasing your branch onto master's tip, not the reverse:

git fetch origin
git checkout my-feature-branch
git rebase origin/master
Rebasing Onto main: The Four-Command Sequence 1. Fetch git fetch origin sync the remote-tracking ref 2. Checkout git checkout my-branch stand on the branch to rewrite 3. Rebase git rebase origin/main replays commits, new hashes 4. Verify git log --oneline --graph confirm the result looks right each replayed commit gets a new hash Only Rebase rewrites anything — Fetch, Checkout and Verify never touch commit history
Fetch, checkout, rebase, verify — the same four-step shape as merging a branch up to date, except the Rebase step replays each commit with a new hash instead of creating one new merge commit.

Rebasing a feature branch with master (or main) is also written the other direction sometimes — git rebase feature branch with master, git rebase main into branch — but the operation is identical regardless of phrasing: whatever branch you have checked out is the one whose commits move; the argument to git rebase is always the new base, never the branch being rewritten. Get that backwards — checking out main and running git rebase my-feature-branch — and you rewrite main's history instead, which on a shared branch is exactly the mistake the golden rule further down exists to prevent.

git rebase --onto: Moving a Branch to a Different Base

Plain git rebase <target> assumes you want to replay everything since the branch's original split point. git rebase --onto is for when that assumption is wrong — moving a branch, or part of one, to a base that isn't its original ancestor at all.

git rebase --onto <new-base> <old-base> <branch>

A concrete case: you branched feature-b off feature-a because it depended on unfinished work there, but feature-a shipped and merged into main before feature-b was ready. Rebasing feature-b straight onto main would try to replay feature-a's commits too, which are already on main and would produce duplicate, conflicting versions of the same changes.

# Replay only feature-b's own commits (everything after feature-a)
# onto main, skipping feature-a's commits entirely
git rebase --onto main feature-a feature-b

Read the arguments right to left: take feature-b, find everything after feature-a (that's the part actually unique to it), and replay just that range onto main. This is also the tool for dropping a bad commit out of the middle of a branch without opening the interactive editor — rebase --onto the commit right after the one you want gone, targeting the commit right before it, and that commit is simply never replayed.

git rebase --onto: Replay Only feature-b's Commits Onto main BEFORE — feature-b stacked on feature-a (already merged into main) main M0 A1 A2 main tip feature-b B1 B2 still stacked on feature-a's tip AFTER — git rebase --onto main feature-a feature-b main M0 A1 A2 B1' B2' feature-b, replayed directly onto A2 A1 and A2 are never replayed again — only B1 and B2 move, becoming B1' and B2'
Before, feature-b's B1 and B2 sit stacked on feature-a's already-merged A1 and A2. --onto replays only B1 and B2 straight onto main's tip — A1 and A2 are skipped because they're already there.

Rebase vs Merge: The Decision Matrix

Git merge rebase questions almost always reduce to one axis: is the history you're about to rewrite shared with anyone else yet? Both operations bring one branch's changes into another, but they diverge completely on what happens to existing commits.

Situation Use Why
Bringing main's changes into your active feature branch Either — merge is safer by default, rebase if the branch is still yours alone Merge never rewrites; rebase gives a cleaner line but needs a force-push if already pushed
Branch is pushed and someone else has it checked out or pulled it Merge Rebasing rewrites commits they already have, forcing everyone to reconcile diverged history
Cleaning up your own branch before opening a pull request Rebase (interactive, for squashing/reordering too) Nobody else depends on the current hashes yet — safe to rewrite freely
Syncing a long-lived branch (release, develop) with main regularly Merge Repeated rebases replay the same conflicts over and over; merge resolves each once and keeps it
Team enforces a linear main branch / squash-and-merge policy Rebase locally, or let the platform's "rebase and merge" button do it Keeps main's log readable as one commit per change with no merge-commit noise
You need to preserve exactly when and how a merge happened, for audit or bisecting Merge Rebase erases the original timeline; merge commits record the real integration history
Same Goal, Different Shape: Merge Keeps Both Lines, Rebase Makes One MERGE — both histories survive main M0 M1 feature F1 F2 M 2 parents M1, F1 and F2 all remain, untouched REBASE — one straight line M0 M1 F1' F2' F1 and F2 rewritten as F1' and F2' — new hashes Merge preserves history; rebase rewrites it into a single line
Merge joins main and feature at a new two-parent commit and leaves both lines on the graph; rebase detaches feature's commits and redraws them as a single straight line on top of main's tip.

The mechanical difference underneath all six rows: merge creates one new commit with two parents and leaves every existing commit untouched, which is why it's safe on a branch anyone else has a copy of. Rebase creates a new commit for every one it replays and abandons the originals, which is exactly what makes the resulting history linear — and exactly what makes it unsafe to do to commits someone else already pulled. The full mechanics of git merge — fast-forward vs three-way, conflict markers, previewing a merge before running it — are covered in their own guide; this one stays focused on what rebase specifically does.

git pull vs git pull --rebase

git pull is shorthand for git fetch followed by an integration step — by default a merge, but --rebase swaps that integration step for a rebase instead. Git pull vs rebase, or git pull vs git rebase as a standalone command rather than a flag, worked out concretely:

# Default: fetch, then merge the remote's changes in
git pull
# same as: git fetch origin && git merge origin/<current-branch>

# Rebase variant: fetch, then replay your local commits on top
git pull --rebase
# same as: git fetch origin && git rebase origin/<current-branch>

Git pull vs rebase matters specifically when you have local commits that aren't on the remote yet and someone else has also pushed in the meantime. Plain git pull creates a merge commit joining your local commits and the ones you just fetched — harmless, but it adds a two-parent commit to the log every time this happens, which on an active branch with frequent small syncs turns into a lot of merge-commit noise. git pull --rebase instead detaches your local commits, fast-forwards to the fetched tip, and replays your commits on top — no merge commit, a straight line, but your local commits get new hashes in the process, same as any rebase.

Git pull rebase vs git rebase — these aren't different operations, just different starting points: git pull --rebase always fetches first and rebases onto whatever it just fetched for your current branch specifically; a standalone git rebase <ref> works against whatever ref you name, fetched or not, current branch or not. In practice git pull --rebase is the everyday convenience wrapper; plain git rebase is the general tool underneath it.

Making --rebase the default for every pull, so a bare git pull behaves this way without typing the flag:

# For the current repo only
git config pull.rebase true

# For every repo on this machine
git config --global pull.rebase true

Should I use git pull --rebase — as a personal default on branches only you push to, generally yes; it keeps history linear with no downside since nothing you're rewriting is shared. On a branch actively shared with teammates, this setting quietly rebases local commits that might already be pushed, which reopens the exact force-push situation covered next — so many teams leave pull.rebase off globally and opt in per-pull with the flag instead.

Resolving Conflicts During a Rebase

A rebase conflict happens for the same underlying reason a merge conflict does — the same region of a file was changed two different ways — but the mechanics of resolving it, and what "ours" and "theirs" mean, are inverted from what a merge conflict trains you to expect.

$ git rebase origin/main
Auto-merging src/config.ts
CONFLICT (content): Merge conflict in src/config.ts
error: could not apply a1b2c3d... Add timeout config
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".

Because a rebase works by checking out the new base and replaying your commits one at a time on top of it, during a rebase, HEAD is the new base you're rebasing onto — not your branch. That flips --ours and --theirs compared to a merge: --ours now means the target branch's version (main), and --theirs means the commit currently being replayed — your own commit. In a merge it's the opposite, since HEAD there is still your own branch. This is the single most common source of "I picked the wrong side" during a rebase, and it's worth checking deliberately rather than trusting muscle memory carried over from merge conflicts.

# During a rebase: keep the target branch's version (main), discard your commit's
git checkout --ours src/config.ts

# During a rebase: keep your commit's version, discard the target's
git checkout --theirs src/config.ts

git add src/config.ts
git rebase --continue

The full resolution loop, once a conflict pauses the rebase:

# 1. See which files need attention
git status

# 2. Edit each one, delete the <<<<<<< ======= >>>>>>> markers,
#    keep the correct code

# 3. Stage the resolution
git add src/config.ts

# 4. Move to the next commit in the replay queue
git rebase --continue

Because a rebase replays commits one by one, a single git rebase can pause on a conflict more than once — resolving one doesn't mean the whole rebase is done, just that commit. git rebase --continue moves to the next commit in the queue and may immediately hit another conflict if a later commit also touches the same lines. Two other controls matter here that don't exist in a merge conflict at all: git rebase --skip drops the current commit entirely instead of resolving it — its changes vanish from the result, which is rarely what you want but occasionally correct for a commit that's now fully redundant with the new base. git rebase --abort is the full undo: it stops the rebase and restores your branch to exactly where it stood before you ran git rebase, no partial replay left behind, the direct rebase equivalent of aborting a merge.

Conflict Markers Flip Meaning: HEAD Is Different in Each DURING A MERGE <<<<<<< HEAD your branch's changes ======= incoming changes >>>>>>> main HEAD = your branch --ours = your branch --theirs = main DURING A REBASE <<<<<<< HEAD target branch's changes (main) ======= your commit's changes >>>>>>> your commit HEAD = the new base, not your branch --ours = main (flipped) --theirs = your commit (flipped) Same marker syntax, opposite meaning — check which side is which before resolving
The marker syntax is identical, but HEAD points at your branch during a merge and at the new base during a rebase — which flips what --ours and --theirs each keep.

For a large or unfamiliar conflict, reading the two competing versions as plain files side by side is often faster than parsing inline markers in a terminal. git show pulls either version out as text:

git show :2:src/config.ts > /tmp/theirs-mine.ts   # your commit's version, mid-rebase
git show :3:src/config.ts > /tmp/ours-main.ts     # the target branch's version, mid-rebase

Diff Checker, a free Chrome extension (also usable directly at diffchecker.pro), doesn't know what a commit or a rebase is — there's no git integration — but it's exactly the right tool for the two files that command just produced. Paste one version into each editable pane and it renders a live side-by-side or unified diff with syntax highlighting, so the exact lines in conflict are visible without scrolling a wall of angle brackets. Ignore Whitespace filters out reformatting noise that would otherwise bury the real disagreement, and "Show Diff Only" collapses everything unchanged so only the disputed region is on screen. For reading the resulting unified-diff-style output more generally, the guide to unified diff format and every git diff flag cover the CLI side in full.

Interactive Rebase in 60 Seconds

git rebase -i runs the exact same replay mechanism as a plain rebase, except before it starts, Git opens an editable todo list — one line per commit — and lets you change what happens to each one instead of just picking it as-is.

git rebase -i origin/main
pick a1b2c3d Add timeout config
pick e4f5a6b Fix typo
pick c7d8e9f Add retry logic

# Commands:
# p, pick   = use commit as-is
# r, reword = use commit, but edit the message
# e, edit   = use commit, but stop to amend it
# s, squash = fold into previous commit, combine messages
# f, fixup  = fold into previous commit, discard this message
# d, drop   = remove commit entirely
Verb What It Does
pickReplay the commit unchanged — the default for every line
rewordReplay it, but pause to let you edit the commit message
editReplay it, then stop entirely so you can amend the commit's content before continuing
squashFold into the commit above it, keeping both messages for you to combine
fixupFold into the commit above it, silently discarding this one's message
dropRemove the commit from the branch entirely (or just delete the line)

Reordering lines reorders the replay itself — move a line up, and that commit applies earlier in the sequence, which only works cleanly if it doesn't depend on changes from a commit still below it. Saving and closing the editor kicks off the replay exactly like a non-interactive rebase, pausing on conflicts the same way. Everything about squash and fixup specifically — the difference between them, autosquash with fixup! commits, squashing the last N commits, verifying a squash before you force-push — has its own full treatment in the guide to squashing commits; this section is only the map of what the todo-list verbs mean.

Rebasing Stacked Branches with --update-refs

Stacked branches — feature-2 built on top of unmerged feature-1, itself built on top of unmerged feature-0 — used to make rebasing painful for a specific reason: rebasing the bottom branch onto main replays its commits with new hashes, but every branch pointer above it in the stack still points at the old, now-abandoned commits. Each one needed its own separate rebase afterward, by hand, in order.

# Git 2.38+ — rebase feature-0 onto main, and update every
# branch pointer stacked on top of it automatically
git checkout feature-0
git rebase --update-refs origin/main

--update-refs (added in Git 2.38) walks any local branch pointers that sit between the commit range being rebased and HEAD, and moves each one to point at its commit's new replayed counterpart. So rebasing feature-0 with --update-refs checked out also drags feature-1 and feature-2's branch pointers forward to the new hashes, in the same operation, instead of leaving them stranded on the old commit chain.

git config --global rebase.updateRefs true

Setting rebase.updateRefs makes this the default behavior for every rebase, so stacked branches stay in sync without remembering the flag. This is worth knowing even outside a formal stacked-diff workflow — anytime a local branch's tip is also the ancestor of another local branch you're tracking, --update-refs is what keeps both pointers correct after a rebase instead of just the one you rebased directly.

The Golden Rule: When Not to Rebase

Don't rebase commits that other people already have a copy of. That's the entire rule as Pro Git's rebasing chapter states it, and everything else in this section is just what it looks like in practice.

  • The branch is pushed and someone else pulled it, forked it, or has it checked out. Rebasing rewrites those commits' hashes on your machine; their copy still has the old ones. The next time they pull, Git sees two histories that share a past but no longer share hashes past the split point, and reconciling that is confusing even for people who understand rebase well.
  • It's a shared long-lived branch — main, master, develop, a release branch. These exist precisely so multiple people can build on a stable, append-only history. Rebasing one is rewriting the ground everyone else is standing on.
  • You're not sure who else has the branch. When in doubt, treat it as shared. Checking is cheap; a force-push that breaks three teammates' local branches is not.
  • The commit history itself is documentation you need to preserve — for an audit trail, for bisecting a regression against exactly what shipped when, or for compliance reasons that require an unaltered record. Rebase erases the original timeline; merge preserves it.

The branches where rebase is unambiguously fine: a feature branch only you have pushed, still open as a pull request nobody else has branched from, that you're cleaning up before requesting review. The moment a second person's workflow depends on your branch's current commits — they branched off it, they're reviewing a specific commit by hash, they pulled it locally — rebasing crosses from "my own scratch space" into "everyone's shared history," and the golden rule applies.

Undoing a Rebase with git reflog

A rebase that went wrong — the wrong commits marked drop, a conflict resolved backward because of the ours/theirs inversion, or just a change of mind mid-way — is recoverable as long as you haven't run git gc since, because the original commits aren't deleted immediately; they just become unreachable from any branch. git reflog is the log of every position your branch pointer has held, and it still records the position from right before the rebase started.

$ git reflog show HEAD
a9f3c21 HEAD@{0}: rebase (finish): returning to refs/heads/my-feature-branch
a9f3c21 HEAD@{1}: rebase (pick): Add retry logic
7d8e1f2 HEAD@{2}: rebase (pick): Fix typo
3b4c5d6 HEAD@{3}: rebase (pick): Add timeout config
3b4c5d6 HEAD@{4}: rebase (start): checkout origin/main
e7b8c9d HEAD@{5}: commit: Add retry logic

HEAD@{5} in that log — the entry immediately before rebase (start) — is your branch exactly as it stood the moment before the rebase touched anything. Resetting there undoes the entire rebase in one command, original commits and original hashes restored:

git reset --hard HEAD@{5}
# or, using the commit hash directly:
git reset --hard e7b8c9d

This is the direct rebase equivalent of what git reflog does for undoing a botched commit or any other reset — the safety net isn't a special rebase feature, it's a general property of how Git keeps every ref update logged locally, regardless of which command moved the pointer. The catch is scope: reflog is local-only and machine-specific, so if you've already force-pushed the rebased branch, restoring locally and pushing again is itself a second force-push, and it's worth confirming with anyone who might have already pulled the rebased version before overwriting it a second time. Reflog entries also aren't permanent — by default Git keeps reachable reflog entries for 90 days and unreachable ones for 30 (gc.reflogExpire and gc.reflogExpireUnreachable), and prunes the unreachable commits behind them on a separate two-week schedule (gc.pruneExpire). So the window isn't infinite, just generous enough to cover "I rebased five minutes ago and it's wrong."

git reflog: The Checkpoint That Survives a Rebase pre-rebase rebase (start) replay replay replay rebase (finish) @5 @4 @3 @2 @1 @0 safe checkpoint current tip git reset --hard HEAD@{5} restores the original commits and hashes
HEAD@{5} is the checkpoint from right before the rebase started; HEAD@{4} through HEAD@{1} are each replayed commit; HEAD@{0} is the finished result — resetting back to HEAD@{5} undoes the whole thing.

Team Rebase Policy: A Practical Matrix

"Never rebase shared branches" is correct but too vague to actually enforce — most teams need a concrete answer for each situation their workflow actually produces, not one blanket rule repeated in onboarding docs and then quietly ignored under deadline pressure.

Scenario Policy Rationale
Your own feature branch, not yet pushed Rebase branch freely Nothing exists anywhere else to diverge from
Your own feature branch, pushed, no one else has pulled it Rebase, then --force-with-lease Still effectively private; lease protects against overwriting a push you didn't see
Feature branch a teammate branched off or is co-authoring Merge only, or coordinate the rebase explicitly beforehand A silent rebase breaks their branch's ancestry without warning
Open pull request, mid-review, minor fixups requested Rebase and force-push is standard practice on most teams Reviewers expect this; GitHub/GitLab track force-pushes and diff against the prior version
main / master / develop / release branches Never rebase — merge or fast-forward only Definitionally shared; rewriting breaks every clone and CI reference
Keeping a long-lived feature branch current with main Merge main in periodically, don't rebase onto a moving main repeatedly Each rebase onto a new main tip risks replaying the same conflicts again
Stacked PRs / stacked branches Rebase with --update-refs, coordinated as a stack Keeps every dependent branch pointer consistent in one operation

The thread running through every row: rebase is safe exactly as far as "no one else's work depends on these specific commit hashes yet," and that boundary is a fact about who's pulled what, not about which branch name you happen to be on. A branch called feature/x that a teammate quietly checked out to review is just as shared, in this sense, as main — the name doesn't confer safety, the absence of other copies does. When it's genuinely unclear whether a branch counts as shared, defaulting to merge — or asking — costs a few minutes; a force-push that breaks someone else's local checkout costs considerably more.

One flag worth internalizing regardless of which row applies: once a force-push is needed at all, git push --force-with-lease instead of a bare git push --force is the safer default. --force-with-lease refuses to overwrite the remote branch if it has moved since your last fetch — someone else pushed in between — where plain --force overwrites unconditionally and silently discards whatever they just added.

Frequently Asked Questions

What does git rebase do?

git rebase moves or replays a sequence of commits from one base commit onto another, rewriting each replayed commit with a new hash instead of creating a merge commit. Practically, git rebase main takes the commits unique to your branch and reapplies them one by one on top of main's current tip, producing a straight, linear history as if you'd started your branch from where main is now.

What's the difference between git rebase and git merge?

git merge combines two branches' histories with a new two-parent commit and never rewrites existing commits, so it's safe on shared branches. git rebase replays your branch's commits onto a new base, giving every replayed commit a new hash — that produces cleaner, linear history but is unsafe on any branch someone else has already pulled, since their copy and yours diverge the moment you rewrite.

How do I recover from a failed or botched rebase?

git reflog show HEAD lists every position your branch pointer held recently, including the exact commit right before the rebase started — usually logged as "rebase (start)" or the last entry before the first "rebase (pick)" line. git reset --hard against that entry's hash restores your branch to exactly its pre-rebase state, original commits and hashes included, as long as you haven't run git gc since.

Can I rebase commits I've already pushed?

Yes, mechanically — rebase doesn't check whether commits are pushed. The catch is what happens after: the rewritten commits have new hashes, so a normal git push is rejected as a non-fast-forward, and you need git push --force-with-lease to overwrite the remote. Anyone else who already pulled the old commits then has a diverged history and has to reconcile it, which is why rebasing shared, already-pushed commits is generally discouraged.

Should I use git pull --rebase instead of a plain git pull?

For a personal feature branch with no one else pulling from it, git pull --rebase is usually the better default — it replays your local, unpushed commits on top of the remote's latest instead of creating a merge commit for every sync, keeping history linear. On a branch actively shared with teammates, a plain git pull (or git pull --no-rebase) is safer, since --rebase rewrites commits that might already be shared.