@matheusbsbs / matheus-bagliano / commits / 32669671c7
Fork 0

checkout: introduce --detach synonym for "git checkout foo^{commit}"

For example, one might use this when making a temporary merge to test that two topics work well together. Patch by Junio, with tests from Jeff King. [jn: with some extra checks for bogus commandline usage] Suggested-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Jonathan Nieder <jrnieder@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Feb 8, 2011 at 04:32 UTC 32669671c7746888aa1e3832907deb7fc8405061
3 files changed +126 -7
Documentation/git-checkout.txt
+11 -2
@@ -9,6 +9,7 @@ SYNOPSIS
9 --------
10 [verse]
11 'git checkout' [-q] [-f] [-m] [<branch>]
12 +'git checkout' [-q] [-f] [-m] [--detach] [<commit>]
13 'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
14 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
15 'git checkout' --patch [<tree-ish>] [--] [<paths>...]
@@ -22,9 +23,10 @@ branch.
23
24 'git checkout' [<branch>]::
25 'git checkout' -b|-B <new_branch> [<start point>]::
26 +'git checkout' [--detach] [<commit>]::
27
28 This form switches branches by updating the index, working
27 - tree, and HEAD to reflect the specified branch.
29 + tree, and HEAD to reflect the specified branch or commit.
30 +
31 If `-b` is given, a new branch is created as if linkgit:git-branch[1]
32 were called and then checked out; in this case you can
@@ -115,6 +117,13 @@ explicitly give a name with '-b' in such a case.
117 Create the new branch's reflog; see linkgit:git-branch[1] for
118 details.
119
120 +--detach::
121 + Rather than checking out a branch to work on it, check out a
122 + commit for inspection and discardable experiments.
123 + This is the default behavior of "git checkout <commit>" when
124 + <commit> is not a branch name. See the "DETACHED HEAD" section
125 + below for details.
126 +
127 --orphan::
128 Create a new 'orphan' branch, named <new_branch>, started from
129 <start_point> and switch to it. The first commit made on this
@@ -204,7 +213,7 @@ leave out at most one of `A` and `B`, in which case it defaults to `HEAD`.
213
214
215
207 -Detached HEAD
216 +DETACHED HEAD
217 -------------
218
219 It is sometimes useful to be able to 'checkout' a commit that is
builtin/checkout.c
+20 -5
@@ -30,6 +30,7 @@ struct checkout_opts {
30 int quiet;
31 int merge;
32 int force;
33 + int force_detach;
34 int writeout_stage;
35 int writeout_error;
36
@@ -563,7 +564,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
564 if (!file_exists(ref_file) && file_exists(log_file))
565 remove_path(log_file);
566 }
566 - } else if (strcmp(new->name, "HEAD")) {
567 + } else if (opts->force_detach || strcmp(new->name, "HEAD")) {
568 update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
569 REF_NODEREF, DIE_ON_ERR);
570 if (!opts->quiet) {
@@ -574,7 +575,8 @@ static void update_refs_for_switch(struct checkout_opts *opts,
575 }
576 remove_branch_state();
577 strbuf_release(&msg);
577 - if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
578 + if (!opts->quiet &&
579 + (new->path || (!opts->force_detach && !strcmp(new->name, "HEAD"))))
580 report_tracking(new);
581 }
582
@@ -677,6 +679,7 @@ static const char *unique_tracking_name(const char *name)
679
680 static int parse_branchname_arg(int argc, const char **argv,
681 int dwim_new_local_branch_ok,
682 + int force_detach,
683 struct branch_info *new,
684 struct tree **source_tree,
685 unsigned char rev[20],
@@ -753,7 +756,8 @@ static int parse_branchname_arg(int argc, const char **argv,
756 new->name = arg;
757 setup_branch_path(new);
758
756 - if (check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
759 + if (!force_detach &&
760 + check_ref_format(new->path) == CHECK_REF_FORMAT_OK &&
761 resolve_ref(new->path, branch_rev, 1, NULL))
762 hashcpy(rev, branch_rev);
763 else
@@ -804,6 +808,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
808 OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
809 "create/reset and checkout a branch"),
810 OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "create reflog for new branch"),
811 + OPT_BOOLEAN(0, "detach", &opts.force_detach, "detach the HEAD at named commit"),
812 OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch",
813 BRANCH_TRACK_EXPLICIT),
814 OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"),
@@ -842,9 +847,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
847 opts.new_branch = opts.new_branch_force;
848
849 if (patch_mode && (opts.track > 0 || opts.new_branch
845 - || opts.new_branch_log || opts.merge || opts.force))
850 + || opts.new_branch_log || opts.merge || opts.force
851 + || opts.force_detach))
852 die ("--patch is incompatible with all other options");
853
854 + if (opts.force_detach && (opts.new_branch || opts.new_orphan_branch))
855 + die("--detach cannot be used with -b/-B/--orphan");
856 + if (opts.force_detach && 0 < opts.track)
857 + die("--detach cannot be used with -t");
858 +
859 /* --track without -b should DWIM */
860 if (0 < opts.track && !opts.new_branch) {
861 const char *argv0 = argv[0];
@@ -895,7 +906,8 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
906 dwim_new_local_branch &&
907 opts.track == BRANCH_TRACK_UNSPECIFIED &&
908 !opts.new_branch;
898 - int n = parse_branchname_arg(argc, argv, dwim_ok,
909 + int n = parse_branchname_arg(argc, argv,
910 + dwim_ok, opts.force_detach,
911 &new, &source_tree, rev, &opts.new_branch);
912 argv += n;
913 argc -= n;
@@ -922,6 +934,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
934 }
935 }
936
937 + if (opts.force_detach)
938 + die("git checkout: --detach does not take a path argument");
939 +
940 if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
941 die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
942
t/t2020-checkout-detach.sh new
+95
@@ -0,0 +1,95 @@
1 +#!/bin/sh
2 +
3 +test_description='checkout into detached HEAD state'
4 +. ./test-lib.sh
5 +
6 +check_detached () {
7 + test_must_fail git symbolic-ref -q HEAD >/dev/null
8 +}
9 +
10 +check_not_detached () {
11 + git symbolic-ref -q HEAD >/dev/null
12 +}
13 +
14 +reset () {
15 + git checkout master &&
16 + check_not_detached
17 +}
18 +
19 +test_expect_success 'setup' '
20 + test_commit one &&
21 + test_commit two &&
22 + git branch branch &&
23 + git tag tag
24 +'
25 +
26 +test_expect_success 'checkout branch does not detach' '
27 + reset &&
28 + git checkout branch &&
29 + check_not_detached
30 +'
31 +
32 +test_expect_success 'checkout tag detaches' '
33 + reset &&
34 + git checkout tag &&
35 + check_detached
36 +'
37 +
38 +test_expect_success 'checkout branch by full name detaches' '
39 + reset &&
40 + git checkout refs/heads/branch &&
41 + check_detached
42 +'
43 +
44 +test_expect_success 'checkout non-ref detaches' '
45 + reset &&
46 + git checkout branch^ &&
47 + check_detached
48 +'
49 +
50 +test_expect_success 'checkout ref^0 detaches' '
51 + reset &&
52 + git checkout branch^0 &&
53 + check_detached
54 +'
55 +
56 +test_expect_success 'checkout --detach detaches' '
57 + reset &&
58 + git checkout --detach branch &&
59 + check_detached
60 +'
61 +
62 +test_expect_success 'checkout --detach without branch name' '
63 + reset &&
64 + git checkout --detach &&
65 + check_detached
66 +'
67 +
68 +test_expect_success 'checkout --detach errors out for non-commit' '
69 + reset &&
70 + test_must_fail git checkout --detach one^{tree} &&
71 + check_not_detached
72 +'
73 +
74 +test_expect_success 'checkout --detach errors out for extra argument' '
75 + reset &&
76 + git checkout master &&
77 + test_must_fail git checkout --detach tag one.t &&
78 + check_not_detached
79 +'
80 +
81 +test_expect_success 'checkout --detached and -b are incompatible' '
82 + reset &&
83 + test_must_fail git checkout --detach -b newbranch tag &&
84 + check_not_detached
85 +'
86 +
87 +test_expect_success 'checkout --detach moves HEAD' '
88 + reset &&
89 + git checkout one &&
90 + git checkout --detach two &&
91 + git diff --exit-code HEAD &&
92 + git diff --exit-code two
93 +'
94 +
95 +test_done