Squashing without interactive rebase Jump to heading
The textbook way to squash commits is an interactive rebase: open the todo list, change every pick after the first to squash, save, then edit a combined message. It works, but it is fiddly for the most common case — “make this whole branch one commit” — and it replays every commit, so conflicts that were resolved along the way can come back. Two simpler commands do the same job without replaying anything. git reset --soft moves the branch back to its base while keeping every change staged; one commit then captures them all. git merge --squash stages a branch’s combined changes onto another branch. This page shows both, what each leaves behind, and when the interactive form is still better, within squash and fixup strategies.
When to use this approach Jump to heading
- You want a whole branch, or the last N commits, as a single commit.
- An interactive rebase of the branch keeps hitting conflicts you already resolved.
- You are squashing in a script, where an editor-driven todo list is awkward.
- If you need to keep some commits separate and fold others in, interactive rebase or autosquash is still the right tool — see using fixup commits and autosquash during review.
Step 1 — Squash a whole branch with reset --soft Jump to heading
git reset --soft <base> moves the branch pointer back to the base but leaves the index and working tree exactly as they were at the tip. Everything the branch changed is now staged as one difference, ready for a single commit.
git switch feature/export-scheduling
base=$(git merge-base main HEAD)
git log --oneline "$base..HEAD" # the commits about to be combined
git reset --soft "$base"
git status --short | head # every change from the branch, staged
git commit -m "feat(export): schedule CSV exports per account" # Verification: the single commit has exactly the branch's old content
git diff ORIG_HEAD HEAD --stat # expect empty: same tree, different history
git log --oneline main..HEAD # one commit Because nothing is replayed, there are no conflicts. The new commit’s tree is identical to the old tip’s tree.
Step 2 — Squash the last N commits Jump to heading
The same idea works for part of a branch: reset to the commit before the ones you want to combine.
git log --oneline -5
git reset --soft HEAD~3 # combine the last three commits
git commit -m "fix(billing): handle zero-value credit notes" To reuse the message of the oldest commit being squashed rather than typing a new one, pass it with -C:
git reset --soft HEAD~3
git commit -C ORIG_HEAD~2 # message and authorship of the first of the three Step 3 — Squash another branch onto yours with merge --squash Jump to heading
git merge --squash computes the merge of another branch into your current one, stages the result, and stops without creating a merge commit or recording the other branch as a parent. You then commit once.
git switch main
git merge --squash feature/export-scheduling
git commit # opens an editor with a summary of squashed commits Conflicts are possible here, exactly as in any merge, because the branch’s changes are being combined with whatever main gained since. Resolve them, then commit.
⚠️ SAFETY WARNING: After
merge --squash, the source branch is not recorded as merged.git branch -dwill refuse to delete it andgit branch --mergedwill not list it, and a later ordinary merge of the same branch would try to apply its changes again. Delete the source branch deliberately with-Donce the squash commit is on main, or detect it as described in detecting squash-merged branches.
Step 4 — Keep authorship and trailers Jump to heading
Both methods create a commit authored by you. If others contributed to the branch, collect them as co-authors before squashing, while the original commits are still easy to read.
base=$(git merge-base main HEAD)
git log --format='Co-authored-by: %an <%ae>' "$base..HEAD" | sort -u | grep -v "$(git config user.email)" > /tmp/coauthors
git reset --soft "$base"
git commit -F - <<EOF
feat(export): schedule CSV exports per account
Exports can now run on a per-account schedule instead of on demand.
$(cat /tmp/coauthors)
EOF The fuller procedure is in preserving co-authors when squashing.
Step 5 — Push the squashed branch safely Jump to heading
A squash rewrites the branch’s history. If the branch was already pushed, force-push it with a lease.
git push --force-with-lease origin feature/export-scheduling Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Is reset --soft dangerous? Jump to heading
It changes only the branch pointer; files and the index stay as they were, so no work is lost. If you change your mind immediately, git reset --soft ORIG_HEAD puts the old commits back.
Why does merge --squash not make a merge commit? Jump to heading
By design: it records the changes but not the relationship. That is exactly what a forge’s squash-merge button does, and why squashed branches do not show as merged.
Can I squash a branch that has merged main into itself? Jump to heading
Yes. reset --soft to the merge base with main captures the branch’s net change, and the merges of main disappear into the single commit — which is usually what you want before opening a pull request.
Related Jump to heading
- Squash & Fixup Strategies — the parent topic.
- Writing a Good Squash Commit Message — what to put in the single commit’s message.
- Amending the Last Commit Safely — the smallest form of squashing.
- Splitting a Large Commit into Reviewable Pieces — the opposite operation.