Windows: avoid the "dup dance" when spawning a child process
[gitweb.git] / builtin-fetch-pack.c
index 67fb80ec48d6d8f0327628afd86fb366d8f97617..8ed4a6feaac2868523e6516e02865132dedbc9f5 100644 (file)
@@ -13,6 +13,7 @@
 static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
 static int unpack_limit = 100;
+static int prefer_ofs_delta = 1;
 static struct fetch_pack_args args = {
        /* .uploadpack = */ "git-upload-pack",
 };
@@ -111,7 +112,7 @@ static void mark_common(struct commit *commit,
   Get the next rev to send, ignoring the common.
 */
 
-static const unsigned charget_rev(void)
+static const unsigned char *get_rev(void)
 {
        struct commit *commit = NULL;
 
@@ -156,6 +157,66 @@ static const unsigned char* get_rev(void)
        return commit->object.sha1;
 }
 
+enum ack_type {
+       NAK = 0,
+       ACK,
+       ACK_continue,
+       ACK_common,
+       ACK_ready
+};
+
+static void consume_shallow_list(int fd)
+{
+       if (args.stateless_rpc && args.depth > 0) {
+               /* If we sent a depth we will get back "duplicate"
+                * shallow and unshallow commands every time there
+                * is a block of have lines exchanged.
+                */
+               char line[1000];
+               while (packet_read_line(fd, line, sizeof(line))) {
+                       if (!prefixcmp(line, "shallow "))
+                               continue;
+                       if (!prefixcmp(line, "unshallow "))
+                               continue;
+                       die("git fetch-pack: expected shallow list");
+               }
+       }
+}
+
+static enum ack_type get_ack(int fd, unsigned char *result_sha1)
+{
+       static char line[1000];
+       int len = packet_read_line(fd, line, sizeof(line));
+
+       if (!len)
+               die("git fetch-pack: expected ACK/NAK, got EOF");
+       if (line[len-1] == '\n')
+               line[--len] = 0;
+       if (!strcmp(line, "NAK"))
+               return NAK;
+       if (!prefixcmp(line, "ACK ")) {
+               if (!get_sha1_hex(line+4, result_sha1)) {
+                       if (strstr(line+45, "continue"))
+                               return ACK_continue;
+                       if (strstr(line+45, "common"))
+                               return ACK_common;
+                       if (strstr(line+45, "ready"))
+                               return ACK_ready;
+                       return ACK;
+               }
+       }
+       die("git fetch_pack: expected ACK/NAK, got '%s'", line);
+}
+
+static void send_request(int fd, struct strbuf *buf)
+{
+       if (args.stateless_rpc) {
+               send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
+               packet_flush(fd);
+       } else
+               safe_write(fd, buf->buf, buf->len);
+}
+
 static int find_common(int fd[2], unsigned char *result_sha1,
                       struct ref *refs)
 {
@@ -164,7 +225,11 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        const unsigned char *sha1;
        unsigned in_vain = 0;
        int got_continue = 0;
+       struct strbuf req_buf = STRBUF_INIT;
+       size_t state_len = 0;
 
+       if (args.stateless_rpc && multi_ack == 1)
+               die("--stateless-rpc requires multi_ack_detailed");
        if (marked)
                for_each_ref(clear_marks, NULL);
        marked = 1;
@@ -174,6 +239,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        fetching = 0;
        for ( ; refs ; refs = refs->next) {
                unsigned char *remote = refs->old_sha1;
+               const char *remote_hex;
                struct object *o;
 
                /*
@@ -191,34 +257,43 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                        continue;
                }
 
-               if (!fetching)
-                       packet_write(fd[1], "want %s%s%s%s%s%s%s%s\n",
-                                    sha1_to_hex(remote),
-                                    (multi_ack ? " multi_ack" : ""),
-                                    (use_sideband == 2 ? " side-band-64k" : ""),
-                                    (use_sideband == 1 ? " side-band" : ""),
-                                    (args.use_thin_pack ? " thin-pack" : ""),
-                                    (args.no_progress ? " no-progress" : ""),
-                                    (args.include_tag ? " include-tag" : ""),
-                                    " ofs-delta");
-               else
-                       packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
+               remote_hex = sha1_to_hex(remote);
+               if (!fetching) {
+                       struct strbuf c = STRBUF_INIT;
+                       if (multi_ack == 2)     strbuf_addstr(&c, " multi_ack_detailed");
+                       if (multi_ack == 1)     strbuf_addstr(&c, " multi_ack");
+                       if (use_sideband == 2)  strbuf_addstr(&c, " side-band-64k");
+                       if (use_sideband == 1)  strbuf_addstr(&c, " side-band");
+                       if (args.use_thin_pack) strbuf_addstr(&c, " thin-pack");
+                       if (args.no_progress)   strbuf_addstr(&c, " no-progress");
+                       if (args.include_tag)   strbuf_addstr(&c, " include-tag");
+                       if (prefer_ofs_delta)   strbuf_addstr(&c, " ofs-delta");
+                       packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf);
+                       strbuf_release(&c);
+               } else
+                       packet_buf_write(&req_buf, "want %s\n", remote_hex);
                fetching++;
        }
+
+       if (!fetching) {
+               strbuf_release(&req_buf);
+               packet_flush(fd[1]);
+               return 1;
+       }
+
        if (is_repository_shallow())
-               write_shallow_commits(fd[1], 1);
+               write_shallow_commits(&req_buf, 1);
        if (args.depth > 0)
-               packet_write(fd[1], "deepen %d", args.depth);
-       packet_flush(fd[1]);
-       if (!fetching)
-               return 1;
+               packet_buf_write(&req_buf, "deepen %d", args.depth);
+       packet_buf_flush(&req_buf);
+       state_len = req_buf.len;
 
        if (args.depth > 0) {
                char line[1024];
                unsigned char sha1[20];
-               int len;
 
-               while ((len = packet_read_line(fd[0], line, sizeof(line)))) {
+               send_request(fd[1], &req_buf);
+               while (packet_read_line(fd[0], line, sizeof(line))) {
                        if (!prefixcmp(line, "shallow ")) {
                                if (get_sha1_hex(line + 8, sha1))
                                        die("invalid shallow line: %s", line);
@@ -239,45 +314,73 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                        }
                        die("expected shallow/unshallow, got %s", line);
                }
+       } else if (!args.stateless_rpc)
+               send_request(fd[1], &req_buf);
+
+       if (!args.stateless_rpc) {
+               /* If we aren't using the stateless-rpc interface
+                * we don't need to retain the headers.
+                */
+               strbuf_setlen(&req_buf, 0);
+               state_len = 0;
        }
 
        flushes = 0;
        retval = -1;
        while ((sha1 = get_rev())) {
-               packet_write(fd[1], "have %s\n", sha1_to_hex(sha1));
+               packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1));
                if (args.verbose)
                        fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
                in_vain++;
                if (!(31 & ++count)) {
                        int ack;
 
-                       packet_flush(fd[1]);
+                       packet_buf_flush(&req_buf);
+                       send_request(fd[1], &req_buf);
+                       strbuf_setlen(&req_buf, state_len);
                        flushes++;
 
                        /*
                         * We keep one window "ahead" of the other side, and
                         * will wait for an ACK only on the next one
                         */
-                       if (count == 32)
+                       if (!args.stateless_rpc && count == 32)
                                continue;
 
+                       consume_shallow_list(fd[0]);
                        do {
                                ack = get_ack(fd[0], result_sha1);
                                if (args.verbose && ack)
                                        fprintf(stderr, "got ack %d %s\n", ack,
                                                        sha1_to_hex(result_sha1));
-                               if (ack == 1) {
+                               switch (ack) {
+                               case ACK:
                                        flushes = 0;
                                        multi_ack = 0;
                                        retval = 0;
                                        goto done;
-                               } else if (ack == 2) {
+                               case ACK_common:
+                               case ACK_ready:
+                               case ACK_continue: {
                                        struct commit *commit =
                                                lookup_commit(result_sha1);
+                                       if (args.stateless_rpc
+                                        && ack == ACK_common
+                                        && !(commit->object.flags & COMMON)) {
+                                               /* We need to replay the have for this object
+                                                * on the next RPC request so the peer knows
+                                                * it is in common with us.
+                                                */
+                                               const char *hex = sha1_to_hex(result_sha1);
+                                               packet_buf_write(&req_buf, "have %s\n", hex);
+                                               state_len = req_buf.len;
+                                       }
                                        mark_common(commit, 0, 1);
                                        retval = 0;
                                        in_vain = 0;
                                        got_continue = 1;
+                                       break;
+                                       }
                                }
                        } while (ack);
                        flushes--;
@@ -289,20 +392,24 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                }
        }
 done:
-       packet_write(fd[1], "done\n");
+       packet_buf_write(&req_buf, "done\n");
+       send_request(fd[1], &req_buf);
        if (args.verbose)
                fprintf(stderr, "done\n");
        if (retval != 0) {
                multi_ack = 0;
                flushes++;
        }
+       strbuf_release(&req_buf);
+
+       consume_shallow_list(fd[0]);
        while (flushes || multi_ack) {
                int ack = get_ack(fd[0], result_sha1);
                if (ack) {
                        if (args.verbose)
                                fprintf(stderr, "got ack (%d) %s\n", ack,
                                        sha1_to_hex(result_sha1));
-                       if (ack == 1)
+                       if (ack == ACK)
                                return 0;
                        multi_ack = 1;
                        continue;
@@ -483,7 +590,9 @@ static int sideband_demux(int fd, void *data)
 {
        int *xd = data;
 
-       return recv_sideband("fetch-pack", xd[0], fd, 2);
+       int ret = recv_sideband("fetch-pack", xd[0], fd);
+       close(fd);
+       return ret;
 }
 
 static int get_pack(int xd[2], char **pack_lockfile)
@@ -582,7 +691,12 @@ static struct ref *do_fetch_pack(int fd[2],
 
        if (is_repository_shallow() && !server_supports("shallow"))
                die("Server does not support shallow clients");
-       if (server_supports("multi_ack")) {
+       if (server_supports("multi_ack_detailed")) {
+               if (args.verbose)
+                       fprintf(stderr, "Server supports multi_ack_detailed\n");
+               multi_ack = 2;
+       }
+       else if (server_supports("multi_ack")) {
                if (args.verbose)
                        fprintf(stderr, "Server supports multi_ack\n");
                multi_ack = 1;
@@ -597,6 +711,11 @@ static struct ref *do_fetch_pack(int fd[2],
                        fprintf(stderr, "Server supports side-band\n");
                use_sideband = 1;
        }
+       if (server_supports("ofs-delta")) {
+               if (args.verbose)
+                       fprintf(stderr, "Server supports ofs-delta\n");
+       } else
+               prefer_ofs_delta = 0;
        if (everything_local(&ref, nr_match, match)) {
                packet_flush(fd[1]);
                goto all_done;
@@ -606,8 +725,10 @@ static struct ref *do_fetch_pack(int fd[2],
                        /* When cloning, it is not unusual to have
                         * no common commit.
                         */
-                       fprintf(stderr, "warning: no common commits\n");
+                       warning("no common commits");
 
+       if (args.stateless_rpc)
+               packet_flush(fd[1]);
        if (get_pack(fd, pack_lockfile))
                die("git fetch-pack: fetch failed.");
 
@@ -649,6 +770,11 @@ static int fetch_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
+               prefer_ofs_delta = git_config_bool(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
@@ -673,6 +799,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        struct ref *ref = NULL;
        char *dest = NULL, **heads;
        int fd[2];
+       char *pack_lockfile = NULL;
+       char **pack_lockfile_ptr = NULL;
        struct child_process *conn;
 
        nr_heads = 0;
@@ -722,6 +850,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
                                args.no_progress = 1;
                                continue;
                        }
+                       if (!strcmp("--stateless-rpc", arg)) {
+                               args.stateless_rpc = 1;
+                               continue;
+                       }
+                       if (!strcmp("--lock-pack", arg)) {
+                               args.lock_pack = 1;
+                               pack_lockfile_ptr = &pack_lockfile;
+                               continue;
+                       }
                        usage(fetch_pack_usage);
                }
                dest = (char *)arg;
@@ -732,19 +869,27 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
        if (!dest)
                usage(fetch_pack_usage);
 
-       conn = git_connect(fd, (char *)dest, args.uploadpack,
-                          args.verbose ? CONNECT_VERBOSE : 0);
-       if (conn) {
-               get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
-
-               ref = fetch_pack(&args, fd, conn, ref, dest, nr_heads, heads, NULL);
-               close(fd[0]);
-               close(fd[1]);
-               if (finish_connect(conn))
-                       ref = NULL;
+       if (args.stateless_rpc) {
+               conn = NULL;
+               fd[0] = 0;
+               fd[1] = 1;
        } else {
-               ref = NULL;
+               conn = git_connect(fd, (char *)dest, args.uploadpack,
+                                  args.verbose ? CONNECT_VERBOSE : 0);
        }
+
+       get_remote_heads(fd[0], &ref, 0, NULL, 0, NULL);
+
+       ref = fetch_pack(&args, fd, conn, ref, dest,
+               nr_heads, heads, pack_lockfile_ptr);
+       if (pack_lockfile) {
+               printf("lock %s\n", pack_lockfile);
+               fflush(stdout);
+       }
+       close(fd[0]);
+       close(fd[1]);
+       if (finish_connect(conn))
+               ref = NULL;
        ret = !ref;
 
        if (!ret && nr_heads) {
@@ -797,31 +942,32 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
 
        if (args.depth > 0) {
                struct cache_time mtime;
+               struct strbuf sb = STRBUF_INIT;
                char *shallow = git_path("shallow");
                int fd;
 
                mtime.sec = st.st_mtime;
-#ifdef USE_NSEC
-               mtime.usec = st.st_mtim.usec;
-#endif
+               mtime.nsec = ST_MTIME_NSEC(st);
                if (stat(shallow, &st)) {
                        if (mtime.sec)
                                die("shallow file was removed during fetch");
                } else if (st.st_mtime != mtime.sec
 #ifdef USE_NSEC
-                               || st.st_mtim.usec != mtime.usec
+                               || ST_MTIME_NSEC(st) != mtime.nsec
 #endif
                          )
                        die("shallow file was changed during fetch");
 
                fd = hold_lock_file_for_update(&lock, shallow,
                                               LOCK_DIE_ON_ERROR);
-               if (!write_shallow_commits(fd, 0)) {
-                       unlink(shallow);
+               if (!write_shallow_commits(&sb, 0)
+                || write_in_full(fd, sb.buf, sb.len) != sb.len) {
+                       unlink_or_warn(shallow);
                        rollback_lock_file(&lock);
                } else {
                        commit_lock_file(&lock);
                }
+               strbuf_release(&sb);
        }
 
        reprepare_packed_git();