Edit a merge commit with git rebase
In Git when I have commits eg. A - B - C
and I want to edit the B
commit, I
- use
git rebase -i <A-commit-hash>
, - in the list I write
edit
command in front ofB
commit, - git rebase stops right after
B
commit so I can fix anything I want usinggit commit --amend
, - and then I continue using
git rebase --continue
.
As far as I know this is the best practice how to do this. With this method I can edit any commit in the past (as long as it hasn't been pushed to remote branch yet), and moreover with -p
flag I can even preserve the merges. This is just great.
My current problem is: I did a mistake (typo) on one line in a merge commit (while resolving a conflict when merging two branches).
I'd like to fix it but I don't know how to make git rebase
to stop at a merge commit. The git rebase -p -i <blah>
list ignores merge commits, so I cannot write edit
command in front of it and make the git rebase
stop there to let me edit it.
Any help please? I just want to fix this line in the merge commit while preserving all the commits (and merges) after it.
Thanks.
Updated answer for 2020:
You can force git rebase -i
to stop at the merge commit via the break
command (added in Git 2.20). You can then edit the merge commit as desired via git commit --amend
.
Detailed steps:
- Run
git rebase -i --rebase-merges $ancestor_of_merge_commit
- Find the merge commit you want to edit in the todo list.
- Insert a new line after the merge commit that contains only
break
(orb
). - Save your changes and exit your editor. Git rebase will check out the merge commit, print something like the following, then return you to your prompt:
Stopped at fb91fab (Merge branch 'foo' into bar)
- Use
git commit --amend
to edit the merge commit as desired. - Run
git rebase --continue
when you are done editing the merge commit.
Alternative approach if you do not have any merge commits after the commit you want to edit:
- Run
git rebase -i $id_of_merge_commit
- Insert a new line at the top of the todo list that contains only
break
(orb
). - Save your changes and exit your editor. Git rebase will check out the merge commit, print something like the following, then return you to your prompt:
Stopped at fb91fab (Merge branch 'foo' into bar)
- Use
git commit --amend
to edit the merge commit as desired. - Run
git rebase --continue
when you are done editing the merge commit.
Original answer from 2012 (pre-break
):
Git does not make it easy to do interactive rebases when merges are involved. The -p
option uses the -i
mechanism internally, so mixing the two doesn't really work.
However, git rebase
is just an automated way to do lots of cherry-picks. You can replicate its behavior by manually cherry-picking to get a bit more control over the process. It's less convenient and more prone to human error, but possible.
This is the approach I suggest:
- use
git rebase
to get to the commit after the merge (the child of the merge) - use
git reset --hard HEAD^
to manually get to the merge - use
git commit --amend
to repair the merge - use
git cherry-pick
to get back to the commit after the merge - use
git rebase --continue
to finish
Here are the specific steps:
- Note the SHA1 ID of the merge commit you want to modify. For discussion, suppose it is
deadbeef
. - Note the SHA1 ID of the commit right after the merge commit you want to modify (the merge commit's child). Suppose it is
facef00d
. - Run
git rebase -i deadbeef
. - Select
facef00d
for editing. - When rebase returns you to a prompt to edit
facef00d
, rungit reset --hard HEAD^
. You should now be atdeadbeef
(git rev-parse HEAD
should printdeadbeef
). - Make your edits to fix the incorrect merge conflict and use
git add
to stage them. - Run
git commit --amend
to fuse the staged fix with the bad merge commit. The result will now have a different SHA1 (notdeadbeef
). - Run
git cherry-pick facef00d
to apply the changes made byfacef00d
to the fixed merge commit. - Run
git rebase --continue
to finish.
This is much easier now with the --rebase-merges
option available in Git 2.22 and above. This option preserves the merge topology, and works with interactive rebases.
It'll look something like this, assuming B
is the merge commit to amend:
label onto
... some branch definitions ...
reset onto
merge -C B branch-name # Merge branch 'B' into whatever
pick C
You can now insert a b
(or break
) in between the merge
and the pick
:
merge -C B branch-name # Merge branch 'xyz' into whatever
break
pick C
At the break commit --amend
the merge, and then continue the rebase.
This also works with a fixup commit. For example, lets say the commit with the fix for the merge is D
. You can move your fixup commit D to immediately after the merge commit:
merge -C B branch-name # Merge branch 'xyz' into whatever
fixup D
pick C
See the Rebasing Merges section of the git-merge man page.
May be easier to create a fixup commit 'D' then use 'git rebase -p -i <blah>
' to reorder 'D' right after 'B' and squash it into 'B'.
pick A
pick B <- merge commit to ammend
fixup D
pick C