summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVicent Martí <vicent@github.com>2013-04-29 13:30:31 -0700
committerVicent Martí <vicent@github.com>2013-04-29 13:30:31 -0700
commit4157851076d476b3b7f9a8bb9b85497517b14cdf (patch)
treeaeebf52e7141625bfbc9f91847f5d22cc32dc9bd
parentbb503dbd0326abd6fe714b99cd7e64c482130867 (diff)
parentd84884571d04d609ed5f3508aecd9e24b84f47c7 (diff)
downloadlibgit2-4157851076d476b3b7f9a8bb9b85497517b14cdf.tar.gz
Merge pull request #1511 from carlosmn/refspec-shorthand
dwim shorthand refspecs for fetch
-rw-r--r--include/git2/refs.h7
-rw-r--r--src/refs.c1
-rw-r--r--src/refspec.c2
-rw-r--r--src/refspec.h1
-rw-r--r--src/remote.c94
-rw-r--r--tests-clar/network/refspecs.c3
-rw-r--r--tests-clar/network/remote/local.c41
7 files changed, 139 insertions, 10 deletions
diff --git a/include/git2/refs.h b/include/git2/refs.h
index 1ff0d4544..e1d425352 100644
--- a/include/git2/refs.h
+++ b/include/git2/refs.h
@@ -422,6 +422,13 @@ typedef enum {
* (e.g., foo/<star>/bar but not foo/bar<star>).
*/
GIT_REF_FORMAT_REFSPEC_PATTERN = (1 << 1),
+
+ /**
+ * Interpret the name as part of a refspec in shorthand form
+ * so the `ONELEVEL` naming rules aren't enforced and 'master'
+ * becomes a valid name.
+ */
+ GIT_REF_FORMAT_REFSPEC_SHORTHAND = (1 << 2),
} git_reference_normalize_t;
/**
diff --git a/src/refs.c b/src/refs.c
index 9c6684a5a..2faa4cb83 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -752,6 +752,7 @@ int git_reference__normalize_name(
goto cleanup;
if ((segments_count == 1 ) &&
+ !(flags & GIT_REF_FORMAT_REFSPEC_SHORTHAND) &&
!(is_all_caps_and_underscore(name, (size_t)segment_len) ||
((flags & GIT_REF_FORMAT_REFSPEC_PATTERN) && !strcmp("*", name))))
goto cleanup;
diff --git a/src/refspec.c b/src/refspec.c
index fbdea9d93..256540819 100644
--- a/src/refspec.c
+++ b/src/refspec.c
@@ -60,7 +60,7 @@ int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch)
refspec->pattern = is_glob;
refspec->src = git__strndup(lhs, llen);
- flags = GIT_REF_FORMAT_ALLOW_ONELEVEL
+ flags = GIT_REF_FORMAT_ALLOW_ONELEVEL | GIT_REF_FORMAT_REFSPEC_SHORTHAND
| (is_glob ? GIT_REF_FORMAT_REFSPEC_PATTERN : 0);
if (is_fetch) {
diff --git a/src/refspec.h b/src/refspec.h
index 29f4d5354..44d484c7b 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 ffce2b6e2..1183137a6 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/refspecs.c b/tests-clar/network/refspecs.c
index b3d80fb85..676a1fa99 100644
--- a/tests-clar/network/refspecs.c
+++ b/tests-clar/network/refspecs.c
@@ -81,4 +81,7 @@ void test_network_refspecs__parsing(void)
assert_refspec(GIT_DIRECTION_FETCH, "refs/heads/*/for-linus:refs/remotes/mine/*", true);
assert_refspec(GIT_DIRECTION_PUSH, "refs/heads/*/for-linus:refs/remotes/mine/*", true);
+
+ assert_refspec(GIT_DIRECTION_FETCH, "master", true);
+ assert_refspec(GIT_DIRECTION_PUSH, "master", true);
}
diff --git a/tests-clar/network/remote/local.c b/tests-clar/network/remote/local.c
index 7e847e654..74ef63dc9 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"));
+}