summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLibravatar Junio C Hamano <gitster@pobox.com>2009-10-18 00:13:47 -0700
committerLibravatar Junio C Hamano <gitster@pobox.com>2009-10-18 00:38:03 -0700
commit70c9ac2f1999b7e0c17a864235537cffe29dfea4 (patch)
tree8766e1459d697f8e1916402d0dec91b41902c339
parentcheck_filename(): make verify_filename() callable without dying (diff)
downloadtgif-70c9ac2f1999b7e0c17a864235537cffe29dfea4.tar.xz
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>
-rw-r--r--builtin-checkout.c60
1 files changed, 57 insertions, 3 deletions
diff --git a/builtin-checkout.c b/builtin-checkout.c
index d050c3789f..fb7e68ac5f 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -572,6 +572,40 @@ static int interactive_checkout(const char *revision, const char **pathspec,
return run_add_interactive(revision, "--patch=checkout", pathspec);
}
+struct tracking_name_data {
+ const char *name;
+ char *remote;
+ int unique;
+};
+
+static int check_tracking_name(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ struct tracking_name_data *cb = cb_data;
+ const char *slash;
+
+ if (prefixcmp(refname, "refs/remotes/"))
+ return 0;
+ slash = strchr(refname + 13, '/');
+ if (!slash || strcmp(slash + 1, cb->name))
+ return 0;
+ if (cb->remote) {
+ cb->unique = 0;
+ return 0;
+ }
+ cb->remote = xstrdup(refname);
+ return 0;
+}
+
+static const char *unique_tracking_name(const char *name)
+{
+ struct tracking_name_data cb_data = { name, NULL, 1 };
+ for_each_ref(check_tracking_name, &cb_data);
+ if (cb_data.unique)
+ return cb_data.remote;
+ free(cb_data.remote);
+ return NULL;
+}
int cmd_checkout(int argc, const char **argv, const char *prefix)
{
@@ -630,8 +664,6 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
opts.new_branch = argv0 + 1;
}
- if (opts.track == BRANCH_TRACK_UNSPECIFIED)
- opts.track = git_branch_track;
if (conflict_style) {
opts.merge = 1; /* implied */
git_xmerge_config("merge.conflictstyle", conflict_style, NULL);
@@ -655,6 +687,11 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
* With no paths, if <something> is a commit, that is to
* switch to the branch or detach HEAD at it.
*
+ * With no paths, if <something> is _not_ a commit, no -t nor -b
+ * was given, and there is a tracking branch whose name is
+ * <something> in one and only one remote, then this is a short-hand
+ * to fork local <something> from that remote tracking branch.
+ *
* Otherwise <something> shall not be ambiguous.
* - If it's *only* a reference, treat it like case (1).
* - 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)
if (get_sha1(arg, rev)) {
if (has_dash_dash) /* case (1) */
die("invalid reference: %s", arg);
- goto no_reference; /* case (3 -> 2) */
+ if (!patch_mode &&
+ opts.track == BRANCH_TRACK_UNSPECIFIED &&
+ !opts.new_branch &&
+ !check_filename(NULL, arg) &&
+ argc == 1) {
+ const char *remote = unique_tracking_name(arg);
+ if (!remote || get_sha1(remote, rev))
+ goto no_reference;
+ opts.new_branch = arg;
+ arg = remote;
+ /* DWIMmed to create local branch */
+ }
+ else
+ goto no_reference;
}
/* 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)
}
no_reference:
+
+ if (opts.track == BRANCH_TRACK_UNSPECIFIED)
+ opts.track = git_branch_track;
+
if (argc) {
const char **pathspec = get_pathspec(prefix, argv);