Commit d84884571d04d609ed5f3508aecd9e24b84f47c7

Carlos Martín Nieto 2013-04-28T16:26:55

remote: dwim the refspecs according to the remote's advertised refs As git allows you to store shorthand refspecs in the configuration, we need to do this ourselves.

diff --git a/src/refspec.h b/src/refspec.h
index 29f4d53..44d484c 100644
--- a/src/refspec.h
+++ b/src/refspec.h
@@ -17,6 +17,7 @@ struct git_refspec {
 	unsigned int force :1,
 		push : 1,
 		pattern :1,
+		dwim :1,
 		matching :1;
 };
 
diff --git a/src/remote.c b/src/remote.c
index ffce2b6..1183137 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -632,28 +632,104 @@ int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_ur
 	return 0;
 }
 
+static int store_refs(git_remote_head *head, void *payload)
+{
+	git_vector *refs = (git_vector *)payload;
+
+	return git_vector_insert(refs, head);
+}
+
+static int dwim_refspecs(git_vector *refspecs, git_vector *refs)
+{
+	git_buf buf = GIT_BUF_INIT;
+	git_refspec *spec;
+	size_t i, j, pos;
+	git_remote_head key;
+
+	const char* formatters[] = {
+		GIT_REFS_DIR "%s",
+		GIT_REFS_TAGS_DIR "%s",
+		GIT_REFS_HEADS_DIR "%s",
+		NULL
+	};
+
+	git_vector_foreach(refspecs, i, spec) {
+		if (spec->dwim)
+			continue;
+
+		/* shorthand on the lhs */
+		if (git__prefixcmp(spec->src, GIT_REFS_DIR)) {
+			for (j = 0; formatters[j]; j++) {
+				git_buf_clear(&buf);
+				if (git_buf_printf(&buf, formatters[j], spec->src) < 0)
+					return -1;
+
+				key.name = (char *) git_buf_cstr(&buf);
+				if (!git_vector_search(&pos, refs, &key)) {
+					/* we found something to match the shorthand, set src to that */
+					git__free(spec->src);
+					spec->src = git_buf_detach(&buf);
+				}
+			}
+		}
+
+		if (spec->dst && git__prefixcmp(spec->dst, GIT_REFS_DIR)) {
+			/* if it starts with "remotes" then we just prepend "refs/" */
+			if (!git__prefixcmp(spec->dst, "remotes/")) {
+				git_buf_puts(&buf, GIT_REFS_DIR);
+			} else {
+				git_buf_puts(&buf, GIT_REFS_HEADS_DIR);
+			}
+
+			if (git_buf_puts(&buf, spec->dst) < 0)
+				return -1;
+
+			git__free(spec->dst);
+			spec->dst = git_buf_detach(&buf);
+		}
+
+		spec->dwim = 1;
+	}
+
+	return 0;
+}
+
+static int remote_head_cmp(const void *_a, const void *_b)
+{
+	const git_remote_head *a = (git_remote_head *) _a;
+	const git_remote_head *b = (git_remote_head *) _b;
+
+	return git__strcmp_cb(a->name, b->name);
+}
+
 int git_remote_download(
 		git_remote *remote,
 		git_transfer_progress_callback progress_cb,
 		void *progress_payload)
 {
 	int error;
+	git_vector refs;
 
 	assert(remote);
 
+	if (git_vector_init(&refs, 16, remote_head_cmp) < 0)
+		return -1;
+
+	if (git_remote_ls(remote, store_refs, &refs) < 0) {
+		return -1;
+	}
+
+	error = dwim_refspecs(&remote->refspecs, &refs);
+	git_vector_free(&refs);
+	if (error < 0)
+		return -1;
+
 	if ((error = git_fetch_negotiate(remote)) < 0)
 		return error;
 
 	return git_fetch_download_pack(remote, progress_cb, progress_payload);
 }
 
-static int update_tips_callback(git_remote_head *head, void *payload)
-{
-	git_vector *refs = (git_vector *)payload;
-
-	return git_vector_insert(refs, head);
-}
-
 static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src)
 {
 	unsigned int i;
@@ -814,7 +890,7 @@ static int update_tips_for_spec(git_remote *remote, git_refspec *spec, git_vecto
 		if (!git_reference_is_valid_name(head->name))
 			continue;
 
-		if (git_refspec_src_matches(spec, head->name)) {
+		if (git_refspec_src_matches(spec, head->name) && spec->dst) {
 			if (git_refspec_transform_r(&refname, spec, head->name) < 0)
 				goto on_error;
 		} else if (remote->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_NONE) {
@@ -887,7 +963,7 @@ int git_remote_update_tips(git_remote *remote)
 	if (git_vector_init(&refs, 16, NULL) < 0)
 		return -1;
 
-	if (git_remote_ls(remote, update_tips_callback, &refs) < 0)
+	if (git_remote_ls(remote, store_refs, &refs) < 0)
 		goto on_error;
 
 	git_vector_foreach(&remote->refspecs, i, spec) {
diff --git a/tests-clar/network/remote/local.c b/tests-clar/network/remote/local.c
index 7e847e6..74ef63d 100644
--- a/tests-clar/network/remote/local.c
+++ b/tests-clar/network/remote/local.c
@@ -100,3 +100,44 @@ void test_network_remote_local__nested_tags_are_completely_peeled(void)
 
 	cl_git_pass(git_remote_ls(remote, &ensure_peeled__cb, NULL));
 }
+
+void test_network_remote_local__shorthand_fetch_refspec0(void)
+{
+	const char *refspec = "master:remotes/sloppy/master";
+	const char *refspec2 = "master:boh/sloppy/master";
+
+	git_reference *ref;
+
+	connect_to_local_repository(cl_fixture("testrepo.git"));
+	cl_git_pass(git_remote_add_fetch(remote, refspec));
+	cl_git_pass(git_remote_add_fetch(remote, refspec2));
+
+	cl_git_pass(git_remote_download(remote, NULL, NULL));
+	cl_git_pass(git_remote_update_tips(remote));
+
+	cl_git_pass(git_reference_lookup(&ref, repo, "refs/remotes/sloppy/master"));
+	git_reference_free(ref);
+
+	cl_git_pass(git_reference_lookup(&ref, repo, "refs/heads/boh/sloppy/master"));
+	git_reference_free(ref);
+}
+
+void test_network_remote_local__shorthand_fetch_refspec1(void)
+{
+	const char *refspec = "master";
+	const char *refspec2 = "hard_tag";
+
+	git_reference *ref;
+
+	connect_to_local_repository(cl_fixture("testrepo.git"));
+	git_remote_clear_refspecs(remote);
+	cl_git_pass(git_remote_add_fetch(remote, refspec));
+	cl_git_pass(git_remote_add_fetch(remote, refspec2));
+
+	cl_git_pass(git_remote_download(remote, NULL, NULL));
+	cl_git_pass(git_remote_update_tips(remote));
+
+	cl_git_fail(git_reference_lookup(&ref, repo, "refs/remotes/master"));
+
+	cl_git_fail(git_reference_lookup(&ref, repo, "refs/tags/hard_tag"));
+}