2

I know two methods to change a commit message in git.

The first is git amend, which only works for the latest commit. Since I want to be able to change older commit messages directly, this is not what I am looking for.

The second is an interactive rebase as described in this answer for example, which can also change commit messages of older commits. The procedure is to use

git rebase -i HEAD~n

where I have to manually count how large is n for my specific case, then scroll through a list of all those commits and change the one commit from pick to reword, then finally type the new commit message and force push.

Honestly, while this works, it is insanely complicated and tedious to do this. So my questions is, is there an easier to use option (perhaps in form of an alias), where this procedure is automatically performed in one step?

Ideally, I would like to have a command like:

git reword <hash> -m "New commit message"

and after that just force push. Is this possible?

Edit: I want to get rid of the interactivity, because I want to programmatically automatize some git commands from my program. Having to interact manually with git during that process kind of defeats the purpose of such an automation.

SampleTime
  • 291
  • 3
  • 19

3 Answers3

2

I like being explicit about which steps I take, especially for "destructive" operations such as a rebase. With that in mind, what you are after is definitely possible by leveraging the GIT_SEQUENCE_EDITOR variable. It defines the "editor" used to edit the todo list of the interactive rebase. The editor does not have to be an interactive editor and can be any executable that modifies a text file, sed being an example of that:

GIT_SEQUENCE_EDITOR='sed -i "1s/^pick/reword/"' git rebase -i commit-you-want-to-amend^

When rebasing until the parent of the commit you want to amend, that commit will be the first in the list and you can easily switch pick to reword with a simple sed script.

That whole thing can be easily converted to an alias:

git config --global alias.reword '!f() {
  GIT_SEQUENCE_EDITOR="sed -i 1s/^pick/reword/" git rebase -i "$1^";
}; f'

Then git reword commit-you-want-to-amend.

The above still requires you to enter the commit message manually. If you want to avoid that too, a clever hack with GIT_EDITOR can help:

git config --global alias.reword '!f() {
  GIT_SEQUENCE_EDITOR="sed -i 1s/^pick/reword/" GIT_EDITOR="printf \"%s\n\" \"$2\" >" git rebase -i "$1^";
}; f'

Note the standard output redirect > in the GIT_EDITOR value.

Then call as git reword commit-you-want-to-amend 'your new commit message'.


A second option could be using git replace which keeps the original commits, but uses the replace refs of Git to show different commits instead.

knittl
  • 246,190
  • 53
  • 318
  • 364
2

With more recent versions of Git, the simplest option seems to be git commit --fixup=reword:commit:

--fixup=[(amend|reword):]<commit>

Create a new commit which "fixes up" <commit> when applied with git rebase --autosquash. […] --fixup=reword:<commit> creates an "amend!" commit which replaces the log message of <commit> with its own log message but makes no changes to the content of <commit>.

[…]

--fixup=reword:<commit> is shorthand for --fixup=amend:<commit> --only. It creates an "amend!" commit with only a log message (ignoring any changes staged in the index). When squashed by git rebase --autosquash, it replaces the log message of <commit> without making any other changes.

So, you would run the following commands:

git commit --fixup=reword:commit-to-amend
git rebase -i --autosquash commit-to-amend^

But this doesn't allow specifying the commit message via CLI arguments, so we need to mimic the behavior of Git by crafting the correct commit message required by autosqash:

git config alias.reword2 '!f() {
  git commit --allow-empty --only -m "amend! $1

$2" &&
  GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash "$1^";
}; f'

And then executed with git reword commit-to-amend 'your new message'.

knittl
  • 246,190
  • 53
  • 318
  • 364
1

You can reference the revision before your target commit directly instead of manually counting back commits from HEAD. Run

$ git rebase -i <rev>^

where <rev> is the commit you want to reword.

From there, you'll still need to finish the rebase manually (change you target line to reword and run git rebase --continue). You could further automate that process with some combination of clever shell scripting and GIT_SEQUENCE_EDITOR, but I'm not sure I would recommend doing so. Rebasing is an extremely invasive operation in git. Having the history you're about to completely re-write flash on your screen briefly and getting the opportunity to abort is a good thing, and may save you many tears and git reflog hours down the road.

Brian61354270
  • 8,690
  • 4
  • 21
  • 43
  • Thanks, this is better than before. However, I am still searching for a non-interactive solution, because I want to do that script based (I edited my question to include that information). I will take a look at GIT_SEQUENCE_EDITOR, but I don't really get why we can reset hard and force push in one line without problems (both are dangerous operations as well), but not do such a simple thing like editing a commit message... (I know it rewrites history, but it is such a common problem to have that it should have an easier-to-use solution nevertheless in my opinion) – SampleTime Jul 30 '23 at 07:33
  • 1
    @SampleTime Fortunately knittl has come to save the day :). Though I'd offer that hard resets and force pushes are less nasty that rebasing, since both of those only move refs around and are easy to fix just by moving the ref back. Rebasing on the other hand moves refs _and_ writes an unlimited number of new objects. – Brian61354270 Jul 30 '23 at 12:47
  • 1
    @Brian61354270 only tangential to the question and answer, but `reset --hard` is definitely more _dangerous_ than a rebase (interactive or not). A hard reset undoes uncommitted changes and these are impossible to restore. – knittl Jul 30 '23 at 20:05