{"thread":{"id":"41510","subject":"[PATCH 1/7] run_processes_parallel: treat output of children as byte array","startedAt":"2016-02-25T01:41:58Z","lastAt":"2016-02-25T01:54:12Z","messageCount":10,"participants":["Stefan Beller","Jonathan Nieder"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"279252","messageId":"1456364525-21190-1-git-send-email-sbeller@google.com","threadId":"41510","inReplyTo":null,"subject":"[PATCHv16 0/7] Expose submodule parallelism to the user","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-02-25T01:41:58Z","receivedAt":"2016-02-25T01:41:58Z","isPatch":false,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"This build on top of 163b9b1f919c762a4bfb693b3aa05ef1aa627fee\n(origin/sb/submodule-parallel-update~5) and replace the series from there on.\n\nThanks Jonathan and Junio for discussion!\n\n* instead of sophisticatedly adding \\n's, require the callbacks to be sane.\n* rename strbuf 'err' to 'out'.\n* no changes to the largest patch of introducing a submodule--helper for cloning\n\nThanks,\nStefan\n\nInterdiff to v15:\ndiff --git a/run-command.c b/run-command.c\nindex 8abaae4..6fad42f 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -897,29 +897,29 @@ struct parallel_processes {\n \tstruct pollfd *pfd;\n \n \tunsigned shutdown : 1;\n-\tunsigned ended_with_newline: 1;\n \n \tint output_owner;\n \tstruct strbuf buffered_output; /* of finished children */\n };\n \n int default_start_failure(struct child_process *cp,\n-\t\t\t  struct strbuf *err,\n+\t\t\t  struct strbuf *out,\n \t\t\t  void *pp_cb,\n \t\t\t  void *pp_task_cb)\n {\n \tint i;\n \n-\tstrbuf_addstr(err, \"Starting a child failed:\");\n+\tstrbuf_addstr(out, \"Starting a child failed:\");\n \tfor (i = 0; cp->argv[i]; i++)\n-\t\tstrbuf_addf(err, \" %s\", cp->argv[i]);\n+\t\tstrbuf_addf(out, \" %s\", cp->argv[i]);\n+\tstrbuf_addch(out, '\\n');\n \n \treturn 0;\n }\n \n int default_task_finished(int result,\n \t\t\t  struct child_process *cp,\n-\t\t\t  struct strbuf *err,\n+\t\t\t  struct strbuf *out,\n \t\t\t  void *pp_cb,\n \t\t\t  void *pp_task_cb)\n {\n@@ -928,9 +928,10 @@ int default_task_finished(int result,\n \tif (!result)\n \t\treturn 0;\n \n-\tstrbuf_addf(err, \"A child failed with return code %d:\", result);\n+\tstrbuf_addf(out, \"A child failed with return code %d:\", result);\n \tfor (i = 0; cp->argv[i]; i++)\n-\t\tstrbuf_addf(err, \" %s\", cp->argv[i]);\n+\t\tstrbuf_addf(out, \" %s\", cp->argv[i]);\n+\tstrbuf_addch(out, '\\n');\n \n \treturn 0;\n }\n@@ -980,7 +981,6 @@ static void pp_init(struct parallel_processes *pp,\n \tpp->nr_processes = 0;\n \tpp->output_owner = 0;\n \tpp->shutdown = 0;\n-\tpp->ended_with_newline = 1;\n \tpp->children = xcalloc(n, sizeof(*pp->children));\n \tpp->pfd = xcalloc(n, sizeof(*pp->pfd));\n \tstrbuf_init(&pp->buffered_output, 0);\n@@ -1013,7 +1013,7 @@ static void pp_cleanup(struct parallel_processes *pp)\n \t * When get_next_task added messages to the buffer in its last\n \t * iteration, the buffered output is non empty.\n \t */\n-\tfputs(pp->buffered_output.buf, stderr);\n+\tstrbuf_write(&pp->buffered_output, stderr);\n \tstrbuf_release(&pp->buffered_output);\n \n \tsigchain_pop_common();\n@@ -1055,7 +1055,6 @@ static int pp_start_one(struct parallel_processes *pp)\n \t\t\t\t\t pp->data,\n \t\t\t\t\t &pp->children[i].data);\n \t\tstrbuf_addbuf(&pp->buffered_output, &pp->children[i].err);\n-\t\tstrbuf_addch(&pp->buffered_output, '\\n');\n \t\tstrbuf_reset(&pp->children[i].err);\n \t\tif (code)\n \t\t\tpp->shutdown = 1;\n@@ -1098,11 +1097,9 @@ static void pp_buffer_stderr(struct parallel_processes *pp, int output_timeout)\n static void pp_output(struct parallel_processes *pp)\n {\n \tint i = pp->output_owner;\n-\tsize_t len = pp->children[i].err.len;\n-\tif (pp->children[i].state == GIT_CP_WORKING && len) {\n-\t\tfputs(pp->children[i].err.buf, stderr);\n-\t\tpp->ended_with_newline =\n-\t\t\t(pp->children[i].err.buf[len - 1] == '\\n');\n+\tif (pp->children[i].state == GIT_CP_WORKING &&\n+\t    pp->children[i].err.len) {\n+\t\tstrbuf_write(&pp->children[i].err, stderr);\n \t\tstrbuf_reset(&pp->children[i].err);\n \t}\n }\n@@ -1112,7 +1109,6 @@ static int pp_collect_finished(struct parallel_processes *pp)\n \tint i, code;\n \tint n = pp->max_processes;\n \tint result = 0;\n-\tssize_t len;\n \n \twhile (pp->nr_processes > 0) {\n \t\tfor (i = 0; i < pp->max_processes; i++)\n@@ -1137,22 +1133,15 @@ static int pp_collect_finished(struct parallel_processes *pp)\n \t\tpp->pfd[i].fd = -1;\n \t\tchild_process_init(&pp->children[i].process);\n \n-\t\tlen = pp->children[i].err.len - 1;\n-\t\tif (len >= 0 && pp->children[i].err.buf[len] != '\\n')\n-\t\t\tstrbuf_addch(&pp->children[i].err, '\\n');\n-\n \t\tif (i != pp->output_owner) {\n \t\t\tstrbuf_addbuf(&pp->buffered_output, &pp->children[i].err);\n \t\t\tstrbuf_reset(&pp->children[i].err);\n \t\t} else {\n-\t\t\tif (len == -1 && !pp->ended_with_newline)\n-\t\t\t\tstrbuf_addch(&pp->children[i].err, '\\n');\n-\t\t\tfputs(pp->children[i].err.buf, stderr);\n+\t\t\tstrbuf_write(&pp->children[i].err, stderr);\n \t\t\tstrbuf_reset(&pp->children[i].err);\n-\t\t\tpp->ended_with_newline = 1;\n \n \t\t\t/* Output all other finished child processes */\n-\t\t\tfputs(pp->buffered_output.buf, stderr);\n+\t\t\tstrbuf_write(&pp->buffered_output, stderr);\n \t\t\tstrbuf_reset(&pp->buffered_output);\n \n \t\t\t/*\ndiff --git a/run-command.h b/run-command.h\nindex a054fa6..c8c64bb 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -159,7 +159,7 @@ typedef int (*get_next_task_fn)(struct child_process *cp,\n  * the negative signal number.\n  */\n typedef int (*start_failure_fn)(struct child_process *cp,\n-\t\t\t\tstruct strbuf *err,\n+\t\t\t\tstruct strbuf *out,\n \t\t\t\tvoid *pp_cb,\n \t\t\t\tvoid *pp_task_cb);\n \n@@ -168,7 +168,7 @@ typedef int (*start_failure_fn)(struct child_process *cp,\n  * exact command which failed.\n  */\n int default_start_failure(struct child_process *cp,\n-\t\t\t  struct strbuf *err,\n+\t\t\t  struct strbuf *out,\n \t\t\t  void *pp_cb,\n \t\t\t  void *pp_task_cb);\n \n@@ -188,7 +188,7 @@ int default_start_failure(struct child_process *cp,\n  */\n typedef int (*task_finished_fn)(int result,\n \t\t\t\tstruct child_process *cp,\n-\t\t\t\tstruct strbuf *err,\n+\t\t\t\tstruct strbuf *out,\n \t\t\t\tvoid *pp_cb,\n \t\t\t\tvoid *pp_task_cb);\n \n@@ -198,7 +198,7 @@ typedef int (*task_finished_fn)(int result,\n  */\n int default_task_finished(int result,\n \t\t\t  struct child_process *cp,\n-\t\t\t  struct strbuf *err,\n+\t\t\t  struct strbuf *out,\n \t\t\t  void *pp_cb,\n \t\t\t  void *pp_task_cb);\n \ndiff --git a/strbuf.c b/strbuf.c\nindex 38686ff..71345cd 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -395,6 +395,12 @@ ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint)\n \treturn cnt;\n }\n \n+ssize_t strbuf_write(struct strbuf *sb, FILE *f)\n+{\n+\treturn fwrite(sb->buf, 1, sb->len, f);\n+}\n+\n+\n #define STRBUF_MAXLINK (2*PATH_MAX)\n \n int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)\ndiff --git a/strbuf.h b/strbuf.h\nindex 2bf90e7..d4f2aa1 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -387,6 +387,12 @@ extern ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint\n extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);\n \n /**\n+ * Write the whole content of the strbuf to the stream not stopping at\n+ * NUL bytes.\n+ */\n+extern ssize_t strbuf_write(struct strbuf *sb, FILE *stream);\n+\n+/**\n  * Read a line from a FILE *, overwriting the existing contents\n  * of the strbuf. The second argument specifies the line\n  * terminator character, typically `'\\n'`.\ndiff --git a/submodule.c b/submodule.c\nindex 8fbc847..051f722 100644\n--- a/submodule.c\n+++ b/submodule.c\n@@ -219,7 +219,7 @@ void gitmodules_config(void)\n int parse_submodule_update_strategy(const char *value,\n \t\tstruct submodule_update_strategy *dst)\n {\n-\tfree((void *)dst->command);\n+\tfree((void*)dst->command);\n \tdst->command = NULL;\n \tif (!strcmp(value, \"none\"))\n \t\tdst->type = SM_UPDATE_NONE;\ndiff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh\nindex 5c6822c..12228b4 100755\n--- a/t/t0061-run-command.sh\n+++ b/t/t0061-run-command.sh\n@@ -77,32 +77,6 @@ test_expect_success 'run_command runs in parallel with more tasks than jobs avai\n \ttest_cmp expect actual\n '\n \n-test_expect_success 'run_command ensures each command ends in LF' '\n-\ttest-run-command run-command-parallel 3 sh -c \"printf \\\"%s\\n%s\\\" Hello World\" 2>actual &&\n-\ttest_cmp expect actual\n-'\n-\n-cat >expect <<-EOF\n-preloaded output of a child\n-preloaded output of a child\n-preloaded output of a child\n-preloaded output of a child\n-EOF\n-\n-test_expect_success 'run_command ensures each command ends in LF when output is only in starting cb' '\n-\ttest-run-command run-command-parallel 3 sh -c true  2>actual &&\n-\ttest_cmp expect actual\n-'\n-\n-cat >expect <<-EOF\n-EOF\n-\n-test_expect_success 'run_command ensures each command ends in LF except when there is no output' '\n-\ttest-run-command run-command-parallel-silent 3 sh -c true  2>actual &&\n-\ttest_cmp expect actual\n-'\n-\n-\n cat >expect <<-EOF\n preloaded output of a child\n asking for a quick stop\ndiff --git a/test-run-command.c b/test-run-command.c\nindex ca1ee97..fbe0a27 100644\n--- a/test-run-command.c\n+++ b/test-run-command.c\n@@ -31,20 +31,6 @@ static int parallel_next(struct child_process *cp,\n \treturn 1;\n }\n \n-static int parallel_next_silent(struct child_process *cp,\n-\t\t\t struct strbuf *err,\n-\t\t\t void *cb,\n-\t\t\t void **task_cb)\n-{\n-\tstruct child_process *d = cb;\n-\tif (number_callbacks >= 4)\n-\t\treturn 0;\n-\n-\targv_array_pushv(&cp->args, d->argv);\n-\tnumber_callbacks++;\n-\treturn 1;\n-}\n-\n static int no_job(struct child_process *cp,\n \t\t  struct strbuf *err,\n \t\t  void *cb,\n@@ -85,10 +71,6 @@ int main(int argc, char **argv)\n \tjobs = atoi(argv[2]);\n \tproc.argv = (const char **)argv + 3;\n \n-\tif (!strcmp(argv[1], \"run-command-parallel-silent\"))\n-\t\texit(run_processes_parallel(jobs, parallel_next_silent,\n-\t\t\t\t\t    NULL, NULL, &proc));\n-\n \tif (!strcmp(argv[1], \"run-command-parallel\"))\n \t\texit(run_processes_parallel(jobs, parallel_next,\n \t\t\t\t\t    NULL, NULL, &proc));\n\nStefan Beller (7):\n  run_processes_parallel: treat output of children as byte array\n  run-command: expose default_{start_failure, task_finished}\n  run_processes_parallel: rename parameters for the callbacks\n  run_processes_parallel: correctly terminate callbacks with an LF\n  git submodule update: have a dedicated helper for cloning\n  submodule update: expose parallelism to the user\n  clone: allow an explicit argument for parallel submodule clones\n\n Documentation/git-clone.txt     |   6 +-\n Documentation/git-submodule.txt |   7 +-\n builtin/clone.c                 |  19 ++-\n builtin/submodule--helper.c     | 251 ++++++++++++++++++++++++++++++++++++++++\n git-submodule.sh                |  56 ++++-----\n run-command.c                   |  36 +++---\n run-command.h                   |  23 +++-\n strbuf.c                        |   6 +\n strbuf.h                        |   6 +\n t/t7406-submodule-update.sh     |  27 +++++\n 10 files changed, 376 insertions(+), 61 deletions(-)\n\n-- \n2.7.2.335.g3f96d05\n"},{"id":"279249","messageId":"1456364525-21190-2-git-send-email-sbeller@google.com","threadId":"41510","inReplyTo":"1456364525-21190-1-git-send-email-sbeller@google.com","subject":"[PATCH 1/7] run_processes_parallel: treat output of children as byte array","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-02-25T01:41:59Z","receivedAt":"2016-02-25T01:41:59Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"We do not want the output to be interrupted by a NUL byte, so we\ncannot use raw fputs. Introduce strbuf_write to avoid having long\narguments in run-command.c.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n run-command.c | 8 ++++----\n strbuf.c      | 6 ++++++\n strbuf.h      | 6 ++++++\n 3 files changed, 16 insertions(+), 4 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex 51fd72c..1f86728 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -1011,7 +1011,7 @@ static void pp_cleanup(struct parallel_processes *pp)\n \t * When get_next_task added messages to the buffer in its last\n \t * iteration, the buffered output is non empty.\n \t */\n-\tfputs(pp->buffered_output.buf, stderr);\n+\tstrbuf_write(&pp->buffered_output, stderr);\n \tstrbuf_release(&pp->buffered_output);\n \n \tsigchain_pop_common();\n@@ -1097,7 +1097,7 @@ static void pp_output(struct parallel_processes *pp)\n \tint i = pp->output_owner;\n \tif (pp->children[i].state == GIT_CP_WORKING &&\n \t    pp->children[i].err.len) {\n-\t\tfputs(pp->children[i].err.buf, stderr);\n+\t\tstrbuf_write(&pp->children[i].err, stderr);\n \t\tstrbuf_reset(&pp->children[i].err);\n \t}\n }\n@@ -1135,11 +1135,11 @@ static int pp_collect_finished(struct parallel_processes *pp)\n \t\t\tstrbuf_addbuf(&pp->buffered_output, &pp->children[i].err);\n \t\t\tstrbuf_reset(&pp->children[i].err);\n \t\t} else {\n-\t\t\tfputs(pp->children[i].err.buf, stderr);\n+\t\t\tstrbuf_write(&pp->children[i].err, stderr);\n \t\t\tstrbuf_reset(&pp->children[i].err);\n \n \t\t\t/* Output all other finished child processes */\n-\t\t\tfputs(pp->buffered_output.buf, stderr);\n+\t\t\tstrbuf_write(&pp->buffered_output, stderr);\n \t\t\tstrbuf_reset(&pp->buffered_output);\n \n \t\t\t/*\ndiff --git a/strbuf.c b/strbuf.c\nindex 38686ff..71345cd 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -395,6 +395,12 @@ ssize_t strbuf_read_once(struct strbuf *sb, int fd, size_t hint)\n \treturn cnt;\n }\n \n+ssize_t strbuf_write(struct strbuf *sb, FILE *f)\n+{\n+\treturn fwrite(sb->buf, 1, sb->len, f);\n+}\n+\n+\n #define STRBUF_MAXLINK (2*PATH_MAX)\n \n int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint)\ndiff --git a/strbuf.h b/strbuf.h\nindex 2bf90e7..d4f2aa1 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -387,6 +387,12 @@ extern ssize_t strbuf_read_file(struct strbuf *sb, const char *path, size_t hint\n extern int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint);\n \n /**\n+ * Write the whole content of the strbuf to the stream not stopping at\n+ * NUL bytes.\n+ */\n+extern ssize_t strbuf_write(struct strbuf *sb, FILE *stream);\n+\n+/**\n  * Read a line from a FILE *, overwriting the existing contents\n  * of the strbuf. The second argument specifies the line\n  * terminator character, typically `'\\n'`.\n-- \n2.7.2.335.g3f96d05\n"},{"id":"279250","messageId":"1456364525-21190-3-git-send-email-sbeller@google.com","threadId":"41510","inReplyTo":"1456364525-21190-1-git-send-email-sbeller@google.com","subject":"[PATCH 2/7] run-command: expose default_{start_failure, task_finished}","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-02-25T01:42:00Z","receivedAt":"2016-02-25T01:42:00Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"We want to reuse the error reporting facilities in a later patch.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n run-command.c | 18 +++++++++---------\n run-command.h | 19 +++++++++++++++++++\n 2 files changed, 28 insertions(+), 9 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex 1f86728..71abafb 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -902,10 +902,10 @@ struct parallel_processes {\n \tstruct strbuf buffered_output; /* of finished children */\n };\n \n-static int default_start_failure(struct child_process *cp,\n-\t\t\t\t struct strbuf *err,\n-\t\t\t\t void *pp_cb,\n-\t\t\t\t void *pp_task_cb)\n+int default_start_failure(struct child_process *cp,\n+\t\t\t  struct strbuf *err,\n+\t\t\t  void *pp_cb,\n+\t\t\t  void *pp_task_cb)\n {\n \tint i;\n \n@@ -916,11 +916,11 @@ static int default_start_failure(struct child_process *cp,\n \treturn 0;\n }\n \n-static int default_task_finished(int result,\n-\t\t\t\t struct child_process *cp,\n-\t\t\t\t struct strbuf *err,\n-\t\t\t\t void *pp_cb,\n-\t\t\t\t void *pp_task_cb)\n+int default_task_finished(int result,\n+\t\t\t  struct child_process *cp,\n+\t\t\t  struct strbuf *err,\n+\t\t\t  void *pp_cb,\n+\t\t\t  void *pp_task_cb)\n {\n \tint i;\n \ndiff --git a/run-command.h b/run-command.h\nindex d5a57f9..a054fa6 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -164,6 +164,15 @@ typedef int (*start_failure_fn)(struct child_process *cp,\n \t\t\t\tvoid *pp_task_cb);\n \n /**\n+ * If a command fails to start, then print an error message stating the\n+ * exact command which failed.\n+ */\n+int default_start_failure(struct child_process *cp,\n+\t\t\t  struct strbuf *err,\n+\t\t\t  void *pp_cb,\n+\t\t\t  void *pp_task_cb);\n+\n+/**\n  * This callback is called on every child process that finished processing.\n  *\n  * You must not write to stdout or stderr in this function. Add your\n@@ -184,6 +193,16 @@ typedef int (*task_finished_fn)(int result,\n \t\t\t\tvoid *pp_task_cb);\n \n /**\n+ * If the child process returns with a non zero error code, print\n+ * an error message of the exact command which failed.\n+ */\n+int default_task_finished(int result,\n+\t\t\t  struct child_process *cp,\n+\t\t\t  struct strbuf *err,\n+\t\t\t  void *pp_cb,\n+\t\t\t  void *pp_task_cb);\n+\n+/**\n  * Runs up to n processes at the same time. Whenever a process can be\n  * started, the callback get_next_task_fn is called to obtain the data\n  * required to start another child process.\n-- \n2.7.2.335.g3f96d05\n"},{"id":"279253","messageId":"1456364525-21190-4-git-send-email-sbeller@google.com","threadId":"41510","inReplyTo":"1456364525-21190-1-git-send-email-sbeller@google.com","subject":"[PATCH 3/7] run_processes_parallel: rename parameters for the callbacks","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-02-25T01:42:01Z","receivedAt":"2016-02-25T01:42:01Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"The refs code has a similar pattern of passing around 'struct strbuf *err',\nwhich is strictly used for error reporting. This is not the case here,\nas the strbuf is used to accumulate all the output (whether it is error\nor not) for the user. Rename it to 'out'.\n\nSuggested-by: Jonathan Nieder <jrnieder@gmail.com>\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n run-command.c | 12 ++++++------\n run-command.h |  8 ++++----\n 2 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/run-command.c b/run-command.c\nindex 71abafb..20489c8 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -903,22 +903,22 @@ struct parallel_processes {\n };\n \n int default_start_failure(struct child_process *cp,\n-\t\t\t  struct strbuf *err,\n+\t\t\t  struct strbuf *out,\n \t\t\t  void *pp_cb,\n \t\t\t  void *pp_task_cb)\n {\n \tint i;\n \n-\tstrbuf_addstr(err, \"Starting a child failed:\");\n+\tstrbuf_addstr(out, \"Starting a child failed:\");\n \tfor (i = 0; cp->argv[i]; i++)\n-\t\tstrbuf_addf(err, \" %s\", cp->argv[i]);\n+\t\tstrbuf_addf(out, \" %s\", cp->argv[i]);\n \n \treturn 0;\n }\n \n int default_task_finished(int result,\n \t\t\t  struct child_process *cp,\n-\t\t\t  struct strbuf *err,\n+\t\t\t  struct strbuf *out,\n \t\t\t  void *pp_cb,\n \t\t\t  void *pp_task_cb)\n {\n@@ -927,9 +927,9 @@ int default_task_finished(int result,\n \tif (!result)\n \t\treturn 0;\n \n-\tstrbuf_addf(err, \"A child failed with return code %d:\", result);\n+\tstrbuf_addf(out, \"A child failed with return code %d:\", result);\n \tfor (i = 0; cp->argv[i]; i++)\n-\t\tstrbuf_addf(err, \" %s\", cp->argv[i]);\n+\t\tstrbuf_addf(out, \" %s\", cp->argv[i]);\n \n \treturn 0;\n }\ndiff --git a/run-command.h b/run-command.h\nindex a054fa6..c8c64bb 100644\n--- a/run-command.h\n+++ b/run-command.h\n@@ -159,7 +159,7 @@ typedef int (*get_next_task_fn)(struct child_process *cp,\n  * the negative signal number.\n  */\n typedef int (*start_failure_fn)(struct child_process *cp,\n-\t\t\t\tstruct strbuf *err,\n+\t\t\t\tstruct strbuf *out,\n \t\t\t\tvoid *pp_cb,\n \t\t\t\tvoid *pp_task_cb);\n \n@@ -168,7 +168,7 @@ typedef int (*start_failure_fn)(struct child_process *cp,\n  * exact command which failed.\n  */\n int default_start_failure(struct child_process *cp,\n-\t\t\t  struct strbuf *err,\n+\t\t\t  struct strbuf *out,\n \t\t\t  void *pp_cb,\n \t\t\t  void *pp_task_cb);\n \n@@ -188,7 +188,7 @@ int default_start_failure(struct child_process *cp,\n  */\n typedef int (*task_finished_fn)(int result,\n \t\t\t\tstruct child_process *cp,\n-\t\t\t\tstruct strbuf *err,\n+\t\t\t\tstruct strbuf *out,\n \t\t\t\tvoid *pp_cb,\n \t\t\t\tvoid *pp_task_cb);\n \n@@ -198,7 +198,7 @@ typedef int (*task_finished_fn)(int result,\n  */\n int default_task_finished(int result,\n \t\t\t  struct child_process *cp,\n-\t\t\t  struct strbuf *err,\n+\t\t\t  struct strbuf *out,\n \t\t\t  void *pp_cb,\n \t\t\t  void *pp_task_cb);\n \n-- \n2.7.2.335.g3f96d05\n"},{"id":"279251","messageId":"1456364525-21190-5-git-send-email-sbeller@google.com","threadId":"41510","inReplyTo":"1456364525-21190-1-git-send-email-sbeller@google.com","subject":"[PATCH 4/7] run_processes_parallel: correctly terminate callbacks with an LF","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-02-25T01:42:02Z","receivedAt":"2016-02-25T01:42:02Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"As the strbufs passed around collect all output to the user, and there\nis no post processing involved we need to care about the line ending\nourselves.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\n---\n run-command.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/run-command.c b/run-command.c\nindex 20489c8..8e60812 100644\n--- a/run-command.c\n+++ b/run-command.c\n@@ -912,6 +912,7 @@ int default_start_failure(struct child_process *cp,\n \tstrbuf_addstr(out, \"Starting a child failed:\");\n \tfor (i = 0; cp->argv[i]; i++)\n \t\tstrbuf_addf(out, \" %s\", cp->argv[i]);\n+\tstrbuf_addch(out, '\\n');\n \n \treturn 0;\n }\n@@ -930,6 +931,7 @@ int default_task_finished(int result,\n \tstrbuf_addf(out, \"A child failed with return code %d:\", result);\n \tfor (i = 0; cp->argv[i]; i++)\n \t\tstrbuf_addf(out, \" %s\", cp->argv[i]);\n+\tstrbuf_addch(out, '\\n');\n \n \treturn 0;\n }\n-- \n2.7.2.335.g3f96d05\n"},{"id":"279256","messageId":"1456364525-21190-6-git-send-email-sbeller@google.com","threadId":"41510","inReplyTo":"1456364525-21190-1-git-send-email-sbeller@google.com","subject":"[PATCH 5/7] git submodule update: have a dedicated helper for cloning","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-02-25T01:42:03Z","receivedAt":"2016-02-25T01:42:03Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"This introduces a new helper function in git submodule--helper\nwhich takes care of cloning all submodules, which we want to\nparallelize eventually.\n\nSome tests (such as empty URL, update_mode=none) are required in the\nhelper to make the decision for cloning. These checks have been\nmoved into the C function as well (no need to repeat them in the\nshell script).\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/submodule--helper.c | 243 ++++++++++++++++++++++++++++++++++++++++++++\n git-submodule.sh            |  47 +++------\n 2 files changed, 256 insertions(+), 34 deletions(-)\n\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex f4c3eff..85fb702 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -255,6 +255,248 @@ static int module_clone(int argc, const char **argv, const char *prefix)\n \treturn 0;\n }\n \n+struct submodule_update_clone {\n+\t/* index into 'list', the list of submodules to look into for cloning */\n+\tint current;\n+\tstruct module_list list;\n+\tint warn_if_uninitialized : 1;\n+\t/* update parameter passed via commandline*/\n+\tstruct submodule_update_strategy update;\n+\t/* configuration parameters which are passed on to the children */\n+\tint quiet;\n+\tconst char *reference;\n+\tconst char *depth;\n+\tconst char *recursive_prefix;\n+\tconst char *prefix;\n+\t/* lines to be output */\n+\tstruct string_list projectlines;\n+\t/* If we want to stop as fast as possible and return an error */\n+\tint quickstop : 1;\n+};\n+#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \\\n+\tSUBMODULE_UPDATE_STRATEGY_INIT, 0, NULL, NULL, NULL, NULL, \\\n+\tSTRING_LIST_INIT_DUP, 0}\n+\n+/**\n+ * Inspect if 'ce' needs to be cloned. If so, prepare the 'child' to be running\n+ * the clone and return non zero.\n+ */\n+static int prepare_to_clone_next_submodule(const struct cache_entry *ce,\n+\t\t\t\t\t   struct child_process *child,\n+\t\t\t\t\t   struct submodule_update_clone *suc,\n+\t\t\t\t\t   struct strbuf *err)\n+{\n+\tconst struct submodule *sub = NULL;\n+\tstruct strbuf displaypath_sb = STRBUF_INIT;\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tconst char *displaypath = NULL;\n+\tchar *url = NULL;\n+\tint needs_cloning = 0;\n+\n+\tif (ce_stage(ce)) {\n+\t\tif (suc->recursive_prefix) {\n+\t\t\tstrbuf_addf(err, \"Skipping unmerged submodule %s/%s\\n\",\n+\t\t\t\t    suc->recursive_prefix, ce->name);\n+\t\t} else {\n+\t\t\tstrbuf_addf(err, \"Skipping unmerged submodule %s\\n\",\n+\t\t\t\t    ce->name);\n+\t\t}\n+\t\tgoto cleanup;\n+\t}\n+\n+\tsub = submodule_from_path(null_sha1, ce->name);\n+\n+\tif (suc->recursive_prefix)\n+\t\tdisplaypath = relative_path(suc->recursive_prefix,\n+\t\t\t\t\t    ce->name, &displaypath_sb);\n+\telse\n+\t\tdisplaypath = ce->name;\n+\n+\tif (suc->update.type == SM_UPDATE_NONE\n+\t    || (suc->update.type == SM_UPDATE_UNSPECIFIED\n+\t\t&& sub->update_strategy.type == SM_UPDATE_NONE)) {\n+\t\tstrbuf_addf(err, \"Skipping submodule '%s'\\n\",\n+\t\t\t    displaypath);\n+\t\tgoto cleanup;\n+\t}\n+\n+\t/*\n+\t * Looking up the url in .git/config.\n+\t * We must not fall back to .gitmodules as we only want\n+\t * to process configured submodules.\n+\t */\n+\tstrbuf_reset(&sb);\n+\tstrbuf_addf(&sb, \"submodule.%s.url\", sub->name);\n+\tgit_config_get_string(sb.buf, &url);\n+\tif (!url) {\n+\t\t/*\n+\t\t * Only mention uninitialized submodules when its\n+\t\t * path have been specified\n+\t\t */\n+\t\tif (suc->warn_if_uninitialized)\n+\t\t\tstrbuf_addf(err, _(\"Submodule path '%s' not initialized\\n\"\n+\t\t\t\t    \"Maybe you want to use 'update --init'?\\n\"),\n+\t\t\t\t    displaypath);\n+\t\tgoto cleanup;\n+\t}\n+\n+\tstrbuf_reset(&sb);\n+\tstrbuf_addf(&sb, \"%s/.git\", ce->name);\n+\tneeds_cloning = !file_exists(sb.buf);\n+\n+\tstrbuf_reset(&sb);\n+\tstrbuf_addf(&sb, \"%06o %s %d %d\\t%s\\n\", ce->ce_mode,\n+\t\t\tsha1_to_hex(ce->sha1), ce_stage(ce),\n+\t\t\tneeds_cloning, ce->name);\n+\tstring_list_append(&suc->projectlines, sb.buf);\n+\n+\tif (!needs_cloning)\n+\t\tgoto cleanup;\n+\n+\tchild->git_cmd = 1;\n+\tchild->no_stdin = 1;\n+\tchild->stdout_to_stderr = 1;\n+\tchild->err = -1;\n+\targv_array_push(&child->args, \"submodule--helper\");\n+\targv_array_push(&child->args, \"clone\");\n+\tif (suc->quiet)\n+\t\targv_array_push(&child->args, \"--quiet\");\n+\tif (suc->prefix)\n+\t\targv_array_pushl(&child->args, \"--prefix\", suc->prefix, NULL);\n+\targv_array_pushl(&child->args, \"--path\", sub->path, NULL);\n+\targv_array_pushl(&child->args, \"--name\", sub->name, NULL);\n+\targv_array_pushl(&child->args, \"--url\", url, NULL);\n+\tif (suc->reference)\n+\t\targv_array_push(&child->args, suc->reference);\n+\tif (suc->depth)\n+\t\targv_array_push(&child->args, suc->depth);\n+\n+cleanup:\n+\tfree(url);\n+\tstrbuf_reset(&displaypath_sb);\n+\tstrbuf_reset(&sb);\n+\n+\treturn needs_cloning;\n+}\n+\n+static int update_clone_get_next_task(struct child_process *child,\n+\t\t\t\t      struct strbuf *err,\n+\t\t\t\t      void *suc_cb,\n+\t\t\t\t      void **void_task_cb)\n+{\n+\tstruct submodule_update_clone *suc = suc_cb;\n+\n+\tfor (; suc->current < suc->list.nr; suc->current++) {\n+\t\tconst struct cache_entry *ce = suc->list.entries[suc->current];\n+\t\tif (prepare_to_clone_next_submodule(ce, child, suc, err)) {\n+\t\t\tsuc->current++;\n+\t\t\treturn 1;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+static int update_clone_start_failure(struct child_process *child,\n+\t\t\t\t      struct strbuf *err,\n+\t\t\t\t      void *suc_cb,\n+\t\t\t\t      void *void_task_cb)\n+{\n+\tstruct submodule_update_clone *suc = suc_cb;\n+\n+\tdefault_start_failure(child, err, suc_cb, void_task_cb);\n+\tsuc->quickstop = 1;\n+\n+\treturn 1;\n+}\n+\n+static int update_clone_task_finished(int result,\n+\t\t\t\t      struct child_process *child,\n+\t\t\t\t      struct strbuf *err,\n+\t\t\t\t      void *suc_cb,\n+\t\t\t\t      void *void_task_cb)\n+{\n+\tstruct submodule_update_clone *suc = suc_cb;\n+\n+\tif (!result)\n+\t\treturn 0;\n+\n+\tdefault_task_finished(result, child, err, suc_cb, void_task_cb);\n+\tsuc->quickstop = 1;\n+\n+\treturn 1;\n+}\n+\n+static int update_clone(int argc, const char **argv, const char *prefix)\n+{\n+\tconst char *update = NULL;\n+\tstruct string_list_item *item;\n+\tstruct pathspec pathspec;\n+\tstruct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;\n+\n+\tstruct option module_update_clone_options[] = {\n+\t\tOPT_STRING(0, \"prefix\", &prefix,\n+\t\t\t   N_(\"path\"),\n+\t\t\t   N_(\"path into the working tree\")),\n+\t\tOPT_STRING(0, \"recursive-prefix\", &suc.recursive_prefix,\n+\t\t\t   N_(\"path\"),\n+\t\t\t   N_(\"path into the working tree, across nested \"\n+\t\t\t      \"submodule boundaries\")),\n+\t\tOPT_STRING(0, \"update\", &update,\n+\t\t\t   N_(\"string\"),\n+\t\t\t   N_(\"rebase, merge, checkout or none\")),\n+\t\tOPT_STRING(0, \"reference\", &suc.reference, N_(\"repo\"),\n+\t\t\t   N_(\"reference repository\")),\n+\t\tOPT_STRING(0, \"depth\", &suc.depth, \"<depth>\",\n+\t\t\t   N_(\"Create a shallow clone truncated to the \"\n+\t\t\t      \"specified number of revisions\")),\n+\t\tOPT__QUIET(&suc.quiet, N_(\"don't print cloning progress\")),\n+\t\tOPT_END()\n+\t};\n+\n+\tconst char *const git_submodule_helper_usage[] = {\n+\t\tN_(\"git submodule--helper update_clone [--prefix=<path>] [<path>...]\"),\n+\t\tNULL\n+\t};\n+\tsuc.prefix = prefix;\n+\n+\targc = parse_options(argc, argv, prefix, module_update_clone_options,\n+\t\t\t     git_submodule_helper_usage, 0);\n+\n+\tif (update)\n+\t\tif (parse_submodule_update_strategy(update, &suc.update) < 0)\n+\t\t\tdie(_(\"bad value for update parameter\"));\n+\n+\tif (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0)\n+\t\treturn 1;\n+\n+\tif (pathspec.nr)\n+\t\tsuc.warn_if_uninitialized = 1;\n+\n+\tgitmodules_config();\n+\t/* Overlay the parsed .gitmodules file with .git/config */\n+\tgit_config(submodule_config, NULL);\n+\trun_processes_parallel(1, update_clone_get_next_task,\n+\t\t\t\t  update_clone_start_failure,\n+\t\t\t\t  update_clone_task_finished,\n+\t\t\t\t  &suc);\n+\n+\t/*\n+\t * We saved the output and put it out all at once now.\n+\t * That means:\n+\t * - the listener does not have to interleave their (checkout)\n+\t *   work with our fetching.  The writes involved in a\n+\t *   checkout involve more straightforward sequential I/O.\n+\t * - the listener can avoid doing any work if fetching failed.\n+\t */\n+\tif (suc.quickstop)\n+\t\treturn 1;\n+\n+\tfor_each_string_list_item(item, &suc.projectlines)\n+\t\tutf8_fprintf(stdout, \"%s\", item->string);\n+\n+\treturn 0;\n+}\n+\n struct cmd_struct {\n \tconst char *cmd;\n \tint (*fn)(int, const char **, const char *);\n@@ -264,6 +506,7 @@ static struct cmd_struct commands[] = {\n \t{\"list\", module_list},\n \t{\"name\", module_name},\n \t{\"clone\", module_clone},\n+\t{\"update-clone\", update_clone}\n };\n \n int cmd_submodule__helper(int argc, const char **argv, const char *prefix)\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex 9ee86d4..a6a82d2 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -664,17 +664,20 @@ cmd_update()\n \t\tcmd_init \"--\" \"$@\" || return\n \tfi\n \n-\tcloned_modules=\n-\tgit submodule--helper list --prefix \"$wt_prefix\" \"$@\" | {\n+\t{\n+\tgit submodule--helper update-clone ${GIT_QUIET:+--quiet} \\\n+\t\t${wt_prefix:+--prefix \"$wt_prefix\"} \\\n+\t\t${prefix:+--recursive-prefix \"$prefix\"} \\\n+\t\t${update:+--update \"$update\"} \\\n+\t\t${reference:+--reference \"$reference\"} \\\n+\t\t${depth:+--depth \"$depth\"} \\\n+\t\t\"$@\" || echo \"#unmatched\"\n+\t} | {\n \terr=\n-\twhile read mode sha1 stage sm_path\n+\twhile read mode sha1 stage just_cloned sm_path\n \tdo\n \t\tdie_if_unmatched \"$mode\"\n-\t\tif test \"$stage\" = U\n-\t\tthen\n-\t\t\techo >&2 \"Skipping unmerged submodule $prefix$sm_path\"\n-\t\t\tcontinue\n-\t\tfi\n+\n \t\tname=$(git submodule--helper name \"$sm_path\") || exit\n \t\turl=$(git config submodule.\"$name\".url)\n \t\tbranch=$(get_submodule_config \"$name\" branch master)\n@@ -691,27 +694,10 @@ cmd_update()\n \n \t\tdisplaypath=$(relative_path \"$prefix$sm_path\")\n \n-\t\tif test \"$update_module\" = \"none\"\n-\t\tthen\n-\t\t\techo >&2 \"Skipping submodule '$displaypath'\"\n-\t\t\tcontinue\n-\t\tfi\n-\n-\t\tif test -z \"$url\"\n-\t\tthen\n-\t\t\t# Only mention uninitialized submodules when its\n-\t\t\t# path have been specified\n-\t\t\ttest \"$#\" != \"0\" &&\n-\t\t\tsay >&2 \"$(eval_gettext \"Submodule path '\\$displaypath' not initialized\n-Maybe you want to use 'update --init'?\")\"\n-\t\t\tcontinue\n-\t\tfi\n-\n-\t\tif ! test -d \"$sm_path\"/.git && ! test -f \"$sm_path\"/.git\n+\t\tif test $just_cloned -eq 1\n \t\tthen\n-\t\t\tgit submodule--helper clone ${GIT_QUIET:+--quiet} --prefix \"$prefix\" --path \"$sm_path\" --name \"$name\" --url \"$url\" \"$reference\" \"$depth\" || exit\n-\t\t\tcloned_modules=\"$cloned_modules;$name\"\n \t\t\tsubsha1=\n+\t\t\tupdate_module=checkout\n \t\telse\n \t\t\tsubsha1=$(clear_local_git_env; cd \"$sm_path\" &&\n \t\t\t\tgit rev-parse --verify HEAD) ||\n@@ -751,13 +737,6 @@ Maybe you want to use 'update --init'?\")\"\n \t\t\t\tdie \"$(eval_gettext \"Unable to fetch in submodule path '\\$displaypath'\")\"\n \t\t\tfi\n \n-\t\t\t# Is this something we just cloned?\n-\t\t\tcase \";$cloned_modules;\" in\n-\t\t\t*\";$name;\"*)\n-\t\t\t\t# then there is no local change to integrate\n-\t\t\t\tupdate_module=checkout ;;\n-\t\t\tesac\n-\n \t\t\tmust_die_on_failure=\n \t\t\tcase \"$update_module\" in\n \t\t\tcheckout)\n-- \n2.7.2.335.g3f96d05\n"},{"id":"279255","messageId":"1456364525-21190-7-git-send-email-sbeller@google.com","threadId":"41510","inReplyTo":"1456364525-21190-1-git-send-email-sbeller@google.com","subject":"[PATCH 6/7] submodule update: expose parallelism to the user","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-02-25T01:42:04Z","receivedAt":"2016-02-25T01:42:04Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Expose possible parallelism either via the \"--jobs\" CLI parameter or\nthe \"submodule.fetchJobs\" setting.\n\nBy having the variable initialized to -1, we make sure 0 can be passed\ninto the parallel processing machine, which will then pick as many parallel\nworkers as there are CPUs.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-submodule.txt |  7 ++++++-\n builtin/submodule--helper.c     | 16 ++++++++++++----\n git-submodule.sh                |  9 +++++++++\n t/t7406-submodule-update.sh     | 12 ++++++++++++\n 4 files changed, 39 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt\nindex 1572f05..13adebf 100644\n--- a/Documentation/git-submodule.txt\n+++ b/Documentation/git-submodule.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n 'git submodule' [--quiet] deinit [-f|--force] [--] <path>...\n 'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch]\n \t      [-f|--force] [--rebase|--merge] [--reference <repository>]\n-\t      [--depth <depth>] [--recursive] [--] [<path>...]\n+\t      [--depth <depth>] [--recursive] [--jobs <n>] [--] [<path>...]\n 'git submodule' [--quiet] summary [--cached|--files] [(-n|--summary-limit) <n>]\n \t      [commit] [--] [<path>...]\n 'git submodule' [--quiet] foreach [--recursive] <command>\n@@ -377,6 +377,11 @@ for linkgit:git-clone[1]'s `--reference` and `--shared` options carefully.\n \tclone with a history truncated to the specified number of revisions.\n \tSee linkgit:git-clone[1]\n \n+-j <n>::\n+--jobs <n>::\n+\tThis option is only valid for the update command.\n+\tClone new submodules in parallel with as many jobs.\n+\tDefaults to the `submodule.fetchJobs` option.\n \n <path>...::\n \tPaths to submodule(s). When specified this will restrict the command\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 85fb702..32254cd 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -429,6 +429,7 @@ static int update_clone_task_finished(int result,\n static int update_clone(int argc, const char **argv, const char *prefix)\n {\n \tconst char *update = NULL;\n+\tint max_jobs = -1;\n \tstruct string_list_item *item;\n \tstruct pathspec pathspec;\n \tstruct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT;\n@@ -449,6 +450,8 @@ static int update_clone(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"depth\", &suc.depth, \"<depth>\",\n \t\t\t   N_(\"Create a shallow clone truncated to the \"\n \t\t\t      \"specified number of revisions\")),\n+\t\tOPT_INTEGER('j', \"jobs\", &max_jobs,\n+\t\t\t    N_(\"parallel jobs\")),\n \t\tOPT__QUIET(&suc.quiet, N_(\"don't print cloning progress\")),\n \t\tOPT_END()\n \t};\n@@ -475,10 +478,15 @@ static int update_clone(int argc, const char **argv, const char *prefix)\n \tgitmodules_config();\n \t/* Overlay the parsed .gitmodules file with .git/config */\n \tgit_config(submodule_config, NULL);\n-\trun_processes_parallel(1, update_clone_get_next_task,\n-\t\t\t\t  update_clone_start_failure,\n-\t\t\t\t  update_clone_task_finished,\n-\t\t\t\t  &suc);\n+\n+\tif (max_jobs < 0)\n+\t\tmax_jobs = parallel_submodules();\n+\n+\trun_processes_parallel(max_jobs,\n+\t\t\t       update_clone_get_next_task,\n+\t\t\t       update_clone_start_failure,\n+\t\t\t       update_clone_task_finished,\n+\t\t\t       &suc);\n \n \t/*\n \t * We saved the output and put it out all at once now.\ndiff --git a/git-submodule.sh b/git-submodule.sh\nindex a6a82d2..86018ee 100755\n--- a/git-submodule.sh\n+++ b/git-submodule.sh\n@@ -645,6 +645,14 @@ cmd_update()\n \t\t--depth=*)\n \t\t\tdepth=$1\n \t\t\t;;\n+\t\t-j|--jobs)\n+\t\t\tcase \"$2\" in '') usage ;; esac\n+\t\t\tjobs=\"--jobs=$2\"\n+\t\t\tshift\n+\t\t\t;;\n+\t\t--jobs=*)\n+\t\t\tjobs=$1\n+\t\t\t;;\n \t\t--)\n \t\t\tshift\n \t\t\tbreak\n@@ -671,6 +679,7 @@ cmd_update()\n \t\t${update:+--update \"$update\"} \\\n \t\t${reference:+--reference \"$reference\"} \\\n \t\t${depth:+--depth \"$depth\"} \\\n+\t\t${jobs:+$jobs} \\\n \t\t\"$@\" || echo \"#unmatched\"\n \t} | {\n \terr=\ndiff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\nindex dda3929..7fd5142 100755\n--- a/t/t7406-submodule-update.sh\n+++ b/t/t7406-submodule-update.sh\n@@ -774,4 +774,16 @@ test_expect_success 'submodule update --recursive drops module name before recur\n \t test_i18ngrep \"Submodule path .deeper/submodule/subsubmodule.: checked out\" actual\n \t)\n '\n+\n+test_expect_success 'submodule update can be run in parallel' '\n+\t(cd super2 &&\n+\t GIT_TRACE=$(pwd)/trace.out git submodule update --jobs 7 &&\n+\t grep \"7 tasks\" trace.out &&\n+\t git config submodule.fetchJobs 8 &&\n+\t GIT_TRACE=$(pwd)/trace.out git submodule update &&\n+\t grep \"8 tasks\" trace.out &&\n+\t GIT_TRACE=$(pwd)/trace.out git submodule update --jobs 9 &&\n+\t grep \"9 tasks\" trace.out\n+\t)\n+'\n test_done\n-- \n2.7.2.335.g3f96d05\n"},{"id":"279254","messageId":"1456364525-21190-8-git-send-email-sbeller@google.com","threadId":"41510","inReplyTo":"1456364525-21190-1-git-send-email-sbeller@google.com","subject":"[PATCH 7/7] clone: allow an explicit argument for parallel submodule clones","fromName":"Stefan Beller","fromEmail":"sbeller@google.com","sentAt":"2016-02-25T01:42:05Z","receivedAt":"2016-02-25T01:42:05Z","isPatch":true,"sender":{"key":"stefanbeller@gmail.com","avatar":"https://avatars.githubusercontent.com/u/455868?v=4"},"body":"Just pass it along to \"git submodule update\", which may pick reasonable\ndefaults if you don't specify an explicit number.\n\nSigned-off-by: Stefan Beller <sbeller@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-clone.txt |  6 +++++-\n builtin/clone.c             | 19 +++++++++++++------\n t/t7406-submodule-update.sh | 15 +++++++++++++++\n 3 files changed, 33 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex 6bf000d..6db7b6d 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -14,7 +14,7 @@ SYNOPSIS\n \t  [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]\n \t  [--dissociate] [--separate-git-dir <git dir>]\n \t  [--depth <depth>] [--[no-]single-branch]\n-\t  [--recursive | --recurse-submodules] [--] <repository>\n+\t  [--recursive | --recurse-submodules] [--jobs <n>] [--] <repository>\n \t  [<directory>]\n \n DESCRIPTION\n@@ -221,6 +221,10 @@ objects from the source repository into a pack in the cloned repository.\n \tThe result is Git repository can be separated from working\n \ttree.\n \n+-j <n>::\n+--jobs <n>::\n+\tThe number of submodules fetched at the same time.\n+\tDefaults to the `submodule.fetchJobs` option.\n \n <repository>::\n \tThe (possibly remote) repository to clone from.  See the\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a0b3cd9..b004fb4 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -50,6 +50,7 @@ static int option_progress = -1;\n static struct string_list option_config;\n static struct string_list option_reference;\n static int option_dissociate;\n+static int max_jobs = -1;\n \n static struct option builtin_clone_options[] = {\n \tOPT__VERBOSITY(&option_verbosity),\n@@ -72,6 +73,8 @@ static struct option builtin_clone_options[] = {\n \t\t    N_(\"initialize submodules in the clone\")),\n \tOPT_BOOL(0, \"recurse-submodules\", &option_recursive,\n \t\t    N_(\"initialize submodules in the clone\")),\n+\tOPT_INTEGER('j', \"jobs\", &max_jobs,\n+\t\t    N_(\"number of submodules cloned in parallel\")),\n \tOPT_STRING(0, \"template\", &option_template, N_(\"template-directory\"),\n \t\t   N_(\"directory from which templates will be used\")),\n \tOPT_STRING_LIST(0, \"reference\", &option_reference, N_(\"repo\"),\n@@ -95,10 +98,6 @@ static struct option builtin_clone_options[] = {\n \tOPT_END()\n };\n \n-static const char *argv_submodule[] = {\n-\t\"submodule\", \"update\", \"--init\", \"--recursive\", NULL\n-};\n-\n static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)\n {\n \tstatic char *suffix[] = { \"/.git\", \"\", \".git/.git\", \".git\" };\n@@ -724,8 +723,16 @@ static int checkout(void)\n \terr |= run_hook_le(NULL, \"post-checkout\", sha1_to_hex(null_sha1),\n \t\t\t   sha1_to_hex(sha1), \"1\", NULL);\n \n-\tif (!err && option_recursive)\n-\t\terr = run_command_v_opt(argv_submodule, RUN_GIT_CMD);\n+\tif (!err && option_recursive) {\n+\t\tstruct argv_array args = ARGV_ARRAY_INIT;\n+\t\targv_array_pushl(&args, \"submodule\", \"update\", \"--init\", \"--recursive\", NULL);\n+\n+\t\tif (max_jobs != -1)\n+\t\t\targv_array_pushf(&args, \"--jobs=%d\", max_jobs);\n+\n+\t\terr = run_command_v_opt(args.argv, RUN_GIT_CMD);\n+\t\targv_array_clear(&args);\n+\t}\n \n \treturn err;\n }\ndiff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh\nindex 7fd5142..090891e 100755\n--- a/t/t7406-submodule-update.sh\n+++ b/t/t7406-submodule-update.sh\n@@ -786,4 +786,19 @@ test_expect_success 'submodule update can be run in parallel' '\n \t grep \"9 tasks\" trace.out\n \t)\n '\n+\n+test_expect_success 'git clone passes the parallel jobs config on to submodules' '\n+\ttest_when_finished \"rm -rf super4\" &&\n+\tGIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules --jobs 7 . super4 &&\n+\tgrep \"7 tasks\" trace.out &&\n+\trm -rf super4 &&\n+\tgit config --global submodule.fetchJobs 8 &&\n+\tGIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules . super4 &&\n+\tgrep \"8 tasks\" trace.out &&\n+\trm -rf super4 &&\n+\tGIT_TRACE=$(pwd)/trace.out git clone --recurse-submodules --jobs 9 . super4 &&\n+\tgrep \"9 tasks\" trace.out &&\n+\trm -rf super4\n+'\n+\n test_done\n-- \n2.7.2.335.g3f96d05\n"},{"id":"279258","messageId":"20160225014702.GQ28749@google.com","threadId":"41510","inReplyTo":"1456364525-21190-4-git-send-email-sbeller@google.com","subject":"Re: [PATCH 3/7] run_processes_parallel: rename parameters for the callbacks","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2016-02-25T01:47:02Z","receivedAt":"2016-02-25T01:47:02Z","isPatch":true,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Stefan Beller wrote:\n\n> +++ b/run-command.h\n> @@ -159,7 +159,7 @@ typedef int (*get_next_task_fn)(struct child_process *cp,\n>   * the negative signal number.\n>   */\n>  typedef int (*start_failure_fn)(struct child_process *cp,\n> -\t\t\t\tstruct strbuf *err,\n> +\t\t\t\tstruct strbuf *out,\n>  \t\t\t\tvoid *pp_cb,\n>  \t\t\t\tvoid *pp_task_cb);\n\nYay, thank you.  The docstrings still refer to err.\n\nJonathan\n"},{"id":"279259","messageId":"20160225015412.GR28749@google.com","threadId":"41510","inReplyTo":"1456364525-21190-1-git-send-email-sbeller@google.com","subject":"Re: [PATCHv16 0/7] Expose submodule parallelism to the user","fromName":"Jonathan Nieder","fromEmail":"jrnieder@gmail.com","sentAt":"2016-02-25T01:54:12Z","receivedAt":"2016-02-25T01:54:12Z","isPatch":false,"sender":{"key":"jrnieder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/281595?v=4"},"body":"Stefan Beller wrote:\n\n> This build on top of 163b9b1f919c762a4bfb693b3aa05ef1aa627fee\n> (origin/sb/submodule-parallel-update~5) and replace the series from there on.\n\nExcept for the comments in patch 3,\nReviewed-by: Jonathan Nieder <jrnieder@gmail.com>\n\nThanks for writing it and sorry it took so long.\n"}]}