test_terminal: redirect child process' stdin to a pty
[gitweb.git] / send-pack.c
index 02d1d52c2475759f2b206912bbd626f749a6b750..2a64fec949ea9495b5b9512399cec7979319de1c 100644 (file)
-#include "cache.h"
+#include "builtin.h"
+#include "commit.h"
 #include "refs.h"
 #include "pkt-line.h"
+#include "sideband.h"
+#include "run-command.h"
+#include "remote.h"
+#include "connect.h"
+#include "send-pack.h"
+#include "quote.h"
+#include "transport.h"
+#include "version.h"
+#include "sha1-array.h"
+#include "gpg-interface.h"
+
+static int feed_object(const unsigned char *sha1, int fd, int negative)
+{
+       char buf[42];
 
-static const char send_pack_usage[] =
-"git-send-pack [--exec=git-receive-pack] [host:]directory [heads]*";
-static const char *exec = "git-receive-pack";
+       if (negative && !has_sha1_file(sha1))
+               return 1;
 
-struct ref {
-       struct ref *next;
-       unsigned char old_sha1[20];
-       unsigned char new_sha1[20];
-       char name[0];
-};
+       memcpy(buf + negative, sha1_to_hex(sha1), 40);
+       if (negative)
+               buf[0] = '^';
+       buf[40 + negative] = '\n';
+       return write_or_whine(fd, buf, 41 + negative, "send-pack: send refs");
+}
 
-static int is_zero_sha1(const unsigned char *sha1)
+/*
+ * Make a pack stream and spit it out into file descriptor fd
+ */
+static int pack_objects(int fd, struct ref *refs, struct sha1_array *extra, struct send_pack_args *args)
 {
+       /*
+        * The child becomes pack-objects --revs; we feed
+        * the revision parameters to it via its stdin and
+        * let its stdout go back to the other end.
+        */
+       const char *argv[] = {
+               "pack-objects",
+               "--all-progress-implied",
+               "--revs",
+               "--stdout",
+               NULL,
+               NULL,
+               NULL,
+               NULL,
+               NULL,
+               NULL,
+       };
+       struct child_process po = CHILD_PROCESS_INIT;
        int i;
 
-       for (i = 0; i < 20; i++) {
-               if (*sha1++)
-                       return 0;
+       i = 4;
+       if (args->use_thin_pack)
+               argv[i++] = "--thin";
+       if (args->use_ofs_delta)
+               argv[i++] = "--delta-base-offset";
+       if (args->quiet || !args->progress)
+               argv[i++] = "-q";
+       if (args->progress)
+               argv[i++] = "--progress";
+       if (is_repository_shallow())
+               argv[i++] = "--shallow";
+       po.argv = argv;
+       po.in = -1;
+       po.out = args->stateless_rpc ? -1 : fd;
+       po.git_cmd = 1;
+       if (start_command(&po))
+               die_errno("git pack-objects failed");
+
+       /*
+        * We feed the pack-objects we just spawned with revision
+        * parameters by writing to the pipe.
+        */
+       for (i = 0; i < extra->nr; i++)
+               if (!feed_object(extra->sha1[i], po.in, 1))
+                       break;
+
+       while (refs) {
+               if (!is_null_sha1(refs->old_sha1) &&
+                   !feed_object(refs->old_sha1, po.in, 1))
+                       break;
+               if (!is_null_sha1(refs->new_sha1) &&
+                   !feed_object(refs->new_sha1, po.in, 0))
+                       break;
+               refs = refs->next;
        }
-       return 1;
-}
 
-static void exec_pack_objects(void)
-{
-       static char *args[] = {
-               "git-pack-objects",
-               "--stdout",
-               NULL
-       };
-       execvp("git-pack-objects", args);
-       die("git-pack-objects exec failed (%s)", strerror(errno));
+       close(po.in);
+
+       if (args->stateless_rpc) {
+               char *buf = xmalloc(LARGE_PACKET_MAX);
+               while (1) {
+                       ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
+                       if (n <= 0)
+                               break;
+                       send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
+               }
+               free(buf);
+               close(po.out);
+               po.out = -1;
+       }
+
+       if (finish_command(&po))
+               return -1;
+       return 0;
 }
 
-static void exec_rev_list(struct ref *refs)
+static int receive_status(int in, struct ref *refs)
 {
-       static char *args[1000];
-       int i = 0;
+       struct ref *hint;
+       int ret = 0;
+       char *line = packet_read_line(in, NULL);
+       if (!starts_with(line, "unpack "))
+               return error("did not receive remote status");
+       if (strcmp(line, "unpack ok")) {
+               error("unpack failed: %s", line + 7);
+               ret = -1;
+       }
+       hint = NULL;
+       while (1) {
+               char *refname;
+               char *msg;
+               line = packet_read_line(in, NULL);
+               if (!line)
+                       break;
+               if (!starts_with(line, "ok ") && !starts_with(line, "ng ")) {
+                       error("invalid ref status from remote: %s", line);
+                       ret = -1;
+                       break;
+               }
 
-       args[i++] = "git-rev-list";     /* 0 */
-       args[i++] = "--objects";        /* 1 */
-       while (refs) {
-               char *buf = malloc(100);
-               if (i > 900)
-                       die("git-rev-list environment overflow");
-               if (!is_zero_sha1(refs->old_sha1)) {
-                       args[i++] = buf;
-                       snprintf(buf, 50, "^%s", sha1_to_hex(refs->old_sha1));
-                       buf += 50;
+               refname = line + 3;
+               msg = strchr(refname, ' ');
+               if (msg)
+                       *msg++ = '\0';
+
+               /* first try searching at our hint, falling back to all refs */
+               if (hint)
+                       hint = find_ref_by_name(hint, refname);
+               if (!hint)
+                       hint = find_ref_by_name(refs, refname);
+               if (!hint) {
+                       warning("remote reported status on unknown ref: %s",
+                                       refname);
+                       continue;
                }
-               if (!is_zero_sha1(refs->new_sha1)) {
-                       args[i++] = buf;
-                       snprintf(buf, 50, "%s", sha1_to_hex(refs->new_sha1));
+               if (hint->status != REF_STATUS_EXPECTING_REPORT) {
+                       warning("remote reported status on unexpected ref: %s",
+                                       refname);
+                       continue;
                }
-               refs = refs->next;
+
+               if (line[0] == 'o' && line[1] == 'k')
+                       hint->status = REF_STATUS_OK;
+               else {
+                       hint->status = REF_STATUS_REMOTE_REJECT;
+                       ret = -1;
+               }
+               if (msg)
+                       hint->remote_status = xstrdup(msg);
+               /* start our next search from the next ref */
+               hint = hint->next;
        }
-       args[i] = NULL;
-       execvp("git-rev-list", args);
-       die("git-rev-list exec failed (%s)", strerror(errno));
+       return ret;
 }
 
-static void rev_list(int fd, struct ref *refs)
+static int sideband_demux(int in, int out, void *data)
 {
-       int pipe_fd[2];
-       pid_t pack_objects_pid;
-
-       if (pipe(pipe_fd) < 0)
-               die("rev-list setup: pipe failed");
-       pack_objects_pid = fork();
-       if (!pack_objects_pid) {
-               dup2(pipe_fd[0], 0);
-               dup2(fd, 1);
-               close(pipe_fd[0]);
-               close(pipe_fd[1]);
-               close(fd);
-               exec_pack_objects();
-               die("pack-objects setup failed");
-       }
-       if (pack_objects_pid < 0)
-               die("pack-objects fork failed");
-       dup2(pipe_fd[1], 1);
-       close(pipe_fd[0]);
-       close(pipe_fd[1]);
-       close(fd);
-       exec_rev_list(refs);
+       int *fd = data, ret;
+#ifdef NO_PTHREADS
+       close(fd[1]);
+#endif
+       ret = recv_sideband("send-pack", fd[0], out);
+       close(out);
+       return ret;
 }
 
-static int pack_objects(int fd, struct ref *refs)
+static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *cb)
 {
-       pid_t rev_list_pid;
-
-       rev_list_pid = fork();
-       if (!rev_list_pid) {
-               rev_list(fd, refs);
-               die("rev-list setup failed");
-       }
-       if (rev_list_pid < 0)
-               die("rev-list fork failed");
-       /*
-        * We don't wait for the rev-list pipeline in the parent:
-        * we end up waiting for the other end instead
-        */
+       struct strbuf *sb = cb;
+       if (graft->nr_parent == -1)
+               packet_buf_write(sb, "shallow %s\n", oid_to_hex(&graft->oid));
        return 0;
 }
 
-static int read_ref(const char *ref, unsigned char *sha1)
+static void advertise_shallow_grafts_buf(struct strbuf *sb)
 {
-       int fd, ret;
-       char buffer[60];
-
-       fd = open(git_path("%s", ref), O_RDONLY);
-       if (fd < 0)
-               return -1;
-       ret = -1;
-       if (read(fd, buffer, sizeof(buffer)) >= 40)
-               ret = get_sha1_hex(buffer, sha1);
-       close(fd);
-       return ret;
+       if (!is_repository_shallow())
+               return;
+       for_each_commit_graft(advertise_shallow_grafts_cb, sb);
 }
 
-static int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
+#define CHECK_REF_NO_PUSH -1
+#define CHECK_REF_STATUS_REJECTED -2
+#define CHECK_REF_UPTODATE -3
+static int check_to_send_update(const struct ref *ref, const struct send_pack_args *args)
 {
-       if (!has_sha1_file(old_sha1))
+       if (!ref->peer_ref && !args->send_mirror)
+               return CHECK_REF_NO_PUSH;
+
+       /* Check for statuses set by set_ref_status_for_push() */
+       switch (ref->status) {
+       case REF_STATUS_REJECT_NONFASTFORWARD:
+       case REF_STATUS_REJECT_ALREADY_EXISTS:
+       case REF_STATUS_REJECT_FETCH_FIRST:
+       case REF_STATUS_REJECT_NEEDS_FORCE:
+       case REF_STATUS_REJECT_STALE:
+       case REF_STATUS_REJECT_NODELETE:
+               return CHECK_REF_STATUS_REJECTED;
+       case REF_STATUS_UPTODATE:
+               return CHECK_REF_UPTODATE;
+       default:
                return 0;
-       /*
-        * FIXME! It is not correct to say that the new one is newer
-        * just because we don't have the old one!
-        *
-        * We should really see if we can reach the old_sha1 commit
-        * from the new_sha1 one.
-        */
-       return 1;
+       }
 }
 
-static int local_ref_nr_match;
-static char **local_ref_match;
-static struct ref **local_ref_list;
+/*
+ * the beginning of the next line, or the end of buffer.
+ *
+ * NEEDSWORK: perhaps move this to git-compat-util.h or somewhere and
+ * convert many similar uses found by "git grep -A4 memchr".
+ */
+static const char *next_line(const char *line, size_t len)
+{
+       const char *nl = memchr(line, '\n', len);
+       if (!nl)
+               return line + len; /* incomplete line */
+       return nl + 1;
+}
 
-static int try_to_match(const char *refname, const unsigned char *sha1)
+static int generate_push_cert(struct strbuf *req_buf,
+                             const struct ref *remote_refs,
+                             struct send_pack_args *args,
+                             const char *cap_string,
+                             const char *push_cert_nonce)
+{
+       const struct ref *ref;
+       char *signing_key = xstrdup(get_signing_key());
+       const char *cp, *np;
+       struct strbuf cert = STRBUF_INIT;
+       int update_seen = 0;
+
+       strbuf_addf(&cert, "certificate version 0.1\n");
+       strbuf_addf(&cert, "pusher %s ", signing_key);
+       datestamp(&cert);
+       strbuf_addch(&cert, '\n');
+       if (args->url && *args->url) {
+               char *anon_url = transport_anonymize_url(args->url);
+               strbuf_addf(&cert, "pushee %s\n", anon_url);
+               free(anon_url);
+       }
+       if (push_cert_nonce[0])
+               strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
+       strbuf_addstr(&cert, "\n");
+
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (check_to_send_update(ref, args) < 0)
+                       continue;
+               update_seen = 1;
+               strbuf_addf(&cert, "%s %s %s\n",
+                           sha1_to_hex(ref->old_sha1),
+                           sha1_to_hex(ref->new_sha1),
+                           ref->name);
+       }
+       if (!update_seen)
+               goto free_return;
+
+       if (sign_buffer(&cert, &cert, signing_key))
+               die(_("failed to sign the push certificate"));
+
+       packet_buf_write(req_buf, "push-cert%c%s", 0, cap_string);
+       for (cp = cert.buf; cp < cert.buf + cert.len; cp = np) {
+               np = next_line(cp, cert.buf + cert.len - cp);
+               packet_buf_write(req_buf,
+                                "%.*s", (int)(np - cp), cp);
+       }
+       packet_buf_write(req_buf, "push-cert-end\n");
+
+free_return:
+       free(signing_key);
+       strbuf_release(&cert);
+       return update_seen;
+}
+
+
+static int atomic_push_failure(struct send_pack_args *args,
+                              struct ref *remote_refs,
+                              struct ref *failing_ref)
 {
        struct ref *ref;
-       int len;
+       /* Mark other refs as failed */
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (!ref->peer_ref && !args->send_mirror)
+                       continue;
 
-       if (!path_match(refname, local_ref_nr_match, local_ref_match))
-               return 0;
+               switch (ref->status) {
+               case REF_STATUS_EXPECTING_REPORT:
+                       ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+                       continue;
+               default:
+                       break; /* do nothing */
+               }
+       }
+       return error("atomic push failed for ref %s. status: %d\n",
+                    failing_ref->name, failing_ref->status);
+}
 
-       len = strlen(refname)+1;
-       ref = xmalloc(sizeof(*ref) + len);
-       memset(ref->old_sha1, 0, 20);
-       memcpy(ref->new_sha1, sha1, 20);
-       memcpy(ref->name, refname, len);
-       ref->next = NULL;
-       *local_ref_list = ref;
-       local_ref_list = &ref->next;
-       return 0;
+#define NONCE_LEN_LIMIT 256
+
+static void reject_invalid_nonce(const char *nonce, int len)
+{
+       int i = 0;
+
+       if (NONCE_LEN_LIMIT <= len)
+               die("the receiving end asked to sign an invalid nonce <%.*s>",
+                   len, nonce);
+
+       for (i = 0; i < len; i++) {
+               int ch = nonce[i] & 0xFF;
+               if (isalnum(ch) ||
+                   ch == '-' || ch == '.' ||
+                   ch == '/' || ch == '+' ||
+                   ch == '=' || ch == '_')
+                       continue;
+               die("the receiving end asked to sign an invalid nonce <%.*s>",
+                   len, nonce);
+       }
 }
 
-static int send_pack(int in, int out, int nr_match, char **match)
+int send_pack(struct send_pack_args *args,
+             int fd[], struct child_process *conn,
+             struct ref *remote_refs,
+             struct sha1_array *extra_have)
 {
-       struct ref *ref_list = NULL, **last_ref = &ref_list;
+       int in = fd[0];
+       int out = fd[1];
+       struct strbuf req_buf = STRBUF_INIT;
+       struct strbuf cap_buf = STRBUF_INIT;
        struct ref *ref;
-       int new_refs;
+       int need_pack_data = 0;
+       int allow_deleting_refs = 0;
+       int status_report = 0;
+       int use_sideband = 0;
+       int quiet_supported = 0;
+       int agent_supported = 0;
+       int use_atomic = 0;
+       int atomic_supported = 0;
+       unsigned cmds_sent = 0;
+       int ret;
+       struct async demux;
+       const char *push_cert_nonce = NULL;
+
+       /* Does the other end support the reporting? */
+       if (server_supports("report-status"))
+               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 (server_supports("quiet"))
+               quiet_supported = 1;
+       if (server_supports("agent"))
+               agent_supported = 1;
+       if (server_supports("no-thin"))
+               args->use_thin_pack = 0;
+       if (server_supports("atomic"))
+               atomic_supported = 1;
+       if (args->push_cert) {
+               int len;
+
+               push_cert_nonce = server_feature_value("push-cert", &len);
+               if (!push_cert_nonce)
+                       die(_("the receiving end does not support --signed push"));
+               reject_invalid_nonce(push_cert_nonce, len);
+               push_cert_nonce = xmemdupz(push_cert_nonce, len);
+       }
+
+       if (!remote_refs) {
+               fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
+                       "Perhaps you should specify a branch such as 'master'.\n");
+               return 0;
+       }
+       if (args->atomic && !atomic_supported)
+               die(_("the receiving end does not support --atomic push"));
+
+       use_atomic = atomic_supported && args->atomic;
+
+       if (status_report)
+               strbuf_addstr(&cap_buf, " report-status");
+       if (use_sideband)
+               strbuf_addstr(&cap_buf, " side-band-64k");
+       if (quiet_supported && (args->quiet || !args->progress))
+               strbuf_addstr(&cap_buf, " quiet");
+       if (use_atomic)
+               strbuf_addstr(&cap_buf, " atomic");
+       if (agent_supported)
+               strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
 
        /*
-        * Read all the refs from the other end
+        * NEEDSWORK: why does delete-refs have to be so specific to
+        * send-pack machinery that set_ref_status_for_push() cannot
+        * set this bit for us???
         */
-       for (;;) {
-               unsigned char old_sha1[20];
-               static char buffer[1000];
-               char *name;
-               int len;
+       for (ref = remote_refs; ref; ref = ref->next)
+               if (ref->deletion && !allow_deleting_refs)
+                       ref->status = REF_STATUS_REJECT_NODELETE;
+
+       if (!args->dry_run)
+               advertise_shallow_grafts_buf(&req_buf);
 
-               len = packet_read_line(in, buffer, sizeof(buffer));
-               if (!len)
+       if (!args->dry_run && args->push_cert)
+               cmds_sent = generate_push_cert(&req_buf, remote_refs, args,
+                                              cap_buf.buf, push_cert_nonce);
+
+       /*
+        * Clear the status for each ref and see if we need to send
+        * the pack data.
+        */
+       for (ref = remote_refs; ref; ref = ref->next) {
+               switch (check_to_send_update(ref, args)) {
+               case 0: /* no error */
                        break;
-               if (buffer[len-1] == '\n')
-                       buffer[--len] = 0;
-
-               if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
-                       die("protocol error: expected sha/ref, got '%s'", buffer);
-               name = buffer + 41;
-               ref = xmalloc(sizeof(*ref) + len - 40);
-               memcpy(ref->old_sha1, old_sha1, 20);
-               memset(ref->new_sha1, 0, 20);
-               memcpy(ref->name, buffer + 41, len - 40);
-               ref->next = NULL;
-               *last_ref = ref;
-               last_ref = &ref->next;
+               case CHECK_REF_STATUS_REJECTED:
+                       /*
+                        * When we know the server would reject a ref update if
+                        * we were to send it and we're trying to send the refs
+                        * atomically, abort the whole operation.
+                        */
+                       if (use_atomic)
+                               return atomic_push_failure(args, remote_refs, ref);
+                       /* Fallthrough for non atomic case. */
+               default:
+                       continue;
+               }
+               if (!ref->deletion)
+                       need_pack_data = 1;
+
+               if (args->dry_run || !status_report)
+                       ref->status = REF_STATUS_OK;
+               else
+                       ref->status = REF_STATUS_EXPECTING_REPORT;
        }
 
        /*
-        * Go through the refs, see if we want to update
-        * any of them..
+        * Finally, tell the other end!
         */
-       for (ref = ref_list; ref; ref = ref->next) {
-               unsigned char new_sha1[20];
-               char *name = ref->name;
+       for (ref = remote_refs; ref; ref = ref->next) {
+               char *old_hex, *new_hex;
 
-               if (nr_match && !path_match(name, nr_match, match))
+               if (args->dry_run || args->push_cert)
                        continue;
 
-               if (read_ref(name, new_sha1) < 0)
+               if (check_to_send_update(ref, args) < 0)
                        continue;
 
-               if (!memcmp(ref->old_sha1, new_sha1, 20)) {
-                       fprintf(stderr, "'%s' unchanged\n", name);
-                       continue;
+               old_hex = sha1_to_hex(ref->old_sha1);
+               new_hex = sha1_to_hex(ref->new_sha1);
+               if (!cmds_sent) {
+                       packet_buf_write(&req_buf,
+                                        "%s %s %s%c%s",
+                                        old_hex, new_hex, ref->name, 0,
+                                        cap_buf.buf);
+                       cmds_sent = 1;
+               } else {
+                       packet_buf_write(&req_buf, "%s %s %s",
+                                        old_hex, new_hex, ref->name);
                }
+       }
 
-               if (!ref_newer(new_sha1, ref->old_sha1)) {
-                       error("remote '%s' points to object I don't have", name);
-                       continue;
+       if (args->stateless_rpc) {
+               if (!args->dry_run && (cmds_sent || is_repository_shallow())) {
+                       packet_buf_flush(&req_buf);
+                       send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
                }
-
-               /* Ok, mark it for update */
-               memcpy(ref->new_sha1, new_sha1, 20);
+       } else {
+               write_or_die(out, req_buf.buf, req_buf.len);
+               packet_flush(out);
        }
-
-       /*
-        * See if we have any refs that the other end didn't have
-        */
-       if (nr_match) {
-               local_ref_nr_match = nr_match;
-               local_ref_match = match;
-               local_ref_list = last_ref;
-               for_each_ref(try_to_match);
+       strbuf_release(&req_buf);
+       strbuf_release(&cap_buf);
+
+       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("send-pack: unable to fork off sideband demultiplexer");
+               in = demux.out;
        }
 
-       /*
-        * Finally, tell the other end!
-        */
-       new_refs = 0;
-       for (ref = ref_list; ref; ref = ref->next) {
-               char old_hex[60], *new_hex;
-               if (is_zero_sha1(ref->new_sha1))
-                       continue;
-               new_refs++;
-               strcpy(old_hex, sha1_to_hex(ref->old_sha1));
-               new_hex = sha1_to_hex(ref->new_sha1);
-               packet_write(out, "%s %s %s", old_hex, new_hex, ref->name);
-               fprintf(stderr, "'%s': updating from %s to %s\n", ref->name, old_hex, new_hex);
+       if (need_pack_data && 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 (args->stateless_rpc)
+                               close(out);
+                       if (git_connection_is_socket(conn))
+                               shutdown(fd[0], SHUT_WR);
+                       if (use_sideband)
+                               finish_async(&demux);
+                       fd[1] = -1;
+                       return -1;
+               }
+               if (!args->stateless_rpc)
+                       /* Closed by pack_objects() via start_command() */
+                       fd[1] = -1;
+       }
+       if (args->stateless_rpc && cmds_sent)
+               packet_flush(out);
+
+       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);
        }
-       
-       packet_flush(out);
-       if (new_refs)
-               pack_objects(out, ref_list);
-       close(out);
-       return 0;
-}
 
-int main(int argc, char **argv)
-{
-       int i, nr_heads = 0;
-       char *dest = NULL;
-       char **heads = NULL;
-       int fd[2], ret;
-       pid_t pid;
-
-       argv++;
-       for (i = 1; i < argc; i++) {
-               char *arg = *argv++;
-
-               if (*arg == '-') {
-                       if (!strncmp(arg, "--exec=", 7)) {
-                               exec = arg + 7;
-                               continue;
-                       }
-                       usage(send_pack_usage);
+       if (ret < 0)
+               return ret;
+
+       if (args->porcelain)
+               return 0;
+
+       for (ref = remote_refs; ref; ref = ref->next) {
+               switch (ref->status) {
+               case REF_STATUS_NONE:
+               case REF_STATUS_UPTODATE:
+               case REF_STATUS_OK:
+                       break;
+               default:
+                       return -1;
                }
-               dest = arg;
-               heads = argv;
-               nr_heads = argc - i -1;
-               break;
        }
-       if (!dest)
-               usage(send_pack_usage);
-       pid = git_connect(fd, dest, exec);
-       if (pid < 0)
-               return 1;
-       ret = send_pack(fd[0], fd[1], nr_heads, heads);
-       close(fd[0]);
-       close(fd[1]);
-       finish_connect(pid);
-       return ret;
+       return 0;
 }