@matheusbsbs / matheus-bagliano / commits / 70c9ac2f19
Fork 0

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 <gitster@pobox.com>

Junio C Hamano committed Oct 18, 2009 at 00:13 UTC 70c9ac2f1999b7e0c17a864235537cffe29dfea4
1 file changed +57 -3
builtin-checkout.c
+57 -3
@@ -572,6 +572,40 @@ static int interactive_checkout(const char *revision, const char **pathspec,
572 return run_add_interactive(revision, "--patch=checkout", pathspec);
573 }
574
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 +}
609
610 int cmd_checkout(int argc, const char **argv, const char *prefix)
611 {
@@ -630,8 +664,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
664 opts.new_branch = argv0 + 1;
665 }
666
633 - if (opts.track == BRANCH_TRACK_UNSPECIFIED)
634 - opts.track = git_branch_track;
667 if (conflict_style) {
668 opts.merge = 1; /* implied */
669 git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
@@ -655,6 +687,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
687 * With no paths, if <something> is a commit, that is to
688 * switch to the branch or detach HEAD at it.
689 *
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 + *
695 * Otherwise <something> shall not be ambiguous.
696 * - If it's *only* a reference, treat it like case (1).
697 * - 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)
714 if (get_sha1(arg, rev)) {
715 if (has_dash_dash) /* case (1) */
716 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;
731 }
732
733 /* 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)
765 }
766
767 no_reference:
768 +
769 + if (opts.track == BRANCH_TRACK_UNSPECIFIED)
770 + opts.track = git_branch_track;
771 +
772 if (argc) {
773 const char **pathspec = get_pathspec(prefix, argv);
774