git-completion.bash: add support for path completion
[gitweb.git] / send-pack.c
index 57e88a7637eb9a0976aaef2664c9d2cc06dc07e6..f50dfd9f488821e6b052affb19c0048afd11109c 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 "send-pack.h"
+#include "quote.h"
+#include "transport.h"
+#include "version.h"
 
-static const char send_pack_usage[] = "git-send-pack [--exec=other] destination [heads]*";
-static const char *exec = "git-receive-pack";
-
-static int path_match(const char *path, int nr, char **match)
+static int feed_object(const unsigned char *sha1, int fd, int negative)
 {
-       int i;
-       int pathlen = strlen(path);
-
-       for (i = 0; i < nr; i++) {
-               char *s = match[i];
-               int len = strlen(s);
+       char buf[42];
 
-               if (!len || len > pathlen)
-                       continue;
-               if (memcmp(path + pathlen - len, s, len))
-                       continue;
-               if (pathlen > len && path[pathlen - len - 1] != '/')
-                       continue;
-               *s = 0;
+       if (negative && !has_sha1_file(sha1))
                return 1;
-       }
-       return 0;
-}
 
-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 void exec_pack_objects(void)
+/*
+ * 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 char *args[] = {
-               "git-pack-objects",
+       /*
+        * 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,
        };
-       execvp("git-pack-objects", args);
-       die("git-pack-objects exec failed (%s)", strerror(errno));
-}
+       struct child_process po;
+       int i;
 
-static void exec_rev_list(struct ref *refs)
-{
-       static char *args[1000];
-       int i = 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";
+       memset(&po, 0, sizeof(po));
+       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->array[i], po.in, 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");
-               args[i++] = buf;
-               snprintf(buf, 50, "^%s", sha1_to_hex(refs->old_sha1));
-               buf += 50;
-               args[i++] = buf;
-               snprintf(buf, 50, "%s", sha1_to_hex(refs->new_sha1));
+               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;
        }
-       args[i] = NULL;
-       execvp("git-rev-list", args);
-       die("git-rev-list exec failed (%s)", strerror(errno));
-}
 
-static void rev_list(int fd, struct ref *refs)
-{
-       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");
+       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 (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);
+
+       if (finish_command(&po))
+               return -1;
+       return 0;
 }
 
-static int pack_objects(int fd, struct ref *refs)
+static int receive_status(int in, struct ref *refs)
 {
-       pid_t rev_list_pid;
+       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))
+               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';
+               error("unpack failed: %s", line + 7);
+               ret = -1;
+       }
+       hint = NULL;
+       while (1) {
+               char *refname;
+               char *msg;
+               len = packet_read_line(in, line, sizeof(line));
+               if (!len)
+                       break;
+               if (len < 3 ||
+                   (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
+                       fprintf(stderr, "protocol error: %s\n", line);
+                       ret = -1;
+                       break;
+               }
+
+               line[strlen(line)-1] = '\0';
+               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 (hint->status != REF_STATUS_EXPECTING_REPORT) {
+                       warning("remote reported status on unexpected ref: %s",
+                                       refname);
+                       continue;
+               }
 
-       rev_list_pid = fork();
-       if (!rev_list_pid) {
-               rev_list(fd, refs);
-               die("rev-list setup failed");
+               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;
        }
-       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
-        */
-       return 0;
+       return ret;
 }
 
-static int read_ref(const char *ref, unsigned char *sha1)
+static int sideband_demux(int in, int out, void *data)
 {
-       int fd, ret;
-       static char pathname[PATH_MAX];
-       char buffer[60];
-       const char *git_dir = gitenv(GIT_DIR_ENVIRONMENT) ? : DEFAULT_GIT_DIR_ENVIRONMENT;
-
-       snprintf(pathname, sizeof(pathname), "%s/%s", git_dir, ref);
-       fd = open(pathname, O_RDONLY);
-       if (fd < 0)
-               return -1;
-       ret = -1;
-       if (read(fd, buffer, sizeof(buffer)) >= 40)
-               ret = get_sha1_hex(buffer, sha1);
-       close(fd);
+       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 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 extra_have_objects *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 ref *ref;
+       int new_refs;
+       int allow_deleting_refs = 0;
+       int status_report = 0;
+       int use_sideband = 0;
+       int quiet_supported = 0;
+       int agent_supported = 0;
+       unsigned cmds_sent = 0;
+       int ret;
+       struct async demux;
 
-       for (;;) {
-               unsigned char old_sha1[20];
-               unsigned char new_sha1[20];
-               static char buffer[1000];
-               char *name;
-               int len;
+       /* 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;
 
-               len = packet_read_line(in, buffer, sizeof(buffer));
-               if (!len)
-                       break;
-               if (buffer[len-1] == '\n')
-                       buffer[--len] = 0;
+       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 (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
-                       die("protocol error: expected sha/ref, got '%s'", buffer);
-               name = buffer + 41;
-               if (nr_match && !path_match(name, nr_match, match))
+       /*
+        * Finally, tell the other end!
+        */
+       new_refs = 0;
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (!ref->peer_ref && !args->send_mirror)
                        continue;
-               if (read_ref(name, new_sha1) < 0)
-                       return error("no such local reference '%s'", name);
-               if (!has_sha1_file(old_sha1))
-                       return error("remote '%s' points to object I don't have", name);
-               if (!memcmp(old_sha1, new_sha1, 20)) {
-                       fprintf(stderr, "'%s' unchanged\n", name);
+
+               /* 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 */
+               }
+
+               if (ref->deletion && !allow_deleting_refs) {
+                       ref->status = REF_STATUS_REJECT_NODELETE;
+                       continue;
+               }
+
+               if (!ref->deletion)
+                       new_refs++;
+
+               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);
+                       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++;
                }
-               ref = xmalloc(sizeof(*ref) + len - 40);
-               memcpy(ref->old_sha1, old_sha1, 20);
-               memcpy(ref->new_sha1, new_sha1, 20);
-               memcpy(ref->name, buffer + 41, len - 40);
-               ref->next = NULL;
-               *last_ref = ref;
-               last_ref = &ref->next;
        }
 
-       for (ref = ref_list; ref; ref = ref->next) {
-               char old_hex[60], *new_hex;
-               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 (args->stateless_rpc) {
+               if (!args->dry_run && cmds_sent) {
+                       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);
+               packet_flush(out);
        }
-       
-       packet_flush(out);
-       if (ref_list)
-               pack_objects(out, ref_list);
-       close(out);
-       return 0;
-}
+       strbuf_release(&req_buf);
 
-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 (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;
+       }
+
+       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 (args->stateless_rpc)
+                               close(out);
+                       if (git_connection_is_socket(conn))
+                               shutdown(fd[0], SHUT_WR);
+                       if (use_sideband)
+                               finish_async(&demux);
+                       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;
+       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);
+       }
+
+       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;
+               }
+       }
+       return 0;
 }