Change a Git commit message after you have committed
The commit is made. The message says fix and it should have said which of three plausible things was fixed, or it carries a ticket number that belongs to a different ticket, or it names a file that got renamed in the same commit. Nothing is broken. The history is just slightly wrong in a way that will cost somebody five minutes in six months.
There are exactly three questions that decide what to do, and they are worth answering in order. Is the commit the most recent one. Has it been pushed. Has anyone else pulled it. The first decides which command. The second decides whether a force push is involved. The third decides whether to do it at all. Everything else, including every variation of interactive rebase, follows from those three answers.
The most recent commit, not yet pushed
This is the easy case and the one worth making into a reflex.
git commit --amend
The editor opens with the existing message already in it. Change the text, save, close. The git commit manual describes --amend as replacing the tip of the current branch by creating a new commit, using the message from the original as the starting point, and keeping the same parents and author as the current one unless --reset-author is passed.
That phrase, creating a new commit, is the part to hold onto. Nothing is edited in place. Git builds a replacement commit and moves the branch to it, and the manual gives the rough equivalent as a soft reset to HEAD^ followed by a commit reusing the old message. Because the content is hashed along with the message, a new message always means a new commit ID. The old commit still exists in the object store, reachable through the reflog, until garbage collection removes it.
Two flags make this quicker. -m supplies the message directly and skips the editor:
git commit --amend -m "Fix timezone handling in the export job"
And --no-edit keeps the message untouched, which the manual illustrates with exactly this combination: git commit --amend --no-edit amends a commit without changing its message. That is the form for the other common case, where the message was fine and a file was missing from the commit.
One caution specific to amending. If files are staged when --amend runs, they are folded into the commit along with the new message. Run git status first, or use --only to restrict the commit to paths named on the command line, so that a message fix does not quietly become a content change.
An older commit, or several of them
Once the target is not the tip, the tool is interactive rebase. The mechanism is the same underneath, applied to a range.
git rebase -i HEAD~5
An editor opens with one line per commit, oldest first, each beginning with pick. The git rebase manual is explicit about what to do next: if you just want to edit the commit message for a commit, replace the command pick with the command reword. Save and close, and Git stops at each reworded commit to open its message in the editor.
pick e499d89 Delete CNAME
reword 0c39034 Better README
reword f7fde4a Change the commit message
That list is from GitHub's own documentation for this task, and it shows the shape worth copying: only the lines you intend to change get touched, everything else stays as pick. The manual warns that the one-line descriptions are there for your convenience only, and that Git reads the commit names rather than the text, so deleting or editing the abbreviated IDs breaks the operation.
Choosing the range needs one moment of care. HEAD~5 covers the last five commits, which means the commit you want must be inside that count. Counting wrong in the safe direction, by including more commits than needed, costs nothing; the extra lines stay as pick. For the oldest commit in a repository, HEAD~n cannot reach far enough, because there is no parent to rebase onto. That is what --root is for, documented as rebasing all commits reachable from the branch instead of limiting them with an upstream, which allows rebasing the root commit.
Two things to expect during the run. Every commit from the reworded one onwards gets a new ID, because each one's parent changed, so a rebase that rewords the fifth commit back rewrites five commits rather than one. And if a conflict appears, git rebase --abort returns the branch to where it started, which makes the whole operation safe to try.
The variant that avoids the editor dance
For a message you already know, git commit --fixup=reword:<commit> creates the correction as its own commit without rewriting anything yet. The manual describes it as shorthand for --fixup=amend:<commit> --only, creating an amend! commit carrying only a log message and ignoring anything staged. The next git rebase --autosquash folds it into place, replacing that commit's message without touching its content, and the manual notes that neither fixup! nor amend! commits change the authorship of the target.
This matters when the correction occurs to you mid-task. Record it, keep working, and let one rebase at the end apply every pending correction at once.
Once it is pushed, the cost changes
Rewriting a commit that exists only on your machine is bookkeeping. Rewriting one that exists on a server is a different act, because the server has a commit your new history does not contain, and the push will be rejected as not a fast-forward.
GitHub's documentation states the situation plainly: changing a commit message creates a new commit ID, and if the commit has already been pushed you must force push the rewritten history. The same page adds that force pushing can disrupt collaborators who have based work on the old commits, and advises caution before rewriting pushed history.
Before any of that, it is worth establishing which case you are actually in, because the answer is not always the one you remember. One command lists the commits on your branch that the upstream does not have:
git log --oneline @{u}..
If the commit in question appears there, it has not reached the server and an amend needs no force push at all. If it does not appear, it is on the remote and everything below applies. The @{u} shorthand refers to the branch's upstream, so this only answers the question when an upstream is configured; git branch -vv shows in square brackets whether one is, along with how far ahead or behind the branch was at the last fetch. Fetch first if that number looks stale, because the counts come from the last fetch rather than from the server.
The safer form of force push is not --force.
git push --force-with-lease origin feature-branch
The git push manual explains what the difference buys. Normally a push refuses to update a remote ref that is not an ancestor of the local ref. --force-with-lease overrides that restriction only if the current value of the remote ref is the expected value, and fails otherwise. The documentation describes it as taking a lease on the ref without explicitly locking it: the update goes through only while the lease is still valid. If a colleague pushed in the meantime, the push fails instead of erasing their commit, which is precisely the failure --force will not give you.
The manual adds one caveat worth knowing. Used without an explicit expected value, --force-with-lease interacts badly with anything that runs git fetch in the background, such as a scheduled fetch in an editor or a cron job, because the background fetch updates the remote-tracking ref that the lease is compared against. On a machine with an editor that fetches automatically, the protection is weaker than it looks.
| Situation | Command | Force push |
|---|---|---|
| Last commit, not pushed | git commit --amend |
No |
| Last commit, pushed, branch is yours | git commit --amend |
Yes, with lease |
| Older commit | git rebase -i HEAD~n, then reword |
Only if pushed |
| Oldest commit in the repository | git rebase -i --root |
Only if pushed |
| Message known now, rewrite later | git commit --fixup=reword:<commit> |
On the eventual rebase |
| Commit is on a shared main branch | Leave it, add a follow-up commit | Never |
The last row is a real answer, not a cop-out. A branch that other people build on is worth more accurate than tidy, and a commit that fixes the description of a previous commit reads perfectly well in a log.
The thing rewriting does not clean up
There is one case where none of this does what people assume it does, and it is the case where it matters most.
GitHub's documentation attaches a warning to this exact task: if a commit message included sensitive information, force pushing an amended commit might not remove the original commit from the remote. The page directs you to contact support with the old commit ID to have it purged. The reason is that the original commit object is still on the server, unreferenced by any branch but reachable by its ID, and pull requests and forks can keep references to it alive.
The practical consequence is a rule with no exceptions. A credential, token or key that has been committed and pushed is compromised from that moment, and the only real remedy is to revoke and reissue it. Rewriting history is worth doing afterwards, for the sake of a clean log, but it is cleanup rather than containment. Treating a force push as the fix is the mistake that turns a leaked token into a leaked token nobody is watching for.
The same asymmetry applies at a smaller scale to the commit graph itself. Rewriting a pushed commit leaves anyone who already pulled it holding a history that no longer matches, and their next pull will try to reconcile two versions of the same work. Telling them before the force push, rather than after, is the entire difference between a minor annoyance and an afternoon.
Making the message worth the rewrite
If a message is being rewritten anyway, it is worth setting up so the next one needs less correction.
commit.verbose puts the full diff of what is being committed into the editor below the message, which the manual describes as a boolean or integer controlling verbosity for git commit. Writing a subject line with the actual changes on screen is the cheapest available fix for vague messages.
core.editor decides what opens. Git falls back to a platform default when it is unset, and on a Mac that default is frequently not what people expect, which is why the first interactive rebase so often becomes a fight with an unfamiliar editor rather than a rewording. Set it once to whatever you already know.
git config set --global core.editor "your-editor --wait"
git config set --global commit.verbose true
The --wait part, or whichever flag your editor uses for it, is not optional. Without it the editor returns immediately, Git sees an unchanged file, and the operation either aborts or keeps the old message with no explanation. Reading the diff, writing the message and running the rebase in one window rather than three is what makes this a habit rather than an interruption, which is the working shape a file manager with a built-in terminal is aiming at, and the reason for not running two apps side by side in the first place.
What to change first
Set core.editor to an editor you already know, with the flag that makes it wait, and set commit.verbose to true so the diff is on screen while the message is being written. Then make git commit --amend the reflex for the commit you just made, and keep --force-with-lease rather than --force for anything already pushed. If a message contained a secret, revoke the secret first and rewrite the history afterwards, in that order. A window where the log, the diff and the editor sit together makes this routine rather than an event, which is the shape Atriens is built around.
Frequently asked questions
How do I change the last commit message?
git commit --amend opens the existing message in your editor, or git commit --amend -m "new message" replaces it without opening anything. Check git status first, because anything staged at that moment is folded into the amended commit along with the new message.
Can I edit a commit message on GitHub in the browser?
The documented procedure is a local git commit --amend or an interactive rebase, followed by a push, rather than an edit in the web interface. GitHub's own instructions for this task begin with navigating to the repository on the command line, which is a reasonable signal about where the work happens.
Does changing a commit message change the commit ID?
Yes, always. The message is part of what the commit hashes, so a new message produces a new commit and the branch is moved to point at it. The original commit stays in the object store and is reachable through the reflog until garbage collection removes it.
Is it safe to force push after amending?
It is safe on a branch nobody else is working on, and --force-with-lease rather than --force is the form to use, because it fails instead of overwriting if somebody pushed in the meantime. On a shared branch, adding a follow-up commit is the better trade, since rewriting leaves everyone who already pulled with a history that no longer matches.
How do I change a commit message from several commits back?
git rebase -i HEAD~n where n reaches past the commit, then change that line's pick to reword. Counting too far back is harmless, since the extra lines stay as pick. For the very first commit in a repository, use git rebase -i --root, which is documented as rebasing all commits reachable from the branch.
I committed an API key in a commit message. Does amending remove it?
Not reliably. GitHub's documentation warns that force pushing an amended commit might not remove the original from the remote, and directs you to contact support with the old commit ID to have it purged. Revoke and reissue the key first. Rewriting the history afterwards is worth doing, but it is tidying rather than a fix.