git: simplify environment save/restore logic
[gitweb.git] / send-pack.c
index 6dc8a460c2165756d839788d98f42d5121dc4ff6..2a64fec949ea9495b5b9512399cec7979319de1c 100644 (file)
@@ -11,6 +11,7 @@
 #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)
 {
@@ -46,8 +47,9 @@ static int pack_objects(int fd, struct ref *refs, struct sha1_array *extra, stru
                NULL,
                NULL,
                NULL,
+               NULL,
        };
-       struct child_process po;
+       struct child_process po = CHILD_PROCESS_INIT;
        int i;
 
        i = 4;
@@ -59,7 +61,8 @@ static int pack_objects(int fd, struct ref *refs, struct sha1_array *extra, stru
                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;
@@ -179,7 +182,7 @@ static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *c
 {
        struct strbuf *sb = cb;
        if (graft->nr_parent == -1)
-               packet_buf_write(sb, "shallow %s\n", sha1_to_hex(graft->sha1));
+               packet_buf_write(sb, "shallow %s\n", oid_to_hex(&graft->oid));
        return 0;
 }
 
@@ -190,10 +193,13 @@ static void advertise_shallow_grafts_buf(struct strbuf *sb)
        for_each_commit_graft(advertise_shallow_grafts_cb, sb);
 }
 
-static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args)
+#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 0;
+               return CHECK_REF_NO_PUSH;
 
        /* Check for statuses set by set_ref_status_for_push() */
        switch (ref->status) {
@@ -203,10 +209,124 @@ static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_a
        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 0;
+               return CHECK_REF_UPTODATE;
        default:
-               return 1;
+               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",
+                           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;
+       /* 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);
        }
 }
 
@@ -226,9 +346,12 @@ int send_pack(struct send_pack_args *args,
        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"))
@@ -245,12 +368,27 @@ int send_pack(struct send_pack_args *args,
                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");
@@ -258,6 +396,8 @@ int send_pack(struct send_pack_args *args,
                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());
 
@@ -273,14 +413,30 @@ int send_pack(struct send_pack_args *args,
        if (!args->dry_run)
                advertise_shallow_grafts_buf(&req_buf);
 
+       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) {
-               if (!ref_update_to_be_sent(ref, args))
+               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)
                        need_pack_data = 1;
 
@@ -296,23 +452,24 @@ int send_pack(struct send_pack_args *args,
        for (ref = remote_refs; ref; ref = ref->next) {
                char *old_hex, *new_hex;
 
-               if (args->dry_run)
+               if (args->dry_run || args->push_cert)
                        continue;
 
-               if (!ref_update_to_be_sent(ref, args))
+               if (check_to_send_update(ref, args) < 0)
                        continue;
 
                old_hex = sha1_to_hex(ref->old_sha1);
                new_hex = sha1_to_hex(ref->new_sha1);
-               if (!cmds_sent)
+               if (!cmds_sent) {
                        packet_buf_write(&req_buf,
                                         "%s %s %s%c%s",
                                         old_hex, new_hex, ref->name, 0,
                                         cap_buf.buf);
-               else
+                       cmds_sent = 1;
+               } else {
                        packet_buf_write(&req_buf, "%s %s %s",
                                         old_hex, new_hex, ref->name);
-               cmds_sent++;
+               }
        }
 
        if (args->stateless_rpc) {