i18n: unpack-trees: avoid substituting only a verb in sentences
[gitweb.git] / send-pack.c
index 97ab336097bfb45c63aec739a6eef2df26cc4790..047bd18fde3d09921902ebb656ab78bfbcf940a8 100644 (file)
@@ -5,10 +5,36 @@
 #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"
+#include "cache.h"
+
+int option_parse_push_signed(const struct option *opt,
+                            const char *arg, int unset)
+{
+       if (unset) {
+               *(int *)(opt->value) = SEND_PACK_PUSH_CERT_NEVER;
+               return 0;
+       }
+       switch (git_parse_maybe_bool(arg)) {
+       case 1:
+               *(int *)(opt->value) = SEND_PACK_PUSH_CERT_ALWAYS;
+               return 0;
+       case 0:
+               *(int *)(opt->value) = SEND_PACK_PUSH_CERT_NEVER;
+               return 0;
+       }
+       if (!strcasecmp("if-asked", arg)) {
+               *(int *)(opt->value) = SEND_PACK_PUSH_CERT_IF_ASKED;
+               return 0;
+       }
+       die("bad %s argument: %s", opt->long_name, arg);
+}
 
 static int feed_object(const unsigned char *sha1, int fd, int negative)
 {
@@ -27,7 +53,7 @@ static int feed_object(const unsigned char *sha1, int fd, int negative)
 /*
  * Make a pack stream and spit it out into file descriptor fd
  */
-static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
+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
@@ -44,8 +70,9 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
                NULL,
                NULL,
                NULL,
+               NULL,
        };
-       struct child_process po;
+       struct child_process po = CHILD_PROCESS_INIT;
        int i;
 
        i = 4;
@@ -57,7 +84,8 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
                argv[i++] = "-q";
        if (args->progress)
                argv[i++] = "--progress";
-       memset(&po, 0, sizeof(po));
+       if (is_repository_shallow())
+               argv[i++] = "--shallow";
        po.argv = argv;
        po.in = -1;
        po.out = args->stateless_rpc ? -1 : fd;
@@ -70,15 +98,15 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
         * parameters by writing to the pipe.
         */
        for (i = 0; i < extra->nr; i++)
-               if (!feed_object(extra->array[i], po.in, 1))
+               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))
+               if (!is_null_oid(&refs->old_oid) &&
+                   !feed_object(refs->old_oid.hash, po.in, 1))
                        break;
-               if (!is_null_sha1(refs->new_sha1) &&
-                   !feed_object(refs->new_sha1, po.in, 0))
+               if (!is_null_oid(&refs->new_oid) &&
+                   !feed_object(refs->new_oid.hash, po.in, 0))
                        break;
                refs = refs->next;
        }
@@ -106,15 +134,11 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
 static int receive_status(int in, struct ref *refs)
 {
        struct ref *hint;
-       char line[1000];
        int ret = 0;
-       int len = packet_read_line(in, line, sizeof(line));
-       if (len < 10 || memcmp(line, "unpack ", 7))
+       char *line = packet_read_line(in, NULL);
+       if (!starts_with(line, "unpack "))
                return error("did not receive remote status");
-       if (memcmp(line, "unpack ok\n", 10)) {
-               char *p = line + strlen(line) - 1;
-               if (*p == '\n')
-                       *p = '\0';
+       if (strcmp(line, "unpack ok")) {
                error("unpack failed: %s", line + 7);
                ret = -1;
        }
@@ -122,17 +146,15 @@ static int receive_status(int in, struct ref *refs)
        while (1) {
                char *refname;
                char *msg;
-               len = packet_read_line(in, line, sizeof(line));
-               if (!len)
+               line = packet_read_line(in, NULL);
+               if (!line)
                        break;
-               if (len < 3 ||
-                   (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
-                       fprintf(stderr, "protocol error: %s\n", line);
+               if (!starts_with(line, "ok ") && !starts_with(line, "ng ")) {
+                       error("invalid ref status from remote: %s", line);
                        ret = -1;
                        break;
                }
 
-               line[strlen(line)-1] = '\0';
                refname = line + 3;
                msg = strchr(refname, ' ');
                if (msg)
@@ -179,24 +201,180 @@ static int sideband_demux(int in, int out, void *data)
        return ret;
 }
 
+static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *cb)
+{
+       struct strbuf *sb = cb;
+       if (graft->nr_parent == -1)
+               packet_buf_write(sb, "shallow %s\n", oid_to_hex(&graft->oid));
+       return 0;
+}
+
+static void advertise_shallow_grafts_buf(struct strbuf *sb)
+{
+       if (!is_repository_shallow())
+               return;
+       for_each_commit_graft(advertise_shallow_grafts_cb, sb);
+}
+
+#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 (!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;
+       }
+}
+
+/*
+ * 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 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",
+                           oid_to_hex(&ref->old_oid),
+                           oid_to_hex(&ref->new_oid),
+                           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;
+       /* Mark other refs as failed */
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (!ref->peer_ref && !args->send_mirror)
+                       continue;
+
+               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);
+}
+
+#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);
+       }
+}
+
 int send_pack(struct send_pack_args *args,
              int fd[], struct child_process *conn,
              struct ref *remote_refs,
-             struct extra_have_objects *extra_have)
+             struct sha1_array *extra_have)
 {
        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"))
@@ -211,80 +389,129 @@ int send_pack(struct send_pack_args *args,
                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 != SEND_PACK_PUSH_CERT_NEVER) {
+               int len;
+               push_cert_nonce = server_feature_value("push-cert", &len);
+               if (push_cert_nonce) {
+                       reject_invalid_nonce(push_cert_nonce, len);
+                       push_cert_nonce = xmemdupz(push_cert_nonce, len);
+               } else if (args->push_cert == SEND_PACK_PUSH_CERT_ALWAYS) {
+                       die(_("the receiving end does not support --signed push"));
+               } else if (args->push_cert == SEND_PACK_PUSH_CERT_IF_ASKED) {
+                       warning(_("not sending a push certificate since the"
+                                 " receiving end does not support --signed"
+                                 " push"));
+               }
+       }
 
        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());
 
        /*
-        * Finally, tell 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???
         */
-       new_refs = 0;
-       for (ref = remote_refs; ref; ref = ref->next) {
-               if (!ref->peer_ref && !args->send_mirror)
-                       continue;
+       for (ref = remote_refs; ref; ref = ref->next)
+               if (ref->deletion && !allow_deleting_refs)
+                       ref->status = REF_STATUS_REJECT_NODELETE;
 
-               /* 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_UPTODATE:
-                       continue;
-               default:
-                       ; /* do nothing */
-               }
+       if (!args->dry_run)
+               advertise_shallow_grafts_buf(&req_buf);
 
-               if (ref->deletion && !allow_deleting_refs) {
-                       ref->status = REF_STATUS_REJECT_NODELETE;
+       if (!args->dry_run && push_cert_nonce)
+               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;
+               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)
-                       new_refs++;
+                       need_pack_data = 1;
 
-               if (args->dry_run) {
+               if (args->dry_run || !status_report)
                        ref->status = REF_STATUS_OK;
+               else
+                       ref->status = REF_STATUS_EXPECTING_REPORT;
+       }
+
+       /*
+        * Finally, tell the other end!
+        */
+       for (ref = remote_refs; ref; ref = ref->next) {
+               char *old_hex, *new_hex;
+
+               if (args->dry_run || push_cert_nonce)
+                       continue;
+
+               if (check_to_send_update(ref, args) < 0)
+                       continue;
+
+               old_hex = oid_to_hex(&ref->old_oid);
+               new_hex = oid_to_hex(&ref->new_oid);
+               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 {
-                       char *old_hex = sha1_to_hex(ref->old_sha1);
-                       char *new_hex = sha1_to_hex(ref->new_sha1);
-                       int quiet = quiet_supported && (args->quiet || !args->progress);
-
-                       if (!cmds_sent && (status_report || use_sideband ||
-                                          quiet || agent_supported)) {
-                               packet_buf_write(&req_buf,
-                                                "%s %s %s%c%s%s%s%s%s",
-                                                old_hex, new_hex, ref->name, 0,
-                                                status_report ? " report-status" : "",
-                                                use_sideband ? " side-band-64k" : "",
-                                                quiet ? " quiet" : "",
-                                                agent_supported ? " agent=" : "",
-                                                agent_supported ? git_user_agent_sanitized() : ""
-                                               );
-                       }
-                       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++;
+                       packet_buf_write(&req_buf, "%s %s %s",
+                                        old_hex, new_hex, ref->name);
                }
        }
 
        if (args->stateless_rpc) {
-               if (!args->dry_run && cmds_sent) {
+               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);
                }
        } else {
-               safe_write(out, req_buf.buf, req_buf.len);
+               write_or_die(out, req_buf.buf, req_buf.len);
                packet_flush(out);
        }
        strbuf_release(&req_buf);
+       strbuf_release(&cap_buf);
 
        if (use_sideband && cmds_sent) {
                memset(&demux, 0, sizeof(demux));
@@ -296,7 +523,7 @@ int send_pack(struct send_pack_args *args,
                in = demux.out;
        }
 
-       if (new_refs && cmds_sent) {
+       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;
@@ -306,8 +533,12 @@ int send_pack(struct send_pack_args *args,
                                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);