checkout --ours/--theirs: allow checking out one side of a conflicting merge
authorJunio C Hamano <gitster@pobox.com>
Sat, 30 Aug 2008 14:48:18 +0000 (07:48 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 31 Aug 2008 02:28:45 +0000 (19:28 -0700)
This lets you to check out 'our' (or 'their') version of an
unmerged path out of the index while resolving conflicts.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-checkout.txt
builtin-checkout.c
t/t7201-co.sh
index 15fdb08ce078fe58db1c4b2484c638d0522703f2..a9ca2f552017cc592c8ff6b553bf0bbf875646a0 100644 (file)
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git checkout' [-q] [-f] [[--track | --no-track] -b <new_branch> [-l]] [-m] [<branch>]
 --------
 [verse]
 'git checkout' [-q] [-f] [[--track | --no-track] -b <new_branch> [-l]] [-m] [<branch>]
-'git checkout' [-f] [<tree-ish>] [--] <paths>...
+'git checkout' [-f|--ours|--theirs] [<tree-ish>] [--] <paths>...
 
 DESCRIPTION
 -----------
 
 DESCRIPTION
 -----------
@@ -33,7 +33,9 @@ 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.
 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.
+Using -f will ignore these unmerged entries.  The contents from a
+specific side of the merge can be checked out of the index by
+using --ours or --theirs.
 
 OPTIONS
 -------
 
 OPTIONS
 -------
@@ -48,6 +50,11 @@ OPTIONS
 When checking out paths from the index, do not fail upon unmerged
 entries; instead, unmerged entries are ignored.
 
 When checking out paths from the index, do not fail upon unmerged
 entries; instead, unmerged entries are ignored.
 
+--ours::
+--theirs::
+       When checking out paths from the index, check out stage #2
+       ('ours') or #3 ('theirs') for unmerged paths.
+
 -b::
        Create a new branch named <new_branch> and start it at
        <branch>.  The new branch name must pass all checks defined
 -b::
        Create a new branch named <new_branch> and start it at
        <branch>.  The new branch name must pass all checks defined
index 1303f3b5b363d82cdc17f925b1ed82ce0d955175..16bfbb66055951e44c19410b6c889b3aa63891ec 100644 (file)
@@ -24,6 +24,7 @@ struct checkout_opts {
        int quiet;
        int merge;
        int force;
        int quiet;
        int merge;
        int force;
+       int writeout_stage;
        int writeout_error;
 
        const char *new_branch;
        int writeout_error;
 
        const char *new_branch;
@@ -95,6 +96,32 @@ static int skip_same_name(struct cache_entry *ce, int pos)
        return pos;
 }
 
        return pos;
 }
 
+static int check_stage(int stage, struct cache_entry *ce, int pos)
+{
+       while (pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name)) {
+               if (ce_stage(active_cache[pos]) == stage)
+                       return 0;
+               pos++;
+       }
+       return error("path '%s' does not have %s version",
+                    ce->name,
+                    (stage == 2) ? "our" : "their");
+}
+
+static int checkout_stage(int stage, struct cache_entry *ce, int pos,
+                         struct checkout *state)
+{
+       while (pos < active_nr &&
+              !strcmp(active_cache[pos]->name, ce->name)) {
+               if (ce_stage(active_cache[pos]) == stage)
+                       return checkout_entry(active_cache[pos], state, NULL);
+               pos++;
+       }
+       return error("path '%s' does not have %s version",
+                    ce->name,
+                    (stage == 2) ? "our" : "their");
+}
 
 static int checkout_paths(struct tree *source_tree, const char **pathspec,
                          struct checkout_opts *opts)
 
 static int checkout_paths(struct tree *source_tree, const char **pathspec,
                          struct checkout_opts *opts)
@@ -106,7 +133,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
        int flag;
        struct commit *head;
        int errs = 0;
        int flag;
        struct commit *head;
        int errs = 0;
-
+       int stage = opts->writeout_stage;
        int newfd;
        struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
 
        int newfd;
        struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
 
@@ -136,6 +163,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
                                continue;
                        if (opts->force) {
                                warning("path '%s' is unmerged", ce->name);
                                continue;
                        if (opts->force) {
                                warning("path '%s' is unmerged", ce->name);
+                       } else if (stage) {
+                               errs |= check_stage(stage, ce, pos);
                        } else {
                                errs = 1;
                                error("path '%s' is unmerged", ce->name);
                        } else {
                                errs = 1;
                                error("path '%s' is unmerged", ce->name);
@@ -157,6 +186,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
                                errs |= checkout_entry(ce, &state, NULL);
                                continue;
                        }
                                errs |= checkout_entry(ce, &state, NULL);
                                continue;
                        }
+                       if (stage)
+                               errs |= checkout_stage(stage, ce, pos, &state);
                        pos = skip_same_name(ce, pos) - 1;
                }
        }
                        pos = skip_same_name(ce, pos) - 1;
                }
        }
@@ -458,6 +489,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
                OPT_SET_INT('t', "track",  &opts.track, "track",
                        BRANCH_TRACK_EXPLICIT),
                OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
                OPT_SET_INT('t', "track",  &opts.track, "track",
                        BRANCH_TRACK_EXPLICIT),
+               OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
+                           2),
+               OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
+                           3),
                OPT_BOOLEAN('f', NULL, &opts.force, "force"),
                OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
                OPT_END(),
                OPT_BOOLEAN('f', NULL, &opts.force, "force"),
                OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
                OPT_END(),
@@ -573,6 +608,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        if (new.name && !new.commit) {
                die("Cannot switch branch to a non-commit.");
        }
        if (new.name && !new.commit) {
                die("Cannot switch branch to a non-commit.");
        }
+       if (opts.writeout_stage)
+               die("--ours/--theirs is incompatible with switching branches.");
 
        return switch_branches(&opts, &new);
 }
 
        return switch_branches(&opts, &new);
 }
index 88692f98774bb22f0fb2850b6331e63e8bb65966..c7ae14118a538414c71170516d683a888878a656 100755 (executable)
@@ -382,4 +382,29 @@ test_expect_success 'checkout with an unmerged path can be ignored' '
        test_cmp sample file
 '
 
        test_cmp sample file
 '
 
+test_expect_success 'checkout unmerged stage' '
+       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 --ours . &&
+       test_cmp expect fild &&
+       test_cmp expect filf &&
+       test_cmp expect file &&
+       git checkout --theirs file &&
+       test ztheirside = "z$(cat file)"
+'
+
 test_done
 test_done