The short answer is no. But that answer is (a) unsatisfying and (b) leaves out what you can do to approach your final goal.
I think this is best illustrated by making some drawings. Let's make a simplified picture of your problem, showing things the way Git sees them, commit-by-commit. We begin with some common commit at the end of some sequence of commits:
...--F--G--H <-- main
We now make some branches (and stop using the name main
entirely for now) and on these side branches we make some commits:
I--...--J <-- branch1
/
...--G--H
\
K--...--L <-- branch2
There are probably many commits on the two non-main
branches here, even though we only show two.
When you merge these, the way Git achieves the merge result is this:
- Git extracts
H
's snapshot, and then J
's snapshot, and compares the two to see what changed on branch1
.
- Git extracts
H
's snapshot, and then L
's snapshot, and compares the two to see what changed on branch2
.
In your cases, this is ... a whole lot of changes. :-) It's so many that you don't want to handle them all. Git, however, will insist on handling all of them. If you get conflicts, git merge
will stop in the middle of the job and exit and force you to fix all the conflicts. However you go about doing that, once you're done, Git will believe that the resulting files are the correct merge result.
To finish the merge after resolving everything, you run git merge --continue
or git commit
. This makes a new merge commit M
:
I--...--J
/ \
...--G--H M
\ /
K--...--L
Commit M
, like every commit, has a full snapshot of all files. This is by definition the correct result of this merge. That's why the short answer is no.
Now for the trick
But—here is why the simple "no" answer is incomplete—you will note that I left off all the branch names in the above. Suppose, before beginning the merge, we create one new branch name, and make that our current branch name, like this:
I--...--J <-- branch1, merge-A (HEAD)
/
...--G--H
\
K--...--L <-- branch2
When we finish the merge, we will have this:
I--...--J <-- branch1
/ \
...--G--H M <-- merge-A (HEAD)
\ /
K--...--L <-- branch2
Note that no existing commit has changed, and only the name merge-A
has changed, to point to new merge commit M
.
What this means is that you can now use all three branches to continue working, to whatever extent you like. Then, on the next day (or week or month), you can go back to branch1
and make another new name, such as merge-B
, and make that the current branch:
I--...--J <-- branch1, merge-B (HEAD)
/
...--G--H
\
K--...--L <-- branch2
(This assumes branch1
and branch2
have not moved. If they have moved, it's probably wise to find the hash IDs of commits J
and L
, and use them here. Note that they are find-able as the two parents of merge commit M
, which still exists—I have just chosen to stop drawing it for the moment.)
We now run the same kind of git merge
, to make a commit MB
or M2
, in which we really only resolve the module-B files. As far as Git is concerned, this is the correct result of this merge, i.e., the module-A and module-C files are all correctly merged (regardless of what we stuck in them for the MB
snapshot). We must remember that modules A and C are mis-merged, just as when we made M
, we had to remember that modules B and C were mis-merged. But in any case we get this result:
I--...--J <-- branch1
/ \
...--G--H MB <-- merge-B (HEAD)
\ /
K--...--L <-- branch2
MB
is a new commit, separate from M
(or MA
if we want to start calling it that).
Later, we can repeat this to make an MC
merge.
If we try to draw them all in, it gets pretty messy:
...--J____
|\ \
| \ MA
| \ /
| X
\ / \
X MB
/ \ /
| X
| / \
| / MC
|/___/
...--K
(this would go better with graph-drawing tools, obviously).
You are now free to extract particular files from each of the three MA
, MB
, and MC
commits, and combine them to produce some new merge commit. Exactly how you go about doing that is up to you. Exactly what you make in terms of new commits to record this is also up to you. I myself might go for a new merge commit with just parents J
and L
—i.e., a fourth merge, made in the same way that each of these three merges were made, but with its snapshot built by extracting the three merge results.
Note that this is not "free"
If development continues after making the three separate MA
, MB
, and MC
merges, you're still stuck with merging in the post-MA
, post-MB
, and post-MC
commits. This requires running git merge
again and getting still more merge conflicts. It will probably be very important to record very explicit merge messages in MA
, MB
, and MC
—and perhaps even in some subsequent commits—to remind yourself and/or others that there is still work to be done in the future. Then, when you make the "correct" merge of that merges all three modules, you'll need to do the extra merge work, perhaps recording the branch-tip commits from post-MA
, post-MB
, and post-MC
commits as the second commits of each of these various merges.
The final graph will be very tangly and confusing. That's somewhat unavoidable here. Using octopus merges, one can perhaps afford each of the many legs of the octopodes equal "status" or "priority" in terms of the final result, but that is also a confusing way to work. Whether Git's built-in -s octopus
strategy—which is how Git normally makes an octopus merge—can handle this is questionable; if it can't, you can use git commit-tree
to make these merge commits manually, or just use regular two-at-a-time merges to build the final result.
The benefit, if any, of such an octopus merge is not very large. Here's a hand ASCII drawing of what one might look like, building on the three-M
merges:
...--J____
|\ \
| \ MA
| \ / \
| X \
\ / \ \
X MB--MM <-- main
/ \ / /
| X /
| / \ /
| / MC
|/___/
...--K
but note here that no work has happened since each of the three separate M
merges. Having made MM
—the master merge of all modules—it's now time to tackle that work.