Skip to content

Commit c0791f3

Browse files
committed
Merge branch 'uk/checkout-ambiguous-ref'
* uk/checkout-ambiguous-ref: Rename t2019 with typo "amiguous" that meant "ambiguous" checkout: rearrange update_refs_for_switch for clarity checkout: introduce --detach synonym for "git checkout foo^{commit}" checkout: split off a function to peel away branchname arg checkout: fix bug with ambiguous refs Conflicts: builtin/checkout.c
2 parents 28afcbf + 6c74ce8 commit c0791f3

File tree

4 files changed

+320
-107
lines changed

4 files changed

+320
-107
lines changed

Documentation/git-checkout.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git checkout' [-q] [-f] [-m] [<branch>]
12+
'git checkout' [-q] [-f] [-m] [--detach] [<commit>]
1213
'git checkout' [-q] [-f] [-m] [[-b|-B|--orphan] <new_branch>] [<start_point>]
1314
'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
1415
'git checkout' --patch [<tree-ish>] [--] [<paths>...]
@@ -22,9 +23,10 @@ branch.
2223

2324
'git checkout' [<branch>]::
2425
'git checkout' -b|-B <new_branch> [<start point>]::
26+
'git checkout' [--detach] [<commit>]::
2527

2628
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.
2830
+
2931
If `-b` is given, a new branch is created as if linkgit:git-branch[1]
3032
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.
115117
Create the new branch's reflog; see linkgit:git-branch[1] for
116118
details.
117119

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+
118127
--orphan::
119128
Create a new 'orphan' branch, named <new_branch>, started from
120129
<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`.
204213

205214

206215

207-
Detached HEAD
216+
DETACHED HEAD
208217
-------------
209218

210219
It is sometimes useful to be able to 'checkout' a commit that is

builtin/checkout.c

Lines changed: 155 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct checkout_opts {
3030
int quiet;
3131
int merge;
3232
int force;
33+
int force_detach;
3334
int writeout_stage;
3435
int writeout_error;
3536

@@ -541,7 +542,17 @@ static void update_refs_for_switch(struct checkout_opts *opts,
541542
strbuf_addf(&msg, "checkout: moving from %s to %s",
542543
old_desc ? old_desc : "(invalid)", new->name);
543544

544-
if (new->path) {
545+
if (!strcmp(new->name, "HEAD") && !new->path && !opts->force_detach) {
546+
/* Nothing to do. */
547+
} else if (opts->force_detach || !new->path) { /* No longer on any branch. */
548+
update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
549+
REF_NODEREF, DIE_ON_ERR);
550+
if (!opts->quiet) {
551+
if (old->path && advice_detached_head)
552+
detach_advice(old->path, new->name);
553+
describe_detached_head("HEAD is now at", new->commit);
554+
}
555+
} else if (new->path) { /* Switch branches. */
545556
create_symref("HEAD", new->path, msg.buf);
546557
if (!opts->quiet) {
547558
if (old->path && !strcmp(new->path, old->path))
@@ -563,18 +574,11 @@ static void update_refs_for_switch(struct checkout_opts *opts,
563574
if (!file_exists(ref_file) && file_exists(log_file))
564575
remove_path(log_file);
565576
}
566-
} else if (strcmp(new->name, "HEAD")) {
567-
update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
568-
REF_NODEREF, DIE_ON_ERR);
569-
if (!opts->quiet) {
570-
if (old->path && advice_detached_head)
571-
detach_advice(old->path, new->name);
572-
describe_detached_head("HEAD is now at", new->commit);
573-
}
574577
}
575578
remove_branch_state();
576579
strbuf_release(&msg);
577-
if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
580+
if (!opts->quiet &&
581+
(new->path || (!opts->force_detach && !strcmp(new->name, "HEAD"))))
578582
report_tracking(new);
579583
}
580584

@@ -675,11 +679,123 @@ static const char *unique_tracking_name(const char *name)
675679
return NULL;
676680
}
677681

682+
static int parse_branchname_arg(int argc, const char **argv,
683+
int dwim_new_local_branch_ok,
684+
struct branch_info *new,
685+
struct tree **source_tree,
686+
unsigned char rev[20],
687+
const char **new_branch)
688+
{
689+
int argcount = 0;
690+
unsigned char branch_rev[20];
691+
const char *arg;
692+
int has_dash_dash;
693+
694+
/*
695+
* case 1: git checkout <ref> -- [<paths>]
696+
*
697+
* <ref> must be a valid tree, everything after the '--' must be
698+
* a path.
699+
*
700+
* case 2: git checkout -- [<paths>]
701+
*
702+
* everything after the '--' must be paths.
703+
*
704+
* case 3: git checkout <something> [<paths>]
705+
*
706+
* With no paths, if <something> is a commit, that is to
707+
* switch to the branch or detach HEAD at it. As a special case,
708+
* if <something> is A...B (missing A or B means HEAD but you can
709+
* omit at most one side), and if there is a unique merge base
710+
* between A and B, A...B names that merge base.
711+
*
712+
* With no paths, if <something> is _not_ a commit, no -t nor -b
713+
* was given, and there is a tracking branch whose name is
714+
* <something> in one and only one remote, then this is a short-hand
715+
* to fork local <something> from that remote-tracking branch.
716+
*
717+
* Otherwise <something> shall not be ambiguous.
718+
* - If it's *only* a reference, treat it like case (1).
719+
* - If it's only a path, treat it like case (2).
720+
* - else: fail.
721+
*
722+
*/
723+
if (!argc)
724+
return 0;
725+
726+
if (!strcmp(argv[0], "--")) /* case (2) */
727+
return 1;
728+
729+
arg = argv[0];
730+
has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
731+
732+
if (!strcmp(arg, "-"))
733+
arg = "@{-1}";
734+
735+
if (get_sha1_mb(arg, rev)) {
736+
if (has_dash_dash) /* case (1) */
737+
die("invalid reference: %s", arg);
738+
if (dwim_new_local_branch_ok &&
739+
!check_filename(NULL, arg) &&
740+
argc == 1) {
741+
const char *remote = unique_tracking_name(arg);
742+
if (!remote || get_sha1(remote, rev))
743+
return argcount;
744+
*new_branch = arg;
745+
arg = remote;
746+
/* DWIMmed to create local branch */
747+
} else {
748+
return argcount;
749+
}
750+
}
751+
752+
/* we can't end up being in (2) anymore, eat the argument */
753+
argcount++;
754+
argv++;
755+
argc--;
756+
757+
new->name = arg;
758+
setup_branch_path(new);
759+
760+
if (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
764+
new->path = NULL; /* not an existing branch */
765+
766+
new->commit = lookup_commit_reference_gently(rev, 1);
767+
if (!new->commit) {
768+
/* not a commit */
769+
*source_tree = parse_tree_indirect(rev);
770+
} else {
771+
parse_commit(new->commit);
772+
*source_tree = new->commit->tree;
773+
}
774+
775+
if (!*source_tree) /* case (1): want a tree */
776+
die("reference is not a tree: %s", arg);
777+
if (!has_dash_dash) {/* case (3 -> 1) */
778+
/*
779+
* Do not complain the most common case
780+
* git checkout branch
781+
* even if there happen to be a file called 'branch';
782+
* it would be extremely annoying.
783+
*/
784+
if (argc)
785+
verify_non_filename(NULL, arg);
786+
} else {
787+
argcount++;
788+
argv++;
789+
argc--;
790+
}
791+
792+
return argcount;
793+
}
794+
678795
int cmd_checkout(int argc, const char **argv, const char *prefix)
679796
{
680797
struct checkout_opts opts;
681798
unsigned char rev[20];
682-
const char *arg;
683799
struct branch_info new;
684800
struct tree *source_tree = NULL;
685801
char *conflict_style = NULL;
@@ -692,6 +808,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
692808
OPT_STRING('B', NULL, &opts.new_branch_force, "branch",
693809
"create/reset and checkout a branch"),
694810
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"),
695812
OPT_SET_INT('t', "track", &opts.track, "set upstream info for new branch",
696813
BRANCH_TRACK_EXPLICIT),
697814
OPT_STRING(0, "orphan", &opts.new_orphan_branch, "new branch", "new unparented branch"),
@@ -709,7 +826,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
709826
PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
710827
OPT_END(),
711828
};
712-
int has_dash_dash;
713829

714830
memset(&opts, 0, sizeof(opts));
715831
memset(&new, 0, sizeof(new));
@@ -731,9 +847,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
731847
opts.new_branch = opts.new_branch_force;
732848

733849
if (patch_mode && (opts.track > 0 || opts.new_branch
734-
|| opts.new_branch_log || opts.merge || opts.force))
850+
|| opts.new_branch_log || opts.merge || opts.force
851+
|| opts.force_detach))
735852
die ("--patch is incompatible with all other options");
736853

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+
737859
/* --track without -b should DWIM */
738860
if (0 < opts.track && !opts.new_branch) {
739861
const char *argv0 = argv[0];
@@ -766,105 +888,30 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
766888
die("git checkout: -f and -m are incompatible");
767889

768890
/*
769-
* case 1: git checkout <ref> -- [<paths>]
770-
*
771-
* <ref> must be a valid tree, everything after the '--' must be
772-
* a path.
773-
*
774-
* case 2: git checkout -- [<paths>]
775-
*
776-
* everything after the '--' must be paths.
777-
*
778-
* case 3: git checkout <something> [<paths>]
779-
*
780-
* With no paths, if <something> is a commit, that is to
781-
* switch to the branch or detach HEAD at it. As a special case,
782-
* if <something> is A...B (missing A or B means HEAD but you can
783-
* omit at most one side), and if there is a unique merge base
784-
* between A and B, A...B names that merge base.
891+
* Extract branch name from command line arguments, so
892+
* all that is left is pathspecs.
785893
*
786-
* With no paths, if <something> is _not_ a commit, no -t nor -b
787-
* was given, and there is a remote-tracking branch whose name is
788-
* <something> in one and only one remote, then this is a short-hand
789-
* to fork local <something> from that remote-tracking branch.
894+
* Handle
790895
*
791-
* Otherwise <something> shall not be ambiguous.
792-
* - If it's *only* a reference, treat it like case (1).
793-
* - If it's only a path, treat it like case (2).
794-
* - else: fail.
896+
* 1) git checkout <tree> -- [<paths>]
897+
* 2) git checkout -- [<paths>]
898+
* 3) git checkout <something> [<paths>]
795899
*
900+
* including "last branch" syntax and DWIM-ery for names of
901+
* remote branches, erroring out for invalid or ambiguous cases.
796902
*/
797903
if (argc) {
798-
if (!strcmp(argv[0], "--")) { /* case (2) */
799-
argv++;
800-
argc--;
801-
goto no_reference;
802-
}
803-
804-
arg = argv[0];
805-
has_dash_dash = (argc > 1) && !strcmp(argv[1], "--");
806-
807-
if (!strcmp(arg, "-"))
808-
arg = "@{-1}";
809-
810-
if (get_sha1_mb(arg, rev)) {
811-
if (has_dash_dash) /* case (1) */
812-
die("invalid reference: %s", arg);
813-
if (!patch_mode &&
814-
dwim_new_local_branch &&
815-
opts.track == BRANCH_TRACK_UNSPECIFIED &&
816-
!opts.new_branch &&
817-
!check_filename(NULL, arg) &&
818-
argc == 1) {
819-
const char *remote = unique_tracking_name(arg);
820-
if (!remote || get_sha1(remote, rev))
821-
goto no_reference;
822-
opts.new_branch = arg;
823-
arg = remote;
824-
/* DWIMmed to create local branch */
825-
}
826-
else
827-
goto no_reference;
828-
}
829-
830-
/* we can't end up being in (2) anymore, eat the argument */
831-
argv++;
832-
argc--;
833-
834-
new.name = arg;
835-
if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
836-
setup_branch_path(&new);
837-
838-
if ((check_ref_format(new.path) == CHECK_REF_FORMAT_OK) &&
839-
resolve_ref(new.path, rev, 1, NULL))
840-
;
841-
else
842-
new.path = NULL;
843-
parse_commit(new.commit);
844-
source_tree = new.commit->tree;
845-
} else
846-
source_tree = parse_tree_indirect(rev);
847-
848-
if (!source_tree) /* case (1): want a tree */
849-
die("reference is not a tree: %s", arg);
850-
if (!has_dash_dash) {/* case (3 -> 1) */
851-
/*
852-
* Do not complain the most common case
853-
* git checkout branch
854-
* even if there happen to be a file called 'branch';
855-
* it would be extremely annoying.
856-
*/
857-
if (argc)
858-
verify_non_filename(NULL, arg);
859-
}
860-
else {
861-
argv++;
862-
argc--;
863-
}
904+
int dwim_ok =
905+
!patch_mode &&
906+
dwim_new_local_branch &&
907+
opts.track == BRANCH_TRACK_UNSPECIFIED &&
908+
!opts.new_branch;
909+
int n = parse_branchname_arg(argc, argv, dwim_ok,
910+
&new, &source_tree, rev, &opts.new_branch);
911+
argv += n;
912+
argc -= n;
864913
}
865914

866-
no_reference:
867-
868915
if (opts.track == BRANCH_TRACK_UNSPECIFIED)
869916
opts.track = git_branch_track;
870917

@@ -886,6 +933,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
886933
}
887934
}
888935

936+
if (opts.force_detach)
937+
die("git checkout: --detach does not take a path argument");
938+
889939
if (1 < !!opts.writeout_stage + !!opts.force + !!opts.merge)
890940
die("git checkout: --ours/--theirs, --force and --merge are incompatible when\nchecking out of the index.");
891941

0 commit comments

Comments
 (0)