TL;DR: git mergetool
cannot be run until after you run git merge
and it stops in the middle with a merge conflict.
Long
A merge operation involves three, not two, commits, and in general is substantially more complicated than a diff operation, which only involves two commits. A diff is, in effect, stateless: it takes what you have and compares things, and shows you the result of the comparison. You can therefore run git diff
or git difftool
at any time. As you saw, changes you make in git difftool
don't normally affect the files: the diff is operating on a copy of each file, and the copy goes away when the operation is done.
Merging is not stateless. Merging has a distinctive starting phase, in which you and Git cooperate to find the three commits of interest. This is followed by an extensive middle part, in which Git computes two diffs—one from the merge base to your current commit, and one from the merge base to the other commit—and attempts to combine the two diffs. Git can sometimes complete this phase on its own. Sometimes, it cannot, and must stop and get help. Even if Git can do this on its own, you can instruct Git to stop, as if it was unable to complete the merge on its own.
In any case, once the second phase is done, the merge enters its third and final phase. This involves making a new commit. The new commit is almost exactly like any ordinary (non-merge) commit, with one key difference: the new commit has two parents instead of the usual single parent.
It is, I think, helpful to view an illustration of how merge works. Imagine each commit being identified by some hash ID (which is actually the case), with each commit linking back to a previous commit (which is also actually the case). Your two branches both descend from some common commit, like this:
o--o--...--T <-- alpha_critical
/
...--o--B
\
o--...--o--H <-- gamma_mine
Each round o
represents some commit, which holds some snapshot of every file. Newer commits appear towards the right. The common shared commit is the the one labeled B
(for Base). Since this commit is on both branches, and is just a single commit, every file here is the same in both branches. So if we compare commit B
to commit T
(--theirs
), whatever is different—whatever diff would show—must be changes that they made. If we compare commit B
to commit H
(--ours
or HEAD
), whatever is different must be changes that you made.
Git's job will be to combine these differences. To the extent that Git can do this on its own, it will apply the combined differences to the snapshot from commit B
. Where it cannot, it will record a merge conflict.
The git mergetool
command does not start or perform a merge. Instead, it picks up from the point where Git gave up with a merge conflict.
Let's say that some file named main.py
appears in commits B
(base), H
(yours), and T
(theirs), but that it is changed in both yours and theirs, and one of the changes was to change the usage message. Unfortunately, they changed the usage message in a different way than the way you changed the usage message. Git was therefore unable to combine your change and their change.
If you were to look at the copy of main.py
in your work-tree, this copy would have conflict markers in it, showing your change (from H
) and their change (from T
). If you set merge.conflictStyle
to diff3
, the file would also show what the conflicting lines looked like in the B
version of this file.
What git mergetool
will do is fish out the three input versions of the file: the one from commit B
, the one from commit H
, and the one from commit T
. (These are not in the work-tree but are easily available to git mergetool
.) It will then invoke your chosen merge tool on these three files as well as the work-tree copy. Your job is to update the work-tree copy so that it contains the correct merge resolution, whatever that is.
The mergetool command repeats this for each conflicted file. It marks the file as properly resolved once you exit your merge tool (though there are various safety checks since not every tool reports this information correctly). The mergetool commmand stops when there are no conflicted files left. It can only be used while there are conflicted files.
Hence, to do a merge, you must:
- run
git merge
, perhaps with --no-commit
if you want to inspect the result even if Git thinks Git got it right;
- finish any conflicted merges that Git left behind, and mark them resolved: you can do this by hand, or using
git mergetool
; and
- if the merge stopped after step 1, use
git merge --continue
(or git commit
) to tell Git to finish the merge. You will also need to supply a merge commit message, just like you supply any commit message, although you can use the (relatively low quality) default that git merge
builds on its own (and most people mostly do).
If the merge does not stop after step 1, it will go on through step 3 on its own unless you use --no-commit
.
Once the merge is complete, you end up with this:
o--o--...--T <-- alpha_critical
/ \
...--o--B M <-- gamma_mine (HEAD)
\ /
o--...--o--H
where new merge commit M
has the old tip commit commit H
as its first parent and commit T
(from their branch) as its second parent. Your branch name now points to the new merge commit.