Skip to content

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.

git city squash [branch] [--message <m>] [--yes] [--dry-run]

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:

add login form
wip
fix validation

Squash them into one:

git city squash

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:

git push --force-with-lease=add-login:<sha> origin add-login

To give the squashed commit a fresh message instead:

git city squash --message "Add login form and validation"

Preview without changing anything:

git city squash --dry-run

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; squash reports 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. squash moves 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:

git city undo

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.