git status --short
git branch backup/before-squash
git rebase -i HEAD~3Start with a clean working tree and a private feature branch. The backup branch preserves the starting commit. In the editor, leave the oldest of the three commits as pick and change the other two to squash or fixup. Save the plan, resolve any conflicts and review the result before publishing it.
Use a fresh backup-branch name if backup/before-squash already exists. These examples assume a simple linear history; merge commits need a deliberate plan rather than blindly counting HEAD~N.
Choose the range before rewriting
Inspect the branch first:
git log --oneline --graph --decorate -10HEAD~3 names the first-parent ancestor three commits back. In a linear history, rebasing onto it selects the last three commits. It requires that ancestor to exist, so it cannot include the repository's root commit. To edit a history including the root, use:
git rebase -i --rootTo select commits unique to your branch and replay them onto the current local main:
git rebase -i mainThat can also incorporate new work from main; it is not merely a different spelling for squashing at the original branch point. Check which base you intend. By default, rebase does not preserve the original merge topology. See merge versus rebase when the branch has merges.
Squash into the preceding commit
The plan lists commits oldest first:
pick a1b2c3d Add login form
squash d4e5f6a Add validation
fixup 7a8b9c0 Fix label typoThe result is one commit containing all three changes. squash and fixup fold their commit into the preceding group, which is why the first selected commit normally remains pick.
Before: base -- A -- B -- C (feature)
After: base -- ABC (feature)The combined commit gets a new object ID; the original objects remain reachable through the backup branch until you remove that reference.
pick, squash, fixup and reword
| Action | What happens |
|---|---|
pick | Replay the commit as a separate commit |
squash | Fold it into the preceding commit and edit the combined message |
fixup | Fold it into the preceding commit and discard this commit's message |
reword | Keep the change but edit its message |
Plain fixup is useful for a typo correction whose message adds no information. Its -c and -C variants have different message behavior; the table describes the plain action.
For a fix intended for a specific earlier commit:
git commit --fixup=a1b2c3d
git rebase -i --autosquash HEAD~5The target must be included in the selected range. Autosquash moves the marked fix next to its target and sets the appropriate action. Review the plan instead of accepting a range that misses the commit you meant to amend.
For one message change, use reword in the plan. If it is only the most recent commit, git commit --amend is simpler, subject to the same shared-history warning.
Conflicts, abort and recovery
When rebase stops on a conflict, edit the files, stage the resolved version and continue:
git add path/to/resolved-file
git rebase --continueTo stop the rebase and return to its starting branch state:
git rebase --abortAbort discards conflict-resolution edits made during the rebase. Save any resolution work you want to keep separately before aborting. git rebase --skip discards the commit currently being replayed; it is not a generic conflict fix.
After completion, compare the final tree to the backup when the only intended change was commit grouping:
git diff backup/before-squash HEAD
git log --oneline --graph --decorate -10For a pure squash, the diff should be empty. If you also rebased onto newer upstream work, inspect those expected changes separately. Run the project's relevant tests.
If the result is wrong, keep the current state on a new branch and inspect the backup or reflog. Do not jump straight to reset --hard with uncommitted work: it can discard that work.
Choose the interactive editor
GIT_SEQUENCE_EDITOR=nano git rebase -i HEAD~3GIT_SEQUENCE_EDITOR chooses the editor for the instruction list. sequence.editor is its configuration equivalent. GIT_EDITOR or core.editor controls ordinary commit-message editing and is the fallback for the plan when no sequence editor is configured. Set GIT_EDITOR=nano as well if you want Nano for the combined commit message.
Squash a branch into the target branch
For “put this feature into one new commit on the target,” merge-squash leaves the original feature branch alone:
git switch main
git merge --squash my-feature
git diff --cached
git commit -m "Add the feature"Start with a clean working tree and an up-to-date target. Resolve conflicts if the merge reports them. --squash prepares the merge result in the working tree and index without making a merge commit or recording the feature as a merged parent. The final commit is an ordinary single-parent commit on main.
On a protected repository, do this on an integration branch and use the required pull-request flow, or use the host's squash-merge option. This operation does not rewrite my-feature, so it does not inherently need a force push.
Reset to a verified base and recommit
For a private, simple feature branch, a soft reset is another way to flatten its changes. Find the base rather than assuming that main still points at the old branch point:
# Run while on the feature branch, with a clean index and working tree.
base=$(git merge-base main HEAD)
git show --no-patch --oneline "$base"
git diff --stat "$base" HEAD
# After checking the base and diff:
git reset --soft "$base"
git diff --cached
git commit -m "Combine the feature changes"This moves the current feature branch to the base and preserves its previous tree in the index. The new commit contains the net difference from that base. It does not bring in later main changes; integrate those separately.
Do not replace the base with main without checking ancestry. If main advanced independently, reset --soft main can stage removal of changes that exist only on main. For a complex history with multiple merge bases, use a reviewed rebase or merge strategy instead of this shortcut.
Publish a rewritten private branch carefully
A local-only branch can normally be pushed without force. Rewriting a branch that already exists remotely can require a non-fast-forward update. Coordinate with anyone using it, respect repository rules and avoid rewriting shared main.
Before rewriting, fetch and inspect the remote branch, then save its exact commit ID in the same shell:
git fetch origin
git log --oneline HEAD..origin/my-feature
expected=$(git rev-parse refs/remotes/origin/my-feature)If the log reveals remote work you have not included, stop and reconcile it. After the rewrite and review:
git push --force-with-lease="refs/heads/my-feature:$expected" origin HEAD:refs/heads/my-featureThis explicit lease requires the remote to still equal the reviewed commit. A teammate's later push causes rejection. The shorter --force-with-lease uses remote-tracking information, which a background fetch can update without your review. Neither form proves that your rewritten content is correct. See force-pushing safely for the full tradeoffs.
FAQ
See also
Sources
Authoritative references this article was fact-checked against.
- Git rebase and interactive editorgit-scm.com
- Git merge --squashgit-scm.com
- Git reset modesgit-scm.com
- Git merge-basegit-scm.com
- Git force-with-lease behaviorgit-scm.com





