This isn't really an answer (and should be a comment) but it needs formatting and won't fit into comment space. Instead, this is instructions on how to find the answer, or at least come up with the right inputs that will lead to the answer.
When you run:
git checkout master
git merge report
Git first finds a merge base commit. Rarely—probably not the case here—Git finds more than one merge base commit, but we'll make sure that isn't the case.
Let's say this merge is going to go wrong, but—importantly—hasn't been done yet. If it has been done already, we need a trick, or a separate repository in which it hasn't been done: either will suffice. The trick is to create a new branch, not named master
, that points to the commit that master
would have pointed-to before the merge:
git checkout -b test-the-merge <hash-ID>
(We can throw away this branch later. Or, we can even not create it at all, using another trick, but for simplicity it's easier to use the test branch.)
Now that we are in the state where the merge hasn't been done yet, we first run:
git merge-base --all HEAD report
Ideally, this produces one hash ID. If it produces more than one hash ID, we have that rare situation where there is more than one merge base, and we need a process that I'm going to omit because it's long and mostly boring. :-)
Now that we know the one hash ID that is the merge base, here's how we see what Git will see, when Git does the merge:
git diff --find-renames <hash> HEAD # what does Git think *we* changed?
git diff --find-renames <hash> report # what does Git think *they* changed?
You might want to send these two git diff
s to files, so that you can peruse them at leisure and/or in parallel. You can also restrict the output to particular files, if there are a lot of diffs that merge correctly and you just want to focus on the particular files that don't merge correctly.
Note that what git diff
finds is not necessarily the change that some person made. What git diff
finds is a minimal set of instructions that produce the same effect. This is usually good enough. For instance, suppose that between the left side commit (the merge base, specified by hash ID) and the right side commit, someone deleted the second line, the first word the
, of a redundant passage:
Paris in
the
the
spring
and Git chooses to instead produce the instructions: delete the second the
(the third line).
Does it matter? Probably not—but sometimes, it does. What if the left and right sides read:
some vaguely C like code {
with redundant stuff
}
more vaguely C like code {
with redundant stuff
}
and:
some vaguely C like code {
with redundant stuff
}
still vaguely C like code {
with redundant stuff
}
more vaguely C like code {
with redundant stuff
}
and Git synchronizes, mistakenly, on close-braces and produces a syntactically-invalid diff? Well, even that usually still works, but sometimes it produces inappropriate conflicts. In extremely rare cases (which nonetheless do occur but are very hard to illustrate), you may miss a conflict that should have occurred, due to Git coming up with a minimal edit that is syntactically correct but semantically wrong.
In any case, what Git now does, having produced the two diff listings, is to come up with the merged set of source files. To do that, Git starts with the merge base version of all the files. Then:
For each file that you, they, or both changed...
Did you change the file? Did they not change the file? If so, take your file.
Did they change the file, and you not change it? If so, take their file.
Otherwise, you both changed the file. Attempt to combine the changes, going line by line through the diffs. Wherever you added some lines, take your added lines. Wherever they added some, take their added lines. Wherever you or they deleted some lines, take the deletion. If you both made the exact same change to the exact same lines, take one copy of the change. If you made different changes to the same lines, or to lines that abut (touch each other) or hit the end of file, declare a merge conflict.
For files you or they deleted entirely, or created as totally-new, or renamed, do the appropriate thing. (What exactly appropriate means gets complicated and does not appear to be your case here, so I won't go into it.)
Having combined everything to the best of its ability, Git now:
- stops with the merge conflict, or
- stops because you told it to (
--no-commit
, for instance), or
- makes a merge commit because everything seems to have gone swimmingly.
If Git makes the merge commit—or if it stops and lets you make it and you do make it—the merge commit has your current branch's old tip commit as its first parent—the new tip commit is the merge commit itself—and the merge commit has the other branch's tip commit as its second parent. That is, after this merge on master
or on test-the-merge
(whichever branch we're on), we have:
git rev-parse <branch>^1 => hash ID of the previous branch tip
git rev-parse <branch>^2 => same hash ID as git rev-parse report
You can look at the two inputs, from the two diffs made from the three commits—merge base, HEAD
, and theirs / tip-of-report
—to see what Git sees, which will explain why Git made the merge that it did.
In any case, this is what git merge
does: it finds a merge base, makes two diffs, and then combines the diffs and applies the combined diffs to the merge base to come up with the merge result. (This of course ignores all the special cases, such as not-actually-merging for various reasons: -s ours
, or fast-forward, or unrelated histories, or merge conflicts, etc.) The diffs are inherently line-oriented and cannot reconstruct what someone really did: they only produce a set of instructions that will come up with the same final text. Combining such instructions tends to work with most programming languages, but is definitely not perfect.
If that process works with your files, you can trust it. If that process doesn't work with your files, you cannot trust it, and you must carefully inspect—and correct if needed—each merge.