--------
[verse]
'git checkout' [-q] [-f] [-m] [<branch>]
+'git checkout' [-q] [-f] [-m] [--detach] [<commit>]
'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' --patch [<tree-ish>] [--] [<paths>...]
'git checkout' [<branch>]::
'git checkout' -b|-B <new_branch> [<start point>]::
+'git checkout' [--detach] [<commit>]::
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 or commit.
+
If `-b` is given, a new branch is created as if linkgit:git-branch[1]
were called and then checked out; in this case you can
Create the new branch's reflog; see linkgit:git-branch[1] for
details.
+--detach::
+ Rather than checking out a branch to work on it, check out a
+ commit for inspection and discardable experiments.
+ This is the default behavior of "git checkout <commit>" when
+ <commit> is not a branch name. See the "DETACHED HEAD" section
+ below for details.
+
--orphan::
Create a new 'orphan' branch, named <new_branch>, started from
<start_point> and switch to it. The first commit made on this
-Detached HEAD
+DETACHED HEAD
-------------
It is sometimes useful to be able to 'checkout' a commit that is
int quiet;
int merge;
int force;
+ int force_detach;
int writeout_stage;
int writeout_error;
if (!file_exists(ref_file) && file_exists(log_file))
remove_path(log_file);
}
- } else if (strcmp(new->name, "HEAD")) {
+ } else if (opts->force_detach || strcmp(new->name, "HEAD")) {
update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
REF_NODEREF, DIE_ON_ERR);
if (!opts->quiet) {
}
remove_branch_state();
strbuf_release(&msg);
- if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+ if (!opts->quiet &&
+ (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD"))))
report_tracking(new);
}
static int parse_branchname_arg(int argc, const char **argv,
int dwim_new_local_branch_ok,
+ int force_detach,
struct branch_info *new,
struct tree **source_tree,
unsigned char rev[20],
new->name = arg;
setup_branch_path(new);
- if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
+ if (!force_detach &&
+ check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
resolve_ref(new->path, branch_rev, 1, NULL))
hashcpy(rev, branch_rev);
else
OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
"create/reset and checkout a branch"),
OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"),
+ OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"),
OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch",
BRANCH_TRACK_EXPLICIT),
OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"),
opts.new_branch = opts.new_branch_force;
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
+ || opts.force_detach))
die ("--patch is incompatible with all other options");
+ if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch))
+ die("--detach cannot be used with -b/-B/--orphan");
+ if (opts.force_detach && 0 < opts.track)
+ die("--detach cannot be used with -t");
+
/* --track without -b should DWIM */
if (0 < opts.track && !opts.new_branch) {
const char *argv0 = argv[0];
dwim_new_local_branch &&
opts.track == BRANCH_TRACK_UNSPECIFIED &&
!opts.new_branch;
- int n = parse_branchname_arg(argc, argv, dwim_ok,
+ int n = parse_branchname_arg(argc, argv,
+ dwim_ok, opts.force_detach,
&new, &source_tree, rev, &opts.new_branch);
argv += n;
argc -= n;
}
}
+ if (opts.force_detach)
+ die("git checkout: --detach does not take a path argument");
+
if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
--- /dev/null
+#!/bin/sh
+
+test_description='checkout into detached HEAD state'
+. ./test-lib.sh
+
+check_detached () {
+ test_must_fail git symbolic-ref -q HEAD >/dev/null
+}
+
+check_not_detached () {
+ git symbolic-ref -q HEAD >/dev/null
+}
+
+reset () {
+ git checkout master &&
+ check_not_detached
+}
+
+test_expect_success 'setup' '
+ test_commit one &&
+ test_commit two &&
+ git branch branch &&
+ git tag tag
+'
+
+test_expect_success 'checkout branch does not detach' '
+ reset &&
+ git checkout branch &&
+ check_not_detached
+'
+
+test_expect_success 'checkout tag detaches' '
+ reset &&
+ git checkout tag &&
+ check_detached
+'
+
+test_expect_success 'checkout branch by full name detaches' '
+ reset &&
+ git checkout refs/heads/branch &&
+ check_detached
+'
+
+test_expect_success 'checkout non-ref detaches' '
+ reset &&
+ git checkout branch^ &&
+ check_detached
+'
+
+test_expect_success 'checkout ref^0 detaches' '
+ reset &&
+ git checkout branch^0 &&
+ check_detached
+'
+
+test_expect_success 'checkout --detach detaches' '
+ reset &&
+ git checkout --detach branch &&
+ check_detached
+'
+
+test_expect_success 'checkout --detach without branch name' '
+ reset &&
+ git checkout --detach &&
+ check_detached
+'
+
+test_expect_success 'checkout --detach errors out for non-commit' '
+ reset &&
+ test_must_fail git checkout --detach one^{tree} &&
+ check_not_detached
+'
+
+test_expect_success 'checkout --detach errors out for extra argument' '
+ reset &&
+ git checkout master &&
+ test_must_fail git checkout --detach tag one.t &&
+ check_not_detached
+'
+
+test_expect_success 'checkout --detached and -b are incompatible' '
+ reset &&
+ test_must_fail git checkout --detach -b newbranch tag &&
+ check_not_detached
+'
+
+test_expect_success 'checkout --detach moves HEAD' '
+ reset &&
+ git checkout one &&
+ git checkout --detach two &&
+ git diff --exit-code HEAD &&
+ git diff --exit-code two
+'
+
+test_done