Writing a good squash commit message Jump to heading
Squash merging turns a pull request’s commits into one commit on main. The default message the forge proposes is either the pull request title with a list of every original commit subject — “fix typo”, “address review”, “wip”, “actually fix it” — or just the title. Both are poor. The first buries the useful information under noise; the second loses it entirely. Six months later, someone runs git blame on a line, lands on that squash commit, and its message is the only explanation they will ever get. Writing it well takes two minutes at merge time and saves an hour of archaeology later. This page sets out what the message should contain and how to produce a good draft quickly, within squash and fixup strategies.
When to use this approach Jump to heading
- Your team squash-merges pull requests, by policy or by habit.
git logon main is full of messages that list intermediate commit subjects.- People complain that
git blameleads to commits that explain nothing. - You also squash locally before pushing, as in squashing without interactive rebase.
Step 1 — Know what the message is for Jump to heading
A squash commit is read in three situations: scanning git log --oneline to see what changed, landing on it from git blame to understand why a line exists, and generating release notes. Each needs something different, and a good message serves all three.
Step 2 — Write the subject as the change, not the activity Jump to heading
The subject should describe the resulting change in the imperative, short enough to read in a one-line log. If your team uses Conventional Commits, the type and scope go first; the pull request number usually goes at the end.
# Weak subjects
Update billing code
Fixes from review
JIRA-412
# Strong subjects
fix(billing): stop applying customer discount twice on credit notes (#812)
feat(export): schedule CSV exports per account (#839) The pull request title is often a good starting point, but it is written to get a review, not to describe the final change. Edit it to match what actually merged.
Step 3 — Write a body that explains why, and what a reader would not guess Jump to heading
The body is the part that justifies the commit’s existence. Three short paragraphs cover most changes: the problem, the approach and anything surprising.
fix(billing): stop applying customer discount twice on credit notes (#812)
Credit notes reused the invoice total calculation, which applies the
customer's discount. The credit note was then discounted again when
issued, refunding less than the customer paid.
Credit notes now pass skip_discount=True so the original invoice's
already-discounted lines are used as-is.
Existing credit notes are not recalculated; a separate data fix (OPS-221)
handles the 37 affected documents.
Fixes: BILL-412
Co-authored-by: Priya Raman Step 4 — Keep trailers and co-authors Jump to heading
Squashing combines commits from possibly several authors into one with a single author. The Co-authored-by trailer is how other contributors keep credit, and forges recognise it. Issue references and other trailers your tooling reads should be preserved too.
# Collect co-authors from the branch's commits for the squash message
git log --format='Co-authored-by: %an <%ae>' main..feature/credit-notes | sort -u \
| grep -v "$(git config user.email)" The details of keeping authorship are in preserving co-authors when squashing.
Step 5 — Generate a draft, then edit it Jump to heading
A small script can produce a starting draft from the pull request: title, description, and the trailers gathered from commits. You still edit it, but you start from something structured instead of a list of “wip” subjects.
#!/bin/sh
# squash-msg.sh <pr-number> — print a draft squash message
set -eu
pr=$1
gh pr view "$pr" --json title,body,number --jq '"\(.title) (#\(.number))\n\n\(.body)"' |
sed '/^<!--/,/-->$/d' # drop template comments
echo
gh pr view "$pr" --json commits --jq '.commits[].authors[] | "Co-authored-by: \(.name) <\(.email)>"' | sort -u Forges also let you set the default squash message format — for example, to use the pull request title and description instead of the commit list. Choose that setting; it makes the default far closer to what you want.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Should the pull request description just become the commit message? Jump to heading
Often most of it can. Descriptions are written for reviewers, though, and include screenshots, checklists and testing notes that do not belong in history. Keep the explanation and drop the review apparatus.
Is it worth enforcing message quality in CI? Jump to heading
Linting the final squash message is hard because it is written at merge time. Enforcing a good pull request title, which becomes the subject, is easy and gets most of the benefit; see enforcing subject line length and format.
What if the pull request did several unrelated things? Jump to heading
Then it should not be one commit. Squash merging works best with focused pull requests; a pull request that does three things should usually be three, or merged with its commits kept.
Related Jump to heading
- Squash & Fixup Strategies — the parent topic.
- Squash vs Merge vs Rebase Decision Matrix — whether to squash at all.
- Generating Pull Request Descriptions from Commits — getting a good description to start from.
- Finding Who Changed a Line with log and blame — where the message is eventually read.