Committing impossible due to unmerged files with git submodules

417 views Asked by At

We have a repo with a bunch of submodules and we are currently working on a develop branch.

Starting from the same common commit, two developers, A and B, made some changes to some files (unimportant here I think) and added a new revision of one of the submodules, then commited to develop. Now develop holds rev1 of subm commited by developer A, while B's develop branch holds rev2 of subm, thus creating a conflict when B tries to pull changes made by A from remote.

This is what git status gives to B, after a merge with conflicts:

Your branch and 'origin/develop' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)

All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
        modified:   path/to/another/submodule
        modified:   path/to/file/file.html

Now, whenever B tries to commit, it gets:

U       path/to/subm
error: Committing is not possible because you have unmerged files.
hint: Fix them up in the work tree, and then use 'git add/rm <file>'
hint: as appropriate to mark resolution and make a commit.
fatal: Exiting because of an unresolved conflict.

I tried looking up for solutions and they all seem to suggest to add the conflicted files to the stage to mark them as resolved, but git status does not list any unmerged files.

It looks like I am missing something here, any help would be gladly appreciated.

1

There are 1 answers

0
torek On

What you need to realize here is that a Git submodule is another Git repository.

Let's label all the repositories in play here. I'm going to assume that both developers have their own clones, so that there is another Git at some third location (perhaps GitHub, perhaps a corporate server, whatever it might be):

  • CentralSuper: this is the repository that both developers cloned.
  • CentralSubmod1: this is submodule #1, that will be cloned into path/to/another/submodule.
  • CentralSubmod2: this is submodule #2, that will be cloned into path/to/subm.
  • DevASuper: this is developer A's superproject clone.
  • DevASubmod1: this is the clone of submodule #1 that developer A is using.
  • DevASubmod2: this it the clone of submodule #2 that developer A is using.
  • DevBSuper: this is developer B's superproject clone.
  • DevBSubmod1: this is developer B's submodule #1 clone.
  • DevBSubmod2: this is developer B's submodule #2 clone.

When developer A does some work in DevASuper, he:

  • modifies some file(s), or doesn't—it's not all that important;
  • enters his DevASubmod2 repository and modifies some file(s);
  • commits in DevASubmod2 to make a new commit, which gets a new and unique hash ID; let's call this X;
  • returns to DevASuper and uses git add to add the new commit in DevASubmod2 as an updated gitlink entry; and
  • commits in DevASuper.

The new commit, in DevASuper, contains a gitlink that says "use commit X in submodule #2".

Developer A can push this new commit to the central superproject (perhaps on a branch). Before he does so, he should also push the new DevASubmod2 commit to CentralSubmod2 (also perhaps on a branch). Whoever merges these, to the branch named develop on CentralSuper, should make sure the submodule repository commit in CentralSubmod2 is available to everyone first, presumably by merging that first. (The person who merges them could be developer A himself. This is just a general pattern: the submodule commit must be available first.)

Let's call the final commit, on the develop branch in CentralSuper, commit SX, with the S standing for Super and the X standing for commit X (commit SX uses / refers-to commit X from submodule#2).

Now we get to Developer B

At any time before or at this point, developer B can make new commits in any of his three repositories. Based on what you've posted, it seems likely that he has made a new commit in DevBSubmod2. Let's call this commit Y. Like commit X, it is some big ugly hash ID.

Like developer A, developer B should—and apparently did—now make a new commit in his DevBSuper repository using git add and git commit. We'll call this commit SY.

So now, developer B wants to merge his new commit with the final result from whoever did the merge of developer A's commit. But commit SX says use hash X while SY says use hash Y.

This is the submodule conflict you are seeing:

U       path/to/subm
error: Committing is not possible because you have unmerged files.
hint: Fix them up in the work tree, and then use 'git add/rm <file>'
hint: as appropriate to mark resolution and make a commit.
fatal: Exiting because of an unresolved conflict.

The superproject that B is working with/on has a commit, SY, that says use Y, and a commit, SX, that says use X. These "use" things are gitlinks in these two commits.

Git cannot resolve this on its own. If the correct resolution is use X, developer B should put that in his superproject's index. If the correct resolution is use Y, developer B should put that in his superproject's index. Chances are very good, however, that neither commit X nor commit Y is the correct commit to use for a new commit in developer B's superproject repository.

If this last is true, what developer B must now do is:

  • Enter his DevBSubmod2 repository.
  • Do work in this repository to combine commits X and Y.
  • Test the result (standalone and with the proposed new commit in developer B's DevBSuper repository). Once all is good, make some new commit Z. Make sure this is the current commit in DevBSubmod2.
  • Return to his superproject—DevBSuper—and tell his Git to add path/to/subm as commit Z, using git add path/to/subm. This resolves the merge conflict and says that the correct gitlink goes to commit Z. Neither commit X nor commit Y is correct; Z is correct.
  • Make a new merge commit in DevBSuper, using this gitlink. We'll call this commit SZ.

At this point developer B can now send these back to the various central repositories, again keeping in mind that the new submodule commit must be available to everyone before making commit SZ available to everyone, since using SZ requires that they obtain commit Z.