Are there any git merge-strategies for ignoring submodule updates on a commit or branch merge into target branch?
Asked Answered
T

2

14

My usecase is: I have a two branches of a repo containing submodules. I want to set up auto-integrate of these two branches. But whenever there are submodule updates on both branches, my auto integrate script fails with conflicts on submodules. Is there way to ask git to ignore the changes in submodule pointers during the merge? Or provide something like merge=ours for the given submodules?

I've read this here.

I tried the merge strategies in the above page for submodule folder like this (but it only works for files)

submodule-name merge=ours
Tyrannicide answered 23/6, 2015 at 5:1 Comment(3)
Are you merging these two branches together, or something else?S
"git checkout branch1" and then "git merge branch2" (branch2 has new commits which has submodule updates)Tyrannicide
wow, this hasn't been answered for 5 years... And I encountered the exact same thing today and haven't found a single hint how to solve this (apart from plenty suggestions to resolve the conflict manually, but this is not an option!)Renzo
H
4

To be honest, I was surprised too, but the documentation quickly explains what is happening:

If the submodule histories have diverged and are committed to diverging branches in a superproject, [...] Git will not attempt even a trivial merge for you.

I guess the answer to the question "why don't merging strategies work?" is: because there is no merging process between different submodules versions. The approach suggested by the doc is still not that complex if you need to do an actual merge, but it is even "easier" in your case.

Suppose you are on master and you want to merge my_branch in it. There are 3 scenarios:

git merge my_branch

This is very well explained by the documentation I already linked, so there is no reason why I should repeat it. I still suggest you to read it because you could end up in an unexpected situation. I am telling you about it at the end of the answer.

git merge -Xours my_branch

I would not know if this is a correct way to solve this, but there is a very simple shortcut to clear any conflict on submodules. Because git does not do any merge operation on submodule versions, it only tells you about the diverging versions. So, if you look at the index, you still find the 3 versions coming from the 3-way merge

(output of git ls-files -s)

100644 acbc19aafbf0c14e67f9a437d465351a7e96388b 0   .gitmodules
100644 3da4fcc7b3a9bc886b50977dc35e10f48a42416b 0   your_files
160000 e826e1b762a17dbc7225b36db4a9f7f6c08774ad 1   submod
160000 fef2abfb901d20ba1f4d1023ba384bbc6afbc392 2   submod
160000 cd5caa8674fe078e0fb875861bb075ccb60cfee0 3   submod

The first one (e826e1b) is the merge base, but you are interested on the one with index 2. Unfortunately, I think you cannot refer to the submodule revision with the common :2:./submod while adding it to the index because it is not a real path. The easy shortcut I told you about is this one :)

git add submod

And then you can commit. It automatically keeps the master submodule version, but it is not the best way of course, since the behaviour could change in some future versions of git. I will show you the other way for the next scenario.

git merge -Xtheirs my_branch

Here things get complicated: we cannot use the above trick (simple add that for now defaults to the master submodule revision), because now we need the revision with index 3, which is :3:./submod that does not work with the add command.

You can also update the index with the plumbing command update-index and pass a raw cache-info entry, like:

git update-index --cacheinfo 160000,cd5caa8674fe078e0fb875861bb075ccb60cfee0,submod

mode 160000 is the one used for submodules (more specifically for git links), cd5caa86... is the object we want to add to the index, and submod is the path. Here, strangely, putting the submodule name works. If you need to script this, you cannot obviously put a hard-code object there, but you can retrieve it with

git rev-parse :3:./submod

In the end, this is the command to run to keep theirs version of submodule:

git update-index --cacheinfo 160000,$(git rev-parse :3:./submod),submod

Instead, to keep ours version, replace :3: with :2:.

Git does not merge submodules, but could reuse a merge

Also this part is very well explained in the reference, but you need to be very careful. In summary, git does not try to merge your submodules, unless:

  • the merge can be solved using a fast-forward strategy, and in this case it uses the most recent commit, OR
  • the merge for conflicting submodule revisions is already in the submodule repository, so git will just add that merge commit to the index (instead of ours or theirs).

In my humble opinion, git team should add an option to give us more control on how we want to handle these situations. By the way, you can always allow the commit and then --amend it, or fix it before committing using git merge --no-commit. In both cases you cannot use the index revisions (like :2:./submod), but HEAD^1 and HEAD^2 in the first case, and the MERGE_HEAD in the second case. If this is a very unlikely scenario, just skip it, otherwise I would commit and --amend if it is acceptable.

Conclusions

I hope there is an easier way to merge branches with different submodule revisions, so do not consider this answer as the one and only possibility. Moreover, after the merge you may need to deinit and reinitialize (or resync) your submodule, but as far as this is scriptable, this is not a real issue. If I were you, I would create an alias containing all these operations.

Hooke answered 26/3, 2021 at 12:52 Comment(7)
Please, if this is what you looked for, do not immediately accept this answer (or reward the bounty) because there are git experts that could contribute a lot on this topic. It is very possible that I overcomplicated the task.Hooke
Thanks for writing this detailed answer. I had to find a workaround quickly and in my special case didn't bother if I lost any of the changes on "my_branch", so finally went with something like git checkout main; git merge -s ours my_branch; git checkout my_branch; git merge master; git update-ref refs/heads/master origin/master. For some reason -s ours does work on submodules apparently... Though I don't care if it doesn't, as the next step is to update the submodule anyway ;) my_branch isn't productive and we can get away with a lot of fooling around, so this isn't a general solution!Renzo
Also wanted to add that this is basically a reset --hard of my_branch to master, which is actually what my script did, until someone had to run it who had no force-push permissions and thus wasn't allowed to change the history ;) The above construct replicates the effect of reset --hard to master with a new commit instead of removing the old ones, so it can be done with fewer permissions.Renzo
@Renzo Happy that you made it through. The strategy you have adopted (-s ours) does not imply a real merge because you just reuse the HEAD tree for the new commit tree, eventually discarding also any change from my_branch that would not have produced a conflict. If this is what you need, then very clever workaround :)Hooke
thanks ;) Yes that's why I wrote it's basically a reset --hard as it discards all commits on my_branch. The solution is really specific to my use-case, as my_branch only contains changes that were done by the same script that's now trying to do similar changes again, just based on latest master. That's also why my workaround doesn't get the bounty ;)Renzo
huh, I've had a look at those docs you linked and they state "If one of the commits is a direct ancestor of the other, then Git will simply choose the latter for the merge". So git just wants me to first merge the submodule, commit that to my_branch in the super, and then do the merge of main! I had tried getting both modules up to date before doing anything else and because it saves switching back and forth, I did the merge for supermodule first... I'll try the other way, too. (Oh man, this is why I don't like submodules 😆)Renzo
@Renzo haha let's say that sometimes working with submodules is not that easy. As I said in the Git does not merge submodules, but could reuse a merge paragraph, both cases are annoying if you are trying to do something different. By the way, yes, submodules merge is supposed to be done first if you want a clean merge.Hooke
S
0

Even this is an old post, just commenting for lazy people like me :P

Basically, you get this issue if your submodule history is different from a branch that you want to merge.

Try this way, if might help you too.

  1. update(or merge) your submodule branch with the branch you want.
  2. In main module commit and push all the updated references of submodules.
  3. Now try to merge, it should work.

As the submodules history is mismatched, try to match them by pulling/reverting/shifting to other branch and commit those submodule references, this worked for me. Hope it works for you too.

Supraorbital answered 7/7, 2021 at 8:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.