Commit 85addddf4c903c83d3f6851c4dfebbe5b9d10376

Patrick Steinhardt 2016-08-04T13:45:28

refspec: do not set empty rhs for fetch refspecs According to git-fetch(1), "[t]he colon can be omitted when <dst> is empty." So according to git, the refspec "refs/heads/master" is the same as the refspec "refs/heads/master:" when fetching changes. When trying to fetch from a remote with a trailing colon with libgit2, though, the fetch actually fails while it works when the trailing colon is left out. So obviously, libgit2 does _not_ treat these two refspec formats the same for fetches. The problem results from parsing refspecs, where the resulting refspec has its destination set to an empty string in the case of a trailing colon and to a `NULL` pointer in the case of no trailing colon. When passing this to our DWIM machinery, the empty string gets translated to "refs/heads/", which is simply wrong. Fix the problem by having the parsing machinery treat both cases the same for fetch refspecs.

diff --git a/src/refspec.c b/src/refspec.c
index debde86..d200e56 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -53,8 +53,10 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
 
 	if (rhs) {
 		size_t rlen = strlen(++rhs);
-		is_glob = (1 <= rlen && strchr(rhs, '*'));
-		refspec->dst = git__strndup(rhs, rlen);
+		if (rlen || !is_fetch) {
+			is_glob = (1 <= rlen && strchr(rhs, '*'));
+			refspec->dst = git__strndup(rhs, rlen);
+		}
 	}
 
 	llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));
diff --git a/tests/online/fetchhead.c b/tests/online/fetchhead.c
index 200edac..9aaad25 100644
--- a/tests/online/fetchhead.c
+++ b/tests/online/fetchhead.c
@@ -35,6 +35,19 @@ static void fetchhead_test_clone(void)
 	cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
 }
 
+static int count_references(void)
+{
+	git_strarray array;
+	int refs;
+
+	cl_git_pass(git_reference_list(&array, g_repo));
+	refs = array.count;
+
+	git_strarray_free(&array);
+
+	return refs;
+}
+
 static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fetchhead)
 {
 	git_remote *remote;
@@ -101,3 +114,41 @@ void test_online_fetchhead__no_merges(void)
 	cl_git_pass(git_tag_delete(g_repo, "commit_tree"));
 	fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA3);
 }
+
+void test_online_fetchhead__explicit_dst_refspec_creates_branch(void)
+{
+	git_reference *ref;
+	int refs;
+
+	fetchhead_test_clone();
+	refs = count_references();
+	fetchhead_test_fetch("refs/heads/first-merge:refs/heads/explicit-refspec", FETCH_HEAD_EXPLICIT_DATA);
+
+	cl_git_pass(git_branch_lookup(&ref, g_repo, "explicit-refspec", GIT_BRANCH_ALL));
+	cl_assert_equal_i(refs + 1, count_references());
+}
+
+void test_online_fetchhead__empty_dst_refspec_creates_no_branch(void)
+{
+	git_reference *ref;
+	int refs;
+
+	fetchhead_test_clone();
+	refs = count_references();
+
+	fetchhead_test_fetch("refs/heads/first-merge", FETCH_HEAD_EXPLICIT_DATA);
+	cl_git_fail(git_branch_lookup(&ref, g_repo, "first-merge", GIT_BRANCH_ALL));
+
+	cl_assert_equal_i(refs, count_references());
+}
+
+void test_online_fetchhead__colon_only_dst_refspec_creates_no_branch(void)
+{
+	int refs;
+
+	fetchhead_test_clone();
+	refs = count_references();
+	fetchhead_test_fetch("refs/heads/first-merge:", FETCH_HEAD_EXPLICIT_DATA);
+
+	cl_assert_equal_i(refs, count_references());
+}