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"
What reset --soft does to a branchThe branch had commits C1 to C4 on top of base B. Soft-resetting to B moves the branch name back to B without touching the files, so the combined change of C1 to C4 is staged. A single new commit S then holds it all.move the name back, keep the files, commit oncebeforeBC1C2C3 C4after resetBafter commitBSnothing is replayed, so no conflict can reappear What reset --soft does to a branchThe branch had commits C1 to C4 on top of base B. Soft-resetting to B moves the branch name back to B without touching the files, so the combined change of C1 to C4 is staged. A single new commit S then holds it all.move the name back, keep the files, commit oncebeforeBC1C2C3 C4after resetBafter commitBSnothing is replayed, so no conflict can reappear
# 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
reset --soft against merge --squashreset --soft rewrites the current branch into one commit on its own base, so it suits tidying your own branch before review. merge --squash brings another branch's combined changes onto the current branch as one new commit, which is what a squash merge into main does.reset --soft <base>merge --squash <branch>acts oncurrent branchanother branchreplays commitsnono (one merge)can conflictneveryes, like a mergetypical usetidy before PRland onto mainboth produce a single ordinary commit with no link to the original commits reset --soft against merge --squashreset --soft rewrites the current branch into one commit on its own base, so it suits tidying your own branch before review. merge --squash brings another branch's combined changes onto the current branch as one new commit, which is what a squash merge into main does.reset --soft <base>merge --squash <branch>acts oncurrent branchanother branchreplays commitsnono (one merge)can conflictneveryes, like a mergetypical usetidy before PRland onto mainboth produce a single ordinary commit with no link to the original 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 -d will refuse to delete it and git branch --merged will 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 -D once 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
Which squashing tool to useTo make your own branch a single commit, use reset --soft to its base. To land another branch onto the current one as a single commit, use merge --squash. To combine some commits and keep others, use interactive rebase or autosquash.What do you want to squash?my whole branchreset --softto merge-baseanother branch, onto this onemerge --squashthen commitsome commits, keep othersrebase -ior --autosquashmost 'squash my branch' requests are the first case Which squashing tool to useTo make your own branch a single commit, use reset --soft to its base. To land another branch onto the current one as a single commit, use merge --squash. To combine some commits and keep others, use interactive rebase or autosquash.What do you want to squash?my whole branchreset --softto merge-baseanother branch, onto this onemerge --squashthen commitsome commits, keep othersrebase -ior --autosquashmost 'squash my branch' requests are the first case

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.