checkout --ours/--theirs: allow checking out one side of a conflicting merge
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>
Junio C Hamano committed
Aug 30, 2008 at 07:48 UTC
38901a48375952ab6c02f22bddfa19ac2bec2c36
3 files changed
+72
-3
Documentation/git-checkout.txt
+9
-2
@@ -9,7 +9,7 @@ SYNOPSIS
9
--------
10
[verse]
11
'git checkout' [-q] [-f] [[--track | --no-track] -b <new_branch> [-l]] [-m] [<branch>]
12
-'git checkout' [-f] [<tree-ish>] [--] <paths>...
12
+'git checkout' [-f|--ours|--theirs] [<tree-ish>] [--] <paths>...
13
14
DESCRIPTION
15
-----------
@@ -33,7 +33,9 @@ working tree.
33
The index may contain unmerged entries after a failed merge. By
34
default, if you try to check out such an entry from the index, the
35
checkout operation will fail and nothing will be checked out.
36
-Using -f will ignore these unmerged entries.
36
+Using -f will ignore these unmerged entries. The contents from a
37
+specific side of the merge can be checked out of the index by
38
+using --ours or --theirs.
39
40
OPTIONS
41
-------
@@ -48,6 +50,11 @@ OPTIONS
50
When checking out paths from the index, do not fail upon unmerged
51
entries; instead, unmerged entries are ignored.
52
53
+--ours::
54
+--theirs::
55
+ When checking out paths from the index, check out stage #2
56
+ ('ours') or #3 ('theirs') for unmerged paths.
57
+
58
-b::
59
Create a new branch named <new_branch> and start it at
60
<branch>. The new branch name must pass all checks defined
builtin-checkout.c
+38
-1
@@ -24,6 +24,7 @@ struct checkout_opts {
24
int quiet;
25
int merge;
26
int force;
27
+ int writeout_stage;
28
int writeout_error;
29
30
const char *new_branch;
@@ -95,6 +96,32 @@ static int skip_same_name(struct cache_entry *ce, int pos)
96
return pos;
97
}
98
99
+static int check_stage(int stage, struct cache_entry *ce, int pos)
100
+{
101
+ while (pos < active_nr &&
102
+ !strcmp(active_cache[pos]->name, ce->name)) {
103
+ if (ce_stage(active_cache[pos]) == stage)
104
+ return 0;
105
+ pos++;
106
+ }
107
+ return error("path '%s' does not have %s version",
108
+ ce->name,
109
+ (stage == 2) ? "our" : "their");
110
+}
111
+
112
+static int checkout_stage(int stage, struct cache_entry *ce, int pos,
113
+ struct checkout *state)
114
+{
115
+ while (pos < active_nr &&
116
+ !strcmp(active_cache[pos]->name, ce->name)) {
117
+ if (ce_stage(active_cache[pos]) == stage)
118
+ return checkout_entry(active_cache[pos], state, NULL);
119
+ pos++;
120
+ }
121
+ return error("path '%s' does not have %s version",
122
+ ce->name,
123
+ (stage == 2) ? "our" : "their");
124
+}
125
126
static int checkout_paths(struct tree *source_tree, const char **pathspec,
127
struct checkout_opts *opts)
@@ -106,7 +133,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
133
int flag;
134
struct commit *head;
135
int errs = 0;
109
-
136
+ int stage = opts->writeout_stage;
137
int newfd;
138
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
139
@@ -136,6 +163,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
163
continue;
164
if (opts->force) {
165
warning("path '%s' is unmerged", ce->name);
166
+ } else if (stage) {
167
+ errs |= check_stage(stage, ce, pos);
168
} else {
169
errs = 1;
170
error("path '%s' is unmerged", ce->name);
@@ -157,6 +186,8 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
186
errs |= checkout_entry(ce, &state, NULL);
187
continue;
188
}
189
+ if (stage)
190
+ errs |= checkout_stage(stage, ce, pos, &state);
191
pos = skip_same_name(ce, pos) - 1;
192
}
193
}
@@ -458,6 +489,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
489
OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
490
OPT_SET_INT('t', "track", &opts.track, "track",
491
BRANCH_TRACK_EXPLICIT),
492
+ OPT_SET_INT('2', "ours", &opts.writeout_stage, "stage",
493
+ 2),
494
+ OPT_SET_INT('3', "theirs", &opts.writeout_stage, "stage",
495
+ 3),
496
OPT_BOOLEAN('f', NULL, &opts.force, "force"),
497
OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
498
OPT_END(),
@@ -573,6 +608,8 @@ no_reference:
608
if (new.name && !new.commit) {
609
die("Cannot switch branch to a non-commit.");
610
}
611
+ if (opts.writeout_stage)
612
+ die("--ours/--theirs is incompatible with switching branches.");
613
614
return switch_branches(&opts, &new);
615
}
t/t7201-co.sh
+25
@@ -382,4 +382,29 @@ test_expect_success 'checkout with an unmerged path can be ignored' '
382
test_cmp sample file
383
'
384
385
+test_expect_success 'checkout unmerged stage' '
386
+ rm -f .git/index &&
387
+ O=$(echo original | git hash-object -w --stdin) &&
388
+ A=$(echo ourside | git hash-object -w --stdin) &&
389
+ B=$(echo theirside | git hash-object -w --stdin) &&
390
+ (
391
+ echo "100644 $A 0 fild" &&
392
+ echo "100644 $O 1 file" &&
393
+ echo "100644 $A 2 file" &&
394
+ echo "100644 $B 3 file" &&
395
+ echo "100644 $A 0 filf"
396
+ ) | git update-index --index-info &&
397
+ echo "none of the above" >sample &&
398
+ echo ourside >expect &&
399
+ cat sample >fild &&
400
+ cat sample >file &&
401
+ cat sample >filf &&
402
+ git checkout --ours . &&
403
+ test_cmp expect fild &&
404
+ test_cmp expect filf &&
405
+ test_cmp expect file &&
406
+ git checkout --theirs file &&
407
+ test ztheirside = "z$(cat file)"
408
+'
409
+
410
test_done