Reputation: 247
We have the following situation: Someone manually resolved A LOT (tens of thousands) of merge conflicts without any git merge tool but did so manually and saved into a different file When trying to fix this, I'm doing a proper merge. Now I know how to resolve all conflicts - take the one in the old, manually resolved file. Is there a way to do this automagically - resolve by "third"?
Upvotes: 0
Views: 384
Reputation: 488253
Besides Mark Adelsberger's answer (which I believe is correct and I've upvoted), it's worth adding another note. (This should therefore be a comment, except that one cannot format comments and there is a small length limit, which this exceeds by, um, "a lot".)
A merge commit is simply a commit with at least two parents. That is, a "normal" or non-merge commit is a commit with one or, in rare cases, no parents.1 Here is one such actual commit:
$ git rev-parse HEAD
ccdcbd54c4475c2238b310f7113ab3075b5abc9c
$ git cat-file -p HEAD | sed 's/@/ /'
tree 191f960868564ef1f0978328589aa191219f1ab8
parent 96f29521a3908eb80b9552f11f2b75ca34475686
author Junio C Hamano <gitster pobox.com> 1525762230 +0900
committer Junio C Hamano <gitster pobox.com> 1525762789 +0900
The fifth batch for 2.18
Signed-off-by: Junio C Hamano <gitster pobox.com>
A merge commit would look the same, but have two or more parent
lines (and of course a different log message). The tree
line is how Git records the source tree snapshot that goes with the commit.
Git makes the source tree snapshot in any commit using Git's index, which is a data structure that is either so important, or was so poorly named originally,2 that it has three names: the index, the staging area, and the cache. In normal, no-merge-conflict cases, the index stores one copy of each file. The index's version is in the special Git-only format, while the work-tree version that you work on is in normal everyday file format on your computer. You work on the work-tree version, then use git add
to copy the work-tree version over top of the index version, transforming the normal format file into a new, special, Git-only version ready to commit.
During a conflicted merge, though, the index takes on a larger role: now instead of just one copy of the file, it holds three copies of a conflicted file, using three extra "slots" that exist per file name. A normal, unconflicted file lives in slot zero. When there is a merge conflict, though, Git puts the base version of the file in slot 1, the current (--ours
, or left or local) version in slot 2, and the other (--theirs
, or right or remote) version in slot 3. So there are three copies of the file in the index, plus of course the one in the work-tree with conflict markers.
What you, as a human, do for Git when you handle merge conflicts is to manipulate the index until it has just one version of the conflicted file, instead of storing all three, using the three versions in the index and the work-tree copy in any way that you like. This is true no matter how you do the merge: Git offers you all four different varieties of the file, three of them in the index and one in the work-tree, and in the end, you tell Git to put something into Git's index in slot zero, and forget the other three version entirely.
Once you have done that—once you have eliminated the three higher-stage slot entries in the index, by resolving the merge conflicts, using your editor or a merge tool or perhaps via numerology or whatevermancy, and run git add
to copy the final version into the normal slot-zero—only then can you git commit
the result. When you do, Git makes the same kind of tree
snapshot as for any commit. This tree object is a snapshot like any commit's tree, so a merge holds a snapshot just like any other commit. In other words, a merge commit is no different from any other commit*, except that it has at least two parent commits.
What makes a merge commit look different is that when you ask Git to tell you about a commit—any commit—Git does so by comparing the commit to its parent. But a merge has at least two parents, so how can Git compare it to the (single) parent? Git's answer varies:
git log
just doesn't bother to compare it at all, by default.git show
produces what Git calls a combined diff, by default. (Combined diffs leave a lot out, on purpose, so they're not terribly useful in terms of figuring out what happened. They mostly show that there was some conflict and it was resolved by something other than just picking one "side" of the merge.)Other commands generally follow one of these two options. The commands that can show a merge generally also have a -m
option, which tells Git to split the merge, at least for comparison purposes, into multiple separate "virtual commits": each such commit has the same tree as the merge, but has just one parent. Now that the "virtual commit" has one parent instead of two-or-more, Git can show it in the usual way, by comparing (diffing) the virtual commit against its (single) parent.
That doesn't change what's in the merge commit, which is an ordinary snapshot, made in the same way as for any ordinary non-merge commit, using whatever you had in the index when you made the commit. So in this sense there's nothing special about resolving conflicts at all! You just write a file with contents into your work-tree, use git add
to put it in the index, and use git commit
to snapshot that into the commit—even if the final commit has two parents.
1A commit with no parents is a root commit, and the very first commit in a repository is always a root commit since it has no previous commit to attach-to. Most repositories will tend to have only this one root commit, although since Git is in part just a graph processing system, you can create as many root commits as you like. Most merge commits have two parents since the normal merge process is to merge one specific commit—usually via a branch name, but Git only cares about the commit here—into your current branch, so that gives "current commit" as parent #1, and "other commit" as parent #2.
Git can make what Git calls octopus merges, using the current commit as parent #1, and a whole slew of additional commits as the rest of the parents, using Git's -s octopus
merge strategy. However, the octopus merge strategy refuses to deal with conflicts because the index does not have room for additional copies of a conflicted file. There are only three "sides" that can go into the index: base, left/local/ours, and right/remote/other/theirs.
2Personally, I think the answer is a little of both: important and poorly-named. :-)
Upvotes: 2
Reputation: 45659
The comments on the question don't entirely make sense; so I suspect some comments may have been deleted (or in any case aren't showing up for me), and I hope I'm not missing anything pertinent. That said:
At the "individual merge of an individual file" level, the best solution is to start the merge and, when prompted to resolve conflicts, copy the pre-resolved file over the working copy; then git add
it and commit
to complete the merge.[1]
And if you have just a single merge of a single file, then that's what you should do; because the setup for anything more automatic will take more time than just doing it.
If, on the other hand, you have a lot of files (possibly across multiple merges), then maybe a bit more automation makes sense. If you can set up a parallel directory structure to your work tree, and put the pre-merged files in their respective places in that parallel structure, then cp -R
could work. Or if you have them committed in git, you might be able to use git checkout <commit> -- <path>
syntax to grab the fixed versions.
I guess if you really wanted to get fancy, you could set up a merge driver that copies in the pre-merged file and avoid reporting the conflict in the first place; but the setup to make that work correctly is still just as tedious (worse, in fact, because of the added steps of creating the merge driver), and the automation just means you'll have a committed merge before you've had the chance to do a final sanity check... Honestly I wouldn't bother with this idea.
[1] In your comments, you say that you're concerned about the log showing individual changes instead of a completely rewritten file, and this is where I feel like some context is missing. At any rate, copying the pre-resolved file in to resolve the conflict will not, in and of itself, cause git to see the file as "completely rewritten". The log output is a calculated patch. It shows the differences between the files, regardless of the process used to produce each version.
So if it shows a "completely rewritten file", that means that it sees a difference on (more or less) every line. That could be due to changes in white space (especially line ending changes tend to sneak through in some situations). Or some sort of gratuitous reformatting.
If something like that is going on, and if you can't reverse the "unnecessary" changes on the pre-merged copy, then you may have to decide whether it's better to redo the merges manually, or to put up with a blip in the log.
Upvotes: 2