squash¶
git city squash compresses all of a feature's own commits into a single commit.
Stacks accumulate work-in-progress commits — "wip", "fix typo", "address review" — that you do not want to keep once a branch is ready to land. squash collapses everything a feature owns (the commits between its parent and its tip) down to one clean commit, in place, without moving the branch in the stack.
With no branch argument, it squashes the current branch. Because squashing rewrites history (and force-pushes when the branch is tracked), it prints the plan and asks for confirmation; pass --yes (or -y) to skip the prompt (required to run non-interactively).
What it does¶
Given a feature with several commits, squash rewrites them into a single commit at the same tip position, then — if the branch tracks a remote — force-pushes the rewritten history with --force-with-lease.
| Option | Meaning |
|---|---|
branch |
The feature to squash. Defaults to the current branch. |
--message <m> |
The message for the resulting commit. Defaults to the branch's first (oldest) commit subject. |
--yes / -y |
Skip the confirmation prompt (and allow non-interactive use). |
--dry-run |
Print the exact git commands that would run, and execute nothing. |
Only the feature's own commits are affected — those that exist between its parent's tip and its own. Commits belonging to the parent (or anything further up the stack toward the trunk) are never touched.
Default commit message
When you omit --message, the resulting commit keeps the subject of the branch's first (oldest) commit. That is usually the line that named the change in the first place, so it tends to be the right summary.
Example¶
You have a feature add-login with three commits:
Squash them into one:
The three commits become a single commit whose message is add login form (the oldest subject). Because add-login tracks origin/add-login, git-city force-pushes the rewritten branch:
To give the squashed commit a fresh message instead:
Preview without changing anything:
Safety¶
squash rewrites history, so it is careful about it.
- At least two commits. A feature with zero or one own commits has nothing to compress;
squashreports this and stops rather than churn the branch. - Atomic. The operation either fully succeeds or leaves the branch exactly as it was. If the commit step fails, git-city restores the branch — you never end up with a half-squashed feature.
- Force-with-lease. When the branch is tracked, the force-push uses
--force-with-lease, so it refuses to clobber remote work you have not seen. - Clean tree required.
squashmoves commits, so it refuses to run on a dirty working tree. Commit or stash first.
Squash rewrites commit hashes
Squashing replaces a branch's commits with one new commit, so every SHA on the feature changes. If a teammate has the old commits checked out, coordinate before squashing a shared branch. --force-with-lease protects the remote, not someone else's local clone.
Errors¶
Errors print as a clean git-city: <message> on stderr with exit code 1.
| Situation | Outcome |
|---|---|
| Target is the trunk (or a perennial) | Refused — a trunk is never rewritten. |
| Fewer than 2 own commits | Refused — nothing to squash. |
Empty --message |
Refused — the commit needs a subject. |
| Dirty working tree | Refused — commit or stash first. |
Undoing a squash¶
squash is a normal git-city operation, so it is reversible. If you squashed the wrong branch or want the individual commits back, run:
undo restores the branch's original refs (the pre-squash commits survive in the reflog) and, if the squash force-pushed, restores the remote branch too.
→ Recovery: undo, continue, abort
See also: the rebase-first model and reversible operations.