builtin/checkout: learn -B

Internally, --track and --orphan still use the 'safe' -b, not -B.

Signed-off-by: Tay Ray Chuan <rctay89@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Tay Ray Chuan 2010-06-24 03:29:00 +08:00 committed by Junio C Hamano
parent 4c6887516c
commit 02ac98374e
3 changed files with 88 additions and 7 deletions

View file

@ -9,7 +9,7 @@ SYNOPSIS
-------- --------
[verse] [verse]
'git checkout' [-q] [-f] [-m] [<branch>] 'git checkout' [-q] [-f] [-m] [<branch>]
'git checkout' [-q] [-f] [-m] [[-b|--orphan] <new_branch>] [<start_point>] 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>... 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
'git checkout' --patch [<tree-ish>] [--] [<paths>...] 'git checkout' --patch [<tree-ish>] [--] [<paths>...]
@ -21,7 +21,7 @@ also update `HEAD` to set the specified branch as the current
branch. branch.
'git checkout' [<branch>]:: 'git checkout' [<branch>]::
'git checkout' -b <new branch> [<start point>]:: 'git checkout' -b|-B <new_branch> [<start point>]::
This form switches branches by updating the index, working This form switches branches by updating the index, working
tree, and HEAD to reflect the specified branch. tree, and HEAD to reflect the specified branch.
@ -31,6 +31,17 @@ were called and then checked out; in this case you can
use the `--track` or `--no-track` options, which will be passed to use the `--track` or `--no-track` options, which will be passed to
'git branch'. As a convenience, `--track` without `-b` implies branch 'git branch'. As a convenience, `--track` without `-b` implies branch
creation; see the description of `--track` below. creation; see the description of `--track` below.
+
If `-B` is given, <new_branch> is created if it doesn't exist; otherwise, it
is reset. This is the transactional equivalent of
+
------------
$ git branch -f <branch> [<start point>]
$ git checkout <branch>
------------
+
that is to say, the branch is not reset/created unless "git checkout" is
successful.
'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...:: 'git checkout' [--patch] [<tree-ish>] [--] <pathspec>...::
@ -75,6 +86,12 @@ entries; instead, unmerged entries are ignored.
Create a new branch named <new_branch> and start it at Create a new branch named <new_branch> and start it at
<start_point>; see linkgit:git-branch[1] for details. <start_point>; see linkgit:git-branch[1] for details.
-B::
Creates the branch <new_branch> and start it at <start_point>;
if it already exists, then reset it to <start_point>. This is
equivalent to running "git branch" with "-f"; see
linkgit:git-branch[1] for details.
-t:: -t::
--track:: --track::
When creating a new branch, set up "upstream" configuration. See When creating a new branch, set up "upstream" configuration. See

View file

@ -32,7 +32,11 @@ struct checkout_opts {
int writeout_stage; int writeout_stage;
int writeout_error; int writeout_error;
/* not set by parse_options */
int branch_exists;
const char *new_branch; const char *new_branch;
const char *new_branch_force;
const char *new_orphan_branch; const char *new_orphan_branch;
int new_branch_log; int new_branch_log;
enum branch_track track; enum branch_track track;
@ -511,7 +515,8 @@ static void update_refs_for_switch(struct checkout_opts *opts,
} }
} }
else else
create_branch(old->name, opts->new_branch, new->name, 0, create_branch(old->name, opts->new_branch, new->name,
opts->new_branch_force ? 1 : 0,
opts->new_branch_log, opts->track); opts->new_branch_log, opts->track);
new->name = opts->new_branch; new->name = opts->new_branch;
setup_branch_path(new); setup_branch_path(new);
@ -531,7 +536,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
new->name); new->name);
else else
fprintf(stderr, "Switched to%s branch '%s'\n", fprintf(stderr, "Switched to%s branch '%s'\n",
opts->new_branch ? " a new" : "", opts->branch_exists ? " and reset" : " a new",
new->name); new->name);
} }
if (old->path && old->name) { if (old->path && old->name) {
@ -659,6 +664,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
OPT__QUIET(&opts.quiet), OPT__QUIET(&opts.quiet),
OPT_STRING('b', NULL, &opts.new_branch, "branch", OPT_STRING('b', NULL, &opts.new_branch, "branch",
"create and checkout a new branch"), "create and checkout a new branch"),
OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
"create/reset and checkout a branch"),
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),
@ -689,6 +696,14 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, options, checkout_usage, argc = parse_options(argc, argv, prefix, options, checkout_usage,
PARSE_OPT_KEEP_DASHDASH); PARSE_OPT_KEEP_DASHDASH);
/* we can assume from now on new_branch = !new_branch_force */
if (opts.new_branch && opts.new_branch_force)
die("-B cannot be used with -b");
/* copy -B over to -b, so that we can just check the latter */
if (opts.new_branch_force)
opts.new_branch = opts.new_branch_force;
if (patch_mode && (opts.track > 0 || opts.new_branch if (patch_mode && (opts.track > 0 || opts.new_branch
|| opts.new_branch_log || opts.merge || opts.force)) || opts.new_branch_log || opts.merge || opts.force))
die ("--patch is incompatible with all other options"); die ("--patch is incompatible with all other options");
@ -710,7 +725,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
if (opts.new_orphan_branch) { if (opts.new_orphan_branch) {
if (opts.new_branch) if (opts.new_branch)
die("--orphan and -b are mutually exclusive"); die("--orphan and -b|-B are mutually exclusive");
if (opts.track > 0) if (opts.track > 0)
die("--orphan cannot be used with -t"); die("--orphan cannot be used with -t");
opts.new_branch = opts.new_orphan_branch; opts.new_branch = opts.new_orphan_branch;
@ -859,8 +874,12 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
if (strbuf_check_branch_ref(&buf, opts.new_branch)) if (strbuf_check_branch_ref(&buf, opts.new_branch))
die("git checkout: we do not like '%s' as a branch name.", die("git checkout: we do not like '%s' as a branch name.",
opts.new_branch); opts.new_branch);
if (!get_sha1(buf.buf, rev)) if (!get_sha1(buf.buf, rev)) {
die("git checkout: branch %s already exists", opts.new_branch); opts.branch_exists = 1;
if (!opts.new_branch_force)
die("git checkout: branch %s already exists",
opts.new_branch);
}
strbuf_release(&buf); strbuf_release(&buf);
} }

View file

@ -118,4 +118,49 @@ test_expect_success 'checkout -b to an existing branch fails' '
test_must_fail do_checkout branch2 $HEAD2 test_must_fail do_checkout branch2 $HEAD2
' '
test_expect_success 'checkout -B to an existing branch resets branch to HEAD' '
git checkout branch1 &&
do_checkout branch2 "" -B
'
test_expect_success 'checkout -B to an existing branch with an explicit ref resets branch to that ref' '
git checkout branch1 &&
do_checkout branch2 $HEAD1 -B
'
test_expect_success 'checkout -B to an existing branch with unmergeable changes fails' '
git checkout branch1 &&
setup_dirty_unmergeable &&
test_must_fail do_checkout branch2 $HEAD1 -B &&
test_dirty_unmergeable
'
test_expect_success 'checkout -f -B to an existing branch with unmergeable changes discards changes' '
# still dirty and on branch1
do_checkout branch2 $HEAD1 "-f -B" &&
test_must_fail test_dirty_unmergeable
'
test_expect_success 'checkout -B to an existing branch preserves mergeable changes' '
git checkout branch1 &&
setup_dirty_mergeable &&
do_checkout branch2 $HEAD1 -B &&
test_dirty_mergeable
'
test_expect_success 'checkout -f -B to an existing branch with mergeable changes discards changes' '
# clean up from previous test
git reset --hard &&
git checkout branch1 &&
setup_dirty_mergeable &&
do_checkout branch2 $HEAD1 "-f -B" &&
test_must_fail test_dirty_mergeable
'
test_done test_done