purefanatic
purefanatic

Reputation: 1023

How to amend successive commits such that they reference other submodule commits?

After amending some commit messages in a submodule I figured I should also update the parent repositories' commits to repair the broken references.

So I did an interactive rebase on the parent repository. I wanted to amend two commits in the parent repository that refer to two other commits in the submodule, like this:

---A---B---C---D--- parent
   |   |
   v   v
---a---b--- sub

Therefore I do git rebase -i A^ and choose to edit A and B:

edit A ...
edit B ...
pick C ...
pick D ...

Now I am at commit A. To restore the correct submodule commit ID I first check the relevant submodule commit out and then amend the commit in the parent repository.

$ cd sub/
$ git checkout a
$ cd ..
$ git add sub
$ git commit --amend

Seems good, so I continue to the next one:

$ git rebase --continue

Now it complains it could not merge. Here my translation from a localized message:

Error merging submodule sub (Commits do not follow any merge base)
automatic merge of sub
CONFLICT (submodule): Merge conflict in sub
error: Could not apply B (B's commit message)
Resolve all conflicts manually, mark them as resolved with
"git add/rm ", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply B (B's commit message)

Fair enough. The second commit has lost its connection to the first one because of the changed submodule ID in the first one. git status says changed by both: sub. Unswervingly, I try to amend once again:

$ cd sub/
$ git checkout b
$ cd ..
$ git add sub
$ git commit --amend

Unfortunately this does not do what I want. The reason is that the erroneous git rebase --continue somehow ate commit B completely. B's changes were put in the staging area but the commit is gone. Therefore the second git commit --amend will amend A and put all changes from B into A! Now I guess I could create a new commit at that point, replicating B. But to get it exactly as B would be rather complicated.

How do I amend submodule references correctly? Why and under which circumstances does rebasing destroy commits like that?

Upvotes: 0

Views: 132

Answers (1)

torek
torek

Reputation: 488453

TL;DR: Do the git commit without --amend use git rebase --continue, which will do the git commit without --amend.

Longer

Let's take your original diagram, minus the connections to the submodule (since they're flimsy and get in the way :-) ):

...--o--A--B--C--D   <-- branch

You run git rebase -i A^ and change A to edit (and so on). Rebase cherry-picks A to a new copy A':

...--o--A--B--C--D   <-- branch
      \
       A'  <-- HEAD

You make your update, git add, and git commit --amend to make new A", leaving A' behind as abandoned:

       A"   <-- HEAD
      /
...--o--A--B--C--D   <-- branch
      \
       A'

Now, regardless of what you put in for instructions for B, Git tries to copy B but fails. So the rebase stops at:

       A"   <-- HEAD
      /
...--o--A--B--C--D   <-- branch
      \
       A'

with some uncommitted changes in the index and work-tree. There is no commit B' yet. There are two things you do can do now. One is to make it yourself:

<fix the submodule>
git add sub
git commit

This creates B':

       A"-B'   <-- HEAD
      /
...--o--A--B--C--D   <-- branch
      \
       A'

and now you can git rebase --continue to have Git go on to the next part of the instructions (hence you don't need to ask to edit B originally, you can just go on to copy C to C' now: if Git now stops to let you edit B' you can just tell it to continue).

The other—since git commit now requires that you type in a commit message—is to set everything up, and instead of git commit, run git rebase --continue to make Git make B':

<fix the submodule>
git add sub
git rebase --continue

The --continue step notices that all conflicts are fixed (or complains and stops if they're not), and then does the git commit and goes on, exactly as if you'd made the commit yourself.

Summary

You need edit as an instruction to make rebase stop and let you --amend a cherry-pick that will succeed. You don't need edit for a commit where the cherry-pick will fail, as Git will stop without having made the commit in the first place.

It's hard to know in advance—in some cases, it might as well be impossible—to know which cherry-picks are going to succeed and which are going to fail. So you wind up having to pay a lot of attention to the exact messages that come out just before git rebase stops to let you do something: did it stop because the cherry-pick failed, or did it stop because you told it to stop after the cherry-pick succeeded? In other words, is there a commit that you should --amend now, or should you (have Git) make a new commit now?

(I get tripped up by this myself. I find it pretty annoying.)

Upvotes: 1

Related Questions