Code

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>]
-'git checkout' [-f] [<tree-ish>] [--] <paths>...
+'git checkout' [-f|--ours|--theirs] [<tree-ish>] [--] <paths>...
 
 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.
-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
 -------
@@ -48,6 +50,11 @@ OPTIONS
 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
index 1303f3b5b363d82cdc17f925b1ed82ce0d955175..16bfbb66055951e44c19410b6c889b3aa63891ec 100644 (file)
@@ -24,6 +24,7 @@ struct checkout_opts {
        int quiet;
        int merge;
        int force;
+       int writeout_stage;
        int writeout_error;
 
        const char *new_branch;
@@ -95,6 +96,32 @@ static int skip_same_name(struct cache_entry *ce, int 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)
@@ -106,7 +133,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
        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));
 
@@ -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);
+                       } else if (stage) {
+                               errs |= check_stage(stage, ce, pos);
                        } 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;
                        }
+                       if (stage)
+                               errs |= checkout_stage(stage, ce, pos, &state);
                        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_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(),
@@ -573,6 +608,8 @@ no_reference:
        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);
 }
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_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