diff options
Diffstat (limited to 'transport-helper.c')
-rw-r--r-- | transport-helper.c | 917 |
1 files changed, 720 insertions, 197 deletions
diff --git a/transport-helper.c b/transport-helper.c index acfc88e3f1..b934183236 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -5,24 +5,32 @@ #include "commit.h" #include "diff.h" #include "revision.h" -#include "quote.h" #include "remote.h" #include "string-list.h" +#include "thread-utils.h" +#include "sigchain.h" +#include "argv-array.h" +#include "refs.h" static int debug; -struct helper_data -{ +struct helper_data { const char *name; struct child_process *helper; FILE *out; unsigned fetch : 1, import : 1, + bidi_import : 1, export : 1, option : 1, push : 1, connect : 1, - no_disconnect_req : 1; + signed_tags : 1, + check_connectivity : 1, + no_disconnect_req : 1, + no_private_update : 1; + char *export_marks; + char *import_marks; /* These go from remote name (as in "list") to private name */ struct refspec *refspecs; int refspec_nr; @@ -41,15 +49,15 @@ static void sendline(struct helper_data *helper, struct strbuf *buffer) die_errno("Full write to remote helper failed"); } -static int recvline_fh(FILE *helper, struct strbuf *buffer) +static int recvline_fh(FILE *helper, struct strbuf *buffer, const char *name) { strbuf_reset(buffer); if (debug) fprintf(stderr, "Debug: Remote helper: Waiting...\n"); - if (strbuf_getline(buffer, helper, '\n') == EOF) { + if (strbuf_getline(buffer, helper) == EOF) { if (debug) fprintf(stderr, "Debug: Remote helper quit.\n"); - exit(128); + return 1; } if (debug) @@ -59,13 +67,7 @@ static int recvline_fh(FILE *helper, struct strbuf *buffer) static int recvline(struct helper_data *helper, struct strbuf *buffer) { - return recvline_fh(helper->out, buffer); -} - -static void xchgline(struct helper_data *helper, struct strbuf *buffer) -{ - sendline(helper, buffer); - recvline(helper, buffer); + return recvline_fh(helper->out, buffer, helper->name); } static void write_constant(int fd, const char *str) @@ -76,7 +78,7 @@ static void write_constant(int fd, const char *str) die_errno("Full write to remote helper failed"); } -const char *remove_ext_force(const char *url) +static const char *remove_ext_force(const char *url) { if (url) { const char *colon = strchr(url, ':'); @@ -95,6 +97,8 @@ static void do_take_over(struct transport *transport) free(data); } +static void standard_options(struct transport *t); + static struct child_process *get_helper(struct transport *transport) { struct helper_data *data = transport->data; @@ -109,17 +113,20 @@ static struct child_process *get_helper(struct transport *transport) if (data->helper) return data->helper; - helper = xcalloc(1, sizeof(*helper)); + helper = xmalloc(sizeof(*helper)); + child_process_init(helper); helper->in = -1; helper->out = -1; helper->err = 0; - helper->argv = xcalloc(4, sizeof(*helper->argv)); - strbuf_addf(&buf, "git-remote-%s", data->name); - helper->argv[0] = strbuf_detach(&buf, NULL); - helper->argv[1] = transport->remote->name; - helper->argv[2] = remove_ext_force(transport->url); + argv_array_pushf(&helper->args, "git-remote-%s", data->name); + argv_array_push(&helper->args, transport->remote->name); + argv_array_push(&helper->args, remove_ext_force(transport->url)); helper->git_cmd = 0; helper->silent_exec_failure = 1; + + argv_array_pushf(&helper->env_array, "%s=%s", GIT_DIR_ENVIRONMENT, + get_git_dir()); + code = start_command(helper); if (code < 0 && errno == ENOENT) die("Unable to find remote helper for '%s'", data->name); @@ -130,7 +137,8 @@ static struct child_process *get_helper(struct transport *transport) data->no_disconnect_req = 0; /* - * Open the output as FILE* so strbuf_getline() can be used. + * Open the output as FILE* so strbuf_getline_*() family of + * functions can be used. * Do this with duped fd because fclose() will close the fd, * and stuff like taking over will require the fd to remain. */ @@ -142,9 +150,10 @@ static struct child_process *get_helper(struct transport *transport) write_constant(helper->in, "capabilities\n"); while (1) { - const char *capname; + const char *capname, *arg; int mandatory = 0; - recvline(data, &buf); + if (recvline(data, &buf)) + exit(128); if (!*buf.buf) break; @@ -165,23 +174,30 @@ static struct child_process *get_helper(struct transport *transport) data->push = 1; else if (!strcmp(capname, "import")) data->import = 1; + else if (!strcmp(capname, "bidi-import")) + data->bidi_import = 1; else if (!strcmp(capname, "export")) data->export = 1; - else if (!data->refspecs && !prefixcmp(capname, "refspec ")) { + else if (!strcmp(capname, "check-connectivity")) + data->check_connectivity = 1; + else if (!data->refspecs && skip_prefix(capname, "refspec ", &arg)) { ALLOC_GROW(refspecs, refspec_nr + 1, refspec_alloc); - refspecs[refspec_nr++] = strdup(buf.buf + strlen("refspec ")); + refspecs[refspec_nr++] = xstrdup(arg); } else if (!strcmp(capname, "connect")) { data->connect = 1; - } else if (!strcmp(buf.buf, "gitdir")) { - struct strbuf gitdir = STRBUF_INIT; - strbuf_addf(&gitdir, "gitdir %s\n", get_git_dir()); - sendline(data, &gitdir); - strbuf_release(&gitdir); + } else if (!strcmp(capname, "signed-tags")) { + data->signed_tags = 1; + } else if (skip_prefix(capname, "export-marks ", &arg)) { + data->export_marks = xstrdup(arg); + } else if (skip_prefix(capname, "import-marks ", &arg)) { + data->import_marks = xstrdup(arg); + } else if (starts_with(capname, "no-private-update")) { + data->no_private_update = 1; } else if (mandatory) { die("Unknown mandatory capability %s. This remote " - "helper probably needs newer version of Git.\n", + "helper probably needs newer version of Git.", capname); } } @@ -189,39 +205,46 @@ static struct child_process *get_helper(struct transport *transport) int i; data->refspec_nr = refspec_nr; data->refspecs = parse_fetch_refspec(refspec_nr, refspecs); - for (i = 0; i < refspec_nr; i++) { + for (i = 0; i < refspec_nr; i++) free((char *)refspecs[i]); - } free(refspecs); + } else if (data->import || data->bidi_import || data->export) { + warning("This remote helper should implement refspec capability."); } strbuf_release(&buf); if (debug) fprintf(stderr, "Debug: Capabilities complete.\n"); + standard_options(transport); return data->helper; } static int disconnect_helper(struct transport *transport) { struct helper_data *data = transport->data; - struct strbuf buf = STRBUF_INIT; + int res = 0; if (data->helper) { if (debug) fprintf(stderr, "Debug: Disconnecting.\n"); if (!data->no_disconnect_req) { - strbuf_addf(&buf, "\n"); - sendline(data, &buf); + /* + * Ignore write errors; there's nothing we can do, + * since we're about to close the pipe anyway. And the + * most likely error is EPIPE due to the helper dying + * to report an error itself. + */ + sigchain_push(SIGPIPE, SIG_IGN); + xwrite(data->helper->in, "\n", 1); + sigchain_pop(SIGPIPE); } close(data->helper->in); close(data->helper->out); fclose(data->out); - finish_command(data->helper); - free((char *)data->helper->argv[0]); - free(data->helper->argv); + res = finish_command(data->helper); free(data->helper); data->helper = NULL; } - return 0; + return res; } static const char *unsupported_options[] = { @@ -230,10 +253,11 @@ static const char *unsupported_options[] = { TRANS_OPT_THIN, TRANS_OPT_KEEP }; + static const char *boolean_options[] = { TRANS_OPT_THIN, TRANS_OPT_KEEP, - TRANS_OPT_FOLLOWTAGS + TRANS_OPT_FOLLOWTAGS, }; static int set_helper_option(struct transport *transport, @@ -267,11 +291,13 @@ static int set_helper_option(struct transport *transport, quote_c_style(value, &buf, NULL, 0); strbuf_addch(&buf, '\n'); - xchgline(data, &buf); + sendline(data, &buf); + if (recvline(data, &buf)) + exit(128); if (!strcmp(buf.buf, "ok")) ret = 0; - else if (!prefixcmp(buf.buf, "error")) { + else if (starts_with(buf.buf, "error")) { ret = -1; } else if (!strcmp(buf.buf, "unsupported")) ret = 1; @@ -295,16 +321,32 @@ static void standard_options(struct transport *t) if (n >= sizeof(buf)) die("impossibly large verbosity value"); set_helper_option(t, "verbosity", buf); + + switch (t->family) { + case TRANSPORT_FAMILY_ALL: + /* + * this is already the default, + * do not break old remote helpers by setting "all" here + */ + break; + case TRANSPORT_FAMILY_IPV4: + set_helper_option(t, "family", "ipv4"); + break; + case TRANSPORT_FAMILY_IPV6: + set_helper_option(t, "family", "ipv6"); + break; + } } static int release_helper(struct transport *transport) { + int res = 0; struct helper_data *data = transport->data; free_refspec(data->refspec_nr, data->refspecs); data->refspecs = NULL; - disconnect_helper(transport); + res = disconnect_helper(transport); free(transport->data); - return 0; + return res; } static int fetch_with_fetch(struct transport *transport, @@ -314,30 +356,34 @@ static int fetch_with_fetch(struct transport *transport, int i; struct strbuf buf = STRBUF_INIT; - standard_options(transport); - 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", - sha1_to_hex(posn->old_sha1), posn->name); + oid_to_hex(&posn->old_oid), + posn->symref ? posn->symref : posn->name); } strbuf_addch(&buf, '\n'); sendline(data, &buf); while (1) { - recvline(data, &buf); + if (recvline(data, &buf)) + exit(128); - if (!prefixcmp(buf.buf, "lock ")) { + if (starts_with(buf.buf, "lock ")) { const char *name = buf.buf + 5; if (transport->pack_lockfile) warning("%s also locked %s", data->name, name); else transport->pack_lockfile = xstrdup(name); } + else if (data->check_connectivity && + 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) break; else @@ -350,38 +396,47 @@ static int fetch_with_fetch(struct transport *transport, static int get_importer(struct transport *transport, struct child_process *fastimport) { struct child_process *helper = get_helper(transport); - memset(fastimport, 0, sizeof(*fastimport)); + struct helper_data *data = transport->data; + int cat_blob_fd, code; + child_process_init(fastimport); fastimport->in = helper->out; - fastimport->argv = xcalloc(5, sizeof(*fastimport->argv)); - fastimport->argv[0] = "fast-import"; - fastimport->argv[1] = "--quiet"; + argv_array_push(&fastimport->args, "fast-import"); + argv_array_push(&fastimport->args, debug ? "--stats" : "--quiet"); + if (data->bidi_import) { + cat_blob_fd = xdup(helper->in); + argv_array_pushf(&fastimport->args, "--cat-blob-fd=%d", cat_blob_fd); + } fastimport->git_cmd = 1; - return start_command(fastimport); + + code = start_command(fastimport); + return code; } static int get_exporter(struct transport *transport, struct child_process *fastexport, - const char *export_marks, - const char *import_marks, struct string_list *revlist_args) { + struct helper_data *data = transport->data; struct child_process *helper = get_helper(transport); - int argc = 0, i; - memset(fastexport, 0, sizeof(*fastexport)); + int i; + + child_process_init(fastexport); /* we need to duplicate helper->in because we want to use it after * fastexport is done with it. */ fastexport->out = dup(helper->in); - fastexport->argv = xcalloc(4 + revlist_args->nr, sizeof(*fastexport->argv)); - fastexport->argv[argc++] = "fast-export"; - if (export_marks) - fastexport->argv[argc++] = export_marks; - if (import_marks) - fastexport->argv[argc++] = import_marks; + argv_array_push(&fastexport->args, "fast-export"); + argv_array_push(&fastexport->args, "--use-done-feature"); + argv_array_push(&fastexport->args, data->signed_tags ? + "--signed-tags=verbatim" : "--signed-tags=warn-strip"); + if (data->export_marks) + argv_array_pushf(&fastexport->args, "--export-marks=%s.tmp", data->export_marks); + if (data->import_marks) + argv_array_pushf(&fastexport->args, "--import-marks=%s", data->import_marks); for (i = 0; i < revlist_args->nr; i++) - fastexport->argv[argc++] = revlist_args->items[i].string; + argv_array_push(&fastexport->args, revlist_args->items[i].string); fastexport->git_cmd = 1; return start_command(fastexport); @@ -406,26 +461,54 @@ static int fetch_with_import(struct transport *transport, if (posn->status & REF_STATUS_UPTODATE) continue; - strbuf_addf(&buf, "import %s\n", posn->name); + strbuf_addf(&buf, "import %s\n", + posn->symref ? posn->symref : posn->name); sendline(data, &buf); strbuf_reset(&buf); } - disconnect_helper(transport); - finish_command(&fastimport); - free(fastimport.argv); - fastimport.argv = NULL; + write_constant(data->helper->in, "\n"); + /* + * remote-helpers that advertise the bidi-import capability are required to + * buffer the complete batch of import commands until this newline before + * sending data to fast-import. + * These helpers read back data from fast-import on their stdin, which could + * be mixed with import commands, otherwise. + */ + + if (finish_command(&fastimport)) + die("Error while running fast-import"); + + /* + * The fast-import stream of a remote helper that advertises + * the "refspec" capability writes to the refs named after the + * right hand side of the first refspec matching each ref we + * were fetching. + * + * (If no "refspec" capability was specified, for historical + * reasons we default to the equivalent of *:*.) + * + * Store the result in to_fetch[i].old_sha1. Callers such + * as "git fetch" can use the value to write feedback to the + * terminal, populate FETCH_HEAD, and determine what new value + * should be written to peer_ref if the update is a + * fast-forward or this is a forced update. + */ for (i = 0; i < nr_heads; i++) { - char *private; + char *private, *name; posn = to_fetch[i]; if (posn->status & REF_STATUS_UPTODATE) continue; + name = posn->symref ? posn->symref : posn->name; if (data->refspecs) - private = apply_refspecs(data->refspecs, data->refspec_nr, posn->name); + private = apply_refspecs(data->refspecs, data->refspec_nr, name); else - private = strdup(posn->name); - read_ref(private, posn->old_sha1); - free(private); + private = xstrdup(name); + if (private) { + if (read_ref(private, posn->old_oid.hash) < 0) + die("Could not read ref %s", private); + free(private); + } } strbuf_release(&buf); return 0; @@ -472,7 +555,9 @@ static int process_connect_service(struct transport *transport, goto exit; sendline(data, &cmdbuf); - recvline_fh(input, &cmdbuf); + if (recvline_fh(input, &cmdbuf, name)) + exit(128); + if (!strcmp(cmdbuf.buf, "")) { data->no_disconnect_req = 1; if (debug) @@ -545,6 +630,16 @@ static int fetch(struct transport *transport, if (!count) return 0; + if (data->check_connectivity && + data->transport_options.check_self_contained_and_connected) + set_helper_option(transport, "check-connectivity", "true"); + + if (transport->cloning) + set_helper_option(transport, "cloning", "true"); + + if (data->transport_options.update_shallow) + set_helper_option(transport, "update-shallow", "true"); + if (data->fetch) return fetch_with_fetch(transport, nr_heads, to_fetch); @@ -554,17 +649,163 @@ static int fetch(struct transport *transport, return -1; } +static int push_update_ref_status(struct strbuf *buf, + struct ref **ref, + struct ref *remote_refs) +{ + char *refname, *msg; + int status, forced = 0; + + if (starts_with(buf->buf, "ok ")) { + status = REF_STATUS_OK; + refname = buf->buf + 3; + } else if (starts_with(buf->buf, "error ")) { + status = REF_STATUS_REMOTE_REJECT; + refname = buf->buf + 6; + } else + die("expected ok/error, helper said '%s'", buf->buf); + + msg = strchr(refname, ' '); + if (msg) { + struct strbuf msg_buf = STRBUF_INIT; + const char *end; + + *msg++ = '\0'; + if (!unquote_c_style(&msg_buf, msg, &end)) + msg = strbuf_detach(&msg_buf, NULL); + else + msg = xstrdup(msg); + strbuf_release(&msg_buf); + + if (!strcmp(msg, "no match")) { + status = REF_STATUS_NONE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "up to date")) { + status = REF_STATUS_UPTODATE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "non-fast forward")) { + status = REF_STATUS_REJECT_NONFASTFORWARD; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "already exists")) { + status = REF_STATUS_REJECT_ALREADY_EXISTS; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "fetch first")) { + status = REF_STATUS_REJECT_FETCH_FIRST; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "needs force")) { + status = REF_STATUS_REJECT_NEEDS_FORCE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "stale info")) { + status = REF_STATUS_REJECT_STALE; + free(msg); + msg = NULL; + } + else if (!strcmp(msg, "forced update")) { + forced = 1; + free(msg); + msg = NULL; + } + } + + if (*ref) + *ref = find_ref_by_name(*ref, refname); + if (!*ref) + *ref = find_ref_by_name(remote_refs, refname); + if (!*ref) { + warning("helper reported unexpected status of %s", refname); + return 1; + } + + if ((*ref)->status != REF_STATUS_NONE) { + /* + * Earlier, the ref was marked not to be pushed, so ignore the ref + * status reported by the remote helper if the latter is 'no match'. + */ + if (status == REF_STATUS_NONE) + return 1; + } + + (*ref)->status = status; + (*ref)->forced_update |= forced; + (*ref)->remote_status = msg; + return !(status == REF_STATUS_OK); +} + +static int push_update_refs_status(struct helper_data *data, + struct ref *remote_refs, + int flags) +{ + struct strbuf buf = STRBUF_INIT; + struct ref *ref = remote_refs; + int ret = 0; + + for (;;) { + char *private; + + if (recvline(data, &buf)) { + ret = 1; + break; + } + + if (!buf.len) + break; + + if (push_update_ref_status(&buf, &ref, remote_refs)) + continue; + + if (flags & TRANSPORT_PUSH_DRY_RUN || !data->refspecs || data->no_private_update) + continue; + + /* propagate back the update to the remote namespace */ + private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name); + if (!private) + continue; + update_ref("update by helper", private, ref->new_oid.hash, NULL, 0, 0); + free(private); + } + strbuf_release(&buf); + return ret; +} + +static void set_common_push_options(struct transport *transport, + const char *name, int flags) +{ + if (flags & TRANSPORT_PUSH_DRY_RUN) { + if (set_helper_option(transport, "dry-run", "true") != 0) + die("helper %s does not support dry-run", name); + } else if (flags & TRANSPORT_PUSH_CERT_ALWAYS) { + if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0) + die("helper %s does not support --signed", name); + } else if (flags & TRANSPORT_PUSH_CERT_IF_ASKED) { + if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "if-asked") != 0) + die("helper %s does not support --signed=if-asked", name); + } +} + static int push_refs_with_push(struct transport *transport, - struct ref *remote_refs, int flags) + struct ref *remote_refs, int flags) { int force_all = flags & TRANSPORT_PUSH_FORCE; int mirror = flags & TRANSPORT_PUSH_MIRROR; struct helper_data *data = transport->data; struct strbuf buf = STRBUF_INIT; - struct child_process *helper; struct ref *ref; + struct string_list cas_options = STRING_LIST_INIT_DUP; + struct string_list_item *cas_option; - helper = get_helper(transport); + get_helper(transport); if (!data->push) return 1; @@ -575,6 +816,8 @@ static int push_refs_with_push(struct transport *transport, /* Check for statuses set by set_ref_status_for_push() */ switch (ref->status) { case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_REJECT_STALE: + case REF_STATUS_REJECT_ALREADY_EXISTS: case REF_STATUS_UPTODATE: continue; default: @@ -591,95 +834,40 @@ static int push_refs_with_push(struct transport *transport, if (ref->peer_ref) strbuf_addstr(&buf, ref->peer_ref->name); else - strbuf_addstr(&buf, sha1_to_hex(ref->new_sha1)); + strbuf_addstr(&buf, oid_to_hex(&ref->new_oid)); } strbuf_addch(&buf, ':'); strbuf_addstr(&buf, ref->name); strbuf_addch(&buf, '\n'); + + /* + * The "--force-with-lease" options without explicit + * values to expect have already been expanded into + * the ref->old_oid_expect[] field; we can ignore + * transport->smart_options->cas altogether and instead + * can enumerate them from the refs. + */ + if (ref->expect_old_sha1) { + struct strbuf cas = STRBUF_INIT; + strbuf_addf(&cas, "%s:%s", + ref->name, oid_to_hex(&ref->old_oid_expect)); + string_list_append(&cas_options, strbuf_detach(&cas, NULL)); + } } - if (buf.len == 0) + if (buf.len == 0) { + string_list_clear(&cas_options, 0); return 0; - - standard_options(transport); - - if (flags & TRANSPORT_PUSH_DRY_RUN) { - if (set_helper_option(transport, "dry-run", "true") != 0) - die("helper %s does not support dry-run", data->name); } + for_each_string_list_item(cas_option, &cas_options) + set_helper_option(transport, "cas", cas_option->string); + set_common_push_options(transport, data->name, flags); + strbuf_addch(&buf, '\n'); sendline(data, &buf); - - ref = remote_refs; - while (1) { - char *refname, *msg; - int status; - - recvline(data, &buf); - if (!buf.len) - break; - - if (!prefixcmp(buf.buf, "ok ")) { - status = REF_STATUS_OK; - refname = buf.buf + 3; - } else if (!prefixcmp(buf.buf, "error ")) { - status = REF_STATUS_REMOTE_REJECT; - refname = buf.buf + 6; - } else - die("expected ok/error, helper said '%s'\n", buf.buf); - - msg = strchr(refname, ' '); - if (msg) { - struct strbuf msg_buf = STRBUF_INIT; - const char *end; - - *msg++ = '\0'; - if (!unquote_c_style(&msg_buf, msg, &end)) - msg = strbuf_detach(&msg_buf, NULL); - else - msg = xstrdup(msg); - strbuf_release(&msg_buf); - - if (!strcmp(msg, "no match")) { - status = REF_STATUS_NONE; - free(msg); - msg = NULL; - } - else if (!strcmp(msg, "up to date")) { - status = REF_STATUS_UPTODATE; - free(msg); - msg = NULL; - } - else if (!strcmp(msg, "non-fast forward")) { - status = REF_STATUS_REJECT_NONFASTFORWARD; - free(msg); - msg = NULL; - } - } - - if (ref) - ref = find_ref_by_name(ref, refname); - if (!ref) - ref = find_ref_by_name(remote_refs, refname); - if (!ref) { - warning("helper reported unexpected status of %s", refname); - continue; - } - - if (ref->status != REF_STATUS_NONE) { - /* - * Earlier, the ref was marked not to be pushed, so ignore the ref - * status reported by the remote helper if the latter is 'no match'. - */ - if (status == REF_STATUS_NONE) - continue; - } - - ref->status = status; - ref->remote_status = msg; - } strbuf_release(&buf); - return 0; + + return push_update_refs_status(data, remote_refs, flags); } static int push_refs_with_export(struct transport *transport, @@ -688,59 +876,77 @@ static int push_refs_with_export(struct transport *transport, struct ref *ref; struct child_process *helper, exporter; struct helper_data *data = transport->data; - char *export_marks = NULL, *import_marks = NULL; - struct string_list revlist_args = STRING_LIST_INIT_NODUP; + struct string_list revlist_args = STRING_LIST_INIT_DUP; struct strbuf buf = STRBUF_INIT; - helper = get_helper(transport); - - write_constant(helper->in, "export\n"); + if (!data->refspecs) + die("remote-helper doesn't support push; refspec needed"); - recvline(data, &buf); - if (debug) - fprintf(stderr, "Debug: Got export_marks '%s'\n", buf.buf); - if (buf.len) { - struct strbuf arg = STRBUF_INIT; - strbuf_addstr(&arg, "--export-marks="); - strbuf_addbuf(&arg, &buf); - export_marks = strbuf_detach(&arg, NULL); + set_common_push_options(transport, data->name, flags); + if (flags & TRANSPORT_PUSH_FORCE) { + if (set_helper_option(transport, "force", "true") != 0) + warning("helper %s does not support 'force'", data->name); } - recvline(data, &buf); - if (debug) - fprintf(stderr, "Debug: Got import_marks '%s'\n", buf.buf); - if (buf.len) { - struct strbuf arg = STRBUF_INIT; - strbuf_addstr(&arg, "--import-marks="); - strbuf_addbuf(&arg, &buf); - import_marks = strbuf_detach(&arg, NULL); - } + helper = get_helper(transport); - strbuf_reset(&buf); + write_constant(helper->in, "export\n"); for (ref = remote_refs; ref; ref = ref->next) { char *private; - unsigned char sha1[20]; + struct object_id oid; - if (!data->refspecs) - continue; private = apply_refspecs(data->refspecs, data->refspec_nr, ref->name); - if (private && !get_sha1(private, sha1)) { + if (private && !get_sha1(private, oid.hash)) { strbuf_addf(&buf, "^%s", private); string_list_append(&revlist_args, strbuf_detach(&buf, NULL)); + oidcpy(&ref->old_oid, &oid); } + free(private); - string_list_append(&revlist_args, ref->name); - + if (ref->peer_ref) { + if (strcmp(ref->name, ref->peer_ref->name)) { + if (!ref->deletion) { + const char *name; + int flag; + + /* Follow symbolic refs (mainly for HEAD). */ + name = resolve_ref_unsafe( + ref->peer_ref->name, + RESOLVE_REF_READING, + oid.hash, &flag); + if (!name || !(flag & REF_ISSYMREF)) + name = ref->peer_ref->name; + + strbuf_addf(&buf, "%s:%s", name, ref->name); + } else + strbuf_addf(&buf, ":%s", ref->name); + + string_list_append(&revlist_args, "--refspec"); + string_list_append(&revlist_args, buf.buf); + strbuf_release(&buf); + } + if (!ref->deletion) + string_list_append(&revlist_args, ref->peer_ref->name); + } } - if (get_exporter(transport, &exporter, - export_marks, import_marks, &revlist_args)) + if (get_exporter(transport, &exporter, &revlist_args)) die("Couldn't run fast-export"); - data->no_disconnect_req = 1; - finish_command(&exporter); - disconnect_helper(transport); + string_list_clear(&revlist_args, 1); + + if (finish_command(&exporter)) + die("Error while running fast-export"); + if (push_update_refs_status(data, remote_refs, flags)) + return 1; + + if (data->export_marks) { + strbuf_addf(&buf, "%s.tmp", data->export_marks); + rename(buf.buf, data->export_marks); + strbuf_release(&buf); + } + return 0; } @@ -809,7 +1015,8 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) while (1) { char *eov, *eon; - recvline(data, &buf); + if (recvline(data, &buf)) + exit(128); if (!*buf.buf) break; @@ -825,11 +1032,14 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) if (buf.buf[0] == '@') (*tail)->symref = xstrdup(buf.buf + 1); else if (buf.buf[0] != '?') - get_sha1_hex(buf.buf, (*tail)->old_sha1); + get_oid_hex(buf.buf, &(*tail)->old_oid); if (eon) { if (has_attribute(eon + 1, "unchanged")) { (*tail)->status |= REF_STATUS_UPTODATE; - read_ref((*tail)->name, (*tail)->old_sha1); + if (read_ref((*tail)->name, + (*tail)->old_oid.hash) < 0) + die(N_("Could not read ref %s"), + (*tail)->name); } } tail = &((*tail)->next); @@ -846,9 +1056,11 @@ static struct ref *get_refs_list(struct transport *transport, int for_push) int transport_helper_init(struct transport *transport, const char *name) { - struct helper_data *data = xcalloc(sizeof(*data), 1); + struct helper_data *data = xcalloc(1, sizeof(*data)); data->name = name; + transport_check_allowed(name); + if (getenv("GIT_TRANSPORT_HELPER_DEBUG")) debug = 1; @@ -862,3 +1074,314 @@ int transport_helper_init(struct transport *transport, const char *name) transport->smart_options = &(data->transport_options); return 0; } + +/* + * Linux pipes can buffer 65536 bytes at once (and most platforms can + * buffer less), so attempt reads and writes with up to that size. + */ +#define BUFFERSIZE 65536 +/* This should be enough to hold debugging message. */ +#define PBUFFERSIZE 8192 + +/* Print bidirectional transfer loop debug message. */ +__attribute__((format (printf, 1, 2))) +static void transfer_debug(const char *fmt, ...) +{ + va_list args; + char msgbuf[PBUFFERSIZE]; + static int debug_enabled = -1; + + if (debug_enabled < 0) + debug_enabled = getenv("GIT_TRANSLOOP_DEBUG") ? 1 : 0; + if (!debug_enabled) + return; + + va_start(args, fmt); + vsnprintf(msgbuf, PBUFFERSIZE, fmt, args); + va_end(args); + fprintf(stderr, "Transfer loop debugging: %s\n", msgbuf); +} + +/* Stream state: More data may be coming in this direction. */ +#define SSTATE_TRANSFERING 0 +/* + * Stream state: No more data coming in this direction, flushing rest of + * data. + */ +#define SSTATE_FLUSHING 1 +/* Stream state: Transfer in this direction finished. */ +#define SSTATE_FINISHED 2 + +#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERING) +#define STATE_NEEDS_WRITING(state) ((state) <= SSTATE_FLUSHING) +#define STATE_NEEDS_CLOSING(state) ((state) == SSTATE_FLUSHING) + +/* Unidirectional transfer. */ +struct unidirectional_transfer { + /* Source */ + int src; + /* Destination */ + int dest; + /* Is source socket? */ + int src_is_sock; + /* Is destination socket? */ + int dest_is_sock; + /* Transfer state (TRANSFERRING/FLUSHING/FINISHED) */ + int state; + /* Buffer. */ + char buf[BUFFERSIZE]; + /* Buffer used. */ + size_t bufuse; + /* Name of source. */ + const char *src_name; + /* Name of destination. */ + const char *dest_name; +}; + +/* Closes the target (for writing) if transfer has finished. */ +static void udt_close_if_finished(struct unidirectional_transfer *t) +{ + if (STATE_NEEDS_CLOSING(t->state) && !t->bufuse) { + t->state = SSTATE_FINISHED; + if (t->dest_is_sock) + shutdown(t->dest, SHUT_WR); + else + close(t->dest); + transfer_debug("Closed %s.", t->dest_name); + } +} + +/* + * Tries to read read data from source into buffer. If buffer is full, + * no data is read. Returns 0 on success, -1 on error. + */ +static int udt_do_read(struct unidirectional_transfer *t) +{ + ssize_t bytes; + + if (t->bufuse == BUFFERSIZE) + return 0; /* No space for more. */ + + transfer_debug("%s is readable", t->src_name); + bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse); + if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN && + errno != EINTR) { + error("read(%s) failed: %s", t->src_name, strerror(errno)); + return -1; + } else if (bytes == 0) { + transfer_debug("%s EOF (with %i bytes in buffer)", + t->src_name, (int)t->bufuse); + t->state = SSTATE_FLUSHING; + } else if (bytes > 0) { + t->bufuse += bytes; + transfer_debug("Read %i bytes from %s (buffer now at %i)", + (int)bytes, t->src_name, (int)t->bufuse); + } + return 0; +} + +/* Tries to write data from buffer into destination. If buffer is empty, + * no data is written. Returns 0 on success, -1 on error. + */ +static int udt_do_write(struct unidirectional_transfer *t) +{ + ssize_t bytes; + + if (t->bufuse == 0) + return 0; /* Nothing to write. */ + + transfer_debug("%s is writable", t->dest_name); + bytes = xwrite(t->dest, t->buf, t->bufuse); + if (bytes < 0 && errno != EWOULDBLOCK) { + error("write(%s) failed: %s", t->dest_name, strerror(errno)); + return -1; + } else if (bytes > 0) { + t->bufuse -= bytes; + if (t->bufuse) + memmove(t->buf, t->buf + bytes, t->bufuse); + transfer_debug("Wrote %i bytes to %s (buffer now at %i)", + (int)bytes, t->dest_name, (int)t->bufuse); + } + return 0; +} + + +/* State of bidirectional transfer loop. */ +struct bidirectional_transfer_state { + /* Direction from program to git. */ + struct unidirectional_transfer ptg; + /* Direction from git to program. */ + struct unidirectional_transfer gtp; +}; + +static void *udt_copy_task_routine(void *udt) +{ + struct unidirectional_transfer *t = (struct unidirectional_transfer *)udt; + while (t->state != SSTATE_FINISHED) { + if (STATE_NEEDS_READING(t->state)) + if (udt_do_read(t)) + return NULL; + if (STATE_NEEDS_WRITING(t->state)) + if (udt_do_write(t)) + return NULL; + if (STATE_NEEDS_CLOSING(t->state)) + udt_close_if_finished(t); + } + return udt; /* Just some non-NULL value. */ +} + +#ifndef NO_PTHREADS + +/* + * Join thread, with appropriate errors on failure. Name is name for the + * thread (for error messages). Returns 0 on success, 1 on failure. + */ +static int tloop_join(pthread_t thread, const char *name) +{ + int err; + void *tret; + err = pthread_join(thread, &tret); + if (!tret) { + error("%s thread failed", name); + return 1; + } + if (err) { + error("%s thread failed to join: %s", name, strerror(err)); + return 1; + } + return 0; +} + +/* + * Spawn the transfer tasks and then wait for them. Returns 0 on success, + * -1 on failure. + */ +static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s) +{ + pthread_t gtp_thread; + pthread_t ptg_thread; + int err; + int ret = 0; + err = pthread_create(>p_thread, NULL, udt_copy_task_routine, + &s->gtp); + if (err) + die("Can't start thread for copying data: %s", strerror(err)); + err = pthread_create(&ptg_thread, NULL, udt_copy_task_routine, + &s->ptg); + if (err) + die("Can't start thread for copying data: %s", strerror(err)); + + ret |= tloop_join(gtp_thread, "Git to program copy"); + ret |= tloop_join(ptg_thread, "Program to git copy"); + return ret; +} +#else + +/* Close the source and target (for writing) for transfer. */ +static void udt_kill_transfer(struct unidirectional_transfer *t) +{ + t->state = SSTATE_FINISHED; + /* + * Socket read end left open isn't a disaster if nobody + * attempts to read from it (mingw compat headers do not + * have SHUT_RD)... + * + * We can't fully close the socket since otherwise gtp + * task would first close the socket it sends data to + * while closing the ptg file descriptors. + */ + if (!t->src_is_sock) + close(t->src); + if (t->dest_is_sock) + shutdown(t->dest, SHUT_WR); + else + close(t->dest); +} + +/* + * Join process, with appropriate errors on failure. Name is name for the + * process (for error messages). Returns 0 on success, 1 on failure. + */ +static int tloop_join(pid_t pid, const char *name) +{ + int tret; + if (waitpid(pid, &tret, 0) < 0) { + error("%s process failed to wait: %s", name, strerror(errno)); + return 1; + } + if (!WIFEXITED(tret) || WEXITSTATUS(tret)) { + error("%s process failed", name); + return 1; + } + return 0; +} + +/* + * Spawn the transfer tasks and then wait for them. Returns 0 on success, + * -1 on failure. + */ +static int tloop_spawnwait_tasks(struct bidirectional_transfer_state *s) +{ + pid_t pid1, pid2; + int ret = 0; + + /* Fork thread #1: git to program. */ + pid1 = fork(); + if (pid1 < 0) + die_errno("Can't start thread for copying data"); + else if (pid1 == 0) { + udt_kill_transfer(&s->ptg); + exit(udt_copy_task_routine(&s->gtp) ? 0 : 1); + } + + /* Fork thread #2: program to git. */ + pid2 = fork(); + if (pid2 < 0) + die_errno("Can't start thread for copying data"); + else if (pid2 == 0) { + udt_kill_transfer(&s->gtp); + exit(udt_copy_task_routine(&s->ptg) ? 0 : 1); + } + + /* + * Close both streams in parent as to not interfere with + * end of file detection and wait for both tasks to finish. + */ + udt_kill_transfer(&s->gtp); + udt_kill_transfer(&s->ptg); + ret |= tloop_join(pid1, "Git to program copy"); + ret |= tloop_join(pid2, "Program to git copy"); + return ret; +} +#endif + +/* + * Copies data from stdin to output and from input to stdout simultaneously. + * Additionally filtering through given filter. If filter is NULL, uses + * identity filter. + */ +int bidirectional_transfer_loop(int input, int output) +{ + struct bidirectional_transfer_state state; + + /* Fill the state fields. */ + state.ptg.src = input; + state.ptg.dest = 1; + state.ptg.src_is_sock = (input == output); + state.ptg.dest_is_sock = 0; + state.ptg.state = SSTATE_TRANSFERING; + state.ptg.bufuse = 0; + state.ptg.src_name = "remote input"; + state.ptg.dest_name = "stdout"; + + state.gtp.src = 0; + state.gtp.dest = output; + state.gtp.src_is_sock = 0; + state.gtp.dest_is_sock = (input == output); + state.gtp.state = SSTATE_TRANSFERING; + state.gtp.bufuse = 0; + state.gtp.src_name = "stdin"; + state.gtp.dest_name = "remote output"; + + return tloop_spawnwait_tasks(&state); +} |