16

You can programmatically edit only the last commit message:

git commit --amend -m 'xxxxxxx'

Or a random commit interactively:

git rebase -i HEAD~n
# Vim opens up, select the commit you want to modify, and change the word "pick" for "edit"
git commit --amend -m "Changing an old commit message!"
git rebase --continue

How do I combine both? I want to change a message programmatically, but to a prior commit, not just the last one.

The commit I want to modify has already been pushed to a git server, but having other people re-sync the git project is not a concern.

jub0bs
  • 60,866
  • 25
  • 183
  • 186
Jesus H
  • 1,180
  • 3
  • 13
  • 25
  • 2
    Why doesn't `rebase -i` work for you? – pishpish May 31 '18 at 21:02
  • 1
    What exactly is the problem? Not being able to edit the message or pushing to the server? – pishpish May 31 '18 at 21:05
  • 2
    @destoryer. I think the question is quite clear on both counts... – Mad Physicist May 31 '18 at 21:06
  • 1
    Related: https://stackoverflow.com/questions/26050327/how-does-git-commit-amend-work-exactly and https://stackoverflow.com/questions/16019900/programatically-use-git-rebase-i – jub0bs May 31 '18 at 21:30
  • 3
    `git rebase -i` has a "reword" option. If you're only changing the commit message of one commit, this should do it. – Schwern May 31 '18 at 21:31
  • Possible duplicate of [With git rebase, is there a way to reword commit messages in the git-rebase-todo using the default commands?](https://stackoverflow.com/questions/29198289/with-git-rebase-is-there-a-way-to-reword-commit-messages-in-the-git-rebase-todo) – Andrew C May 31 '18 at 23:26
  • 1
    @AndrewC The link you provided discusses an interactive solution rather than a programmatic solution. I don't see how the linked question would be a duplicate. – Jesus H May 31 '18 at 23:38
  • @Jesus H you combine that with something that auto-edits the interactive rebase todo list and you have a programmatic solution – Andrew C May 31 '18 at 23:40
  • @AndrewC that is not in the spirit of what OP is asking, and doesn't justify the dupe vote, but might provide a nice solution neverthless. – Mad Physicist Jun 01 '18 at 01:56
  • Use "reword" instead of "pick" during the interactive rebase. – Code-Apprentice Oct 14 '18 at 17:18

3 Answers3

15

The reason you can not simply "amend" an arbitrary commit is that commits are immutable. When you amend a commit, it actually replaces the current commit with another and moves your branch to the new commit. The commit with the old message, author name, etc. is still there in the history until you clean it up:

Before:

        master
          |
          v
A -- B -- C

After:

        master
          |
          v
A -- B -- C'
      \
       \- C

To simulate "amending" an arbitrary commit, you would have to rewrite not only that commit, but the entire history after it, since a commit includes its parents as part of its immutable data:

Before:

        master
          |
          v
A -- B -- C

After:

         master
           |
           v
A -- B' -- C'
 \ 
  \- B --- C

You can do this by creating a branch on the commit you are interested in, amending it, and rebasing the range of commits following the original to the tip of your original branch onto the new branch. Here is an example showing what you are after:

Start:

             master
               |
               v
A -- B -- C -- D

New Branch:

             master
               |
               v
A -- B -- C -- D
     ^
     |
    temp

Amend:

             master
               |
               v
A -- B -- C -- D
 \
  \- B'
     ^
     |
    temp

Rebase:

A -- B  -- C  -- D
 \
  \- B' -- C' -- D'
     ^           ^
     |           |
    temp       master

Cleanup:

A -- B  -- C  -- D
 \
  \- B' -- C' -- D'
                 ^
                 |
               master

This is pretty much exactly what interactive rebase does when you only modify a single commit, by the way, except without the explicit temporary branch.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
13

If you're just changing a few commits, use git rebase -i and the "reword" option. For example...

pick 6256642 mv file1 file2
pick 20c2e82 Add another line to file2

# Rebase 8236784..20c2e82 onto 8236784 (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit

Switch pick to reword and you'll be offered an editor to rewrite the commit message.


If you need to do the same thing to a lot of commits, use git filter-branch with a --msg-filter. The original commit message is on stdin, the new commit message is on stdout. Here's one to change "color" to "colour" in all commits in the current branch.

git filter-branch --msg-filter "perl -ple 's{color}{colour}g'"
Schwern
  • 153,029
  • 25
  • 195
  • 336
  • filter-branch is applied to all commits. Would it be possible to apply a filter to a specific commit? I tried passing a commit hash at the end, but filter-branch expects a hash..ref. If I pass hash..HEAD, it is applied to all commits after the specified hash. If it's not possible, I think I can work with the provided solution (by parsing the stdin message). Thanks! – Jesus H May 31 '18 at 23:40
  • @JesusH `git filter-branch` takes the same revision arguments as `git rev-list`, so you can cook up a way to specify one commit. For example, `git rev-list HEAD^..HEAD` to just do `HEAD`. If you just want to do one commit, use `git rebase -i`. I don't understand why you want to write a program to change the message of one commit. That's very unusual. If you explained that we could help better. – Schwern Jun 01 '18 at 00:46
7

Since you want to make the change programmatically, interactive rebase (git rebase -i) is not an option.

Editing an old commit, for whatever reason, will effectively rebase all of the commits on top of that. If you're changing only the commit message, then you don't need to worry about merge conflicts.

You create a new temporary branch with the target commit as its HEAD, edit the commit message, merge the old branch onto the new one, and then delete the old temporary branch.

In a shell script:

CURBRANCH=`git rev-parse --abbrev-ref HEAD`
TMPBRANCH=tmp$$
git checkout $SHA -b $TMPBRANCH
MSG=`tempfile`
git log --format=%B -n 1 HEAD > $MSG
... edit the $MSG file
git commit --amend -F $MSG
SHA=`git rev-list -n 1 HEAD`   # Commit has change, so SHA has also changed
rm $MSG
git rebase --onto $TMPBRANCH HEAD $CURBRANCH
git branch -d $TMPBRANCH