{"thread":{"id":"21917","subject":"[REROLL PATCH v2 1/8] Add remote helper debug mode","startedAt":"2009-12-09T15:26:26Z","lastAt":"2009-12-10T17:10:05Z","messageCount":11,"participants":["Ilari Liusvaara","Shawn O. Pearce","Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":8},"messages":[{"id":"129644","messageId":"1260372394-16427-1-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"21917","inReplyTo":null,"subject":"[REROLL PATCH v2 0/8] Remote helpers smart transport extensions","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2009-12-09T15:26:26Z","receivedAt":"2009-12-09T15:26:26Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Changes from reroll v1:\n\n- Wording fix in pass unknown protocols patch commit message\n- Fix NO_CURL and hopefully make remote-http building bit clearer\n- Remove extraneous fflush(stderr)\n- Simplify remove_ext_force().\n- Use strchr() instead of strchrc(). strchr() not const-correct, but...\n- Simplify URL checking in is_url()\n- Expand stdin and stdout in remote helper documentation\n- _recvline -> recvline_fh\n- _process_connect -> process_connect_service\n- Reworded remote archive patch message\n- gitoptions -> transport_options.\n\nIlari Liusvaara (8):\n  Add remote helper debug mode\n  Support mandatory capabilities\n  Pass unknown protocols to external protocol handlers\n  Refactor git transport options parsing\n  Support taking over transports\n  Support remote helpers implementing smart transports\n  Support remote archive from all smart transports\n  Remove special casing of http, https and ftp\n\n .gitignore                           |    4 +\n Documentation/git-remote-helpers.txt |   30 ++++-\n Makefile                             |   27 +++-\n builtin-archive.c                    |   17 ++-\n transport-helper.c                   |  283 ++++++++++++++++++++++++++++++----\n transport.c                          |  214 ++++++++++++++++++++------\n transport.h                          |   22 +++\n 7 files changed, 504 insertions(+), 93 deletions(-)\n"},{"id":"129643","messageId":"1260372394-16427-2-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"21917","inReplyTo":"1260372394-16427-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[REROLL PATCH v2 1/8] Add remote helper debug mode","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2009-12-09T15:26:27Z","receivedAt":"2009-12-09T15:26:27Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Remote helpers deadlock easily, so support debug mode which shows the\ninteraction steps.\n\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n transport-helper.c |   94 ++++++++++++++++++++++++++++++++++++++--------------\n 1 files changed, 69 insertions(+), 25 deletions(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 11f3d7e..a721dc2 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -8,6 +8,8 @@\n #include \"quote.h\"\n #include \"remote.h\"\n \n+static int debug;\n+\n struct helper_data\n {\n \tconst char *name;\n@@ -22,6 +24,45 @@ struct helper_data\n \tint refspec_nr;\n };\n \n+static void sendline(struct helper_data *helper, struct strbuf *buffer)\n+{\n+\tif (debug)\n+\t\tfprintf(stderr, \"Debug: Remote helper: -> %s\", buffer->buf);\n+\tif (write_in_full(helper->helper->in, buffer->buf, buffer->len)\n+\t\t!= buffer->len)\n+\t\tdie_errno(\"Full write to remote helper failed\");\n+}\n+\n+static int recvline(struct helper_data *helper, struct strbuf *buffer)\n+{\n+\tstrbuf_reset(buffer);\n+\tif (debug)\n+\t\tfprintf(stderr, \"Debug: Remote helper: Waiting...\\n\");\n+\tif (strbuf_getline(buffer, helper->out, '\\n') == EOF) {\n+\t\tif (debug)\n+\t\t\tfprintf(stderr, \"Debug: Remote helper quit.\\n\");\n+\t\texit(128);\n+\t}\n+\n+\tif (debug)\n+\t\tfprintf(stderr, \"Debug: Remote helper: <- %s\\n\", buffer->buf);\n+\treturn 0;\n+}\n+\n+static void xchgline(struct helper_data *helper, struct strbuf *buffer)\n+{\n+\tsendline(helper, buffer);\n+\trecvline(helper, buffer);\n+}\n+\n+static void write_constant(int fd, const char *str)\n+{\n+\tif (debug)\n+\t\tfprintf(stderr, \"Debug: Remote helper: -> %s\", str);\n+\tif (write_in_full(fd, str, strlen(str)) != strlen(str))\n+\t\tdie_errno(\"Full write to remote helper failed\");\n+}\n+\n static struct child_process *get_helper(struct transport *transport)\n {\n \tstruct helper_data *data = transport->data;\n@@ -48,15 +89,16 @@ static struct child_process *get_helper(struct transport *transport)\n \t\tdie(\"Unable to run helper: git %s\", helper->argv[0]);\n \tdata->helper = helper;\n \n-\twrite_str_in_full(helper->in, \"capabilities\\n\");\n+\twrite_constant(helper->in, \"capabilities\\n\");\n \n \tdata->out = xfdopen(helper->out, \"r\");\n \twhile (1) {\n-\t\tif (strbuf_getline(&buf, data->out, '\\n') == EOF)\n-\t\t\texit(128); /* child died, message supplied already */\n+\t\trecvline(data, &buf);\n \n \t\tif (!*buf.buf)\n \t\t\tbreak;\n+\t\tif (debug)\n+\t\t\tfprintf(stderr, \"Debug: Got cap %s\\n\", buf.buf);\n \t\tif (!strcmp(buf.buf, \"fetch\"))\n \t\t\tdata->fetch = 1;\n \t\tif (!strcmp(buf.buf, \"option\"))\n@@ -82,14 +124,21 @@ static struct child_process *get_helper(struct transport *transport)\n \t\tfree(refspecs);\n \t}\n \tstrbuf_release(&buf);\n+\tif (debug)\n+\t\tfprintf(stderr, \"Debug: Capabilities complete.\\n\");\n \treturn data->helper;\n }\n \n static int disconnect_helper(struct transport *transport)\n {\n \tstruct helper_data *data = transport->data;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\n \tif (data->helper) {\n-\t\twrite_str_in_full(data->helper->in, \"\\n\");\n+\t\tif (debug)\n+\t\t\tfprintf(stderr, \"Debug: Disconnecting.\\n\");\n+\t\tstrbuf_addf(&buf, \"\\n\");\n+\t\tsendline(data, &buf);\n \t\tclose(data->helper->in);\n \t\tfclose(data->out);\n \t\tfinish_command(data->helper);\n@@ -117,10 +166,11 @@ static int set_helper_option(struct transport *transport,\n \t\t\t  const char *name, const char *value)\n {\n \tstruct helper_data *data = transport->data;\n-\tstruct child_process *helper = get_helper(transport);\n \tstruct strbuf buf = STRBUF_INIT;\n \tint i, ret, is_bool = 0;\n \n+\tget_helper(transport);\n+\n \tif (!data->option)\n \t\treturn 1;\n \n@@ -143,12 +193,7 @@ static int set_helper_option(struct transport *transport,\n \t\tquote_c_style(value, &buf, NULL, 0);\n \tstrbuf_addch(&buf, '\\n');\n \n-\tif (write_in_full(helper->in, buf.buf, buf.len) != buf.len)\n-\t\tdie_errno(\"cannot send option to %s\", data->name);\n-\n-\tstrbuf_reset(&buf);\n-\tif (strbuf_getline(&buf, data->out, '\\n') == EOF)\n-\t\texit(128); /* child died, message supplied already */\n+\txchgline(data, &buf);\n \n \tif (!strcmp(buf.buf, \"ok\"))\n \t\tret = 0;\n@@ -208,13 +253,10 @@ static int fetch_with_fetch(struct transport *transport,\n \t}\n \n \tstrbuf_addch(&buf, '\\n');\n-\tif (write_in_full(data->helper->in, buf.buf, buf.len) != buf.len)\n-\t\tdie_errno(\"cannot send fetch to %s\", data->name);\n+\tsendline(data, &buf);\n \n \twhile (1) {\n-\t\tstrbuf_reset(&buf);\n-\t\tif (strbuf_getline(&buf, data->out, '\\n') == EOF)\n-\t\t\texit(128); /* child died, message supplied already */\n+\t\trecvline(data, &buf);\n \n \t\tif (!prefixcmp(buf.buf, \"lock \")) {\n \t\t\tconst char *name = buf.buf + 5;\n@@ -249,12 +291,13 @@ static int fetch_with_import(struct transport *transport,\n \t\t\t     int nr_heads, struct ref **to_fetch)\n {\n \tstruct child_process fastimport;\n-\tstruct child_process *helper = get_helper(transport);\n \tstruct helper_data *data = transport->data;\n \tint i;\n \tstruct ref *posn;\n \tstruct strbuf buf = STRBUF_INIT;\n \n+\tget_helper(transport);\n+\n \tif (get_importer(transport, &fastimport))\n \t\tdie(\"Couldn't run fast-import\");\n \n@@ -264,7 +307,7 @@ static int fetch_with_import(struct transport *transport,\n \t\t\tcontinue;\n \n \t\tstrbuf_addf(&buf, \"import %s\\n\", posn->name);\n-\t\twrite_in_full(helper->in, buf.buf, buf.len);\n+\t\tsendline(data, &buf);\n \t\tstrbuf_reset(&buf);\n \t}\n \tdisconnect_helper(transport);\n@@ -369,17 +412,14 @@ static int push_refs(struct transport *transport,\n \t}\n \n \tstrbuf_addch(&buf, '\\n');\n-\tif (write_in_full(helper->in, buf.buf, buf.len) != buf.len)\n-\t\texit(128);\n+\tsendline(data, &buf);\n \n \tref = remote_refs;\n \twhile (1) {\n \t\tchar *refname, *msg;\n \t\tint status;\n \n-\t\tstrbuf_reset(&buf);\n-\t\tif (strbuf_getline(&buf, data->out, '\\n') == EOF)\n-\t\t\texit(128); /* child died, message supplied already */\n+\t\trecvline(data, &buf);\n \t\tif (!buf.len)\n \t\t\tbreak;\n \n@@ -471,8 +511,7 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)\n \n \twhile (1) {\n \t\tchar *eov, *eon;\n-\t\tif (strbuf_getline(&buf, data->out, '\\n') == EOF)\n-\t\t\texit(128); /* child died, message supplied already */\n+\t\trecvline(data, &buf);\n \n \t\tif (!*buf.buf)\n \t\t\tbreak;\n@@ -497,6 +536,8 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)\n \t\t}\n \t\ttail = &((*tail)->next);\n \t}\n+\tif (debug)\n+\t\tfprintf(stderr, \"Debug: Read ref listing.\\n\");\n \tstrbuf_release(&buf);\n \n \tfor (posn = ret; posn; posn = posn->next)\n@@ -510,6 +551,9 @@ int transport_helper_init(struct transport *transport, const char *name)\n \tstruct helper_data *data = xcalloc(sizeof(*data), 1);\n \tdata->name = name;\n \n+\tif (getenv(\"GIT_TRANSPORT_HELPER_DEBUG\"))\n+\t\tdebug = 1;\n+\n \ttransport->data = data;\n \ttransport->set_option = set_helper_option;\n \ttransport->get_refs_list = get_refs_list;\n-- \n1.6.6.rc1.300.gfbc27\n"},{"id":"129650","messageId":"1260372394-16427-3-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"21917","inReplyTo":"1260372394-16427-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[REROLL PATCH v2 2/8] Support mandatory capabilities","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2009-12-09T15:26:28Z","receivedAt":"2009-12-09T15:26:28Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Add support for marking capability as mandatory for hosting git version\nto understand. This is useful for helpers which require various types\nof assistance from main git binary.\n\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n Documentation/git-remote-helpers.txt |    5 ++++-\n transport-helper.c                   |   25 +++++++++++++++++++------\n 2 files changed, 23 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt\nindex 5cfdc0c..20a05fe 100644\n--- a/Documentation/git-remote-helpers.txt\n+++ b/Documentation/git-remote-helpers.txt\n@@ -25,7 +25,10 @@ Commands are given by the caller on the helper's standard input, one per line.\n \n 'capabilities'::\n \tLists the capabilities of the helper, one per line, ending\n-\twith a blank line.\n+\twith a blank line. Each capability may be preceeded with '*'.\n+\tThis marks them mandatory for git version using the remote\n+\thelper to understand (unknown mandatory capability is fatal\n+\terror).\n \n 'list'::\n \tLists the refs, one per line, in the format \"<value> <name>\ndiff --git a/transport-helper.c b/transport-helper.c\nindex a721dc2..4b17aaa 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -93,25 +93,38 @@ static struct child_process *get_helper(struct transport *transport)\n \n \tdata->out = xfdopen(helper->out, \"r\");\n \twhile (1) {\n+\t\tconst char *capname;\n+\t\tint mandatory = 0;\n \t\trecvline(data, &buf);\n \n \t\tif (!*buf.buf)\n \t\t\tbreak;\n+\n+\t\tif (*buf.buf == '*') {\n+\t\t\tcapname = buf.buf + 1;\n+\t\t\tmandatory = 1;\n+\t\t} else\n+\t\t\tcapname = buf.buf;\n+\n \t\tif (debug)\n-\t\t\tfprintf(stderr, \"Debug: Got cap %s\\n\", buf.buf);\n-\t\tif (!strcmp(buf.buf, \"fetch\"))\n+\t\t\tfprintf(stderr, \"Debug: Got cap %s\\n\", capname);\n+\t\tif (!strcmp(capname, \"fetch\"))\n \t\t\tdata->fetch = 1;\n-\t\tif (!strcmp(buf.buf, \"option\"))\n+\t\telse if (!strcmp(capname, \"option\"))\n \t\t\tdata->option = 1;\n-\t\tif (!strcmp(buf.buf, \"push\"))\n+\t\telse if (!strcmp(capname, \"push\"))\n \t\t\tdata->push = 1;\n-\t\tif (!strcmp(buf.buf, \"import\"))\n+\t\telse if (!strcmp(capname, \"import\"))\n \t\t\tdata->import = 1;\n-\t\tif (!data->refspecs && !prefixcmp(buf.buf, \"refspec \")) {\n+\t\telse if (!data->refspecs && !prefixcmp(capname, \"refspec \")) {\n \t\t\tALLOC_GROW(refspecs,\n \t\t\t\t   refspec_nr + 1,\n \t\t\t\t   refspec_alloc);\n \t\t\trefspecs[refspec_nr++] = strdup(buf.buf + strlen(\"refspec \"));\n+\t\t} else if (mandatory) {\n+\t\t\tdie(\"Unknown madatory capability %s. This remote \"\n+\t\t\t    \"helper probably needs newer version of Git.\\n\",\n+\t\t\t    capname);\n \t\t}\n \t}\n \tif (refspecs) {\n-- \n1.6.6.rc1.300.gfbc27\n"},{"id":"129648","messageId":"1260372394-16427-4-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"21917","inReplyTo":"1260372394-16427-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[REROLL PATCH v2 3/8] Pass unknown protocols to external protocol handlers","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2009-12-09T15:26:29Z","receivedAt":"2009-12-09T15:26:29Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Change URL handling to allow external protocol handlers to implement\nnew protocols without the '::' syntax if helper name does not conflict\nwith any built-in protocol.\n\nfoo:// now invokes git-remote-foo with foo:// as the URL.\n\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n transport-helper.c |   12 +++++++-\n transport.c        |   76 +++++++++++++++++++++++++++++++++++++++++----------\n 2 files changed, 72 insertions(+), 16 deletions(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 4b17aaa..271af34 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -63,6 +63,16 @@ static void write_constant(int fd, const char *str)\n \t\tdie_errno(\"Full write to remote helper failed\");\n }\n \n+const char *remove_ext_force(const char *url)\n+{\n+\tif (url) {\n+\t\tconst char *colon = strchr(url, ':');\n+\t\tif (colon && colon[1] == ':')\n+\t\t\treturn colon + 2;\n+\t}\n+\treturn url;\n+}\n+\n static struct child_process *get_helper(struct transport *transport)\n {\n \tstruct helper_data *data = transport->data;\n@@ -83,7 +93,7 @@ static struct child_process *get_helper(struct transport *transport)\n \tstrbuf_addf(&buf, \"remote-%s\", data->name);\n \thelper->argv[0] = strbuf_detach(&buf, NULL);\n \thelper->argv[1] = transport->remote->name;\n-\thelper->argv[2] = transport->url;\n+\thelper->argv[2] = remove_ext_force(transport->url);\n \thelper->git_cmd = 1;\n \tif (start_command(helper))\n \t\tdie(\"Unable to run helper: git %s\", helper->argv[0]);\ndiff --git a/transport.c b/transport.c\nindex 3eea836..dea37d0 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -780,6 +780,44 @@ static int is_file(const char *url)\n \treturn S_ISREG(buf.st_mode);\n }\n \n+static int is_url(const char *url)\n+{\n+\tconst char *url2, *first_slash;\n+\n+\tif (!url)\n+\t\treturn 0;\n+\turl2 = url;\n+\tfirst_slash = strchr(url, '/');\n+\n+\t/* Input with no slash at all or slash first can't be URL. */\n+\tif (!first_slash || first_slash == url)\n+\t\treturn 0;\n+\t/* Character before must be : and next must be /. */\n+\tif (first_slash[-1] != ':' || first_slash[1] != '/')\n+\t\treturn 0;\n+\t/* There must be something before the :// */\n+\tif (first_slash == url + 1)\n+\t\treturn 0;\n+\t/*\n+\t * Check all characters up to first slash - 1. Only alphanum\n+\t * is allowed.\n+\t */\n+\turl2 = url;\n+\twhile (url2 < first_slash - 1) {\n+\t\tif (!isalnum((unsigned char)*url2))\n+\t\t\treturn 0;\n+\t\turl2++;\n+\t}\n+\n+\t/* Valid enough. */\n+\treturn 1;\n+}\n+\n+static int external_specification_len(const char *url)\n+{\n+\treturn strchr(url, ':') - url;\n+}\n+\n struct transport *transport_get(struct remote *remote, const char *url)\n {\n \tstruct transport *ret = xcalloc(1, sizeof(*ret));\n@@ -805,30 +843,23 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \n \tif (remote && remote->foreign_vcs) {\n \t\ttransport_helper_init(ret, remote->foreign_vcs);\n-\t\treturn ret;\n-\t}\n-\n-\tif (!prefixcmp(url, \"rsync:\")) {\n+\t} else if (!prefixcmp(url, \"rsync:\")) {\n \t\tret->get_refs_list = get_refs_via_rsync;\n \t\tret->fetch = fetch_objs_via_rsync;\n \t\tret->push = rsync_transport_push;\n-\n-\t} else if (!prefixcmp(url, \"http://\")\n-\t        || !prefixcmp(url, \"https://\")\n-\t        || !prefixcmp(url, \"ftp://\")) {\n-\t\ttransport_helper_init(ret, \"curl\");\n-#ifdef NO_CURL\n-\t\terror(\"git was compiled without libcurl support.\");\n-#endif\n-\n \t} else if (is_local(url) && is_file(url)) {\n \t\tstruct bundle_transport_data *data = xcalloc(1, sizeof(*data));\n \t\tret->data = data;\n \t\tret->get_refs_list = get_refs_from_bundle;\n \t\tret->fetch = fetch_refs_from_bundle;\n \t\tret->disconnect = close_bundle;\n-\n-\t} else {\n+\t} else if (!is_url(url)\n+\t\t|| !prefixcmp(url, \"file://\")\n+\t\t|| !prefixcmp(url, \"git://\")\n+\t\t|| !prefixcmp(url, \"ssh://\")\n+\t\t|| !prefixcmp(url, \"git+ssh://\")\n+\t\t|| !prefixcmp(url, \"ssh+git://\")) {\n+\t\t/* These are builtin smart transports. */\n \t\tstruct git_transport_data *data = xcalloc(1, sizeof(*data));\n \t\tret->data = data;\n \t\tret->set_option = set_git_option;\n@@ -845,6 +876,21 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \t\tdata->receivepack = \"git-receive-pack\";\n \t\tif (remote->receivepack)\n \t\t\tdata->receivepack = remote->receivepack;\n+\t} else if (!prefixcmp(url, \"http://\")\n+\t\t|| !prefixcmp(url, \"https://\")\n+\t\t|| !prefixcmp(url, \"ftp://\")) {\n+\t\t/* These three are just plain special. */\n+\t\ttransport_helper_init(ret, \"curl\");\n+#ifdef NO_CURL\n+\t\terror(\"git was compiled without libcurl support.\");\n+#endif\n+\t} else {\n+\t\t/* Unknown protocol in URL. Pass to external handler. */\n+\t\tint len = external_specification_len(url);\n+\t\tchar *handler = xmalloc(len + 1);\n+\t\thandler[len] = 0;\n+\t\tstrncpy(handler, url, len);\n+\t\ttransport_helper_init(ret, handler);\n \t}\n \n \treturn ret;\n-- \n1.6.6.rc1.300.gfbc27\n"},{"id":"129651","messageId":"1260372394-16427-5-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"21917","inReplyTo":"1260372394-16427-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[REROLL PATCH v2 4/8] Refactor git transport options parsing","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2009-12-09T15:26:30Z","receivedAt":"2009-12-09T15:26:30Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Refactor the transport options parsing so that protocols that aren't\ndirectly smart transports (file://, git://, ssh:// & co) can record\nthe smart transport options for the case if it turns that transport\ncan actually be smart.\n\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n transport.c |   78 +++++++++++++++++++++++++++++++++++-----------------------\n transport.h |   15 +++++++++++\n 2 files changed, 62 insertions(+), 31 deletions(-)\n\ndiff --git a/transport.c b/transport.c\nindex dea37d0..e6eb20e 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -395,41 +395,35 @@ static int close_bundle(struct transport *transport)\n }\n \n struct git_transport_data {\n-\tunsigned thin : 1;\n-\tunsigned keep : 1;\n-\tunsigned followtags : 1;\n-\tint depth;\n+\tstruct git_transport_options options;\n \tstruct child_process *conn;\n \tint fd[2];\n-\tconst char *uploadpack;\n-\tconst char *receivepack;\n \tstruct extra_have_objects extra_have;\n };\n \n-static int set_git_option(struct transport *connection,\n+static int set_git_option(struct git_transport_options *opts,\n \t\t\t  const char *name, const char *value)\n {\n-\tstruct git_transport_data *data = connection->data;\n \tif (!strcmp(name, TRANS_OPT_UPLOADPACK)) {\n-\t\tdata->uploadpack = value;\n+\t\topts->uploadpack = value;\n \t\treturn 0;\n \t} else if (!strcmp(name, TRANS_OPT_RECEIVEPACK)) {\n-\t\tdata->receivepack = value;\n+\t\topts->receivepack = value;\n \t\treturn 0;\n \t} else if (!strcmp(name, TRANS_OPT_THIN)) {\n-\t\tdata->thin = !!value;\n+\t\topts->thin = !!value;\n \t\treturn 0;\n \t} else if (!strcmp(name, TRANS_OPT_FOLLOWTAGS)) {\n-\t\tdata->followtags = !!value;\n+\t\topts->followtags = !!value;\n \t\treturn 0;\n \t} else if (!strcmp(name, TRANS_OPT_KEEP)) {\n-\t\tdata->keep = !!value;\n+\t\topts->keep = !!value;\n \t\treturn 0;\n \t} else if (!strcmp(name, TRANS_OPT_DEPTH)) {\n \t\tif (!value)\n-\t\t\tdata->depth = 0;\n+\t\t\topts->depth = 0;\n \t\telse\n-\t\t\tdata->depth = atoi(value);\n+\t\t\topts->depth = atoi(value);\n \t\treturn 0;\n \t}\n \treturn 1;\n@@ -439,7 +433,8 @@ static int connect_setup(struct transport *transport, int for_push, int verbose)\n {\n \tstruct git_transport_data *data = transport->data;\n \tdata->conn = git_connect(data->fd, transport->url,\n-\t\t\t\t for_push ? data->receivepack : data->uploadpack,\n+\t\t\t\t for_push ? data->options.receivepack :\n+\t\t\t\t data->options.uploadpack,\n \t\t\t\t verbose ? CONNECT_VERBOSE : 0);\n \treturn 0;\n }\n@@ -469,15 +464,15 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tstruct ref *refs_tmp = NULL;\n \n \tmemset(&args, 0, sizeof(args));\n-\targs.uploadpack = data->uploadpack;\n-\targs.keep_pack = data->keep;\n+\targs.uploadpack = data->options.uploadpack;\n+\targs.keep_pack = data->options.keep;\n \targs.lock_pack = 1;\n-\targs.use_thin_pack = data->thin;\n-\targs.include_tag = data->followtags;\n+\targs.use_thin_pack = data->options.thin;\n+\targs.include_tag = data->options.followtags;\n \targs.verbose = (transport->verbose > 0);\n \targs.quiet = (transport->verbose < 0);\n \targs.no_progress = args.quiet || (!transport->progress && !isatty(1));\n-\targs.depth = data->depth;\n+\targs.depth = data->options.depth;\n \n \tfor (i = 0; i < nr_heads; i++)\n \t\torigh[i] = heads[i] = xstrdup(to_fetch[i]->name);\n@@ -734,7 +729,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \tmemset(&args, 0, sizeof(args));\n \targs.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);\n \targs.force_update = !!(flags & TRANSPORT_PUSH_FORCE);\n-\targs.use_thin_pack = data->thin;\n+\targs.use_thin_pack = data->options.thin;\n \targs.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);\n \targs.quiet = !!(flags & TRANSPORT_PUSH_QUIET);\n \targs.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);\n@@ -847,12 +842,14 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \t\tret->get_refs_list = get_refs_via_rsync;\n \t\tret->fetch = fetch_objs_via_rsync;\n \t\tret->push = rsync_transport_push;\n+\t\tret->smart_options = NULL;\n \t} else if (is_local(url) && is_file(url)) {\n \t\tstruct bundle_transport_data *data = xcalloc(1, sizeof(*data));\n \t\tret->data = data;\n \t\tret->get_refs_list = get_refs_from_bundle;\n \t\tret->fetch = fetch_refs_from_bundle;\n \t\tret->disconnect = close_bundle;\n+\t\tret->smart_options = NULL;\n \t} else if (!is_url(url)\n \t\t|| !prefixcmp(url, \"file://\")\n \t\t|| !prefixcmp(url, \"git://\")\n@@ -862,20 +859,14 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \t\t/* These are builtin smart transports. */\n \t\tstruct git_transport_data *data = xcalloc(1, sizeof(*data));\n \t\tret->data = data;\n-\t\tret->set_option = set_git_option;\n+\t\tret->set_option = NULL;\n \t\tret->get_refs_list = get_refs_via_connect;\n \t\tret->fetch = fetch_refs_via_pack;\n \t\tret->push_refs = git_transport_push;\n \t\tret->disconnect = disconnect_git;\n+\t\tret->smart_options = &(data->options);\n \n-\t\tdata->thin = 1;\n \t\tdata->conn = NULL;\n-\t\tdata->uploadpack = \"git-upload-pack\";\n-\t\tif (remote->uploadpack)\n-\t\t\tdata->uploadpack = remote->uploadpack;\n-\t\tdata->receivepack = \"git-receive-pack\";\n-\t\tif (remote->receivepack)\n-\t\t\tdata->receivepack = remote->receivepack;\n \t} else if (!prefixcmp(url, \"http://\")\n \t\t|| !prefixcmp(url, \"https://\")\n \t\t|| !prefixcmp(url, \"ftp://\")) {\n@@ -893,14 +884,39 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \t\ttransport_helper_init(ret, handler);\n \t}\n \n+\tif (ret->smart_options) {\n+\t\tret->smart_options->thin = 1;\n+\t\tret->smart_options->uploadpack = \"git-upload-pack\";\n+\t\tif (remote->uploadpack)\n+\t\t\tret->smart_options->uploadpack = remote->uploadpack;\n+\t\tret->smart_options->receivepack = \"git-receive-pack\";\n+\t\tif (remote->receivepack)\n+\t\t\tret->smart_options->receivepack = remote->receivepack;\n+\t}\n+\n \treturn ret;\n }\n \n int transport_set_option(struct transport *transport,\n \t\t\t const char *name, const char *value)\n {\n+\tint git_reports = 1, protocol_reports = 1;\n+\n+\tif (transport->smart_options)\n+\t\tgit_reports = set_git_option(transport->smart_options,\n+\t\t\t\t\t     name, value);\n+\n \tif (transport->set_option)\n-\t\treturn transport->set_option(transport, name, value);\n+\t\tprotocol_reports = transport->set_option(transport, name,\n+\t\t\t\t\t\t\tvalue);\n+\n+\t/* If either report is 0, report 0 (success). */\n+\tif (!git_reports || !protocol_reports)\n+\t\treturn 0;\n+\t/* If either reports -1 (invalid value), report -1. */\n+\tif ((git_reports == -1) || (protocol_reports == -1))\n+\t\treturn -1;\n+\t/* Otherwise if both report unknown, report unknown. */\n \treturn 1;\n }\n \ndiff --git a/transport.h b/transport.h\nindex 9e74406..e90c285 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -4,6 +4,15 @@\n #include \"cache.h\"\n #include \"remote.h\"\n \n+struct git_transport_options {\n+\tunsigned thin : 1;\n+\tunsigned keep : 1;\n+\tunsigned followtags : 1;\n+\tint depth;\n+\tconst char *uploadpack;\n+\tconst char *receivepack;\n+};\n+\n struct transport {\n \tstruct remote *remote;\n \tconst char *url;\n@@ -65,6 +74,12 @@ struct transport {\n \tsigned verbose : 3;\n \t/* Force progress even if the output is not a tty */\n \tunsigned progress : 1;\n+\t/*\n+\t * If transport is at least potentially smart, this points to\n+\t * git_transport_options structure to use in case transport\n+\t * actually turns out to be smart.\n+\t */\n+\tstruct git_transport_options *smart_options;\n };\n \n #define TRANSPORT_PUSH_ALL 1\n-- \n1.6.6.rc1.300.gfbc27\n"},{"id":"129649","messageId":"1260372394-16427-6-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"21917","inReplyTo":"1260372394-16427-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[REROLL PATCH v2 5/8] Support taking over transports","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2009-12-09T15:26:31Z","receivedAt":"2009-12-09T15:26:31Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Add support for taking over transports that turn out to be smart.\n\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n transport-helper.c |   19 ++++++++++++++++++-\n transport.c        |   51 ++++++++++++++++++++++++++++++++++++++++++++++-----\n transport.h        |    2 ++\n 3 files changed, 66 insertions(+), 6 deletions(-)\n\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 271af34..97eed6c 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -22,6 +22,10 @@ struct helper_data\n \t/* These go from remote name (as in \"list\") to private name */\n \tstruct refspec *refspecs;\n \tint refspec_nr;\n+\t/* Transport options for fetch-pack/send-pack (should one of\n+\t * those be invoked).\n+\t */\n+\tstruct git_transport_options transport_options;\n };\n \n static void sendline(struct helper_data *helper, struct strbuf *buffer)\n@@ -81,6 +85,7 @@ static struct child_process *get_helper(struct transport *transport)\n \tconst char **refspecs = NULL;\n \tint refspec_nr = 0;\n \tint refspec_alloc = 0;\n+\tint duped;\n \n \tif (data->helper)\n \t\treturn data->helper;\n@@ -99,9 +104,19 @@ static struct child_process *get_helper(struct transport *transport)\n \t\tdie(\"Unable to run helper: git %s\", helper->argv[0]);\n \tdata->helper = helper;\n \n+\t/*\n+\t * Open the output as FILE* so strbuf_getline() can be used.\n+\t * Do this with duped fd because fclose() will close the fd,\n+\t * and stuff like taking over will require the fd to remain.\n+\t *\n+\t */\n+\tduped = dup(helper->out);\n+\tif (duped < 0)\n+\t\tdie_errno(\"Can't dup helper output fd\");\n+\tdata->out = xfdopen(duped, \"r\");\n+\n \twrite_constant(helper->in, \"capabilities\\n\");\n \n-\tdata->out = xfdopen(helper->out, \"r\");\n \twhile (1) {\n \t\tconst char *capname;\n \t\tint mandatory = 0;\n@@ -163,6 +178,7 @@ static int disconnect_helper(struct transport *transport)\n \t\tstrbuf_addf(&buf, \"\\n\");\n \t\tsendline(data, &buf);\n \t\tclose(data->helper->in);\n+\t\tclose(data->helper->out);\n \t\tfclose(data->out);\n \t\tfinish_command(data->helper);\n \t\tfree((char *)data->helper->argv[0]);\n@@ -583,5 +599,6 @@ int transport_helper_init(struct transport *transport, const char *name)\n \ttransport->fetch = fetch;\n \ttransport->push_refs = push_refs;\n \ttransport->disconnect = release_helper;\n+\ttransport->smart_options = &(data->transport_options);\n \treturn 0;\n }\ndiff --git a/transport.c b/transport.c\nindex e6eb20e..ad25b98 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -398,6 +398,7 @@ struct git_transport_data {\n \tstruct git_transport_options options;\n \tstruct child_process *conn;\n \tint fd[2];\n+\tunsigned got_remote_heads : 1;\n \tstruct extra_have_objects extra_have;\n };\n \n@@ -432,10 +433,15 @@ static int set_git_option(struct git_transport_options *opts,\n static int connect_setup(struct transport *transport, int for_push, int verbose)\n {\n \tstruct git_transport_data *data = transport->data;\n+\n+\tif (data->conn)\n+\t\treturn 0;\n+\n \tdata->conn = git_connect(data->fd, transport->url,\n \t\t\t\t for_push ? data->options.receivepack :\n \t\t\t\t data->options.uploadpack,\n \t\t\t\t verbose ? CONNECT_VERBOSE : 0);\n+\n \treturn 0;\n }\n \n@@ -447,6 +453,7 @@ static struct ref *get_refs_via_connect(struct transport *transport, int for_pus\n \tconnect_setup(transport, for_push, 0);\n \tget_remote_heads(data->fd[0], &refs, 0, NULL,\n \t\t\t for_push ? REF_NORMAL : 0, &data->extra_have);\n+\tdata->got_remote_heads = 1;\n \n \treturn refs;\n }\n@@ -477,9 +484,10 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tfor (i = 0; i < nr_heads; i++)\n \t\torigh[i] = heads[i] = xstrdup(to_fetch[i]->name);\n \n-\tif (!data->conn) {\n+\tif (!data->got_remote_heads) {\n \t\tconnect_setup(transport, 0, 0);\n \t\tget_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);\n+\t\tdata->got_remote_heads = 1;\n \t}\n \n \trefs = fetch_pack(&args, data->fd, data->conn,\n@@ -490,6 +498,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \tif (finish_connect(data->conn))\n \t\trefs = NULL;\n \tdata->conn = NULL;\n+\tdata->got_remote_heads = 0;\n \n \tfree_refs(refs_tmp);\n \n@@ -718,12 +727,13 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \tstruct send_pack_args args;\n \tint ret;\n \n-\tif (!data->conn) {\n+\tif (!data->got_remote_heads) {\n \t\tstruct ref *tmp_refs;\n \t\tconnect_setup(transport, 1, 0);\n \n \t\tget_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,\n \t\t\t\t NULL);\n+\t\tdata->got_remote_heads = 1;\n \t}\n \n \tmemset(&args, 0, sizeof(args));\n@@ -741,6 +751,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \tclose(data->fd[0]);\n \tret |= finish_connect(data->conn);\n \tdata->conn = NULL;\n+\tdata->got_remote_heads = 0;\n \n \treturn ret;\n }\n@@ -749,7 +760,8 @@ static int disconnect_git(struct transport *transport)\n {\n \tstruct git_transport_data *data = transport->data;\n \tif (data->conn) {\n-\t\tpacket_flush(data->fd[1]);\n+\t\tif (data->got_remote_heads)\n+\t\t\tpacket_flush(data->fd[1]);\n \t\tclose(data->fd[0]);\n \t\tclose(data->fd[1]);\n \t\tfinish_connect(data->conn);\n@@ -759,6 +771,32 @@ static int disconnect_git(struct transport *transport)\n \treturn 0;\n }\n \n+void transport_take_over(struct transport *transport,\n+\t\t\t struct child_process *child)\n+{\n+\tstruct git_transport_data *data;\n+\n+\tif (!transport->smart_options)\n+\t\tdie(\"Bug detected: Taking over transport requires non-NULL \"\n+\t\t    \"smart_options field.\");\n+\n+\tdata = xcalloc(1, sizeof(*data));\n+\tdata->options = *transport->smart_options;\n+\tdata->conn = child;\n+\tdata->fd[0] = data->conn->out;\n+\tdata->fd[1] = data->conn->in;\n+\tdata->got_remote_heads = 0;\n+\ttransport->data = data;\n+\n+\ttransport->set_option = NULL;\n+\ttransport->get_refs_list = get_refs_via_connect;\n+\ttransport->fetch = fetch_refs_via_pack;\n+\ttransport->push = NULL;\n+\ttransport->push_refs = git_transport_push;\n+\ttransport->disconnect = disconnect_git;\n+\ttransport->smart_options = &(data->options);\n+}\n+\n static int is_local(const char *url)\n {\n \tconst char *colon = strchr(url, ':');\n@@ -867,6 +905,7 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \t\tret->smart_options = &(data->options);\n \n \t\tdata->conn = NULL;\n+\t\tdata->got_remote_heads = 0;\n \t} else if (!prefixcmp(url, \"http://\")\n \t\t|| !prefixcmp(url, \"https://\")\n \t\t|| !prefixcmp(url, \"ftp://\")) {\n@@ -927,9 +966,9 @@ int transport_push(struct transport *transport,\n \t*nonfastforward = 0;\n \tverify_remote_names(refspec_nr, refspec);\n \n-\tif (transport->push)\n+\tif (transport->push) {\n \t\treturn transport->push(transport, refspec_nr, refspec, flags);\n-\tif (transport->push_refs) {\n+\t} else if (transport->push_refs) {\n \t\tstruct ref *remote_refs =\n \t\t\ttransport->get_refs_list(transport, 1);\n \t\tstruct ref *local_refs = get_local_heads();\n@@ -973,6 +1012,7 @@ const struct ref *transport_get_remote_refs(struct transport *transport)\n {\n \tif (!transport->remote_refs)\n \t\ttransport->remote_refs = transport->get_refs_list(transport, 0);\n+\n \treturn transport->remote_refs;\n }\n \n@@ -1007,6 +1047,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)\n \t}\n \n \trc = transport->fetch(transport, nr_heads, heads);\n+\n \tfree(heads);\n \treturn rc;\n }\ndiff --git a/transport.h b/transport.h\nindex e90c285..781db2e 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -130,6 +130,8 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs);\n void transport_unlock_pack(struct transport *transport);\n int transport_disconnect(struct transport *transport);\n char *transport_anonymize_url(const char *url);\n+void transport_take_over(struct transport *transport,\n+\t\t\t struct child_process *child);\n \n /* Transport methods defined outside transport.c */\n int transport_helper_init(struct transport *transport, const char *name);\n-- \n1.6.6.rc1.300.gfbc27\n"},{"id":"129647","messageId":"1260372394-16427-7-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"21917","inReplyTo":"1260372394-16427-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[REROLL PATCH v2 6/8] Support remote helpers implementing smart transports","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2009-12-09T15:26:32Z","receivedAt":"2009-12-09T15:26:32Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n Documentation/git-remote-helpers.txt |   25 +++++++-\n transport-helper.c                   |  126 ++++++++++++++++++++++++++++++++--\n 2 files changed, 144 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-remote-helpers.txt b/Documentation/git-remote-helpers.txt\nindex 20a05fe..4685a89 100644\n--- a/Documentation/git-remote-helpers.txt\n+++ b/Documentation/git-remote-helpers.txt\n@@ -93,6 +93,20 @@ Supported if the helper has the \"push\" capability.\n +\n Supported if the helper has the \"import\" capability.\n \n+'connect' <service>::\n+\tConnects to given service. Standard input and standard output\n+\tof helper are connected to specified service (git prefix is\n+\tincluded in service name so e.g. fetching uses 'git-upload-pack'\n+\tas service) on remote side. Valid replies to this command are\n+\tempty line (connection established), 'fallback' (no smart\n+\ttransport support, fall back to dumb transports) and just\n+\texiting with error message printed (can't connect, don't\n+\tbother trying to fall back). After line feed terminating the\n+\tpositive (empty) response, the output of service starts. After\n+\tthe connection ends, the remote helper exits.\n++\n+Supported if the helper has the \"connect\" capability.\n+\n If a fatal error occurs, the program writes the error message to\n stderr and exits. The caller should expect that a suitable error\n message has been printed if the child closes the connection without\n@@ -126,6 +140,9 @@ CAPABILITIES\n \tall, it must cover all refs reported by the list command; if\n \tit is not used, it is effectively \"*:*\"\n \n+'connect'::\n+\tThis helper supports the 'connect' command.\n+\n REF LIST ATTRIBUTES\n -------------------\n \n@@ -168,9 +185,15 @@ OPTIONS\n \tbut don't actually change any repository data.\tFor most\n \thelpers this only applies to the 'push', if supported.\n \n+'option servpath <c-style-quoted-path>'::\n+\tSet service path (--upload-pack, --receive-pack etc.) for\n+\tnext connect. Remote helper MAY support this option. Remote\n+\thelper MUST NOT rely on this option being set before\n+\tconnect request occurs.\n+\n Documentation\n -------------\n-Documentation by Daniel Barkalow.\n+Documentation by Daniel Barkalow and Ilari Liusvaara\n \n GIT\n ---\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 97eed6c..216af87 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -18,7 +18,9 @@ struct helper_data\n \tunsigned fetch : 1,\n \t\timport : 1,\n \t\toption : 1,\n-\t\tpush : 1;\n+\t\tpush : 1,\n+\t\tconnect : 1,\n+\t\tno_disconnect_req : 1;\n \t/* These go from remote name (as in \"list\") to private name */\n \tstruct refspec *refspecs;\n \tint refspec_nr;\n@@ -37,12 +39,12 @@ static void sendline(struct helper_data *helper, struct strbuf *buffer)\n \t\tdie_errno(\"Full write to remote helper failed\");\n }\n \n-static int recvline(struct helper_data *helper, struct strbuf *buffer)\n+static int recvline_fh(FILE *helper, struct strbuf *buffer)\n {\n \tstrbuf_reset(buffer);\n \tif (debug)\n \t\tfprintf(stderr, \"Debug: Remote helper: Waiting...\\n\");\n-\tif (strbuf_getline(buffer, helper->out, '\\n') == EOF) {\n+\tif (strbuf_getline(buffer, helper, '\\n') == EOF) {\n \t\tif (debug)\n \t\t\tfprintf(stderr, \"Debug: Remote helper quit.\\n\");\n \t\texit(128);\n@@ -53,6 +55,11 @@ static int recvline(struct helper_data *helper, struct strbuf *buffer)\n \treturn 0;\n }\n \n+static int recvline(struct helper_data *helper, struct strbuf *buffer)\n+{\n+\treturn recvline_fh(helper->out, buffer);\n+}\n+\n static void xchgline(struct helper_data *helper, struct strbuf *buffer)\n {\n \tsendline(helper, buffer);\n@@ -77,6 +84,15 @@ const char *remove_ext_force(const char *url)\n \treturn url;\n }\n \n+static void do_take_over(struct transport *transport)\n+{\n+\tstruct helper_data *data;\n+\tdata = (struct helper_data*)transport->data;\n+\ttransport_take_over(transport, data->helper);\n+\tfclose(data->out);\n+\tfree(data);\n+}\n+\n static struct child_process *get_helper(struct transport *transport)\n {\n \tstruct helper_data *data = transport->data;\n@@ -103,12 +119,12 @@ static struct child_process *get_helper(struct transport *transport)\n \tif (start_command(helper))\n \t\tdie(\"Unable to run helper: git %s\", helper->argv[0]);\n \tdata->helper = helper;\n+\tdata->no_disconnect_req = 0;\n \n \t/*\n \t * Open the output as FILE* so strbuf_getline() can be used.\n \t * Do this with duped fd because fclose() will close the fd,\n \t * and stuff like taking over will require the fd to remain.\n-\t *\n \t */\n \tduped = dup(helper->out);\n \tif (duped < 0)\n@@ -146,6 +162,8 @@ static struct child_process *get_helper(struct transport *transport)\n \t\t\t\t   refspec_nr + 1,\n \t\t\t\t   refspec_alloc);\n \t\t\trefspecs[refspec_nr++] = strdup(buf.buf + strlen(\"refspec \"));\n+\t\t} else if (!strcmp(capname, \"connect\")) {\n+\t\t\tdata->connect = 1;\n \t\t} else if (mandatory) {\n \t\t\tdie(\"Unknown madatory capability %s. This remote \"\n \t\t\t    \"helper probably needs newer version of Git.\\n\",\n@@ -175,8 +193,10 @@ static int disconnect_helper(struct transport *transport)\n \tif (data->helper) {\n \t\tif (debug)\n \t\t\tfprintf(stderr, \"Debug: Disconnecting.\\n\");\n-\t\tstrbuf_addf(&buf, \"\\n\");\n-\t\tsendline(data, &buf);\n+\t\tif(!data->no_disconnect_req) {\n+\t\t\tstrbuf_addf(&buf, \"\\n\");\n+\t\t\tsendline(data, &buf);\n+\t\t}\n \t\tclose(data->helper->in);\n \t\tclose(data->helper->out);\n \t\tfclose(data->out);\n@@ -370,12 +390,96 @@ static int fetch_with_import(struct transport *transport,\n \treturn 0;\n }\n \n+static int process_connect_service(struct transport *transport,\n+\t\t\t\t   const char *name, const char *exec)\n+{\n+\tstruct helper_data *data = transport->data;\n+\tstruct strbuf cmdbuf = STRBUF_INIT;\n+\tstruct child_process *helper;\n+\tint r, duped, ret = 0;\n+\tFILE *input;\n+\n+\thelper = get_helper(transport);\n+\n+\t/*\n+\t * Yes, dup the pipe another time, as we need unbuffered version\n+\t * of input pipe as FILE*. fclose() closes the underlying fd and\n+\t * stream buffering only can be changed before first I/O operation\n+\t * on it.\n+\t */\n+\tduped = dup(helper->out);\n+\tif (duped < 0)\n+\t\tdie_errno(\"Can't dup helper output fd\");\n+\tinput = xfdopen(duped, \"r\");\n+\tsetvbuf(input, NULL, _IONBF, 0);\n+\n+\t/*\n+\t * Handle --upload-pack and friends. This is fire and forget...\n+\t * just warn if it fails.\n+\t */\n+\tif (strcmp(name, exec)) {\n+\t\tr = set_helper_option(transport, \"servpath\", exec);\n+\t\tif (r > 0)\n+\t\t\tfprintf(stderr, \"Warning: Setting remote service path \"\n+\t\t\t\t\"not supported by protocol.\\n\");\n+\t\telse if (r < 0)\n+\t\t\tfprintf(stderr, \"Warning: Invalid remote service \"\n+\t\t\t\t\"path.\\n\");\n+\t}\n+\n+\tif (data->connect)\n+\t\tstrbuf_addf(&cmdbuf, \"connect %s\\n\", name);\n+\telse\n+\t\tgoto exit;\n+\n+\tsendline(data, &cmdbuf);\n+\trecvline_fh(input, &cmdbuf);\n+\tif (!strcmp(cmdbuf.buf, \"\")) {\n+\t\tdata->no_disconnect_req = 1;\n+\t\tif (debug)\n+\t\t\tfprintf(stderr, \"Debug: Smart transport connection \"\n+\t\t\t\t\"ready.\\n\");\n+\t\tret = 1;\n+\t} else if (!strcmp(cmdbuf.buf, \"fallback\")) {\n+\t\tif (debug)\n+\t\t\tfprintf(stderr, \"Debug: Falling back to dumb \"\n+\t\t\t\t\"transport.\\n\");\n+\t} else\n+\t\tdie(\"Unknown response to connect: %s\",\n+\t\t\tcmdbuf.buf);\n+\n+exit:\n+\tfclose(input);\n+\treturn ret;\n+}\n+\n+static int process_connect(struct transport *transport,\n+\t\t\t\t     int for_push)\n+{\n+\tstruct helper_data *data = transport->data;\n+\tconst char *name;\n+\tconst char *exec;\n+\n+\tname = for_push ? \"git-receive-pack\" : \"git-upload-pack\";\n+\tif (for_push)\n+\t\texec = data->transport_options.receivepack;\n+\telse\n+\t\texec = data->transport_options.uploadpack;\n+\n+\treturn process_connect_service(transport, name, exec);\n+}\n+\n static int fetch(struct transport *transport,\n \t\t int nr_heads, struct ref **to_fetch)\n {\n \tstruct helper_data *data = transport->data;\n \tint i, count;\n \n+\tif (process_connect(transport, 0)) {\n+\t\tdo_take_over(transport);\n+\t\treturn transport->fetch(transport, nr_heads, to_fetch);\n+\t}\n+\n \tcount = 0;\n \tfor (i = 0; i < nr_heads; i++)\n \t\tif (!(to_fetch[i]->status & REF_STATUS_UPTODATE))\n@@ -403,6 +507,11 @@ static int push_refs(struct transport *transport,\n \tstruct child_process *helper;\n \tstruct ref *ref;\n \n+\tif (process_connect(transport, 1)) {\n+\t\tdo_take_over(transport);\n+\t\treturn transport->push_refs(transport, remote_refs, flags);\n+\t}\n+\n \tif (!remote_refs)\n \t\treturn 0;\n \n@@ -543,6 +652,11 @@ static struct ref *get_refs_list(struct transport *transport, int for_push)\n \n \thelper = get_helper(transport);\n \n+\tif (process_connect(transport, for_push)) {\n+\t\tdo_take_over(transport);\n+\t\treturn transport->get_refs_list(transport, for_push);\n+\t}\n+\n \tif (data->push && for_push)\n \t\twrite_str_in_full(helper->in, \"list for-push\\n\");\n \telse\n-- \n1.6.6.rc1.300.gfbc27\n"},{"id":"129646","messageId":"1260372394-16427-8-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"21917","inReplyTo":"1260372394-16427-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[REROLL PATCH v2 7/8] Support remote archive from all smart transports","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2009-12-09T15:26:33Z","receivedAt":"2009-12-09T15:26:33Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"Previously, remote archive required internal (non remote-helper)\nsmart transport. Extend the remote archive to also support smart\ntransports implemented by remote helpers.\n\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n builtin-archive.c  |   17 ++++++++++-------\n transport-helper.c |   19 +++++++++++++++++++\n transport.c        |   21 +++++++++++++++++++++\n transport.h        |    5 +++++\n 4 files changed, 55 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin-archive.c b/builtin-archive.c\nindex 12351e9..d34b3fd 100644\n--- a/builtin-archive.c\n+++ b/builtin-archive.c\n@@ -5,6 +5,7 @@\n #include \"cache.h\"\n #include \"builtin.h\"\n #include \"archive.h\"\n+#include \"transport.h\"\n #include \"parse-options.h\"\n #include \"pkt-line.h\"\n #include \"sideband.h\"\n@@ -25,12 +26,16 @@ static void create_output_file(const char *output_file)\n static int run_remote_archiver(int argc, const char **argv,\n \t\t\t       const char *remote, const char *exec)\n {\n-\tchar *url, buf[LARGE_PACKET_MAX];\n+\tchar buf[LARGE_PACKET_MAX];\n \tint fd[2], i, len, rv;\n-\tstruct child_process *conn;\n+\tstruct transport *transport;\n+\tstruct remote *_remote;\n \n-\turl = xstrdup(remote);\n-\tconn = git_connect(fd, url, exec, 0);\n+\t_remote = remote_get(remote);\n+\tif (!_remote->url[0])\n+\t\tdie(\"git archive: Remote with no URL\");\n+\ttransport = transport_get(_remote, _remote->url[0]);\n+\ttransport_connect(transport, \"git-upload-archive\", exec, fd);\n \n \tfor (i = 1; i < argc; i++)\n \t\tpacket_write(fd[1], \"argument %s\\n\", argv[i]);\n@@ -53,9 +58,7 @@ static int run_remote_archiver(int argc, const char **argv,\n \n \t/* Now, start reading from fd[0] and spit it out to stdout */\n \trv = recv_sideband(\"archive\", fd[0], 1);\n-\tclose(fd[0]);\n-\tclose(fd[1]);\n-\trv |= finish_connect(conn);\n+\trv |= transport_disconnect(transport);\n \n \treturn !!rv;\n }\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 216af87..2ce4ef5 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -469,6 +469,24 @@ static int process_connect(struct transport *transport,\n \treturn process_connect_service(transport, name, exec);\n }\n \n+static int connect_helper(struct transport *transport, const char *name,\n+\t\t   const char *exec, int fd[2])\n+{\n+\tstruct helper_data *data = transport->data;\n+\n+\t/* Get_helper so connect is inited. */\n+\tget_helper(transport);\n+\tif (!data->connect)\n+\t\tdie(\"Operation not supported by protocol.\");\n+\n+\tif (!process_connect_service(transport, name, exec))\n+\t\tdie(\"Can't connect to subservice %s.\", name);\n+\n+\tfd[0] = data->helper->out;\n+\tfd[1] = data->helper->in;\n+\treturn 0;\n+}\n+\n static int fetch(struct transport *transport,\n \t\t int nr_heads, struct ref **to_fetch)\n {\n@@ -713,6 +731,7 @@ int transport_helper_init(struct transport *transport, const char *name)\n \ttransport->fetch = fetch;\n \ttransport->push_refs = push_refs;\n \ttransport->disconnect = release_helper;\n+\ttransport->connect = connect_helper;\n \ttransport->smart_options = &(data->transport_options);\n \treturn 0;\n }\ndiff --git a/transport.c b/transport.c\nindex ad25b98..a7d67eb 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -756,6 +756,17 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \treturn ret;\n }\n \n+static int connect_git(struct transport *transport, const char *name,\n+\t\t       const char *executable, int fd[2])\n+{\n+\tstruct git_transport_data *data = transport->data;\n+\tdata->conn = git_connect(data->fd, transport->url,\n+\t\t\t\t executable, 0);\n+\tfd[0] = data->fd[0];\n+\tfd[1] = data->fd[1];\n+\treturn 0;\n+}\n+\n static int disconnect_git(struct transport *transport)\n {\n \tstruct git_transport_data *data = transport->data;\n@@ -901,6 +912,7 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \t\tret->get_refs_list = get_refs_via_connect;\n \t\tret->fetch = fetch_refs_via_pack;\n \t\tret->push_refs = git_transport_push;\n+\t\tret->connect = connect_git;\n \t\tret->disconnect = disconnect_git;\n \t\tret->smart_options = &(data->options);\n \n@@ -1061,6 +1073,15 @@ void transport_unlock_pack(struct transport *transport)\n \t}\n }\n \n+int transport_connect(struct transport *transport, const char *name,\n+\t\t      const char *exec, int fd[2])\n+{\n+\tif (transport->connect)\n+\t\treturn transport->connect(transport, name, exec, fd);\n+\telse\n+\t\tdie(\"Operation not supported by protocol\");\n+}\n+\n int transport_disconnect(struct transport *transport)\n {\n \tint ret = 0;\ndiff --git a/transport.h b/transport.h\nindex 781db2e..97ba251 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -64,6 +64,8 @@ struct transport {\n \t **/\n \tint (*push_refs)(struct transport *transport, struct ref *refs, int flags);\n \tint (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);\n+\tint (*connect)(struct transport *connection, const char *name,\n+\t\t       const char *executable, int fd[2]);\n \n \t/** get_refs_list(), fetch(), and push_refs() can keep\n \t * resources (such as a connection) reserved for futher\n@@ -133,6 +135,9 @@ char *transport_anonymize_url(const char *url);\n void transport_take_over(struct transport *transport,\n \t\t\t struct child_process *child);\n \n+int transport_connect(struct transport *transport, const char *name,\n+\t\t      const char *exec, int fd[2]);\n+\n /* Transport methods defined outside transport.c */\n int transport_helper_init(struct transport *transport, const char *name);\n \n-- \n1.6.6.rc1.300.gfbc27\n"},{"id":"129645","messageId":"1260372394-16427-9-git-send-email-ilari.liusvaara@elisanet.fi","threadId":"21917","inReplyTo":"1260372394-16427-1-git-send-email-ilari.liusvaara@elisanet.fi","subject":"[REROLL PATCH v2 8/8] Remove special casing of http, https and ftp","fromName":"Ilari Liusvaara","fromEmail":"ilari.liusvaara@elisanet.fi","sentAt":"2009-12-09T15:26:34Z","receivedAt":"2009-12-09T15:26:34Z","isPatch":true,"sender":{"key":"ilari.liusvaara@elisanet.fi","avatar":null},"body":"HTTP, HTTPS and FTP are no longer special to transport code. Also\nadd support for FTPS (curl supports it so it is easy).\n\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n---\n .gitignore  |    4 ++++\n Makefile    |   27 +++++++++++++++++++++++++--\n transport.c |    8 --------\n 3 files changed, 29 insertions(+), 10 deletions(-)\n\ndiff --git a/.gitignore b/.gitignore\nindex ac02a58..aa7a8ac 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -107,6 +107,10 @@\n /git-relink\n /git-remote\n /git-remote-curl\n+/git-remote-http\n+/git-remote-https\n+/git-remote-ftp\n+/git-remote-ftps\n /git-repack\n /git-replace\n /git-repo-config\ndiff --git a/Makefile b/Makefile\nindex 2ad7e36..87fc7ff 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -424,6 +424,16 @@ BUILT_INS += git-stage$X\n BUILT_INS += git-status$X\n BUILT_INS += git-whatchanged$X\n \n+ifdef NO_CURL\n+REMOTE_CURL_PRIMARY =\n+REMOTE_CURL_ALIASES =\n+REMOTE_CURL_NAMES =\n+else\n+REMOTE_CURL_PRIMARY = git-remote-http$X\n+REMOTE_CURL_ALIASES = git-remote-https$X git-remote-ftp$X git-remote-ftps$X\n+REMOTE_CURL_NAMES = $(REMOTE_CURL_PRIMARY) $(REMOTE_CURL_ALIASES)\n+endif\n+\n # what 'all' will build and 'install' will install in gitexecdir,\n # excluding programs for built-in commands\n ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)\n@@ -1097,7 +1107,7 @@ else\n \telse\n \t\tCURL_LIBCURL = -lcurl\n \tendif\n-\tPROGRAMS += git-remote-curl$X git-http-fetch$X\n+\tPROGRAMS += $(REMOTE_CURL_NAMES) git-http-fetch$X\n \tcurl_check := $(shell (echo 070908; curl-config --vernum) | sort -r | sed -ne 2p)\n \tifeq \"$(curl_check)\" \"070908\"\n \t\tifndef NO_EXPAT\n@@ -1676,7 +1686,13 @@ git-http-push$X: revision.o http.o http-push.o $(GITLIBS)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)\n \n-git-remote-curl$X: remote-curl.o http.o http-walker.o $(GITLIBS)\n+$(REMOTE_CURL_ALIASES): $(REMOTE_CURL_PRIMARY)\n+\t$(QUIET_LNCP)$(RM) $@ && \\\n+\tln $< $@ 2>/dev/null || \\\n+\tln -s $< $@ 2>/dev/null || \\\n+\tcp $< $@\n+\n+$(REMOTE_CURL_PRIMARY): remote-curl.o http.o http-walker.o $(GITLIBS)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)\n \n@@ -1852,6 +1868,7 @@ endif\n ifneq (,$X)\n \t$(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)\n endif\n+\n \tbindir=$$(cd '$(DESTDIR_SQ)$(bindir_SQ)' && pwd) && \\\n \texecdir=$$(cd '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' && pwd) && \\\n \t{ test \"$$bindir/\" = \"$$execdir/\" || \\\n@@ -1865,6 +1882,12 @@ endif\n \t\tln -s \"git$X\" \"$$execdir/$$p\" 2>/dev/null || \\\n \t\tcp \"$$execdir/git$X\" \"$$execdir/$$p\" || exit; \\\n \t  done; } && \\\n+\t{ for p in $(REMOTE_CURL_ALIASES); do \\\n+\t\t$(RM) \"$$execdir/$$p\" && \\\n+\t\tln \"$$execdir/git-remote-http$X\" \"$$execdir/$$p\" 2>/dev/null || \\\n+\t\tln -s \"git-remote-http$X\" \"$$execdir/$$p\" 2>/dev/null || \\\n+\t\tcp \"$$execdir/git-remote-http$X\" \"$$execdir/$$p\" || exit; \\\n+\t  done; } && \\\n \t./check_bindir \"z$$bindir\" \"z$$execdir\" \"$$bindir/git-add$X\"\n \n install-doc:\ndiff --git a/transport.c b/transport.c\nindex a7d67eb..652bf0b 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -918,14 +918,6 @@ struct transport *transport_get(struct remote *remote, const char *url)\n \n \t\tdata->conn = NULL;\n \t\tdata->got_remote_heads = 0;\n-\t} else if (!prefixcmp(url, \"http://\")\n-\t\t|| !prefixcmp(url, \"https://\")\n-\t\t|| !prefixcmp(url, \"ftp://\")) {\n-\t\t/* These three are just plain special. */\n-\t\ttransport_helper_init(ret, \"curl\");\n-#ifdef NO_CURL\n-\t\terror(\"git was compiled without libcurl support.\");\n-#endif\n \t} else {\n \t\t/* Unknown protocol in URL. Pass to external handler. */\n \t\tint len = external_specification_len(url);\n-- \n1.6.6.rc1.300.gfbc27\n"},{"id":"129698","messageId":"20091210155811.GC30665@spearce.org","threadId":"21917","inReplyTo":"1260372394-16427-7-git-send-email-ilari.liusvaara@elisanet.fi","subject":"Re: [REROLL PATCH v2 6/8] Support remote helpers implementing smart transports","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-12-10T15:58:11Z","receivedAt":"2009-12-10T15:58:11Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Ilari Liusvaara <ilari.liusvaara@elisanet.fi> wrote:\n> Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n\nThis is better.\n\n> +\tif (strcmp(name, exec)) {\n> +\t\tr = set_helper_option(transport, \"servpath\", exec);\n> +\t\tif (r > 0)\n> +\t\t\tfprintf(stderr, \"Warning: Setting remote service path \"\n> +\t\t\t\t\"not supported by protocol.\\n\");\n> +\t\telse if (r < 0)\n> +\t\t\tfprintf(stderr, \"Warning: Invalid remote service \"\n> +\t\t\t\t\"path.\\n\");\n\nStyle-nit: We prefer \"warning: \" with lowercase.\n\n-- \nShawn.\n"},{"id":"129699","messageId":"7vvdgekb6a.fsf@alter.siamese.dyndns.org","threadId":"21917","inReplyTo":"20091210155811.GC30665@spearce.org","subject":"Re: [REROLL PATCH v2 6/8] Support remote helpers implementing smart transports","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-12-10T17:10:05Z","receivedAt":"2009-12-10T17:10:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Shawn O. Pearce\" <spearce@spearce.org> writes:\n\n> Ilari Liusvaara <ilari.liusvaara@elisanet.fi> wrote:\n>> Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n>> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n>> Signed-off-by: Ilari Liusvaara <ilari.liusvaara@elisanet.fi>\n>\n> This is better.\n\nIs this sarcasm?\n\nAs far as I can tell, there is no \"Junio received and added some that is\ncopyrightable, and Ilari later received it and worked on it\" anywhere in\nthis series to require recording of patch ping-pong, except perhaps 3/8\nwhere I gave remove_ext_force() with a shorter implementation.  The only\nedit I did myself was to fix style.\n\nI did suggest renaming a few functions but that (1) eventually was done by\nIlari, and (2) isn't large enough change to be copyrightable, I think.\n\n>> +\tif (strcmp(name, exec)) {\n>> +\t\tr = set_helper_option(transport, \"servpath\", exec);\n>> +\t\tif (r > 0)\n>> +\t\t\tfprintf(stderr, \"Warning: Setting remote service path \"\n>> +\t\t\t\t\"not supported by protocol.\\n\");\n>> +\t\telse if (r < 0)\n>> +\t\t\tfprintf(stderr, \"Warning: Invalid remote service \"\n>> +\t\t\t\t\"path.\\n\");\n>\n> Style-nit: We prefer \"warning: \" with lowercase.\n\nAlso this could be a semantic nit; we prefer to call warning(), which is\nmeant to be overridable by the main program (it probably does not matter,\nthough).\n"}]}