0

I must edit a commit message of a parent of a merge commit (the parent is actually itself a merge). How do I do this ?

Here F is my current HEAD, result of the merge of E and D. D is itself the result of the merge of A and C, and D is the commit whose message I need to edit.

  ...A-D-F - branch where I work
      / /
...B-C-E   - master

git rebase -i D~ only shows commits from the master for editing - E, C, B ...

None of it is pushed. Any hopes ?

Charles
  • 988
  • 1
  • 11
  • 28
  • One way would be to use `git rebase --interactive ~` and mark commit `D` as `reword`. – 0x5453 Apr 11 '18 at 15:24
  • Of course I tried that, but in the editor D is not present ... actually only the commits from the master are present. – Charles Apr 11 '18 at 15:28
  • You might need to use `^2` to specify that the rebase should happen from the second parent commit of `D`. See https://stackoverflow.com/a/2222920/3282436 – 0x5453 Apr 11 '18 at 15:57

1 Answers1

0

By default, rebase tries to produce a linear history. Also, rebase assumes that all changes are represented in non-merge commits (which, with the exception of conflict resolutions or "evil merges", should be true).

With two caveats, you can convince rebase to acknowledge merge commits by using the --preserve-merges option. The caveats are:

1) rebase still won't properly handle changes made in a merge. It will try to re-create merges using default strategy, and will let you re-resolve conflicts if necessary. If the merge was created using --no-commit in order to manually change things even though it wouldn't have conflicted, rebase will silently change the result.

2) mixing --preserve-merges with -i is not generally safe. In specific cases it can be done, and this is one that could work if you're careful to limit what you're doing to just what needs done.

So you could try doing the rebase with --preserve-merges and change nothing in the TODO list except for setting the command for D to "reword".

This will replace commits D and F, so in the event other refs point to either of them you'll have to move those refs as well. But as nothing has been pushed, the rewrite shouldn't cause any other major headaches.

There are other ways to do the rewrite, so if --preserve-merges doesn't work right for you (or if you just don't want to mess with it), you could use git filter-branch instead, with --msg-filter. This is harder to set up, though, as you need to write a script that will transform Ds message while passing any other commit's message through. See https://git-scm.com/docs/git-filter-branch

Or you can manually rebuild the history. For example if you know that D and F are non-conflicting default merges, you can say

git checkout myBranch
git reset --hard HEAD~2
git merge master^
# enter the new commit message for D
git merge master
# re-enter the commit message for E

If you're not sure that the commits were non-conflicting default merges, you could do something like

git checkout myBranch
git tag temp
git reset --hard HEAD~2
git merge --no-commit master^
rm -rf *
git checkout temp^ -- .
git add .
git commit -m"new commit message for D"
git merge --no-commit master
rm -rf *
git checkout temp -- .
git add .
git commit -m"commit message for F"
Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
  • --preserve-merges works down to one thing : it changes the hash. Another repo includes this repo as a subrepo and already is pointing to that hash at its latest commit. This is also unpushed stuff. If you tell me how to solve that we're good to go ! – Charles Apr 11 '18 at 16:30
  • @Charles: If you change the message, you have a new, different commit. This new and different commit will have a different hash. That's a fundamental Git property: commits, like all Git objects, are *completely unchangeable*. You can only copy them to new (slightly different) objects and switch to using the new objects. Hence, if you have two repositories, one a submodule of the other, you can rewrite in the submodule (obtaining new commits with new hashes), then rewrite in the superproject (obtaining new commits with new hashes that use the new submodule hashes). Otherwise you're stuck. – torek Apr 11 '18 at 17:12
  • ... and my question is how to do that (rewrite in the superproject). – Charles Apr 12 '18 at 15:45