checkout -f: allow ignoring unmerged paths when checking out of the index
authorJunio C Hamano <gitster@pobox.com>
Sat, 30 Aug 2008 14:46:55 +0000 (07:46 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 31 Aug 2008 02:16:12 +0000 (19:16 -0700)
Earlier we made "git checkout $pathspec" to atomically refuse
the operation of $pathspec matched any path with unmerged
stages. This patch allows:

$ git checkout -f a b c

to ignore, instead of error out on, such unmerged paths. The
fix to prevent checkout of an unmerged path from random stages
is still there.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-checkout.txt
builtin-checkout.c
t/t7201-co.sh
index 5aa69c0e12a6756fd6f79c117008a373f65ba5f5..15fdb08ce078fe58db1c4b2484c638d0522703f2 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git checkout' [-q] [-f] [[--track | --no-track] -b <new_branch> [-l]] [-m] [<branch>]
-'git checkout' [<tree-ish>] [--] <paths>...
+'git checkout' [-f] [<tree-ish>] [--] <paths>...
 
 DESCRIPTION
 -----------
@@ -23,14 +23,17 @@ options, which will be passed to `git branch`.
 
 When <paths> are given, this command does *not* switch
 branches.  It updates the named paths in the working tree from
-the index file (i.e. it runs `git checkout-index -f -u`), or
-from a named commit.  In
-this case, the `-f` and `-b` options are meaningless and giving
+the index file, or from a named commit.  In
+this case, the `-b` options is meaningless and giving
 either of them results in an error.  <tree-ish> argument can be
 used to specify a specific tree-ish (i.e. commit, tag or tree)
 to update the index for the given paths before updating the
 working tree.
 
+The index may contain unmerged entries after a failed merge.  By
+default, if you try to check out such an entry from the index, the
+checkout operation will fail and nothing will be checked out.
+Using -f will ignore these unmerged entries.
 
 OPTIONS
 -------
@@ -38,8 +41,12 @@ OPTIONS
        Quiet, suppress feedback messages.
 
 -f::
-       Proceed even if the index or the working tree differs
-       from HEAD.  This is used to throw away local changes.
+       When switching branches, proceed even if the index or the
+       working tree differs from HEAD.  This is used to throw away
+       local changes.
++
+When checking out paths from the index, do not fail upon unmerged
+entries; instead, unmerged entries are ignored.
 
 -b::
        Create a new branch named <new_branch> and start it at
index 854401099410d5eeb590caf2ef56d4679ba77926..1303f3b5b363d82cdc17f925b1ed82ce0d955175 100644 (file)
@@ -20,6 +20,17 @@ static const char * const checkout_usage[] = {
        NULL,
 };
 
+struct checkout_opts {
+       int quiet;
+       int merge;
+       int force;
+       int writeout_error;
+
+       const char *new_branch;
+       int new_branch_log;
+       enum branch_track track;
+};
+
 static int post_checkout_hook(struct commit *old, struct commit *new,
                              int changed)
 {
@@ -85,7 +96,8 @@ static int skip_same_name(struct cache_entry *ce, int pos)
 }
 
 
-static int checkout_paths(struct tree *source_tree, const char **pathspec)
+static int checkout_paths(struct tree *source_tree, const char **pathspec,
+                         struct checkout_opts *opts)
 {
        int pos;
        struct checkout state;
@@ -122,8 +134,12 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec)
                if (pathspec_match(pathspec, NULL, ce->name, 0)) {
                        if (!ce_stage(ce))
                                continue;
-                       errs = 1;
-                       error("path '%s' is unmerged", ce->name);
+                       if (opts->force) {
+                               warning("path '%s' is unmerged", ce->name);
+                       } else {
+                               errs = 1;
+                               error("path '%s' is unmerged", ce->name);
+                       }
                        pos = skip_same_name(ce, pos) - 1;
                }
        }
@@ -178,17 +194,6 @@ static void describe_detached_head(char *msg, struct commit *commit)
        strbuf_release(&sb);
 }
 
-struct checkout_opts {
-       int quiet;
-       int merge;
-       int force;
-       int writeout_error;
-
-       char *new_branch;
-       int new_branch_log;
-       enum branch_track track;
-};
-
 static int reset_tree(struct tree *tree, struct checkout_opts *o, int worktree)
 {
        struct unpack_trees_options opts;
@@ -554,15 +559,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                        die("invalid path specification");
 
                /* Checkout paths */
-               if (opts.new_branch || opts.force || opts.merge) {
+               if (opts.new_branch || opts.merge) {
                        if (argc == 1) {
-                               die("git checkout: updating paths is incompatible with switching branches/forcing\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
+                               die("git checkout: updating paths is incompatible with switching branches.\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
                        } else {
-                               die("git checkout: updating paths is incompatible with switching branches/forcing");
+                               die("git checkout: updating paths is incompatible with switching branches.");
                        }
                }
 
-               return checkout_paths(source_tree, pathspec);
+               return checkout_paths(source_tree, pathspec, &opts);
        }
 
        if (new.name && !new.commit) {
index 83a366f1e7dcbe1d1678cadd87b9862713e82b97..88692f98774bb22f0fb2850b6331e63e8bb65966 100755 (executable)
@@ -359,4 +359,27 @@ test_expect_success 'checkout an unmerged path should fail' '
        test_cmp sample file
 '
 
+test_expect_success 'checkout with an unmerged path can be ignored' '
+       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 &&
+       echo ourside >expect &&
+       cat sample >fild &&
+       cat sample >file &&
+       cat sample >filf &&
+       git checkout -f fild file filf &&
+       test_cmp expect fild &&
+       test_cmp expect filf &&
+       test_cmp sample file
+'
+
 test_done