DevOps & Workflow6 min read

    How to use git rebase

    By DanaServer Monitoring & Linux
    Share

    git rebase takes the commits on your branch and replays them, one by one, on top of a different base commit. The result is a linear history that looks as if you had branched from the latest tip of the target instead of where you originally branched. It's how teams keep main history readable and how individuals tidy up their work before opening a PR.

    Rebase is powerful but rewrites commit SHAs. Use it on local branches and feature branches you alone work on; do not rebase commits other people have already based work on.


    Basic rebase: bring your branch up to date

    You're on feature/x, branched from main. main has moved on. Bring your branch on top of the new main:

    git switch feature/x
    git fetch origin
    git rebase origin/main
    

    What happens:

    1. Git temporarily sets aside your feature/x commits.
    2. It moves your branch to point at origin/main.
    3. It replays your commits on top, one at a time.
    4. Each commit gets a new SHA but the same author, message, and changes.

    If there are no conflicts, you end up with a clean linear history. Push:

    git push --force-with-lease
    

    The --force-with-lease is needed because the SHAs changed; a regular git push is rejected. Never use plain --force--force-with-lease refuses if someone else has pushed in the meantime.


    Interactive rebase: squash, edit, reorder, drop

    git rebase -i HEAD~5
    

    Git opens an editor with the last 5 commits listed in chronological order:

    pick a1b2c3d feat: search ui
    pick d4e5f6g fix typo
    pick 7h8i9j0 wip
    pick k1l2m3n add tests
    pick o4p5q6r polish styling
    

    Change pick on each line to one of:

    • p, pick — keep as-is.
    • r, reword — keep changes, edit the commit message.
    • e, edit — pause after this commit so you can amend it.
    • s, squash — combine with the previous commit; keep the message of both.
    • f, fixup — same as squash but discards this commit's message.
    • d, drop — remove the commit entirely.

    Reorder lines to reorder commits. Save and quit the editor — git replays the commits according to your edits, pausing whenever it needs input.

    A common pre-PR cleanup pass:

    pick a1b2c3d feat: search ui
    fixup d4e5f6g fix typo
    fixup 7h8i9j0 wip
    pick k1l2m3n add tests
    pick o4p5q6r polish styling
    

    Squash mistakes into the feature commit, keep the test and polish commits separate.


    Pause to edit a commit

    If you set a line to edit, git stops after applying that commit:

    # git is paused here. Make your changes, then:
    git add path/to/file
    git commit --amend
    git rebase --continue
    

    Or, to drop in a new commit at this point:

    git commit -m "missed change"
    git rebase --continue
    

    Resolving conflicts during rebase

    A rebase can produce one conflict per replayed commit:

    git rebase main
    # CONFLICT (content): Merge conflict in src/config.ts
    # resolve markers in src/config.ts
    git add src/config.ts
    git rebase --continue
    

    If you don't want this commit at all:

    git rebase --skip
    

    To bail out completely:

    git rebase --abort
    

    --abort is safe — it puts your branch back exactly where it was before the rebase started.

    Tip: enable git rerere once and forget about it:

    git config --global rerere.enabled true
    

    Git remembers your conflict resolutions and replays them automatically the next time the same conflict comes up — invaluable on long rebases.


    --onto: rebase a slice of commits onto a different base

    --onto is the answer to "I branched from the wrong place" or "I want to lift the last 3 commits onto a different branch":

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

    Example: you branched feature/x from feature/y, but feature/y is being abandoned. Move feature/x to be based on main instead:

    git rebase --onto main feature/y feature/x
    

    This replays the commits that are on feature/x but not on feature/y onto main.


    Pull with rebase instead of merge

    To avoid merge commits when pulling:

    git pull --rebase
    

    Or set it as the default:

    git config --global pull.rebase true
    

    Now git pull rebases your local commits on top of the fetched remote instead of merging.


    Autosquash: pre-mark fixup commits

    If you make a fix commit while still working on a feature, label it for a future squash:

    git commit --fixup <sha-of-the-target-commit>
    

    Later, run:

    git rebase -i --autosquash main
    

    The fixup commits are pre-arranged in the right places with the right keywords. You just save and quit.

    To make autosquash always implied:

    git config --global rebase.autosquash true
    

    The golden rule

    Do not rebase commits that exist outside your local repository and that other people may have based work on.

    Rebasing rewrites SHAs. If a teammate based their branch on your old SHAs and you force-push the new ones, their next pull will be a mess. Safe candidates for rebase:

    • Local branches you haven't pushed.
    • Feature branches that only you work on, even if pushed (force-push needed afterwards).
    • Branches where the team has explicitly agreed rebase is the workflow.

    Unsafe:

    • main, master, release branches.
    • Any branch other people have checked out and based work on.
    • Pushed branches with open PRs that already have review comments tied to specific SHAs (you'll lose the comment anchors).

    When in doubt, use git merge instead. It produces a merge commit but doesn't rewrite anything.


    Practical recipes

    Clean up before a PR

    git fetch origin
    git rebase -i origin/main
    # squash WIPs, reword messages, drop dead-end commits
    git push --force-with-lease
    

    Pull in latest main without a merge commit

    git fetch origin
    git rebase origin/main
    

    Move the last commit to a different branch

    git switch other-branch
    git cherry-pick <sha>
    git switch original-branch
    git reset --hard HEAD~1
    

    (Yes, the simpler cherry-pick + reset is often cleaner than --onto for a single commit.)

    Abort and pretend it never happened

    git rebase --abort
    

    Recover after a botched rebase

    git reflog show <branch>
    # find the entry just before the rebase ("rebase: checkout ..." line)
    git reset --hard <branch>@{N}
    

    Or, before risky rebases, make a backup branch first:

    git branch backup/feature-x
    git rebase -i main
    # if it goes wrong:
    git reset --hard backup/feature-x
    

    Pitfalls

    • Force-pushing to a shared branch. Coordinate, and use --force-with-lease, never plain --force.
    • Rebase makes line-by-line review harder. PR review tools sometimes can't compare across a rebase. Push small fixup commits during review and squash only at the end.
    • Long rebases conflict on every commit. If a feature branch is wildly out of date, sometimes a clean merge from main followed by a single squash is less work than a 40-commit rebase.
    • Tags and signed commits. Rebase rewrites commits, breaking signatures. Re-sign with git rebase --exec 'git commit --amend --no-edit -S' or sign at the end.

    Summary

    • git rebase <base> replays your commits on top of <base>, producing a linear history.
    • git rebase -i HEAD~N is your tool for squashing, reordering, and rewording before a PR.
    • Conflicts: resolve, git add, git rebase --continue. Bail with --abort.
    • Push rewritten branches with --force-with-lease, never plain --force.
    • Don't rebase shared history. Use merge there.
    • git reflog is the safety net if a rebase goes wrong.