Most of us know what a good commit message looks like. We just don't write one at 6 PM when the tests finally pass. The fix isn't more willpower. It's setting up your Mac and Git so the good version is the easy one: a template that asks the right questions, an editor that opens instantly, your diff right in front of you while you write, and a pull request description that mostly fills itself in. Here's the setup I use, step by step. Everything below uses stock Git, the GitHub CLI, and tools that ship with macOS.
1. Stop using -m for anything that matters
git commit -m "fix stuff" is fast because it skips the editor, and that's the problem: a one-liner is the only thing it encourages. Keep -m for truly trivial commits (a typo, a version bump) and let Git open an editor for everything else. That's where the next few steps pay off.
2. Pick an editor that opens fast and blocks properly
Git opens whatever core.editor is set to and waits until that editor closes the file. If you've never set it, you're probably getting Vim, which is fine if you know Vim and miserable if you don't.
For VS Code (after running Shell Command: Install 'code' command in PATH from the Command Palette):
git config --global core.editor "code --wait"
The --wait flag matters. Without it, code returns right away and Git sees an empty message and aborts the commit.
If you'd rather stay in the terminal, nano ships with macOS and needs no setup:
git config --global core.editor "nano"
Write the message, press Control-O to save, Control-X to exit, and the commit goes through.
3. Add a commit template that asks the right questions
A template is a plain text file Git pre-fills into the editor every time you commit without -m. Lines starting with # are comments and get stripped automatically, so you can leave yourself prompts that never end up in history.
Create ~/.gitmessage:
# Subject: imperative, ~50 chars, no period
# e.g. "Fix race in session refresh"
#
# Body (wrap at 72): WHY this change, not what the diff shows.
# - What was wrong or missing?
# - Why this approach over the obvious alternative?
# - Anything a reviewer or future you should watch for?
#
# Trailers (optional):
# Refs: #123
# Co-authored-by: Name <email>
Then point Git at it:
git config --global commit.template ~/.gitmessage
The empty first line is deliberate: your cursor lands where the subject goes. The prompts do the real work. When you're tired, "why this approach over the obvious alternative?" is the question you'd skip, and it's usually the one a reviewer most wants answered.
If a repo has its own conventions (say, Conventional Commits like feat: and fix:), set a repo-level template that overrides the global one:
git config commit.template .gitmessage
4. Write with the diff on screen
Half the effort of a commit message is remembering what you actually changed. Let Git show you:
git config --global commit.verbose true
Now every commit editor includes the full staged diff below the message, under a "scissors" line. Everything from that line down is removed before the commit is saved, so it's there for reference only. You can scroll down, see exactly what's staged, and describe it accurately instead of from memory.
Two more commands worth running before you write:
git diff --staged --stat # which files, how much
git log --oneline -10 # how this repo usually phrases subjects
The second one is the fastest way to match a team's style without reading a contributing guide.
5. Commit in smaller pieces so each message is easy
A message is hard to write when the commit does four things. git add -p walks through your changes hunk by hunk and asks whether to stage each one (y, n, s to split a hunk, q to quit). Stage one logical change, commit it, and repeat. Each message then has a single subject that's obvious.
For follow-up fixes to an earlier commit on your branch, skip the message entirely:
git commit --fixup=<sha>
git rebase -i --autosquash main
--fixup creates a commit titled fixup! <original subject>, and --autosquash folds it into the original during the rebase. Your branch ends up with clean commits and no "address review comments" noise.
And if you notice a bad message right after committing, git commit --amend reopens the last message in your editor (only do this before you've pushed, or be ready to force-push your own branch).
6. Add trailers without typing them
Recent versions of Git have a --trailer flag that appends a properly formatted trailer to the message:
git commit --trailer "Co-authored-by: Sam Lee <sam@example.com>"
If you pair with the same people often, turn it into a Git alias in ~/.gitconfig:
[alias]
cosam = commit --trailer "Co-authored-by: Sam Lee <sam@example.com>"
Now git cosam opens your normal template with the trailer already added.
7. Give the repo a pull request template
GitHub pre-fills new pull requests with the contents of .github/pull_request_template.md if it exists on the default branch. A short one works better than a long one, because people actually fill it in:
## What
<!-- One or two sentences. -->
## Why
<!-- The problem, link to the issue. -->
## How to test
1.
## Notes for reviewers
<!-- Risky parts, follow-ups, anything you're unsure about. -->
HTML comments are hidden when the description is rendered, so the prompts don't clutter the final PR.
8. Draft the PR from the terminal with gh
If you've written good commits, most of the PR description already exists. The GitHub CLI (brew install gh, then gh auth login) can reuse it:
gh pr create --fill
--fill uses your commit info for the title and body. For a single-commit branch, that's often the whole PR. For bigger branches, build a body from your commit subjects and copy it to the clipboard with macOS's built-in pbcopy:
git log --reverse --format='- %s' main..HEAD | pbcopy
Paste that into the "What" section as a starting changelog, then spend your effort on "Why" and "Notes for reviewers," which no tool can write for you.
Other flags worth knowing:
-
gh pr create --draftopens it as a draft so CI can run before reviewers get pinged. -
gh pr create --body-file pr.mduses a file you wrote in your editor. -
gh pr create --webopens the browser form with your branch selected, if you prefer to finish there.
9. Keep short review phrases one keystroke away
For the bits you type over and over in PR descriptions and review comments ("How to test," "nit:", "LGTM once CI passes"), macOS Text Replacements work in Safari and most native text fields. Add them under System Settings > Keyboard > Text Replacements, and they sync across your Apple devices. Browser-based editors and Electron apps don't always honor them, so test in the places you actually write.
A five-minute setup checklist
- Set
core.editorto something with a proper--wait. - Create
~/.gitmessageand setcommit.template. - Turn on
commit.verbose. - Get in the habit of
git add -pandgit commit --fixup. - Add
.github/pull_request_template.mdto your repos. - Install
ghand trygh pr create --fillon your next branch.
None of this writes your messages for you. It removes the friction around writing, so the thirty seconds you spend explaining why goes into the message instead of fighting your tools.
One more tool, if you want it
Disclosure: I built DraftKey, a Mac app that adds private, on-device AI autocomplete and rewriting to every Mac app, which helps when you're drafting PR descriptions and review comments. Nothing you type leaves your Mac. It's $5 lifetime and needs macOS 14 or later on Apple Silicon: getdraftkey.com. The setup above works perfectly well without it.
Top comments (0)