?

Log in

No account? Create an account

December 26th, 2009


Previous Entry Share Next Entry
12:40 am - Fun with undoing conflict resolution with "git checkout -m file"
We deal with conflicts in many situations while integrating changes from different sources:
  • We may be merging two branches that independently worked on different theme that happened to touch overlapping parts of the code ("git merge");
  • We may be transplanting the change made by a commit in an unrelated part of the history on top of our current state ("git cherry-pick");
  • We may be recording our wish to reverse an earlier commit that introduced a wrong change in our current history ("git revert");
  • We may be forward-porting an earlier work of ours on top of the upstream that has been updated ("git rebase");
  • We may be accepting a patch that is based on the source on a wrong branch ("git am -3"); or
  • We may have started local changes on a wrong branch, and are switching to the correct branch on which we should have started the local changes ("git checkout -m branch").
These mergy operations are handled exactly in the same way in all of these situations. Our task is to replay the change one side made on top of the state of the other side.  Three versions of files are involved: the base version, our version (i.e. the version to which the changes from the other side is replayed), and their version (i.e. the version the changes to be replayed is taken from).

If there is no overlap between our changes (since base) and their changes (again, since base), the merge is automatically resolved cleanly, and you don't have to deal with manual resolution. The merge results are already recorded in the index, and are waiting for you to make a commit out of them.

When there are overlaps, however, these operations record the three versions for each conflicted file in the index, and you will see changes from both our side and their side in the file in your work tree, marked with conflict markers, to resolve manually.

The workflow to resolve these is the same in all of these situations:
  1. Run a mergy operation (e.g. "git merge", "git cherry-pick") that results in conflicts;
  2. Check with "git diff" to examine conflicts;
  3. Edit the conflicted files;
  4. "git add files" to mark the result resolved; and
  5. Conclude the mergy operation (e.g. "git commit" after "git merge" conflicted; "git rebase --continue" after one step of "git rebase" conflicted).
One issue is that we are not perfect. We often make mistakes and end up with a wrong conflict resolution in the "Edit the conflicted files" step. Even if we wish to redo the resolution from scratch, once edited away, the originally conflicted state is not visible in the files in the work tree anymore.

With recent enough git (v1.6.1 or newer), we can use:

git checkout -m file

to reproduce the automerged result (with conflict markers) of a conflicted path file, using the three versions (base, ours and theirs) recorded in the index. With versions of git up to v1.6.6 (released recently), however, this is only possible until we "git add file" to mark the path as resolved, at which point the three versions are discarded from the index and replaced with the result of the merge. Hence, the workflow to use "git checkout -m file" becomes like this:
  1. Run a mergy operation (e.g. "git merge", "git cherry-pick") that results in conflicts;
  2. Check with "git diff" to examine conflicts;
  3. Edit the conflicted files, go back to "git diff" step as necessary;
  4. If you made a mistake in resolving conflicts in file, "git checkout -m file" to recover, and go back to "Edit the conflicted files" step;
  5. Make sure everything is correct;
  6. "git add files" to mark the result resolved; and
  7. Conclude the mergy operation (e.g. "git commit" after "git merge" conflicted; "git rebase --continue" after one step of "git rebase" conflicted).
In other words, "git add" step becomes the point of no return if you want to be able to fix a botched resolution. This is very unfortunate, as it inevitably leads to a rather awkward workflow of editing each and every conflict away, making sure all of them are correct and then finally "git add" to mark all of them as resolved, as outlined above.

When dealing with a large merge, which is where we need a better tool support the most, we would rather wish to be able to work incrementally.

Today, because everybody is eating his or her ham (or whatever one celebrates one's end-of-year holidays with) and not hacking, I managed to spend some quality time addressing this issue. The main idea is to record the conflicted state in a hidden part of the index as an "undo" information, every time a conflicted path is marked as resolved by using "git add". With this information, even after a path is marked as resolved with "git add file", "git checkout -m file" can first resurrect the conflicts in the index from this "undo" information, and then reproduce the conflicted state in the work tree. I've written minimum test and queued the result in the 'pu' branch, and hopefully we can perfect the series before v1.7.0 ships. With this change, the workflow will become:
  1. Run a mergy operation (e.g. "git merge", "git cherry-pick") that results in conflicts;
  2. Check with "git diff" to examine conflicts;
  3. Edit some conflicted files, go back to "git diff" step as necessary;
  4. "git add file" to mark a path as dealt with (in other words, you do not have to have total confidence in the result at this point), go back to "git diff" step to review the result, or to deal with other paths;
  5. If you made a mistake in resolving conflicts in file, "git checkout -m file" to recover, and go back to "Edit some conflicted files" step;
  6. Make sure everything is dealt with and is correct; and
  7. Conclude the mergy operation (e.g. "git commit" after "git merge" conflicted; "git rebase --continue" after one step of "git rebase" conflicted).
This is much nicer as it does not have the point of no return anymore, except for the obvious and inevitable "Conclude" step.


 
Tags:

(3 comments | Leave a comment)

Comments:


From:ext_220019
Date:December 29th, 2009 05:22 pm (UTC)

Sounds good

(Link)
Looking forward to seeing this in next, would love to try it out in daily use :). The new workflow you described nicely matches what I (want to) do when doing merges.
[User Picture]
From:tbreisacher
Date:December 30th, 2010 01:23 am (UTC)
(Link)
Did this make it into the 1.7.something release?
[User Picture]
From:gitster
Date:January 13th, 2011 06:22 pm (UTC)

Ancient news

(Link)
"checkout -m" first appeared in 1.7.0 and has been part of us since early February 2010.

Please notice the year of the original entry ;-) You are commenting on an old news.
Fun with undoing conflict resolution with "git checkout -m file" - gitster's journal

> Recent Entries
> Archive
> Friends
> Profile

Links
Pages at the k.org
Gifts
貢ぎ物
The latest blog by Gitster

> Go to Top
LiveJournal.com