Subject: [PATCH/RFC] builtin-checkout: suggest creating local branch when appropriate to do so

From: Jay Soffian <[email-masked]>
Date: Mon, 5 Oct 2009 16:46:23 -0400

A user who has just cloned a remote repository and wishes to then work on a
branch other than master may not realize they first need to create the local
branch. e.g.:

$ git clone git://git.kernel.org/pub/scm/git/git.git
$ cd git
$ git checkout next
error: pathspec 'next' did not match any file(s) known to git.

This commit teaches git to make a suggestion to the user:

$ git clone git://git.kernel.org/pub/scm/git/git.git
$ cd git
$ git checkout next
error: pathspec 'next' did not match any file(s) known to git.
To create a local branch from the same named remote branch, use
  git checkout -b next origin/next

Motivated by http://article.gmane.org/gmane.comp.version-control.git/129528

Signed-off-by: Jay Soffian <[email-masked]>
---
 builtin-checkout.c |   43 +++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 41 insertions(+), 2 deletions(-)

I dunno, this seems like a lot of code just to make a suggestion to the
user. Is it worth it?

Also, I initially was going to use for_each_remote_ref and compare every
remote ref name to see if it tail matched what the user gave us, but it was
easier to use for_each_remote and build up the remote ref name and then check
for its existence. Not sure if either approach is preferable.

Thoughts/comments?

diff --git a/builtin-checkout.c b/builtin-checkout.c
index d050c37..7f2e215 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -145,6 +145,38 @@ static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
 	mm->size = size;
 }
 
+struct suggest_new_branch_name_data {
+	const char *name, *found;
+	int matches;
+};
+
+static int suggest_new_branch_name_compare(struct remote *remote, void *priv)
+{
+	struct suggest_new_branch_name_data *data = priv;
+	unsigned char sha1[20];
+	struct strbuf buf = STRBUF_INIT;
+	strbuf_addf(&buf, "refs/remotes/%s/%s", remote->name, data->name);
+	if (resolve_ref(buf.buf, sha1, 1, NULL)) {
+		data->matches++;
+		if (data->found)
+			strbuf_release(&buf);
+		else
+			data->found = strbuf_detach(&buf, NULL);
+	}
+	return 0;
+}
+
+static void suggest_new_branch_name(const char *name)
+{
+	struct suggest_new_branch_name_data data;
+	data.name = name;
+	data.found = NULL;
+	data.matches = 0;
+	for_each_remote(suggest_new_branch_name_compare, &data);
+	if (data.matches == 1)
+		fprintf(stderr, "To create a local branch from the same named remote branch, use\n  git checkout -b %s %s\n", name, prettify_refname(data.found));
+}
+
 static int checkout_merged(int pos, struct checkout *state)
 {
 	struct cache_entry *ce = active_cache[pos];
@@ -231,8 +263,13 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
 		match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, ps_matched);
 	}
 
-	if (report_path_error(ps_matched, pathspec, 0))
+	if (report_path_error(ps_matched, pathspec, 0)) {
+		for (pos = 0; pathspec[pos]; pos++)
+			;
+		if (pos == 1)
+			suggest_new_branch_name(pathspec[0]);
 		return 1;
+	}
 
 	/* Any unmerged paths? */
 	for (pos = 0; pos < active_nr; pos++) {
@@ -675,8 +712,10 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
 			arg = "@{-1}";
 
 		if (get_sha1(arg, rev)) {
-			if (has_dash_dash)          /* case (1) */
+			if (has_dash_dash) {         /* case (1) */
+				suggest_new_branch_name(arg);
 				die("invalid reference: %s", arg);
+			}
 			goto no_reference;          /* case (3 -> 2) */
 		}
 
-- 
1.6.4.2