Merge branch 'js/reflog'
[gitweb.git] / send-pack.c
index 447666665bc43090849a66681edec663fe75000c..6756264b293e18467a59dc0222ee82e586cda76f 100644 (file)
@@ -14,114 +14,49 @@ static int send_all;
 static int force_update;
 static int use_thin_pack;
 
-static int is_zero_sha1(const unsigned char *sha1)
-{
-       int i;
-
-       for (i = 0; i < 20; i++) {
-               if (*sha1++)
-                       return 0;
-       }
-       return 1;
-}
-
-static void exec_pack_objects(void)
-{
-       static const char *args[] = {
-               "pack-objects",
-               "--all-progress",
-               "--stdout",
-               NULL
-       };
-       execv_git_cmd(args);
-       die("git-pack-objects exec failed (%s)", strerror(errno));
-}
-
-static void exec_rev_list(struct ref *refs)
-{
-       static const char *args[4];
-       int i = 0;
-
-       args[i++] = "rev-list"; /* 0 */
-       if (use_thin_pack)      /* 1 */
-               args[i++] = "--objects-edge";
-       else
-               args[i++] = "--objects";
-
-       args[i++] = "--stdin";
-
-       args[i] = NULL;
-       execv_git_cmd(args);
-       die("git-rev-list exec failed (%s)", strerror(errno));
-}
-
 /*
- * Run "rev-list --stdin | pack-objects" pipe.
- */
-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) {
-               /* The child becomes pack-objects; reads from pipe
-                * and writes to the original fd
-                */
-               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");
-
-       /* We become rev-list --stdin; output goes to pipe. */
-       dup2(pipe_fd[1], 1);
-       close(pipe_fd[0]);
-       close(pipe_fd[1]);
-       close(fd);
-       exec_rev_list(refs);
-}
-
-/*
- * Create "rev-list --stdin | pack-objects" pipe and feed
- * the refs into the pipeline.
+ * Make a pack stream and spit it out into file descriptor fd
  */
-static void rev_list_generate(int fd, struct ref *refs)
+static int pack_objects(int fd, struct ref *refs)
 {
        int pipe_fd[2];
-       pid_t rev_list_generate_pid;
+       pid_t pid;
 
        if (pipe(pipe_fd) < 0)
-               die("rev-list-generate setup: pipe failed");
-       rev_list_generate_pid = fork();
-       if (!rev_list_generate_pid) {
-               /* The child becomes the "rev-list | pack-objects"
-                * pipeline.  It takes input from us, and its output
-                * goes to fd.
+               return error("send-pack: pipe failed");
+       pid = fork();
+       if (!pid) {
+               /*
+                * 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.
                 */
+               static const char *args[] = {
+                       "pack-objects",
+                       "--all-progress",
+                       "--revs",
+                       "--stdout",
+                       NULL,
+                       NULL,
+               };
+               if (use_thin_pack)
+                       args[4] = "--thin";
                dup2(pipe_fd[0], 0);
                dup2(fd, 1);
                close(pipe_fd[0]);
                close(pipe_fd[1]);
                close(fd);
-               rev_list(fd, refs);
-               die("rev-list setup failed");
+               execv_git_cmd(args);
+               die("git-pack-objects exec failed (%s)", strerror(errno));
        }
-       if (rev_list_generate_pid < 0)
-               die("rev-list-generate fork failed");
 
-       /* We feed the rev parameters to them.  We do not write into
-        * fd nor read from the pipe.
+       /*
+        * We feed the pack-objects we just spawned with revision
+        * parameters by writing to the pipe.
         */
        close(pipe_fd[0]);
        close(fd);
+
        while (refs) {
                char buf[42];
 
@@ -130,38 +65,38 @@ static void rev_list_generate(int fd, struct ref *refs)
                        memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
                        buf[0] = '^';
                        buf[41] = '\n';
-                       write(pipe_fd[1], buf, 42);
+                       if (!write_or_whine(pipe_fd[1], buf, 42,
+                                               "send-pack: send refs"))
+                               break;
                }
                if (!is_null_sha1(refs->new_sha1)) {
                        memcpy(buf, sha1_to_hex(refs->new_sha1), 40);
                        buf[40] = '\n';
-                       write(pipe_fd[1], buf, 41);
+                       if (!write_or_whine(pipe_fd[1], buf, 41,
+                                               "send-pack: send refs"))
+                               break;
                }
                refs = refs->next;
        }
        close(pipe_fd[1]);
-       // waitpid(rev_list_generate_pid);
-       exit(0);
-}
 
-/*
- * Make a pack stream and spit it out into file descriptor fd
- */
-static void pack_objects(int fd, struct ref *refs)
-{
-       pid_t rev_list_pid;
+       for (;;) {
+               int status, code;
+               pid_t waiting = waitpid(pid, &status, 0);
 
-       rev_list_pid = fork();
-       if (!rev_list_pid) {
-               rev_list_generate(fd, refs);
-               die("rev-list setup failed");
+               if (waiting < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       return error("waitpid failed (%s)", strerror(errno));
+               }
+               if ((waiting != pid) || WIFSIGNALED(status) ||
+                   !WIFEXITED(status))
+                       return error("pack-objects died with strange error");
+               code = WEXITSTATUS(status);
+               if (code)
+                       return -code;
+               return 0;
        }
-       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
-        */
 }
 
 static void unmark_and_free(struct commit_list *list, unsigned int mark)
@@ -271,6 +206,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        int new_refs;
        int ret = 0;
        int ask_for_status_report = 0;
+       int allow_deleting_refs = 0;
        int expect_status_report = 0;
 
        /* No funny business with the matcher */
@@ -280,6 +216,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        /* Does the other end support the reporting? */
        if (server_supports("report-status"))
                ask_for_status_report = 1;
+       if (server_supports("delete-refs"))
+               allow_deleting_refs = 1;
 
        /* match them up */
        if (!remote_tail)
@@ -299,9 +237,19 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        new_refs = 0;
        for (ref = remote_refs; ref; ref = ref->next) {
                char old_hex[60], *new_hex;
+               int delete_ref;
+
                if (!ref->peer_ref)
                        continue;
-               if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
+
+               delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
+               if (delete_ref && !allow_deleting_refs) {
+                       error("remote does not support deleting refs");
+                       ret = -2;
+                       continue;
+               }
+               if (!delete_ref &&
+                   !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
                        if (verbose)
                                fprintf(stderr, "'%s': up-to-date\n", ref->name);
                        continue;
@@ -321,10 +269,14 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                 *
                 * (3) if both new and old are commit-ish, and new is a
                 *     descendant of old, it is OK.
+                *
+                * (4) regardless of all of the above, removing :B is
+                *     always allowed.
                 */
 
                if (!force_update &&
-                   !is_zero_sha1(ref->old_sha1) &&
+                   !delete_ref &&
+                   !is_null_sha1(ref->old_sha1) &&
                    !ref->force) {
                        if (!has_sha1_file(ref->old_sha1) ||
                            !ref_newer(ref->peer_ref->new_sha1,
@@ -347,12 +299,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                        }
                }
                hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               if (is_zero_sha1(ref->new_sha1)) {
-                       error("cannot happen anymore");
-                       ret = -3;
-                       continue;
-               }
-               new_refs++;
+               if (!delete_ref)
+                       new_refs++;
                strcpy(old_hex, sha1_to_hex(ref->old_sha1));
                new_hex = sha1_to_hex(ref->new_sha1);
 
@@ -366,15 +314,21 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                else
                        packet_write(out, "%s %s %s",
                                     old_hex, new_hex, ref->name);
-               fprintf(stderr, "updating '%s'", ref->name);
-               if (strcmp(ref->name, ref->peer_ref->name))
-                       fprintf(stderr, " using '%s'", ref->peer_ref->name);
-               fprintf(stderr, "\n  from %s\n  to   %s\n", old_hex, new_hex);
+               if (delete_ref)
+                       fprintf(stderr, "deleting '%s'\n", ref->name);
+               else {
+                       fprintf(stderr, "updating '%s'", ref->name);
+                       if (strcmp(ref->name, ref->peer_ref->name))
+                               fprintf(stderr, " using '%s'",
+                                       ref->peer_ref->name);
+                       fprintf(stderr, "\n  from %s\n  to   %s\n",
+                               old_hex, new_hex);
+               }
        }
 
        packet_flush(out);
        if (new_refs)
-               pack_objects(out, remote_refs);
+               ret = pack_objects(out, remote_refs);
        close(out);
 
        if (expect_status_report) {
@@ -387,6 +341,25 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        return ret;
 }
 
+static void verify_remote_names(int nr_heads, char **heads)
+{
+       int i;
+
+       for (i = 0; i < nr_heads; i++) {
+               const char *remote = strchr(heads[i], ':');
+
+               remote = remote ? (remote + 1) : heads[i];
+               switch (check_ref_format(remote)) {
+               case 0: /* ok */
+               case -2: /* ok but a single level -- that is fine for
+                         * a match pattern.
+                         */
+                       continue;
+               }
+               die("remote part of refspec is not a valid name in %s",
+                   heads[i]);
+       }
+}
 
 int main(int argc, char **argv)
 {
@@ -438,6 +411,8 @@ int main(int argc, char **argv)
                usage(send_pack_usage);
        if (heads && send_all)
                usage(send_pack_usage);
+       verify_remote_names(nr_heads, heads);
+
        pid = git_connect(fd, dest, exec);
        if (pid < 0)
                return 1;