Merge branch 'jc/maint-checkout-fix'
authorJunio C Hamano <gitster@pobox.com>
Mon, 8 Sep 2008 05:44:45 +0000 (22:44 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 8 Sep 2008 05:44:45 +0000 (22:44 -0700)
* jc/maint-checkout-fix:
checkout: do not check out unmerged higher stages randomly

Conflicts:
t/t7201-co.sh

1  2 
builtin-checkout.c
t/t7201-co.sh
diff --combined builtin-checkout.c
index efdb1e02bf21fb3b901d375a547d7fbf44e0894b,854401099410d5eeb590caf2ef56d4679ba77926..d986ac7abbd619c486a377e4433cf3fca687ea7a
@@@ -76,6 -76,15 +76,15 @@@ static int read_tree_some(struct tree *
        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;
        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;
        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;
                }
        }
  
@@@ -157,7 -184,7 +184,7 @@@ struct checkout_opts 
        int force;
        int writeout_error;
  
 -      char *new_branch;
 +      const char *new_branch;
        int new_branch_log;
        enum branch_track track;
  };
@@@ -386,11 -413,13 +413,11 @@@ static int switch_branches(struct check
        }
  
        /*
 -       * If the new thing isn't a branch and isn't HEAD and we're
 -       * not starting a new branch, and we want messages, and we
 -       * weren't on a branch, and we're moving to a new commit,
 -       * describe the old commit.
 +       * If we were on a detached HEAD, but we are now moving to
 +       * a new commit, we want to mention the old commit once more
 +       * to remind the user that it might be lost.
         */
 -      if (!new->path && strcmp(new->name, "HEAD") && !opts->new_branch &&
 -          !opts->quiet && !old.path && new->commit != old.commit)
 +      if (!opts->quiet && !old.path && new->commit != old.commit)
                describe_detached_head("Previous HEAD position was", old.commit);
  
        if (!old.commit) {
@@@ -435,28 -464,13 +462,28 @@@ int cmd_checkout(int argc, const char *
  
        git_config(git_default_config, NULL);
  
 -      opts.track = git_branch_track;
 +      opts.track = BRANCH_TRACK_UNSPECIFIED;
  
        argc = parse_options(argc, argv, options, checkout_usage,
                             PARSE_OPT_KEEP_DASHDASH);
  
 -      if (!opts.new_branch && (opts.track != git_branch_track))
 -              die("git checkout: --track and --no-track require -b");
 +      /* --track without -b should DWIM */
 +      if (0 < opts.track && !opts.new_branch) {
 +              const char *argv0 = argv[0];
 +              if (!argc || !strcmp(argv0, "--"))
 +                      die ("--track needs a branch name");
 +              if (!prefixcmp(argv0, "refs/"))
 +                      argv0 += 5;
 +              if (!prefixcmp(argv0, "remotes/"))
 +                      argv0 += 8;
 +              argv0 = strchr(argv0, '/');
 +              if (!argv0 || !argv0[1])
 +                      die ("Missing branch name; try -b");
 +              opts.new_branch = argv0 + 1;
 +      }
 +
 +      if (opts.track == BRANCH_TRACK_UNSPECIFIED)
 +              opts.track = git_branch_track;
  
        if (opts.force && opts.merge)
                die("git checkout: -f and -m are incompatible");
diff --combined t/t7201-co.sh
index 543b1c289866d01f8aa3f931f3579565a963de9d,83a366f1e7dcbe1d1678cadd87b9862713e82b97..25181388f8a8f1730d05fce128e0e1fc66eb3c0e
@@@ -3,7 -3,7 +3,7 @@@
  # Copyright (c) 2006 Junio C Hamano
  #
  
 -test_description='git-checkout tests.
 +test_description='git checkout tests.
  
  Creates master, forks renamer and side branches from it.
  Test switching across them.
@@@ -337,36 -337,26 +337,58 @@@ test_expect_success 
      test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
      test_must_fail git checkout --track -b track'
  
 +test_expect_success \
 +    'checkout with --track fakes a sensible -b <name>' '
 +    git update-ref refs/remotes/origin/koala/bear renamer &&
 +    git update-ref refs/new/koala/bear renamer &&
 +
 +    git checkout --track origin/koala/bear &&
 +    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
 +    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
 +
 +    git checkout master && git branch -D koala/bear &&
 +
 +    git checkout --track refs/remotes/origin/koala/bear &&
 +    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
 +    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
 +
 +    git checkout master && git branch -D koala/bear &&
 +
 +    git checkout --track remotes/origin/koala/bear &&
 +    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
 +    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)" &&
 +
 +    git checkout master && git branch -D koala/bear &&
 +
 +    git checkout --track refs/new/koala/bear &&
 +    test "refs/heads/koala/bear" = "$(git symbolic-ref HEAD)" &&
 +    test "$(git rev-parse HEAD)" = "$(git rev-parse renamer)"
 +'
 +
 +test_expect_success \
 +    'checkout with --track, but without -b, fails with too short tracked name' '
 +    test_must_fail git checkout --track renamer'
 +
+ 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