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.
This commit is contained in:
Patrick Steinhardt 2016-08-04 13:45:28 +02:00 committed by Carlos Martín Nieto
parent d711165d03
commit 85addddf4c
2 changed files with 55 additions and 2 deletions

View File

@ -53,8 +53,10 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
if (rhs) { if (rhs) {
size_t rlen = strlen(++rhs); size_t rlen = strlen(++rhs);
is_glob = (1 <= rlen && strchr(rhs, '*')); if (rlen || !is_fetch) {
refspec->dst = git__strndup(rhs, rlen); is_glob = (1 <= rlen && strchr(rhs, '*'));
refspec->dst = git__strndup(rhs, rlen);
}
} }
llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs)); llen = (rhs ? (size_t)(rhs - lhs - 1) : strlen(lhs));

View File

@ -35,6 +35,19 @@ static void fetchhead_test_clone(void)
cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); 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) static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fetchhead)
{ {
git_remote *remote; git_remote *remote;
@ -101,3 +114,41 @@ void test_online_fetchhead__no_merges(void)
cl_git_pass(git_tag_delete(g_repo, "commit_tree")); cl_git_pass(git_tag_delete(g_repo, "commit_tree"));
fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA3); 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());
}