Teach fetch to send refspecs to the underlying transport, and teach all components used by the HTTP transport (remote-curl, transport-helper, fetch-pack) to understand and propagate the names and SHA-1s of the refs fetched. Signed-off-by: Jonathan Tan <jonathantanmy@xxxxxxxxxx> --- builtin/clone.c | 4 +- builtin/fetch-pack.c | 8 ++- builtin/fetch.c | 100 ++++++++++++++++++++++++++++++------- remote-curl.c | 91 ++++++++++++++++++++------------- t/t5552-upload-pack-ref-in-want.sh | 4 +- transport-helper.c | 74 +++++++++++++++++++++------ transport.c | 10 ++-- transport.h | 20 +++++--- 8 files changed, 229 insertions(+), 82 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 3191da391..765a3a3b6 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1078,7 +1078,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } if (!is_local && !complete_refs_before_fetch) { - transport_fetch_refs(transport, mapped_refs, + transport_fetch_refs(transport, NULL, 0, mapped_refs, &new_remote_refs); if (new_remote_refs) { refs = new_remote_refs; @@ -1124,7 +1124,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (is_local) clone_local(path, git_dir); else if (refs && complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs, NULL); + transport_fetch_refs(transport, NULL, 0, mapped_refs, NULL); update_remote_refs(refs, mapped_refs, remote_head_points_at, branch_top.buf, reflog_msg.buf, transport, !is_local); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 24af3b7c5..ed1608c12 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -35,6 +35,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct fetch_pack_args args; struct sha1_array shallow = SHA1_ARRAY_INIT; struct string_list deepen_not = STRING_LIST_INIT_DUP; + int always_print_refs = 0; packet_trace_identity("fetch-pack"); @@ -126,6 +127,10 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.update_shallow = 1; continue; } + if (!strcmp("--always-print-refs", arg)) { + always_print_refs = 1; + continue; + } usage(fetch_pack_usage); } if (deepen_not.nr) @@ -218,7 +223,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) ret = 1; } - if (args.stateless_rpc) + if (args.stateless_rpc && !always_print_refs) goto cleanup; while (ref) { @@ -226,6 +231,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) oid_to_hex(&ref->old_oid), ref->name); ref = ref->next; } + fflush(stdout); cleanup: close(fd[0]); diff --git a/builtin/fetch.c b/builtin/fetch.c index 19e3f40a0..87de00e49 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -302,10 +302,75 @@ static void find_non_local_tags(const struct ref *refs, string_list_clear(&remote_refs, 0); } +static void get_effective_refspecs(struct refspec **e_rs, int *e_rs_nr, + const struct remote *remote, + const struct refspec *cli_rs, int cli_rs_nr, + int tags, int *autotags) +{ + static struct refspec head_refspec; + + const struct refspec *base_rs; + int base_rs_nr; + struct branch *merge_branch = NULL; + int i; + + struct refspec *rs; + int nr, alloc; + + if (cli_rs_nr) { + base_rs = cli_rs; + base_rs_nr = cli_rs_nr; + } else if (refmap_array) { + die("--refmap option is only meaningful with command-line refspec(s)."); + } else { + /* Use the defaults */ + struct branch *branch = branch_get(NULL); + int has_merge = branch_has_merge_config(branch); + /* Note: has_merge implies non-NULL branch->remote_name */ + if (has_merge && !strcmp(branch->remote_name, remote->name)) + /* + * if the remote we're fetching from is the same + * as given in branch.<name>.remote, we add the + * ref given in branch.<name>.merge, too. + */ + merge_branch = branch; + if (remote && + (remote->fetch_refspec_nr || merge_branch)) { + base_rs = remote->fetch; + base_rs_nr = remote->fetch_refspec_nr; + } else { + head_refspec.src = "HEAD"; + base_rs = &head_refspec; + base_rs_nr = 1; + } + } + + for (i = 0; i < base_rs_nr; i++) + if (base_rs[i].dst && base_rs[i].dst[0]) { + *autotags = 1; + break; + } + + alloc = base_rs_nr + + (merge_branch ? merge_branch->merge_nr : 0) + + (tags == TAGS_SET); + rs = xcalloc(alloc, sizeof(*rs)); + memcpy(rs, base_rs, base_rs_nr * sizeof(*rs)); + nr = base_rs_nr; + if (merge_branch) + for (i = 0; i < merge_branch->merge_nr; i++) + rs[nr++].src = merge_branch->merge[i]->src; + if (tags == TAGS_SET) + rs[nr++] = *tag_refspec; + + *e_rs = rs; + *e_rs_nr = nr; +} + static struct ref *get_ref_map(const struct remote *remote, const struct ref *remote_refs, struct refspec *refspecs, int refspec_count, - int tags, int *autotags) + int tags, int autotags) { int i; struct ref *rm; @@ -321,11 +386,8 @@ static struct ref *get_ref_map(const struct remote *remote, struct refspec *fetch_refspec; int fetch_refspec_nr; - for (i = 0; i < refspec_count; i++) { + for (i = 0; i < refspec_count; i++) get_fetch_map(remote_refs, &refspecs[i], &tail, 0); - if (refspecs[i].dst && refspecs[i].dst[0]) - *autotags = 1; - } /* Merge everything on the command line (but not --tags) */ for (rm = ref_map; rm; rm = rm->next) rm->fetch_head_status = FETCH_HEAD_MERGE; @@ -372,9 +434,6 @@ static struct ref *get_ref_map(const struct remote *remote, (has_merge && !strcmp(branch->remote_name, remote->name)))) { for (i = 0; i < remote->fetch_refspec_nr; i++) { get_fetch_map(remote_refs, &remote->fetch[i], &tail, 0); - if (remote->fetch[i].dst && - remote->fetch[i].dst[0]) - *autotags = 1; if (!i && !has_merge && ref_map && !remote->fetch[0].pattern) ref_map->fetch_head_status = FETCH_HEAD_MERGE; @@ -401,7 +460,7 @@ static struct ref *get_ref_map(const struct remote *remote, if (tags == TAGS_SET) /* also fetch all tags */ get_fetch_map(remote_refs, tag_refspec, &tail, 0); - else if (tags == TAGS_DEFAULT && *autotags) + else if (tags == TAGS_DEFAULT && autotags) find_non_local_tags(remote_refs, &ref_map, &tail); /* Now append any refs to be updated opportunistically: */ @@ -911,13 +970,14 @@ static int quickfetch(struct ref *ref_map) return check_connected(iterate_ref_map, &rm, &opt); } -static int fetch_refs(struct transport *transport, struct ref *ref_map, - struct ref **updated_remote_refs) +static int fetch_refs(struct transport *transport, + struct refspec *refspecs, int refspec_nr, + struct ref *ref_map, struct ref **updated_remote_refs) { int ret = quickfetch(ref_map); if (ret) - ret = transport_fetch_refs(transport, ref_map, - updated_remote_refs); + ret = transport_fetch_refs(transport, refspecs, refspec_nr, + ref_map, updated_remote_refs); if (ret) transport_unlock_pack(transport); return ret; @@ -1068,7 +1128,7 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - if (!fetch_refs(transport, ref_map, NULL)) + if (!fetch_refs(transport, NULL, 0, ref_map, NULL)) consume_refs(transport, ref_map); if (gsecondary) { @@ -1083,6 +1143,10 @@ static int do_fetch(struct transport *transport, struct ref *ref_map; int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; + + struct refspec *e_rs; + int e_rs_nr; + const struct ref *remote_refs; struct ref *new_remote_refs = NULL; @@ -1103,9 +1167,11 @@ static int do_fetch(struct transport *transport, goto cleanup; } + get_effective_refspecs(&e_rs, &e_rs_nr, transport->remote, + refs, ref_count, tags, &autotags); remote_refs = transport_get_remote_refs(transport); ref_map = get_ref_map(transport->remote, remote_refs, refs, ref_count, - tags, &autotags); + tags, autotags); if (!update_head_ok) check_not_current_branch(ref_map); @@ -1126,7 +1192,7 @@ static int do_fetch(struct transport *transport, transport->url); } } - if (fetch_refs(transport, ref_map, &new_remote_refs)) { + if (fetch_refs(transport, e_rs, e_rs_nr, ref_map, &new_remote_refs)) { free_refs(ref_map); retcode = 1; goto cleanup; @@ -1134,7 +1200,7 @@ static int do_fetch(struct transport *transport, if (new_remote_refs) { free_refs(ref_map); ref_map = get_ref_map(transport->remote, new_remote_refs, - refs, ref_count, tags, &autotags); + refs, ref_count, tags, autotags); free_refs(new_remote_refs); } diff --git a/remote-curl.c b/remote-curl.c index 34a97e732..e78959d47 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -12,6 +12,7 @@ #include "credential.h" #include "sha1-array.h" #include "send-pack.h" +#include "refs.h" static struct remote *remote; /* always ends with a trailing slash */ @@ -31,7 +32,8 @@ struct options { thin : 1, /* One of the SEND_PACK_PUSH_CERT_* constants. */ push_cert : 2, - deepen_relative : 1; + deepen_relative : 1, + echo_refs : 1; }; static struct options options; static struct string_list cas_options = STRING_LIST_INIT_DUP; @@ -139,6 +141,14 @@ static int set_option(const char *name, const char *value) else return -1; return 0; + } else if (!strcmp(name, "echo-refs")) { + if (!strcmp(value, "true")) + options.echo_refs = 1; + else if (!strcmp(value, "false")) + options.echo_refs = 0; + else + return -1; + return 0; #if LIBCURL_VERSION_NUM >= 0x070a08 } else if (!strcmp(name, "family")) { @@ -750,7 +760,7 @@ static int rpc_service(struct rpc_state *rpc, struct discovery *heads) return err; } -static int fetch_dumb(int nr_heads, struct ref **to_fetch) +static int fetch_dumb(int nr_heads, const struct ref **to_fetch) { struct walker *walker; char **targets; @@ -775,11 +785,24 @@ static int fetch_dumb(int nr_heads, struct ref **to_fetch) free(targets[i]); free(targets); + if (options.echo_refs) { + struct strbuf sb = STRBUF_INIT; + for (i = 0; i < nr_heads; i++) { + strbuf_reset(&sb); + strbuf_addf(&sb, + "%s %s\n", + oid_to_hex(&to_fetch[i]->old_oid), + to_fetch[i]->name); + write_or_die(1, sb.buf, sb.len); + } + } + return ret ? error("fetch failed.") : 0; } static int fetch_git(struct discovery *heads, - int nr_heads, struct ref **to_fetch) + int nr_refspec, const struct refspec *refspecs, + int nr_heads, const struct ref **to_fetch) { struct rpc_state rpc; struct strbuf preamble = STRBUF_INIT; @@ -811,10 +834,15 @@ static int fetch_git(struct discovery *heads, options.deepen_not.items[i].string); if (options.deepen_relative && options.depth) argv_array_push(&args, "--deepen-relative"); + if (options.echo_refs) + argv_array_push(&args, "--always-print-refs"); argv_array_push(&args, url.buf); - for (i = 0; i < nr_heads; i++) { - struct ref *ref = to_fetch[i]; + if (refspecs) { + for (i = 0; i < nr_refspec; i++) + packet_buf_write(&preamble, "%s\n", refspecs[i].src); + } else { + const struct ref *ref = to_fetch[i]; if (!*ref->name) die("cannot fetch by sha1 over smart http"); packet_buf_write(&preamble, "%s %s\n", @@ -837,46 +865,38 @@ static int fetch_git(struct discovery *heads, return err; } -static int fetch(int nr_heads, struct ref **to_fetch) +static int fetch(int nr_refspec, const struct refspec *refspecs) { + const struct ref **to_fetch; + int nr; + int ret, i; struct discovery *d = discover_refs("git-upload-pack", 0); + get_ref_array(&to_fetch, &nr, d->refs, refspecs, nr_refspec); + if (d->proto_git) - return fetch_git(d, nr_heads, to_fetch); + ret = fetch_git(d, nr_refspec, refspecs, nr, to_fetch); else - return fetch_dumb(nr_heads, to_fetch); + ret = fetch_dumb(nr, to_fetch); + + for (i = 0; i < nr; i++) { + free((void *) to_fetch[i]); + } + free(to_fetch); + + return ret; } static void parse_fetch(struct strbuf *buf) { - struct ref **to_fetch = NULL; - struct ref *list_head = NULL; - struct ref **list = &list_head; - int alloc_heads = 0, nr_heads = 0; + struct refspec *to_fetch = NULL; + int alloc = 0, nr = 0; do { const char *p; if (skip_prefix(buf->buf, "fetch ", &p)) { - const char *name; - struct ref *ref; - struct object_id old_oid; - - if (get_oid_hex(p, &old_oid)) - die("protocol error: expected sha/ref, got %s'", p); - if (p[GIT_SHA1_HEXSZ] == ' ') - name = p + GIT_SHA1_HEXSZ + 1; - else if (!p[GIT_SHA1_HEXSZ]) - name = ""; - else - die("protocol error: expected sha/ref, got %s'", p); - - ref = alloc_ref(name); - oidcpy(&ref->old_oid, &old_oid); - - *list = ref; - list = &ref->next; - - ALLOC_GROW(to_fetch, nr_heads + 1, alloc_heads); - to_fetch[nr_heads++] = ref; + nr++; + ALLOC_GROW(to_fetch, nr, alloc); + parse_ref_or_pattern(&to_fetch[nr - 1], p); } else die("http transport does not support %s", buf->buf); @@ -888,10 +908,8 @@ static void parse_fetch(struct strbuf *buf) break; } while (1); - if (fetch(nr_heads, to_fetch)) + if (fetch(nr, to_fetch)) exit(128); /* error already reported */ - free_refs(list_head); - free(to_fetch); printf("\n"); fflush(stdout); @@ -1084,6 +1102,7 @@ int cmd_main(int argc, const char **argv) printf("option\n"); printf("push\n"); printf("check-connectivity\n"); + printf("echo-refs\n"); printf("\n"); fflush(stdout); } else { diff --git a/t/t5552-upload-pack-ref-in-want.sh b/t/t5552-upload-pack-ref-in-want.sh index 80cf2263a..26e785f3b 100755 --- a/t/t5552-upload-pack-ref-in-want.sh +++ b/t/t5552-upload-pack-ref-in-want.sh @@ -345,7 +345,7 @@ test_expect_success 'server is initially ahead - no ref in want' ' grep "ERR upload-pack: not our ref" err ' -test_expect_failure 'server is initially ahead - ref in want' ' +test_expect_success 'server is initially ahead - ref in want' ' git -C "$REPO" config uploadpack.advertiseRefInWant true && rm -rf local && cp -r "$LOCAL_PRISTINE" local && @@ -369,7 +369,7 @@ test_expect_success 'server is initially behind - no ref in want' ' test_cmp expected actual ' -test_expect_failure 'server is initially behind - ref in want' ' +test_expect_success 'server is initially behind - ref in want' ' git -C "$REPO" config uploadpack.advertiseRefInWant true && rm -rf local && cp -r "$LOCAL_PRISTINE" local && diff --git a/transport-helper.c b/transport-helper.c index be0aa6d39..fcd9edcdc 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -28,7 +28,8 @@ struct helper_data { signed_tags : 1, check_connectivity : 1, no_disconnect_req : 1, - no_private_update : 1; + no_private_update : 1, + echo_refs : 1; char *export_marks; char *import_marks; /* These go from remote name (as in "list") to private name */ @@ -195,6 +196,8 @@ static struct child_process *get_helper(struct transport *transport) data->import_marks = xstrdup(arg); } else if (starts_with(capname, "no-private-update")) { data->no_private_update = 1; + } else if (!strcmp(capname, "echo-refs")) { + data->echo_refs = 1; } else if (mandatory) { die("Unknown mandatory capability %s. This remote " "helper probably needs newer version of Git.", @@ -383,27 +386,49 @@ static int release_helper(struct transport *transport) return res; } +static struct ref *copy_ref_array(struct ref **array, int nr) +{ + struct ref *head = NULL, **tail = &head; + int i; + for (i = 0; i < nr; i++) { + *tail = copy_ref(array[i]); + tail = &(*tail)->next; + } + return head; +} + static int fetch_with_fetch(struct transport *transport, - int nr_heads, const struct ref **to_fetch) + int nr_refspec, const struct refspec *refspecs, + int nr_heads, const struct ref **to_fetch, + struct ref **fetched_refs) { struct helper_data *data = transport->data; int i; struct strbuf buf = STRBUF_INIT; - - for (i = 0; i < nr_heads; i++) { - const struct ref *posn = to_fetch[i]; - if (posn->status & REF_STATUS_UPTODATE) - continue; - - strbuf_addf(&buf, "fetch %s %s\n", - oid_to_hex(&posn->old_oid), - posn->symref ? posn->symref : posn->name); + int use_echo_refs = data->echo_refs && refspecs; + struct ref *fetched = NULL; + + if (use_echo_refs) { + set_helper_option(transport, "echo-refs", "true"); + for (i = 0; i < nr_refspec; i++) + strbuf_addf(&buf, "fetch %s\n", refspecs[i].src); + } else { + for (i = 0; i < nr_heads; i++) { + const struct ref *posn = to_fetch[i]; + if (posn->status & REF_STATUS_UPTODATE) + continue; + + strbuf_addf(&buf, "fetch %s %s\n", + oid_to_hex(&posn->old_oid), + posn->symref ? posn->symref : posn->name); + } } strbuf_addch(&buf, '\n'); sendline(data, &buf); while (1) { + struct object_id oid; if (recvline(data, &buf)) exit(128); @@ -418,12 +443,29 @@ static int fetch_with_fetch(struct transport *transport, data->transport_options.check_self_contained_and_connected && !strcmp(buf.buf, "connectivity-ok")) data->transport_options.self_contained_and_connected = 1; - else if (!buf.len) + else if (use_echo_refs && !get_oid_hex(buf.buf, &oid) + && buf.buf[GIT_SHA1_HEXSZ] == ' ') { + struct ref *ref = alloc_ref(buf.buf + GIT_SHA1_HEXSZ + 1); + oidcpy(&ref->old_oid, &oid); + ref->next = fetched; + fetched = ref; + } else if (!buf.len) break; else warning("%s unexpectedly said: '%s'", data->name, buf.buf); } strbuf_release(&buf); + + if (use_echo_refs) { + if (fetched_refs) + *fetched_refs = fetched; + } else { + assert(fetched == NULL); + if (fetched_refs) + *fetched_refs = copy_ref_array((struct ref **) to_fetch, + nr_heads); + } + return 0; } @@ -657,6 +699,7 @@ static int connect_helper(struct transport *transport, const char *name, } static int fetch(struct transport *transport, + int nr_refspec, const struct refspec *refspecs, int nr_heads, const struct ref **to_fetch, struct ref **fetched_refs) { @@ -665,8 +708,8 @@ static int fetch(struct transport *transport, if (process_connect(transport, 0)) { do_take_over(transport); - return transport->fetch(transport, nr_heads, to_fetch, - fetched_refs); + return transport->fetch(transport, nr_refspec, refspecs, + nr_heads, to_fetch, fetched_refs); } count = 0; @@ -688,7 +731,8 @@ static int fetch(struct transport *transport, set_helper_option(transport, "update-shallow", "true"); if (data->fetch) - return fetch_with_fetch(transport, nr_heads, to_fetch); + return fetch_with_fetch(transport, nr_refspec, refspecs, + nr_heads, to_fetch, fetched_refs); if (data->import) return fetch_with_import(transport, nr_heads, to_fetch, fetched_refs); diff --git a/transport.c b/transport.c index 85a4c5369..734c605b1 100644 --- a/transport.c +++ b/transport.c @@ -95,6 +95,7 @@ static struct ref *get_refs_from_bundle(struct transport *transport, int for_pus } static int fetch_refs_from_bundle(struct transport *transport, + int nr_refspec, const struct refspec *refspecs, int nr_heads, const struct ref **to_fetch, struct ref **fetched_refs) { @@ -203,6 +204,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus } static int fetch_refs_via_pack(struct transport *transport, + int nr_refspec, const struct refspec *refspecs, int nr_heads, const struct ref **to_fetch, struct ref **fetched_refs) { @@ -1096,8 +1098,9 @@ const struct ref *transport_get_remote_refs(struct transport *transport) return transport->remote_refs; } -int transport_fetch_refs(struct transport *transport, struct ref *refs, - struct ref **fetched_refs) +int transport_fetch_refs(struct transport *transport, + const struct refspec *refspecs, int refspec_nr, + struct ref *refs, struct ref **fetched_refs) { int rc; int nr_heads = 0, nr_alloc = 0, nr_refs = 0; @@ -1136,7 +1139,8 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs, heads[nr_heads++] = rm; } - rc = transport->fetch(transport, nr_heads, heads, fetched_refs); + rc = transport->fetch(transport, refspec_nr, refspecs, + nr_heads, heads, fetched_refs); if (nop_head) { *nop_tail = *fetched_refs; *fetched_refs = nop_head; diff --git a/transport.h b/transport.h index 326ff9bd6..d7a007d21 100644 --- a/transport.h +++ b/transport.h @@ -82,13 +82,20 @@ struct transport { * Fetch the objects for the given refs. Note that this gets * an array, and should ignore the list structure. * + * The user may provide the array of refspecs used to generate the + * given refs. If provided, the transport should prefer the refspecs if + * possible (but may still use the refs for pre-fetch optimizations, + * for example). + * * The transport *may* provide, in fetched_refs, the list of refs that - * it fetched. If the transport knows anything about the fetched refs - * that the caller does not know (for example, shallow status or ref - * hashes), it should provide that list of refs and include that - * information in the list. + * it fetched, and must do so if it is different from the given refs. + * If the transport knows anything about the fetched refs that the + * caller does not know (for example, shallow status or ref hashes), it + * should provide that list of refs and include that information in the + * list. **/ int (*fetch)(struct transport *transport, + int refspec_nr, const struct refspec *refspecs, int refs_nr, const struct ref **refs, struct ref **fetched_refs); @@ -234,8 +241,9 @@ int transport_push(struct transport *connection, const struct ref *transport_get_remote_refs(struct transport *transport); -int transport_fetch_refs(struct transport *transport, struct ref *refs, - struct ref **fetched_refs); +int transport_fetch_refs(struct transport *transport, + const struct refspec *refspecs, int refspec_nr, + struct ref *refs, struct ref **fetched_refs); void transport_unlock_pack(struct transport *transport); int transport_disconnect(struct transport *transport); char *transport_anonymize_url(const char *url); -- 2.11.0.483.g087da7b7c-goog