Interactive Rebase Workflows Jump to heading

Interactive rebase gives you direct, surgical control over commit history before it lands on a shared branch. As part of Conflict Resolution & Safe Merge Operations, this workflow sits between raw feature development and final integration: you linearize commits, fix messages, collapse WIP noise, and eliminate merge artifacts before reviewers ever see the diff.

Prerequisites Jump to heading

Before starting any interactive rebase session, confirm:


Interactive rebase commit lifecycleFlowchart showing five stages: feature branch commits, rebase todo list, action execution loop (pick/squash/fixup/reword/edit/drop), conflict resolution gate, and final force-with-lease push to remote.Feature branchN commitsRebase todo listpick a1b2 feat: add Xsquash c3d4 wipfixup e5f6 typoApply actionpick / rewordsquash / fixupedit / dropper commitnext commitConflict?Y / Nno conflictResolvegit add--continueyesForce-push--force-with-leaseβ‘  branchβ‘‘ editorβ‘’ apply loopβ‘£ gateβ‘€ publish

Step 1 β€” Configure the Environment Jump to heading

Intent: Enable the Git settings that make interactive rebase safer and less repetitive.

# Cache conflict resolutions; replays them if the same hunk recurs
git config --global rerere.enabled true

# Auto-order --fixup commits above their targets when rebasing
git config --global rebase.autoSquash true

# Move sibling branch pointers when rebasing stacked branches (Git 2.38+)
git config --global rebase.updateRefs true

Verification:

git config --global --list | grep -E "rerere|rebase"
# Expected output:
# rerere.enabled=true
# rebase.autosquash=true
# rebase.updaterefs=true

Step 2 β€” Open the Interactive Todo List Jump to heading

Intent: Identify the commits to rewrite and choose an action for each one.

# Rebase every commit since main that is not yet merged
git rebase -i main

# Alternative: rebase the last N commits regardless of branch
git rebase -i HEAD~5

Git opens your $EDITOR with one line per commit, oldest first:

pick a1b2c3d feat: add payment gateway integration
pick e4f5g6h fix: handle nil user in checkout
pick i7j8k9l wip: debug logging
pick m0n1o2p chore: remove debug logging

Available action verbs:

ActionEffect
pickKeep the commit exactly as-is
rewordKeep the commit but open the editor to change its message
editPause after applying; use git commit --amend or split the commit
squashMerge into the commit above; opens editor to combine messages
fixupMerge into the commit above; silently discards this message
dropRemove the commit entirely from the branch

Verification: After saving and closing the editor, run:

git log --oneline main..HEAD
# Confirm the commit list matches your intended shape

Step 3 β€” Use --fixup and --autosquash for Automated Compaction Jump to heading

Intent: Mark correction commits at write-time so --autosquash collapses them automatically, avoiding manual todo-list editing.

# While working: create a fixup targeting a specific earlier commit
git commit --fixup a1b2c3d   # message becomes "fixup! feat: add payment gateway integration"

# Rebase: --autosquash reorders and collapses fixup commits automatically
git rebase -i --autosquash main

With rebase.autoSquash=true in your config, omit the flag:

git rebase -i main   # --autosquash applied automatically

Verification:

git log --oneline main..HEAD
# The fixup commit should no longer appear as a separate entry

SAFETY WARNING: --autosquash silently reorders commits. If a fixup target SHA no longer exists (because a prior rebase already squashed it), Git will error. Always check git log --oneline before starting.

Step 4 β€” Resolve Conflicts During the Rebase Loop Jump to heading

Intent: Unblock a halted rebase when an individual commit’s patch conflicts with the current branch state.

When a conflict stops the rebase, Git prints the conflicting paths and waits:

# Check what is conflicting
git status
# Look for "both modified" entries

# Open the file in your editor, resolve the conflict markers, then stage
git add src/payments/gateway.py

# Resume applying the remaining commits
git rebase --continue

If rerere has seen this conflict before, it replays the cached resolution automatically β€” you still need to verify and run git add before --continue.

To cancel and restore your branch to its pre-rebase state:

git rebase --abort

Verification: After each --continue, run git diff HEAD~1 HEAD to confirm the resulting commit contains only the intended change.

SAFETY WARNING: Never edit files directly inside .git/rebase-merge/. Always use git add + git rebase --continue to maintain index integrity. Editing those internal files corrupts the rebase state and may require git rebase --abort to recover.

Step 5 β€” Force-Push the Rewritten Branch Jump to heading

Intent: Publish the rewritten history to the remote without overwriting work pushed by others since your last fetch.

git push --force-with-lease origin feat/payment-gateway

--force-with-lease checks that the remote ref still matches the SHA you last fetched. If another engineer pushed to the same branch while you were rebasing, the push is rejected and no work is lost. Bare --force skips this check and must not be used on shared branches.

For branches with pre-push validation rules configured, the hook runs before the push. If it fails, fix the issue β€” do not bypass hooks with --no-verify.

Verification:

git log --oneline origin/feat/payment-gateway..feat/payment-gateway
# Should output nothing β€” local and remote are in sync

SAFETY WARNING: Force-pushing to a branch that other engineers have based work on rewrites their merge bases and forces them to re-sync manually. Confirm isolation or coordinate a push window before proceeding.

Integration with Adjacent Workflows Jump to heading

3-Way Merge Fundamentals Jump to heading

When the underlying goal is integration rather than history cleanup, 3-way merge fundamentals produce a non-destructive record of the merge event. The boundary of responsibility is clear: use interactive rebase to clean up a feature branch before opening a pull request; use merge to record the integration event on the mainline. The two are complementary, not competing.

Squash and Fixup Strategies Jump to heading

Squash and fixup strategies extend the squash/fixup actions into a team-wide convention, covering when to squash on merge via the GitHub UI versus during the rebase itself. The key distinction: squashing during rebase preserves the logical commit boundary on the feature branch for local review; squashing on merge collapses everything to a single commit on main. Many teams combine both: clean up within the feature branch via interactive rebase, then allow the merge queue to squash to one commit on mainline.

CI/CD Automation Jump to heading

After a force-push, any CI/CD pipeline trigger mapping that caches artifacts by commit SHA must invalidate those caches. Configure path-aware triggers so that only the changed sub-trees re-run expensive jobs. Pre-rebase hooks β€” managed through local hook configuration with Husky β€” can validate commit message conventions and run fast linters before the operation starts, embedding quality gates before history is rewritten.

Troubleshooting Jump to heading

SymptomLikely CauseFix
fatal: invalid upstream 'main'Branch name mismatch or missing remote refRun git branch -a to confirm the base name; use origin/main if the local tracking branch is absent
Rebase stops mid-way with no conflict shownedit action pauses intentionallyAmend the commit with git commit --amend, then git rebase --continue
--force-with-lease push rejectedRemote was updated after your last fetchRun git fetch origin, inspect the new commits, and re-run the rebase on top of the updated remote
rerere replays a wrong resolutionCached resolution is stale or was recorded on an unrelated conflictClear the cache with git rerere forget <path> and re-resolve manually
GPG signatures missing after rebaseRebase rewrites SHAs, invalidating signaturesAdd --gpg-sign to the rebase command or set commit.gpgSign=true globally
Stacked branch pointer left behindrebase.updateRefs not enabledEnable with git config --global rebase.updateRefs true (Git 2.38+) and re-run

Recovery Procedures Jump to heading

If a rebase is interrupted or produces wrong results, git reflog provides a full chronological record of every HEAD movement:

git reflog | head -20
# Look for the entry immediately before "rebase (start)"

Restore from that SHA:

git reset --hard <sha-before-rebase>

ORIG_HEAD also points to the pre-rebase HEAD immediately after the operation completes:

git reset --hard ORIG_HEAD

SAFETY WARNING: git reset --hard discards all uncommitted changes in the working tree. Stash or commit anything you need before running it.

For the full coordination protocol on shared branches β€” including reflog retention policies, GPG verification, and DCO compliance after rewrites β€” see Safe git rebase -i for shared branches.

Frequently Asked Questions Jump to heading

Is it safe to rebase a branch that is already open as a pull request? Jump to heading

It is safe only if you are the sole contributor and you notify any reviewers before force-pushing. GitHub and GitLab preserve the review thread but mark existing diff comments as outdated after the force-push. Always use --force-with-lease. If the PR is under active review with multiple participants, consider completing the review cycle first, then rebasing before final merge.

What is the difference between squash and fixup in git rebase -i? Jump to heading

Both merge the marked commit into the one above it. squash opens the editor so you can combine or edit the resulting message. fixup silently discards the child commit message and keeps only the parent message, making it the right choice for automated --autosquash workflows where the correction commit is noise, not content.

How do I recover from a rebase that went wrong? Jump to heading

Run git reflog to find the SHA labelled rebase (start) and restore it with git reset --hard <sha>. Alternatively, git reset --hard ORIG_HEAD works immediately after most rebase operations complete. If the rebase is still in progress, use git rebase --abort to cancel it cleanly.

Can I rebase and keep GPG commit signatures intact? Jump to heading

No. Rebasing rewrites SHA hashes, which invalidates existing GPG signatures. Pass --gpg-sign to the rebase command, or set commit.gpgSign=true globally, to re-sign each replayed commit.

What does rebase.updateRefs do? Jump to heading

When you maintain stacked branches β€” branch B based on branch A, branch C based on B β€” rebase.updateRefs=true automatically moves the sibling branch pointers as each branch is rebased, preventing them from being left pointing at orphaned commits. Available in Git 2.38 and later.