checkout: allow dwim for branch creation for "git checkout $branch --"

The "--" notation disambiguates files and branches, but as a side-effect of the previous implementation, also disabled the branch auto-creation when $branch does not exist. A possible scenario is then: git checkout $branch => fails if $branch is both a ref and a file, and suggests -- git checkout $branch -- => refuses to create the $branch This patch allows the second form to create $branch, and since the -- is provided, it does not look for file named $branch. Signed-off-by: Matthieu Moy <Matthieu.Moy@imag.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Matthieu Moy committed Oct 18, 2013 at 11:25 UTC a047fafc7866cc4087201e284dc1f53e8f9a32d5
2 files changed +76 -23
builtin/checkout.c
+55 -23
@@ -884,20 +884,30 @@ static int parse_branchname_arg(int argc, const char **argv,
884 *
885 * everything after the '--' must be paths.
886 *
887 - * case 3: git checkout <something> [<paths>]
887 + * case 3: git checkout <something> [--]
888 *
889 - * With no paths, if <something> is a commit, that is to
890 - * switch to the branch or detach HEAD at it. As a special case,
891 - * if <something> is A...B (missing A or B means HEAD but you can
892 - * omit at most one side), and if there is a unique merge base
893 - * between A and B, A...B names that merge base.
889 + * (a) If <something> is a commit, that is to
890 + * switch to the branch or detach HEAD at it. As a special case,
891 + * if <something> is A...B (missing A or B means HEAD but you can
892 + * omit at most one side), and if there is a unique merge base
893 + * between A and B, A...B names that merge base.
894 *
895 - * With no paths, if <something> is _not_ a commit, no -t nor -b
896 - * was given, and there is a tracking branch whose name is
897 - * <something> in one and only one remote, then this is a short-hand
898 - * to fork local <something> from that remote-tracking branch.
895 + * (b) If <something> is _not_ a commit, either "--" is present
896 + * or <something> is not a path, no -t nor -b was given, and
897 + * and there is a tracking branch whose name is <something>
898 + * in one and only one remote, then this is a short-hand to
899 + * fork local <something> from that remote-tracking branch.
900 *
900 - * Otherwise <something> shall not be ambiguous.
901 + * (c) Otherwise, if "--" is present, treat it like case (1).
902 + *
903 + * (d) Otherwise :
904 + * - if it's a reference, treat it like case (1)
905 + * - else if it's a path, treat it like case (2)
906 + * - else: fail.
907 + *
908 + * case 4: git checkout <something> <paths>
909 + *
910 + * The first argument must not be ambiguous.
911 * - If it's *only* a reference, treat it like case (1).
912 * - If it's only a path, treat it like case (2).
913 * - else: fail.
@@ -916,18 +926,40 @@ static int parse_branchname_arg(int argc, const char **argv,
926 arg = "@{-1}";
927
928 if (get_sha1_mb(arg, rev)) {
919 - if (has_dash_dash) /* case (1) */
920 - die(_("invalid reference: %s"), arg);
921 - if (dwim_new_local_branch_ok &&
922 - !check_filename(NULL, arg) &&
923 - argc == 1) {
929 + /*
930 + * Either case (3) or (4), with <something> not being
931 + * a commit, or an attempt to use case (1) with an
932 + * invalid ref.
933 + *
934 + * It's likely an error, but we need to find out if
935 + * we should auto-create the branch, case (3).(b).
936 + */
937 + int recover_with_dwim = dwim_new_local_branch_ok;
938 +
939 + if (check_filename(NULL, arg) && !has_dash_dash)
940 + recover_with_dwim = 0;
941 + /*
942 + * Accept "git checkout foo" and "git checkout foo --"
943 + * as candidates for dwim.
944 + */
945 + if (!(argc == 1 && !has_dash_dash) &&
946 + !(argc == 2 && has_dash_dash))
947 + recover_with_dwim = 0;
948 +
949 + if (recover_with_dwim) {
950 const char *remote = unique_tracking_name(arg, rev);
925 - if (!remote)
926 - return argcount;
927 - *new_branch = arg;
928 - arg = remote;
929 - /* DWIMmed to create local branch */
930 - } else {
951 + if (remote) {
952 + *new_branch = arg;
953 + arg = remote;
954 + /* DWIMmed to create local branch, case (3).(b) */
955 + } else {
956 + recover_with_dwim = 0;
957 + }
958 + }
959 +
960 + if (!recover_with_dwim) {
961 + if (has_dash_dash)
962 + die(_("invalid reference: %s"), arg);
963 return argcount;
964 }
965 }
@@ -957,7 +989,7 @@ static int parse_branchname_arg(int argc, const char **argv,
989
990 if (!*source_tree) /* case (1): want a tree */
991 die(_("reference is not a tree: %s"), arg);
960 - if (!has_dash_dash) {/* case (3 -> 1) */
992 + if (!has_dash_dash) {/* case (3).(d) -> (1) */
993 /*
994 * Do not complain the most common case
995 * git checkout branch
t/t2024-checkout-dwim.sh
+21
@@ -164,4 +164,25 @@ test_expect_success 'checkout of branch from a single remote succeeds #4' '
164 test_branch_upstream eggs repo_d eggs
165 '
166
167 +test_expect_success 'checkout of branch with a file having the same name fails' '
168 + git checkout -B master &&
169 + test_might_fail git branch -D spam &&
170 +
171 + >spam &&
172 + test_must_fail git checkout spam &&
173 + test_must_fail git rev-parse --verify refs/heads/spam &&
174 + test_branch master
175 +'
176 +
177 +test_expect_success 'checkout <branch> -- succeeds, even if a file with the same name exists' '
178 + git checkout -B master &&
179 + test_might_fail git branch -D spam &&
180 +
181 + >spam &&
182 + git checkout spam -- &&
183 + test_branch spam &&
184 + test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD &&
185 + test_branch_upstream spam repo_c spam
186 +'
187 +
188 test_done