18

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.

10
  • 2
    Why doesn't rebase -i work for you? Commented May 31, 2018 at 21:02
  • 1
    What exactly is the problem? Not being able to edit the message or pushing to the server? Commented May 31, 2018 at 21:05
  • 3
    @destoryer. I think the question is quite clear on both counts... Commented May 31, 2018 at 21:06
  • 3
    git rebase -i has a "reword" option. If you're only changing the commit message of one commit, this should do it. Commented May 31, 2018 at 21:31
  • 2
    @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. Commented May 31, 2018 at 23:38

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

Comments

14

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'"

2 Comments

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!
@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.
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

Comments

0

Instead of rebasing and force pushing the modified branch, it's possible to replace a commit with a different message without affecting the existing commit hashes:

git checkout <commit>
git commit --amend -m "New message"
git replace <commit> $(git rev-parse HEAD)

This can also be done interactively with:

git replace --edit <commit>

Note that the modified references will then have to be pushed and fetched explicitly with:

git push origin 'refs/replace/*'
git fetch origin 'refs/replace/*:refs/replace/*'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.