Code

checkout: do not check out unmerged higher stages randomly
authorJunio C Hamano <gitster@pobox.com>
Fri, 29 Aug 2008 20:40:36 +0000 (13:40 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 30 Aug 2008 23:46:25 +0000 (16:46 -0700)
During a conflicted merge when you have unmerged stages for a
path F in the index, if you said:

    $ git checkout F

we rewrote F as many times as we have stages for it, and the
last one (typically "theirs") was left in the work tree, without
resolving the conflict.

This fixes it by noticing that a specified pathspec pattern
matches an unmerged path, and by erroring out.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin-checkout.c
t/t7201-co.sh

index 411cc513c65ba854221ad52dd6aeaaac7d213c9d..854401099410d5eeb590caf2ef56d4679ba77926 100644 (file)
@@ -76,6 +76,15 @@ static int read_tree_some(struct tree *tree, const char **pathspec)
        return 0;
 }
 
+static int skip_same_name(struct cache_entry *ce, int pos)
+{
+       while (++pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name))
+               ; /* skip */
+       return pos;
+}
+
+
 static int checkout_paths(struct tree *source_tree, const char **pathspec)
 {
        int pos;
@@ -107,6 +116,20 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
        if (report_path_error(ps_matched, pathspec, 0))
                return 1;
 
+       /* Any unmerged paths? */
+       for (pos = 0; pos < active_nr; pos++) {
+               struct cache_entry *ce = active_cache[pos];
+               if (pathspec_match(pathspec, NULL, ce->name, 0)) {
+                       if (!ce_stage(ce))
+                               continue;
+                       errs = 1;
+                       error("path '%s' is unmerged", ce->name);
+                       pos = skip_same_name(ce, pos) - 1;
+               }
+       }
+       if (errs)
+               return 1;
+
        /* Now we are committed to check them out */
        memset(&state, 0, sizeof(state));
        state.force = 1;
@@ -114,7 +137,11 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
                if (pathspec_match(pathspec, NULL, ce->name, 0)) {
-                       errs |= checkout_entry(ce, &state, NULL);
+                       if (!ce_stage(ce)) {
+                               errs |= checkout_entry(ce, &state, NULL);
+                               continue;
+                       }
+                       pos = skip_same_name(ce, pos) - 1;
                }
        }
 
index 9ad5d635a2881c920fff8e524aea0ed931f68e6c..83a366f1e7dcbe1d1678cadd87b9862713e82b97 100755 (executable)
@@ -337,4 +337,26 @@ test_expect_success \
     test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
     test_must_fail git checkout --track -b track'
 
+test_expect_success 'checkout an unmerged path should fail' '
+       rm -f .git/index &&
+       O=$(echo original | git hash-object -w --stdin) &&
+       A=$(echo ourside | git hash-object -w --stdin) &&
+       B=$(echo theirside | git hash-object -w --stdin) &&
+       (
+               echo "100644 $A 0       fild" &&
+               echo "100644 $O 1       file" &&
+               echo "100644 $A 2       file" &&
+               echo "100644 $B 3       file" &&
+               echo "100644 $A 0       filf"
+       ) | git update-index --index-info &&
+       echo "none of the above" >sample &&
+       cat sample >fild &&
+       cat sample >file &&
+       cat sample >filf &&
+       test_must_fail git checkout fild file filf &&
+       test_cmp sample fild &&
+       test_cmp sample filf &&
+       test_cmp sample file
+'
+
 test_done