Merge branch 'sp/maint-push-sideband' into sp/push-sideband
authorJunio C Hamano <gitster@pobox.com>
Sat, 6 Feb 2010 05:08:53 +0000 (21:08 -0800)
committerJunio C Hamano <gitster@pobox.com>
Sat, 6 Feb 2010 05:08:53 +0000 (21:08 -0800)
* sp/maint-push-sideband:
receive-pack: Send hook output over side band #2
receive-pack: Wrap status reports inside side-band-64k
receive-pack: Refactor how capabilities are shown to the client
send-pack: demultiplex a sideband stream with status data
run-command: support custom fd-set in async
run-command: Allow stderr to be a caller supplied pipe
Update git fsck --full short description to mention packs

Conflicts:
run-command.c

1  2 
builtin-receive-pack.c
builtin-send-pack.c
convert.c
remote-curl.c
run-command.c
run-command.h
t/t5401-update-hooks.sh
diff --combined builtin-receive-pack.c
index 4320c93e700a08911e42e3e949656af67b675244,da1c26b6be14f7aaee99806017cef9554fa65e6d..b704fbd5b5a84d00d0fe28db145d869d77a4f320
@@@ -2,6 -2,7 +2,7 @@@
  #include "pack.h"
  #include "refs.h"
  #include "pkt-line.h"
+ #include "sideband.h"
  #include "run-command.h"
  #include "exec_cmd.h"
  #include "commit.h"
@@@ -27,11 -28,12 +28,12 @@@ static int receive_unpack_limit = -1
  static int transfer_unpack_limit = -1;
  static int unpack_limit = 100;
  static int report_status;
+ static int use_sideband;
  static int prefer_ofs_delta = 1;
  static int auto_update_server_info;
  static int auto_gc = 1;
  static const char *head_name;
- static char *capabilities_to_send;
+ static int sent_capabilities;
  
  static enum deny_action parse_deny_action(const char *var, const char *value)
  {
@@@ -105,19 -107,21 +107,21 @@@ static int receive_pack_config(const ch
  
  static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
  {
-       if (!capabilities_to_send)
+       if (sent_capabilities)
                packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
        else
-               packet_write(1, "%s %s%c%s\n",
-                            sha1_to_hex(sha1), path, 0, capabilities_to_send);
-       capabilities_to_send = NULL;
+               packet_write(1, "%s %s%c%s%s\n",
+                            sha1_to_hex(sha1), path, 0,
+                            " report-status delete-refs side-band-64k",
+                            prefer_ofs_delta ? " ofs-delta" : "");
+       sent_capabilities = 1;
        return 0;
  }
  
  static void write_head_info(void)
  {
        for_each_ref(show_ref, NULL);
-       if (capabilities_to_send)
+       if (!sent_capabilities)
                show_ref("capabilities^{}", null_sha1, 0, NULL);
  
  }
@@@ -135,11 -139,25 +139,25 @@@ static struct command *commands
  static const char pre_receive_hook[] = "hooks/pre-receive";
  static const char post_receive_hook[] = "hooks/post-receive";
  
+ static int copy_to_sideband(int in, int out, void *arg)
+ {
+       char data[128];
+       while (1) {
+               ssize_t sz = xread(in, data, sizeof(data));
+               if (sz <= 0)
+                       break;
+               send_sideband(1, 2, data, sz, use_sideband);
+       }
+       close(in);
+       return 0;
+ }
  static int run_receive_hook(const char *hook_name)
  {
        static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
        struct command *cmd;
        struct child_process proc;
+       struct async muxer;
        const char *argv[2];
        int have_input = 0, code;
  
        proc.in = -1;
        proc.stdout_to_stderr = 1;
  
+       if (use_sideband) {
+               memset(&muxer, 0, sizeof(muxer));
+               muxer.proc = copy_to_sideband;
+               muxer.in = -1;
+               code = start_async(&muxer);
+               if (code)
+                       return code;
+               proc.err = muxer.in;
+       }
        code = start_command(&proc);
-       if (code)
+       if (code) {
+               if (use_sideband)
+                       finish_async(&muxer);
                return code;
+       }
        for (cmd = commands; cmd; cmd = cmd->next) {
                if (!cmd->error_string) {
                        size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
                }
        }
        close(proc.in);
+       if (use_sideband)
+               finish_async(&muxer);
        return finish_command(&proc);
  }
  
@@@ -180,6 -214,8 +214,8 @@@ static int run_update_hook(struct comma
  {
        static const char update_hook[] = "hooks/update";
        const char *argv[5];
+       struct child_process proc;
+       int code;
  
        if (access(update_hook, X_OK) < 0)
                return 0;
        argv[3] = sha1_to_hex(cmd->new_sha1);
        argv[4] = NULL;
  
-       return run_command_v_opt(argv, RUN_COMMAND_NO_STDIN |
-                                       RUN_COMMAND_STDOUT_TO_STDERR);
+       memset(&proc, 0, sizeof(proc));
+       proc.no_stdin = 1;
+       proc.stdout_to_stderr = 1;
+       proc.err = use_sideband ? -1 : 0;
+       proc.argv = argv;
+       code = start_command(&proc);
+       if (code)
+               return code;
+       if (use_sideband)
+               copy_to_sideband(proc.err, -1, NULL);
+       return finish_command(&proc);
  }
  
  static int is_ref_checked_out(const char *ref)
        return !strcmp(head_name, ref);
  }
  
 -static char *warn_unconfigured_deny_msg[] = {
 -      "Updating the currently checked out branch may cause confusion,",
 -      "as the index and work tree do not reflect changes that are in HEAD.",
 -      "As a result, you may see the changes you just pushed into it",
 -      "reverted when you run 'git diff' over there, and you may want",
 -      "to run 'git reset --hard' before starting to work to recover.",
 +static char *refuse_unconfigured_deny_msg[] = {
 +      "By default, updating the current branch in a non-bare repository",
 +      "is denied, because it will make the index and work tree inconsistent",
 +      "with what you pushed, and will require 'git reset --hard' to match",
 +      "the work tree to HEAD.",
        "",
        "You can set 'receive.denyCurrentBranch' configuration variable to",
 -      "'refuse' in the remote repository to forbid pushing into its",
 -      "current branch."
 +      "'ignore' or 'warn' in the remote repository to allow pushing into",
 +      "its current branch; however, this is not recommended unless you",
 +      "arranged to update its work tree to match what you pushed in some",
 +      "other way.",
        "",
 -      "To allow pushing into the current branch, you can set it to 'ignore';",
 -      "but this is not recommended unless you arranged to update its work",
 -      "tree to match what you pushed in some other way.",
 -      "",
 -      "To squelch this message, you can set it to 'warn'.",
 -      "",
 -      "Note that the default will change in a future version of git",
 -      "to refuse updating the current branch unless you have the",
 -      "configuration variable set to either 'ignore' or 'warn'."
 +      "To squelch this message and still keep the default behaviour, set",
 +      "'receive.denyCurrentBranch' configuration variable to 'refuse'."
  };
  
 -static void warn_unconfigured_deny(void)
 +static void refuse_unconfigured_deny(void)
  {
        int i;
 -      for (i = 0; i < ARRAY_SIZE(warn_unconfigured_deny_msg); i++)
 -              warning("%s", warn_unconfigured_deny_msg[i]);
 +      for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
 +              error("%s", refuse_unconfigured_deny_msg[i]);
  }
  
 -static char *warn_unconfigured_deny_delete_current_msg[] = {
 -      "Deleting the current branch can cause confusion by making the next",
 -      "'git clone' not check out any file.",
 +static char *refuse_unconfigured_deny_delete_current_msg[] = {
 +      "By default, deleting the current branch is denied, because the next",
 +      "'git clone' won't result in any file checked out, causing confusion.",
        "",
        "You can set 'receive.denyDeleteCurrent' configuration variable to",
 -      "'refuse' in the remote repository to disallow deleting the current",
 -      "branch.",
 -      "",
 -      "You can set it to 'ignore' to allow such a delete without a warning.",
 +      "'warn' or 'ignore' in the remote repository to allow deleting the",
 +      "current branch, with or without a warning message.",
        "",
 -      "To make this warning message less loud, you can set it to 'warn'.",
 -      "",
 -      "Note that the default will change in a future version of git",
 -      "to refuse deleting the current branch unless you have the",
 -      "configuration variable set to either 'ignore' or 'warn'."
 +      "To squelch this message, you can set it to 'refuse'."
  };
  
 -static void warn_unconfigured_deny_delete_current(void)
 +static void refuse_unconfigured_deny_delete_current(void)
  {
        int i;
        for (i = 0;
 -           i < ARRAY_SIZE(warn_unconfigured_deny_delete_current_msg);
 +           i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
             i++)
 -              warning("%s", warn_unconfigured_deny_delete_current_msg[i]);
 +              error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
  }
  
  static const char *update(struct command *cmd)
                switch (deny_current_branch) {
                case DENY_IGNORE:
                        break;
 -              case DENY_UNCONFIGURED:
                case DENY_WARN:
                        warning("updating the current branch");
 -                      if (deny_current_branch == DENY_UNCONFIGURED)
 -                              warn_unconfigured_deny();
                        break;
                case DENY_REFUSE:
 +              case DENY_UNCONFIGURED:
                        error("refusing to update checked out branch: %s", name);
 +                      if (deny_current_branch == DENY_UNCONFIGURED)
 +                              refuse_unconfigured_deny();
                        return "branch is currently checked out";
                }
        }
                        case DENY_IGNORE:
                                break;
                        case DENY_WARN:
 -                      case DENY_UNCONFIGURED:
 -                              if (deny_delete_current == DENY_UNCONFIGURED)
 -                                      warn_unconfigured_deny_delete_current();
                                warning("deleting the current branch");
                                break;
                        case DENY_REFUSE:
 +                      case DENY_UNCONFIGURED:
 +                              if (deny_delete_current == DENY_UNCONFIGURED)
 +                                      refuse_unconfigured_deny_delete_current();
                                error("refusing to delete the current branch: %s", name);
                                return "deletion of the current branch prohibited";
                        }
@@@ -368,8 -426,9 +414,9 @@@ static char update_post_hook[] = "hooks
  static void run_update_post_hook(struct command *cmd)
  {
        struct command *cmd_p;
-       int argc, status;
+       int argc;
        const char **argv;
+       struct child_process proc;
  
        for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
                if (cmd_p->error_string)
                argc++;
        }
        argv[argc] = NULL;
-       status = run_command_v_opt(argv, RUN_COMMAND_NO_STDIN
-                       | RUN_COMMAND_STDOUT_TO_STDERR);
+       memset(&proc, 0, sizeof(proc));
+       proc.no_stdin = 1;
+       proc.stdout_to_stderr = 1;
+       proc.err = use_sideband ? -1 : 0;
+       proc.argv = argv;
+       if (!start_command(&proc)) {
+               if (use_sideband)
+                       copy_to_sideband(proc.err, -1, NULL);
+               finish_command(&proc);
+       }
  }
  
  static void execute_commands(const char *unpacker_error)
@@@ -452,6 -521,8 +509,8 @@@ static void read_head_info(void
                if (reflen + 82 < len) {
                        if (strstr(refname + reflen + 1, "report-status"))
                                report_status = 1;
+                       if (strstr(refname + reflen + 1, "side-band-64k"))
+                               use_sideband = LARGE_PACKET_MAX;
                }
                cmd = xmalloc(sizeof(struct command) + len - 80);
                hashcpy(cmd->old_sha1, old_sha1);
@@@ -551,17 -622,25 +610,25 @@@ static const char *unpack(void
  static void report(const char *unpack_status)
  {
        struct command *cmd;
-       packet_write(1, "unpack %s\n",
-                    unpack_status ? unpack_status : "ok");
+       struct strbuf buf = STRBUF_INIT;
+       packet_buf_write(&buf, "unpack %s\n",
+                        unpack_status ? unpack_status : "ok");
        for (cmd = commands; cmd; cmd = cmd->next) {
                if (!cmd->error_string)
-                       packet_write(1, "ok %s\n",
-                                    cmd->ref_name);
+                       packet_buf_write(&buf, "ok %s\n",
+                                        cmd->ref_name);
                else
-                       packet_write(1, "ng %s %s\n",
-                                    cmd->ref_name, cmd->error_string);
+                       packet_buf_write(&buf, "ng %s %s\n",
+                                        cmd->ref_name, cmd->error_string);
        }
-       packet_flush(1);
+       packet_buf_flush(&buf);
+       if (use_sideband)
+               send_sideband(1, 1, buf.buf, buf.len, use_sideband);
+       else
+               safe_write(1, buf.buf, buf.len);
+       strbuf_release(&buf);
  }
  
  static int delete_only(struct command *cmd)
@@@ -658,10 -737,6 +725,6 @@@ int cmd_receive_pack(int argc, const ch
        else if (0 <= receive_unpack_limit)
                unpack_limit = receive_unpack_limit;
  
-       capabilities_to_send = (prefer_ofs_delta) ?
-               " report-status delete-refs ofs-delta " :
-               " report-status delete-refs ";
        if (advertise_refs || !stateless_rpc) {
                add_alternate_refs();
                write_head_info();
                if (auto_update_server_info)
                        update_server_info(0);
        }
+       if (use_sideband)
+               packet_flush(1);
        return 0;
  }
diff --combined builtin-send-pack.c
index 76c72065de73ea3f0da4665c0a47a64610e2ead2,2478e1851a355f48f2af228e46934aa3c8fc1c9d..2183a470524048eabef1b0f31499c5d04aec5850
@@@ -372,6 -372,14 +372,14 @@@ static void print_helper_status(struct 
        strbuf_release(&buf);
  }
  
+ static int sideband_demux(int in, int out, void *data)
+ {
+       int *fd = data;
+       int ret = recv_sideband("send-pack", fd[0], out);
+       close(out);
+       return ret;
+ }
  int send_pack(struct send_pack_args *args,
              int fd[], struct child_process *conn,
              struct ref *remote_refs,
        struct strbuf req_buf = STRBUF_INIT;
        struct ref *ref;
        int new_refs;
-       int ask_for_status_report = 0;
        int allow_deleting_refs = 0;
-       int expect_status_report = 0;
+       int status_report = 0;
+       int use_sideband = 0;
+       unsigned cmds_sent = 0;
        int ret;
+       struct async demux;
  
        /* Does the other end support the reporting? */
        if (server_supports("report-status"))
-               ask_for_status_report = 1;
+               status_report = 1;
        if (server_supports("delete-refs"))
                allow_deleting_refs = 1;
        if (server_supports("ofs-delta"))
                args->use_ofs_delta = 1;
+       if (server_supports("side-band-64k"))
+               use_sideband = 1;
  
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
         */
        new_refs = 0;
        for (ref = remote_refs; ref; ref = ref->next) {
 -
 -              if (ref->peer_ref)
 -                      hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
 -              else if (!args->send_mirror)
 +              if (!ref->peer_ref && !args->send_mirror)
                        continue;
  
 -              ref->deletion = is_null_sha1(ref->new_sha1);
 -              if (ref->deletion && !allow_deleting_refs) {
 -                      ref->status = REF_STATUS_REJECT_NODELETE;
 -                      continue;
 -              }
 -              if (!ref->deletion &&
 -                  !hashcmp(ref->old_sha1, ref->new_sha1)) {
 -                      ref->status = REF_STATUS_UPTODATE;
 +              /* Check for statuses set by set_ref_status_for_push() */
 +              switch (ref->status) {
 +              case REF_STATUS_REJECT_NONFASTFORWARD:
 +              case REF_STATUS_UPTODATE:
                        continue;
 +              default:
 +                      ; /* do nothing */
                }
  
 -              /* This part determines what can overwrite what.
 -               * The rules are:
 -               *
 -               * (0) you can always use --force or +A:B notation to
 -               *     selectively force individual ref pairs.
 -               *
 -               * (1) if the old thing does not exist, it is OK.
 -               *
 -               * (2) if you do not have the old thing, you are not allowed
 -               *     to overwrite it; you would not know what you are losing
 -               *     otherwise.
 -               *
 -               * (3) if both new and old are commit-ish, and new is a
 -               *     descendant of old, it is OK.
 -               *
 -               * (4) regardless of all of the above, removing :B is
 -               *     always allowed.
 -               */
 -
 -              ref->nonfastforward =
 -                  !ref->deletion &&
 -                  !is_null_sha1(ref->old_sha1) &&
 -                  (!has_sha1_file(ref->old_sha1)
 -                    || !ref_newer(ref->new_sha1, ref->old_sha1));
 -
 -              if (ref->nonfastforward && !ref->force && !args->force_update) {
 -                      ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
 +              if (ref->deletion && !allow_deleting_refs) {
 +                      ref->status = REF_STATUS_REJECT_NODELETE;
                        continue;
                }
  
                if (!ref->deletion)
                        new_refs++;
  
-               if (!args->dry_run) {
+               if (args->dry_run) {
+                       ref->status = REF_STATUS_OK;
+               } else {
                        char *old_hex = sha1_to_hex(ref->old_sha1);
                        char *new_hex = sha1_to_hex(ref->new_sha1);
  
-                       if (ask_for_status_report) {
-                               packet_buf_write(&req_buf, "%s %s %s%c%s",
+                       if (!cmds_sent && (status_report || use_sideband)) {
+                               packet_buf_write(&req_buf, "%s %s %s%c%s%s",
                                        old_hex, new_hex, ref->name, 0,
-                                       "report-status");
-                               ask_for_status_report = 0;
-                               expect_status_report = 1;
+                                       status_report ? " report-status" : "",
+                                       use_sideband ? " side-band-64k" : "");
                        }
                        else
                                packet_buf_write(&req_buf, "%s %s %s",
                                        old_hex, new_hex, ref->name);
+                       ref->status = status_report ?
+                               REF_STATUS_EXPECTING_REPORT :
+                               REF_STATUS_OK;
+                       cmds_sent++;
                }
-               ref->status = expect_status_report ?
-                       REF_STATUS_EXPECTING_REPORT :
-                       REF_STATUS_OK;
        }
  
        if (args->stateless_rpc) {
-               if (!args->dry_run) {
+               if (!args->dry_run && cmds_sent) {
                        packet_buf_flush(&req_buf);
                        send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
                }
        }
        strbuf_release(&req_buf);
  
-       if (new_refs && !args->dry_run) {
+       if (use_sideband && cmds_sent) {
+               memset(&demux, 0, sizeof(demux));
+               demux.proc = sideband_demux;
+               demux.data = fd;
+               demux.out = -1;
+               if (start_async(&demux))
+                       die("receive-pack: unable to fork off sideband demultiplexer");
+               in = demux.out;
+       }
+       if (new_refs && cmds_sent) {
                if (pack_objects(out, remote_refs, extra_have, args) < 0) {
                        for (ref = remote_refs; ref; ref = ref->next)
                                ref->status = REF_STATUS_NONE;
+                       if (use_sideband)
+                               finish_async(&demux);
                        return -1;
                }
        }
-       if (args->stateless_rpc && !args->dry_run)
+       if (args->stateless_rpc && cmds_sent)
                packet_flush(out);
  
-       if (expect_status_report)
+       if (status_report && cmds_sent)
                ret = receive_status(in, remote_refs);
        else
                ret = 0;
        if (args->stateless_rpc)
                packet_flush(out);
  
+       if (use_sideband && cmds_sent) {
+               if (finish_async(&demux)) {
+                       error("error in sideband demultiplexer");
+                       ret = -1;
+               }
+               close(demux.out);
+       }
        if (ret < 0)
                return ret;
        for (ref = remote_refs; ref; ref = ref->next) {
@@@ -643,9 -707,6 +677,9 @@@ int cmd_send_pack(int argc, const char 
        if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
                return -1;
  
 +      set_ref_status_for_push(remote_refs, args.send_mirror,
 +              args.force_update);
 +
        ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
  
        if (helper_status)
diff --combined convert.c
index 27acce58bc4bec60a394f03db1f6e60e1e4cfc3e,e70ee094a76d850cd655338d406a7c4efc37c7d5..4f8fcb7bbb00b66f1eaa354119784e6ef57e1eb4
+++ b/convert.c
@@@ -241,7 -241,7 +241,7 @@@ struct filter_params 
        const char *cmd;
  };
  
- static int filter_buffer(int fd, void *data)
+ static int filter_buffer(int in, int out, void *data)
  {
        /*
         * Spawn cmd and feed the buffer contents through its stdin.
        struct child_process child_process;
        struct filter_params *params = (struct filter_params *)data;
        int write_err, status;
 -      const char *argv[] = { "sh", "-c", params->cmd, NULL };
 +      const char *argv[] = { params->cmd, NULL };
  
        memset(&child_process, 0, sizeof(child_process));
        child_process.argv = argv;
 +      child_process.use_shell = 1;
        child_process.in = -1;
-       child_process.out = fd;
+       child_process.out = out;
  
        if (start_command(&child_process))
                return error("cannot fork to run external filter %s", params->cmd);
@@@ -292,6 -291,7 +292,7 @@@ static int apply_filter(const char *pat
        memset(&async, 0, sizeof(async));
        async.proc = filter_buffer;
        async.data = &params;
+       async.out = -1;
        params.src = src;
        params.size = len;
        params.cmd = cmd;
@@@ -378,9 -378,9 +379,9 @@@ static void setup_convert_check(struct 
        static struct git_attr *attr_filter;
  
        if (!attr_crlf) {
 -              attr_crlf = git_attr("crlf", 4);
 -              attr_ident = git_attr("ident", 5);
 -              attr_filter = git_attr("filter", 6);
 +              attr_crlf = git_attr("crlf");
 +              attr_ident = git_attr("ident");
 +              attr_filter = git_attr("filter");
                user_convert_tail = &user_convert;
                git_config(read_convert_config, NULL);
        }
diff --combined remote-curl.c
index a904164e425097380781c1ecd9bb13f4feec0de6,6bb3366264874ed1fad34ae58e31677dc9644934..d38812085151b6738e7ca95be10968b973bf13b4
@@@ -184,13 -184,13 +184,13 @@@ static struct discovery* discover_refs(
        return last;
  }
  
- static int write_discovery(int fd, void *data)
+ static int write_discovery(int in, int out, void *data)
  {
        struct discovery *heads = data;
        int err = 0;
-       if (write_in_full(fd, heads->buf, heads->len) != heads->len)
+       if (write_in_full(out, heads->buf, heads->len) != heads->len)
                err = 1;
-       close(fd);
+       close(out);
        return err;
  }
  
@@@ -202,6 -202,7 +202,7 @@@ static struct ref *parse_git_refs(struc
        memset(&async, 0, sizeof(async));
        async.proc = write_discovery;
        async.data = heads;
+       async.out = -1;
  
        if (start_async(&async))
                die("cannot start thread to parse advertised refs");
@@@ -304,7 -305,6 +305,7 @@@ struct rpc_state 
        int out;
        struct strbuf result;
        unsigned gzip_request : 1;
 +      unsigned initial_buffer : 1;
  };
  
  static size_t rpc_out(void *ptr, size_t eltsize,
        size_t avail = rpc->len - rpc->pos;
  
        if (!avail) {
 +              rpc->initial_buffer = 0;
                avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
                if (!avail)
                        return 0;
        return avail;
  }
  
 +#ifndef NO_CURL_IOCTL
 +static curlioerr rpc_ioctl(CURL *handle, int cmd, void *clientp)
 +{
 +      struct rpc_state *rpc = clientp;
 +
 +      switch (cmd) {
 +      case CURLIOCMD_NOP:
 +              return CURLIOE_OK;
 +
 +      case CURLIOCMD_RESTARTREAD:
 +              if (rpc->initial_buffer) {
 +                      rpc->pos = 0;
 +                      return CURLIOE_OK;
 +              }
 +              fprintf(stderr, "Unable to rewind rpc post data - try increasing http.postBuffer\n");
 +              return CURLIOE_FAILRESTART;
 +
 +      default:
 +              return CURLIOE_UNKNOWNCMD;
 +      }
 +}
 +#endif
 +
  static size_t rpc_in(const void *ptr, size_t eltsize,
                size_t nmemb, void *buffer_)
  {
@@@ -409,13 -385,8 +410,13 @@@ static int post_rpc(struct rpc_state *r
                 */
                headers = curl_slist_append(headers, "Expect: 100-continue");
                headers = curl_slist_append(headers, "Transfer-Encoding: chunked");
 +              rpc->initial_buffer = 1;
                curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, rpc_out);
                curl_easy_setopt(slot->curl, CURLOPT_INFILE, rpc);
 +#ifndef NO_CURL_IOCTL
 +              curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, rpc_ioctl);
 +              curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, rpc);
 +#endif
                if (options.verbosity > 1) {
                        fprintf(stderr, "POST %s (chunked)\n", rpc->service_name);
                        fflush(stderr);
diff --combined run-command.c
index 2feb493951322617692085998ac8507cdba9dd30,0d95340833aa999ecee58ad70c1c61a832fcdf38..0cd7f02ffe597d3708873d4f3d6b4c0e88e8fae7
@@@ -8,130 -8,12 +8,130 @@@ static inline void close_pair(int fd[2]
        close(fd[1]);
  }
  
 +#ifndef WIN32
  static inline void dup_devnull(int to)
  {
        int fd = open("/dev/null", O_RDWR);
        dup2(fd, to);
        close(fd);
  }
 +#endif
 +
 +static const char **prepare_shell_cmd(const char **argv)
 +{
 +      int argc, nargc = 0;
 +      const char **nargv;
 +
 +      for (argc = 0; argv[argc]; argc++)
 +              ; /* just counting */
 +      /* +1 for NULL, +3 for "sh -c" plus extra $0 */
 +      nargv = xmalloc(sizeof(*nargv) * (argc + 1 + 3));
 +
 +      if (argc < 1)
 +              die("BUG: shell command is empty");
 +
 +      if (strcspn(argv[0], "|&;<>()$`\\\"' \t\n*?[#~=%") != strlen(argv[0])) {
 +              nargv[nargc++] = "sh";
 +              nargv[nargc++] = "-c";
 +
 +              if (argc < 2)
 +                      nargv[nargc++] = argv[0];
 +              else {
 +                      struct strbuf arg0 = STRBUF_INIT;
 +                      strbuf_addf(&arg0, "%s \"$@\"", argv[0]);
 +                      nargv[nargc++] = strbuf_detach(&arg0, NULL);
 +              }
 +      }
 +
 +      for (argc = 0; argv[argc]; argc++)
 +              nargv[nargc++] = argv[argc];
 +      nargv[nargc] = NULL;
 +
 +      return nargv;
 +}
 +
 +#ifndef WIN32
 +static int execv_shell_cmd(const char **argv)
 +{
 +      const char **nargv = prepare_shell_cmd(argv);
 +      trace_argv_printf(nargv, "trace: exec:");
 +      execvp(nargv[0], (char **)nargv);
 +      free(nargv);
 +      return -1;
 +}
 +#endif
 +
 +#ifndef WIN32
 +static int child_err = 2;
 +static int child_notifier = -1;
 +
 +static void notify_parent(void)
 +{
 +      write(child_notifier, "", 1);
 +}
 +
 +static NORETURN void die_child(const char *err, va_list params)
 +{
 +      char msg[4096];
 +      int len = vsnprintf(msg, sizeof(msg), err, params);
 +      if (len > sizeof(msg))
 +              len = sizeof(msg);
 +
 +      write(child_err, "fatal: ", 7);
 +      write(child_err, msg, len);
 +      write(child_err, "\n", 1);
 +      exit(128);
 +}
 +
 +static inline void set_cloexec(int fd)
 +{
 +      int flags = fcntl(fd, F_GETFD);
 +      if (flags >= 0)
 +              fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
 +}
 +#endif
 +
 +static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
 +{
 +      int status, code = -1;
 +      pid_t waiting;
 +      int failed_errno = 0;
 +
 +      while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
 +              ;       /* nothing */
 +
 +      if (waiting < 0) {
 +              failed_errno = errno;
 +              error("waitpid for %s failed: %s", argv0, strerror(errno));
 +      } else if (waiting != pid) {
 +              error("waitpid is confused (%s)", argv0);
 +      } else if (WIFSIGNALED(status)) {
 +              code = WTERMSIG(status);
 +              error("%s died of signal %d", argv0, code);
 +              /*
 +               * This return value is chosen so that code & 0xff
 +               * mimics the exit code that a POSIX shell would report for
 +               * a program that died from this signal.
 +               */
 +              code -= 128;
 +      } else if (WIFEXITED(status)) {
 +              code = WEXITSTATUS(status);
 +              /*
 +               * Convert special exit code when execvp failed.
 +               */
 +              if (code == 127) {
 +                      code = -1;
 +                      failed_errno = ENOENT;
 +                      if (!silent_exec_failure)
 +                              error("cannot run %s: %s", argv0,
 +                                      strerror(ENOENT));
 +              }
 +      } else {
 +              error("waitpid is confused (%s)", argv0);
 +      }
 +      errno = failed_errno;
 +      return code;
 +}
  
  int start_command(struct child_process *cmd)
  {
@@@ -194,30 -76,9 +194,30 @@@ fail_pipe
        trace_argv_printf(cmd->argv, "trace: run_command:");
  
  #ifndef WIN32
 +{
 +      int notify_pipe[2];
 +      if (pipe(notify_pipe))
 +              notify_pipe[0] = notify_pipe[1] = -1;
 +
        fflush(NULL);
        cmd->pid = fork();
        if (!cmd->pid) {
 +              /*
 +               * Redirect the channel to write syscall error messages to
 +               * before redirecting the process's stderr so that all die()
 +               * in subsequent call paths use the parent's stderr.
 +               */
 +              if (cmd->no_stderr || need_err) {
 +                      child_err = dup(2);
 +                      set_cloexec(child_err);
 +              }
 +              set_die_routine(die_child);
 +
 +              close(notify_pipe[0]);
 +              set_cloexec(notify_pipe[1]);
 +              child_notifier = notify_pipe[1];
 +              atexit(notify_parent);
 +
                if (cmd->no_stdin)
                        dup_devnull(0);
                else if (need_in) {
                else if (need_err) {
                        dup2(fderr[1], 2);
                        close_pair(fderr);
+               } else if (cmd->err > 1) {
+                       dup2(cmd->err, 2);
+                       close(cmd->err);
                }
  
                if (cmd->no_stdout)
                                        unsetenv(*cmd->env);
                        }
                }
 -              if (cmd->preexec_cb)
 +              if (cmd->preexec_cb) {
 +                      /*
 +                       * We cannot predict what the pre-exec callback does.
 +                       * Forgo parent notification.
 +                       */
 +                      close(child_notifier);
 +                      child_notifier = -1;
 +
                        cmd->preexec_cb();
 +              }
                if (cmd->git_cmd) {
                        execv_git_cmd(cmd->argv);
 +              } else if (cmd->use_shell) {
 +                      execv_shell_cmd(cmd->argv);
                } else {
                        execvp(cmd->argv[0], (char *const*) cmd->argv);
                }
 -              trace_printf("trace: exec '%s' failed: %s\n", cmd->argv[0],
 -                              strerror(errno));
 -              exit(127);
 +              /*
 +               * Do not check for cmd->silent_exec_failure; the parent
 +               * process will check it when it sees this exit code.
 +               */
 +              if (errno == ENOENT)
 +                      exit(127);
 +              else
 +                      die_errno("cannot exec '%s'", cmd->argv[0]);
        }
        if (cmd->pid < 0)
                error("cannot fork() for %s: %s", cmd->argv[0],
                        strerror(failed_errno = errno));
 +
 +      /*
 +       * Wait for child's execvp. If the execvp succeeds (or if fork()
 +       * failed), EOF is seen immediately by the parent. Otherwise, the
 +       * child process sends a single byte.
 +       * Note that use of this infrastructure is completely advisory,
 +       * therefore, we keep error checks minimal.
 +       */
 +      close(notify_pipe[1]);
 +      if (read(notify_pipe[0], &notify_pipe[1], 1) == 1) {
 +              /*
 +               * At this point we know that fork() succeeded, but execvp()
 +               * failed. Errors have been reported to our stderr.
 +               */
 +              wait_or_whine(cmd->pid, cmd->argv[0],
 +                            cmd->silent_exec_failure);
 +              failed_errno = errno;
 +              cmd->pid = -1;
 +      }
 +      close(notify_pipe[0]);
 +}
  #else
  {
 -      int s0 = -1, s1 = -1, s2 = -1;  /* backups of stdin, stdout, stderr */
 +      int fhin = 0, fhout = 1, fherr = 2;
        const char **sargv = cmd->argv;
        char **env = environ;
  
 -      if (cmd->no_stdin) {
 -              s0 = dup(0);
 -              dup_devnull(0);
 -      } else if (need_in) {
 -              s0 = dup(0);
 -              dup2(fdin[0], 0);
 -      } else if (cmd->in) {
 -              s0 = dup(0);
 -              dup2(cmd->in, 0);
 -      }
 -
 -      if (cmd->no_stderr) {
 -              s2 = dup(2);
 -              dup_devnull(2);
 -      } else if (need_err) {
 -              s2 = dup(2);
 -              dup2(fderr[1], 2);
 -      } else if (cmd->err > 2) {
 -              s2 = dup(2);
 -              dup2(cmd->err, 2);
 -      }
 -
 -      if (cmd->no_stdout) {
 -              s1 = dup(1);
 -              dup_devnull(1);
 -      } else if (cmd->stdout_to_stderr) {
 -              s1 = dup(1);
 -              dup2(2, 1);
 -      } else if (need_out) {
 -              s1 = dup(1);
 -              dup2(fdout[1], 1);
 -      } else if (cmd->out > 1) {
 -              s1 = dup(1);
 -              dup2(cmd->out, 1);
 -      }
 +      if (cmd->no_stdin)
 +              fhin = open("/dev/null", O_RDWR);
 +      else if (need_in)
 +              fhin = dup(fdin[0]);
 +      else if (cmd->in)
 +              fhin = dup(cmd->in);
 +
 +      if (cmd->no_stderr)
 +              fherr = open("/dev/null", O_RDWR);
 +      else if (need_err)
 +              fherr = dup(fderr[1]);
++      else if (cmd->err > 2)
++              fherr = dup(cmd->err);
 +
 +      if (cmd->no_stdout)
 +              fhout = open("/dev/null", O_RDWR);
 +      else if (cmd->stdout_to_stderr)
 +              fhout = dup(fherr);
 +      else if (need_out)
 +              fhout = dup(fdout[1]);
 +      else if (cmd->out > 1)
 +              fhout = dup(cmd->out);
  
        if (cmd->dir)
                die("chdir in start_command() not implemented");
  
        if (cmd->git_cmd) {
                cmd->argv = prepare_git_cmd(cmd->argv);
 +      } else if (cmd->use_shell) {
 +              cmd->argv = prepare_shell_cmd(cmd->argv);
        }
  
 -      cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env);
 +      cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, env,
 +                                fhin, fhout, fherr);
        failed_errno = errno;
        if (cmd->pid < 0 && (!cmd->silent_exec_failure || errno != ENOENT))
                error("cannot spawn %s: %s", cmd->argv[0], strerror(errno));
                free(cmd->argv);
  
        cmd->argv = sargv;
 -      if (s0 >= 0)
 -              dup2(s0, 0), close(s0);
 -      if (s1 >= 0)
 -              dup2(s1, 1), close(s1);
 -      if (s2 >= 0)
 -              dup2(s2, 2), close(s2);
 +      if (fhin != 0)
 +              close(fhin);
 +      if (fhout != 1)
 +              close(fhout);
 +      if (fherr != 2)
 +              close(fherr);
  }
  #endif
  
  
        if (need_err)
                close(fderr[1]);
+       else if (cmd->err)
+               close(cmd->err);
  
        return 0;
  }
  
 -static int wait_or_whine(pid_t pid, const char *argv0, int silent_exec_failure)
 -{
 -      int status, code = -1;
 -      pid_t waiting;
 -      int failed_errno = 0;
 -
 -      while ((waiting = waitpid(pid, &status, 0)) < 0 && errno == EINTR)
 -              ;       /* nothing */
 -
 -      if (waiting < 0) {
 -              failed_errno = errno;
 -              error("waitpid for %s failed: %s", argv0, strerror(errno));
 -      } else if (waiting != pid) {
 -              error("waitpid is confused (%s)", argv0);
 -      } else if (WIFSIGNALED(status)) {
 -              code = WTERMSIG(status);
 -              error("%s died of signal %d", argv0, code);
 -              /*
 -               * This return value is chosen so that code & 0xff
 -               * mimics the exit code that a POSIX shell would report for
 -               * a program that died from this signal.
 -               */
 -              code -= 128;
 -      } else if (WIFEXITED(status)) {
 -              code = WEXITSTATUS(status);
 -              /*
 -               * Convert special exit code when execvp failed.
 -               */
 -              if (code == 127) {
 -                      code = -1;
 -                      failed_errno = ENOENT;
 -                      if (!silent_exec_failure)
 -                              error("cannot run %s: %s", argv0,
 -                                      strerror(ENOENT));
 -              }
 -      } else {
 -              error("waitpid is confused (%s)", argv0);
 -      }
 -      errno = failed_errno;
 -      return code;
 -}
 -
  int finish_command(struct child_process *cmd)
  {
        return wait_or_whine(cmd->pid, cmd->argv[0], cmd->silent_exec_failure);
@@@ -421,7 -305,6 +428,7 @@@ static void prepare_run_command_v_opt(s
        cmd->git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
        cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
        cmd->silent_exec_failure = opt & RUN_SILENT_EXEC_FAILURE ? 1 : 0;
 +      cmd->use_shell = opt & RUN_USING_SHELL ? 1 : 0;
  }
  
  int run_command_v_opt(const char **argv, int opt)
@@@ -444,17 -327,51 +451,51 @@@ int run_command_v_opt_cd_env(const cha
  static unsigned __stdcall run_thread(void *data)
  {
        struct async *async = data;
-       return async->proc(async->fd_for_proc, async->data);
+       return async->proc(async->proc_in, async->proc_out, async->data);
  }
  #endif
  
  int start_async(struct async *async)
  {
-       int pipe_out[2];
+       int need_in, need_out;
+       int fdin[2], fdout[2];
+       int proc_in, proc_out;
  
-       if (pipe(pipe_out) < 0)
-               return error("cannot create pipe: %s", strerror(errno));
-       async->out = pipe_out[0];
+       need_in = async->in < 0;
+       if (need_in) {
+               if (pipe(fdin) < 0) {
+                       if (async->out > 0)
+                               close(async->out);
+                       return error("cannot create pipe: %s", strerror(errno));
+               }
+               async->in = fdin[1];
+       }
+       need_out = async->out < 0;
+       if (need_out) {
+               if (pipe(fdout) < 0) {
+                       if (need_in)
+                               close_pair(fdin);
+                       else if (async->in)
+                               close(async->in);
+                       return error("cannot create pipe: %s", strerror(errno));
+               }
+               async->out = fdout[0];
+       }
+       if (need_in)
+               proc_in = fdin[0];
+       else if (async->in)
+               proc_in = async->in;
+       else
+               proc_in = -1;
+       if (need_out)
+               proc_out = fdout[1];
+       else if (async->out)
+               proc_out = async->out;
+       else
+               proc_out = -1;
  
  #ifndef WIN32
        /* Flush stdio before fork() to avoid cloning buffers */
        async->pid = fork();
        if (async->pid < 0) {
                error("fork (async) failed: %s", strerror(errno));
-               close_pair(pipe_out);
-               return -1;
+               goto error;
        }
        if (!async->pid) {
-               close(pipe_out[0]);
-               exit(!!async->proc(pipe_out[1], async->data));
+               if (need_in)
+                       close(fdin[1]);
+               if (need_out)
+                       close(fdout[0]);
+               exit(!!async->proc(proc_in, proc_out, async->data));
        }
-       close(pipe_out[1]);
+       if (need_in)
+               close(fdin[0]);
+       else if (async->in)
+               close(async->in);
+       if (need_out)
+               close(fdout[1]);
+       else if (async->out)
+               close(async->out);
  #else
-       async->fd_for_proc = pipe_out[1];
+       async->proc_in = proc_in;
+       async->proc_out = proc_out;
        async->tid = (HANDLE) _beginthreadex(NULL, 0, run_thread, async, 0, NULL);
        if (!async->tid) {
                error("cannot create thread: %s", strerror(errno));
-               close_pair(pipe_out);
-               return -1;
+               goto error;
        }
  #endif
        return 0;
+ error:
+       if (need_in)
+               close_pair(fdin);
+       else if (async->in)
+               close(async->in);
+       if (need_out)
+               close_pair(fdout);
+       else if (async->out)
+               close(async->out);
+       return -1;
  }
  
  int finish_async(struct async *async)
diff --combined run-command.h
index 967ba8cc09786934724132b629587419f195b245,65ccb1c60f425d3fb612592178ac8cfd12a8c027..94619f52d95888b320664b7f19db3eeb7d6d8cca
@@@ -18,7 -18,7 +18,7 @@@ struct child_process 
         * - Specify > 0 to set a channel to a particular FD as follows:
         *     .in: a readable FD, becomes child's stdin
         *     .out: a writable FD, becomes child's stdout/stderr
-        *     .err > 0 not supported
+        *     .err: a writable FD, becomes child's stderr
         *   The specified FD is closed by start_command(), even in case
         *   of errors!
         */
@@@ -33,7 -33,6 +33,7 @@@
        unsigned git_cmd:1; /* if this is to be git sub-command */
        unsigned silent_exec_failure:1;
        unsigned stdout_to_stderr:1;
 +      unsigned use_shell:1;
        void (*preexec_cb)(void);
  };
  
@@@ -47,7 -46,6 +47,7 @@@ extern int run_hook(const char *index_f
  #define RUN_GIT_CMD        2  /*If this is to be git sub-command */
  #define RUN_COMMAND_STDOUT_TO_STDERR 4
  #define RUN_SILENT_EXEC_FAILURE 8
 +#define RUN_USING_SHELL 16
  int run_command_v_opt(const char **argv, int opt);
  
  /*
@@@ -66,17 -64,20 +66,20 @@@ int run_command_v_opt_cd_env(const cha
   */
  struct async {
        /*
-        * proc writes to fd and closes it;
+        * proc reads from in; closes it before return
+        * proc writes to out; closes it before return
         * returns 0 on success, non-zero on failure
         */
-       int (*proc)(int fd, void *data);
+       int (*proc)(int in, int out, void *data);
        void *data;
+       int in;         /* caller writes here and closes it */
        int out;        /* caller reads from here and closes it */
  #ifndef WIN32
        pid_t pid;
  #else
        HANDLE tid;
-       int fd_for_proc;
+       int proc_in;
+       int proc_out;
  #endif
  };
  
diff --combined t/t5401-update-hooks.sh
index 325714e5299a5b59157c3741e7fa0d98d70b9990,c3cf397b030e298b0fafdf8fc8a9bbf88f7d6bf4..0686390d2f107fec3ce7605993c897d822a9d291
@@@ -18,7 -18,6 +18,7 @@@ test_expect_success setup 
        git update-ref refs/heads/master $commit0 &&
        git update-ref refs/heads/tofail $commit1 &&
        git clone ./. victim &&
 +      GIT_DIR=victim/.git git config receive.denyCurrentBranch warn &&
        GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 &&
        git update-ref refs/heads/master $commit1 &&
        git update-ref refs/heads/tofail $commit0
@@@ -119,19 -118,19 +119,19 @@@ test_expect_success 'send-pack produce
  '
  
  cat <<EOF >expect
- STDOUT pre-receive
- STDERR pre-receive
- STDOUT update refs/heads/master
- STDERR update refs/heads/master
- STDOUT update refs/heads/tofail
- STDERR update refs/heads/tofail
- STDOUT post-receive
- STDERR post-receive
- STDOUT post-update
- STDERR post-update
remote: STDOUT pre-receive
remote: STDERR pre-receive
remote: STDOUT update refs/heads/master
remote: STDERR update refs/heads/master
remote: STDOUT update refs/heads/tofail
remote: STDERR update refs/heads/tofail
remote: STDOUT post-receive
remote: STDERR post-receive
remote: STDOUT post-update
remote: STDERR post-update
  EOF
  test_expect_success 'send-pack stderr contains hook messages' '
-       grep ^STD send.err >actual &&
+       grep ^remote: send.err | sed "s/ *\$//" >actual &&
        test_cmp - actual <expect
  '