gitgit-submodulesrebasegit-rewrite-history

Rebase git submodule and parent repo


My situation: You have a feature branch based off master, and someone commits to master. Now your history is like this:

A - B - C - D (master)
         \
           E - F - G  (feature)

So you'd like to rebase feature onto master for a clean history, as one does. But consider this: that repo is a submodule of another, and the parent repo references the submodule's commits like so:

A - B - C - D (submodule:master)
         \
           E - F - G  (submodule:feature)
           *    *
           *     *
           X - Y - Z             (parent:feature)
              (asterisks represent references to submodule)

If I rebase the submodule naively, the parent's references to submodule's commits will be invalid. Let's assume that some of the commits in the feature branches are meaningful enough to separate, so squashing them into one commit is out.

Any way to do it and maintain those references? (both 'feature' branches can be freely rewritten).


Solution

  • A - B - C - D (submodule:master)
             \
               E - F - G  (submodule:feature)
               *    *
               *     *
               X - Y - Z             (parent:feature)
                  (asterisks represent references to submodule)
    

    To rebase a submodule (e.g. feature onto master) and update the parent's gitlinks:

    1. Create another branch at the submodule's rebase tip (like 'old_feature' at 'feature').

    2. Do the rebase in the submodule.

    3. Note that you can figure the mapping between the old submodule commits (E -> G) and the new (E' -> G') and their ids. You'll need these later.

    4. You should also know the range of commits to alter in the parent repo (X -> Z), and which specific commits have gitlinks that need to be updated.

    5. Do an interactive rebase in parent on said commits. Insert a 'break' command after each that needs to be altered.

    6. As git drops to shell each time:

      1. For the current parent commit's old gitlink, checkout in the submodule the appropriate new commit.

      2. In the parent, stage the submodule and git commit --amend. This updates the gitlink.

      3. Continue rebase. If there are conflicts (should be a lot), prefer the ones with gitlinks to submodule's old commits, as these conflicts occur before we update them. (when I did it this was the option "use the remote changes" in local vs remote in git mergetool).

    7. Done


    It's rather involved (and potentially slow if you have a lot) and it's a question of whether you think it's worth it.

    The easier way, per @NicolasVoron

    1. Classic, clean way : do a merge instead of a rebase

    Do a merge. E,F,G will be linked to the merge commit. In that way, X,Y,Z will remain for ever and unchanged, even if the branch is deleted (only the ref of the branch would be in that case. But at the expense of a un-linear history on master). When you will push parent branch, X,Y,Z will be pushed too.