Skip to content

Commit 70c9ac2

Browse files
committed
DWIM "git checkout frotz" to "git checkout -b frotz origin/frotz"
When 'frotz' is not a valid object name and not a tracked filename, we used to complain and failed this command. When there is only one remote that has 'frotz' as one of its tracking branches, we can DWIM it as a request to create a local branch 'frotz' forking from the matching remote tracking branch. Signed-off-by: Junio C Hamano <[email protected]>
1 parent c6e8c80 commit 70c9ac2

File tree

1 file changed

+57
-3
lines changed

1 file changed

+57
-3
lines changed

builtin-checkout.c

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,40 @@ static int interactive_checkout(const char *revision, const char **pathspec,
572572
return run_add_interactive(revision, "--patch=checkout", pathspec);
573573
}
574574

575+
struct tracking_name_data {
576+
const char *name;
577+
char *remote;
578+
int unique;
579+
};
580+
581+
static int check_tracking_name(const char *refname, const unsigned char *sha1,
582+
int flags, void *cb_data)
583+
{
584+
struct tracking_name_data *cb = cb_data;
585+
const char *slash;
586+
587+
if (prefixcmp(refname, "refs/remotes/"))
588+
return 0;
589+
slash = strchr(refname + 13, '/');
590+
if (!slash || strcmp(slash + 1, cb->name))
591+
return 0;
592+
if (cb->remote) {
593+
cb->unique = 0;
594+
return 0;
595+
}
596+
cb->remote = xstrdup(refname);
597+
return 0;
598+
}
599+
600+
static const char *unique_tracking_name(const char *name)
601+
{
602+
struct tracking_name_data cb_data = { name, NULL, 1 };
603+
for_each_ref(check_tracking_name, &cb_data);
604+
if (cb_data.unique)
605+
return cb_data.remote;
606+
free(cb_data.remote);
607+
return NULL;
608+
}
575609

576610
int cmd_checkout(int argc, const char **argv, const char *prefix)
577611
{
@@ -630,8 +664,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
630664
opts.new_branch = argv0 + 1;
631665
}
632666

633-
if (opts.track == BRANCH_TRACK_UNSPECIFIED)
634-
opts.track = git_branch_track;
635667
if (conflict_style) {
636668
opts.merge = 1; /* implied */
637669
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
@@ -655,6 +687,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
655687
* With no paths, if <something> is a commit, that is to
656688
* switch to the branch or detach HEAD at it.
657689
*
690+
* With no paths, if <something> is _not_ a commit, no -t nor -b
691+
* was given, and there is a tracking branch whose name is
692+
* <something> in one and only one remote, then this is a short-hand
693+
* to fork local <something> from that remote tracking branch.
694+
*
658695
* Otherwise <something> shall not be ambiguous.
659696
* - If it's *only* a reference, treat it like case (1).
660697
* - If it's only a path, treat it like case (2).
@@ -677,7 +714,20 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
677714
if (get_sha1(arg, rev)) {
678715
if (has_dash_dash) /* case (1) */
679716
die("invalid reference: %s", arg);
680-
goto no_reference; /* case (3 -> 2) */
717+
if (!patch_mode &&
718+
opts.track == BRANCH_TRACK_UNSPECIFIED &&
719+
!opts.new_branch &&
720+
!check_filename(NULL, arg) &&
721+
argc == 1) {
722+
const char *remote = unique_tracking_name(arg);
723+
if (!remote || get_sha1(remote, rev))
724+
goto no_reference;
725+
opts.new_branch = arg;
726+
arg = remote;
727+
/* DWIMmed to create local branch */
728+
}
729+
else
730+
goto no_reference;
681731
}
682732

683733
/* we can't end up being in (2) anymore, eat the argument */
@@ -715,6 +765,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
715765
}
716766

717767
no_reference:
768+
769+
if (opts.track == BRANCH_TRACK_UNSPECIFIED)
770+
opts.track = git_branch_track;
771+
718772
if (argc) {
719773
const char **pathspec = get_pathspec(prefix, argv);
720774

0 commit comments

Comments
 (0)