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>
This commit is contained in:
Junio C Hamano 2008-08-30 07:48:18 -07:00
parent db9410990e
commit 38901a4837
3 changed files with 72 additions and 3 deletions

View File

@ -9,7 +9,7 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git checkout' [-q] [-f] [[--track | --no-track] -b <new_branch> [-l]] [-m] [<branch>] '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 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 default, if you try to check out such an entry from the index, the
checkout operation will fail and nothing will be checked out. 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 When checking out paths from the index, do not fail upon unmerged
entries; instead, unmerged entries are ignored. 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:: -b::
Create a new branch named <new_branch> and start it at Create a new branch named <new_branch> and start it at
<branch>. The new branch name must pass all checks defined <branch>. The new branch name must pass all checks defined

View File

@ -24,6 +24,7 @@ struct checkout_opts {
int quiet; int quiet;
int merge; int merge;
int force; int force;
int writeout_stage;
int writeout_error; int writeout_error;
const char *new_branch; 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, static int checkout_paths(struct tree *source_tree, const char **pathspec,
struct checkout_opts *opts) struct checkout_opts *opts)
@ -106,7 +133,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
int flag; int flag;
struct commit *head; struct commit *head;
int errs = 0; int errs = 0;
int stage = opts->writeout_stage;
int newfd; int newfd;
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); 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; continue;
if (opts->force) { if (opts->force) {
warning("path '%s' is unmerged", ce->name); warning("path '%s' is unmerged", ce->name);
} else if (stage) {
errs |= check_stage(stage, ce, pos);
} else { } else {
errs = 1; errs = 1;
error("path '%s' is unmerged", ce->name); 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); errs |= checkout_entry(ce, &state, NULL);
continue; 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_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
OPT_SET_INT('t', "track", &opts.track, "track", OPT_SET_INT('t', "track", &opts.track, "track",
BRANCH_TRACK_EXPLICIT), 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('f', NULL, &opts.force, "force"),
OPT_BOOLEAN('m', NULL, &opts.merge, "merge"), OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
OPT_END(), OPT_END(),
@ -573,6 +608,8 @@ no_reference:
if (new.name && !new.commit) { if (new.name && !new.commit) {
die("Cannot switch branch to a non-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);
} }

View File

@ -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