Documentation: reset: describe new "--keep" option
[gitweb.git] / builtin-send-pack.c
index d65d01969252332eeee12b0419e4ba3a806952b1..76c72065de73ea3f0da4665c0a47a64610e2ead2 100644 (file)
@@ -1,19 +1,18 @@
 #include "cache.h"
 #include "commit.h"
-#include "tag.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"
 
 static const char send_pack_usage[] =
 "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
 "  --all and explicit <ref> specification are mutually exclusive.";
 
-static struct send_pack_args args = {
-       /* .receivepack = */ "git-receive-pack",
-};
+static struct send_pack_args args;
 
 static int feed_object(const unsigned char *sha1, int fd, int negative)
 {
@@ -32,7 +31,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)
+static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
 {
        /*
         * The child becomes pack-objects --revs; we feed
@@ -41,24 +40,31 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
         */
        const char *argv[] = {
                "pack-objects",
-               "--all-progress",
+               "--all-progress-implied",
                "--revs",
                "--stdout",
                NULL,
                NULL,
+               NULL,
+               NULL,
        };
        struct child_process po;
        int i;
 
-       if (args.use_thin_pack)
-               argv[4] = "--thin";
+       i = 4;
+       if (args->use_thin_pack)
+               argv[i++] = "--thin";
+       if (args->use_ofs_delta)
+               argv[i++] = "--delta-base-offset";
+       if (args->quiet)
+               argv[i++] = "-q";
        memset(&po, 0, sizeof(po));
        po.argv = argv;
        po.in = -1;
-       po.out = fd;
+       po.out = args->stateless_rpc ? -1 : fd;
        po.git_cmd = 1;
        if (start_command(&po))
-               die("git pack-objects failed (%s)", strerror(errno));
+               die_errno("git pack-objects failed");
 
        /*
         * We feed the pack-objects we just spawned with revision
@@ -79,87 +85,25 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
        }
 
        close(po.in);
-       if (finish_command(&po))
-               return error("pack-objects died with strange error");
-       return 0;
-}
-
-static void unmark_and_free(struct commit_list *list, unsigned int mark)
-{
-       while (list) {
-               struct commit_list *temp = list;
-               temp->item->object.flags &= ~mark;
-               list = temp->next;
-               free(temp);
-       }
-}
-
-static int ref_newer(const unsigned char *new_sha1,
-                    const unsigned char *old_sha1)
-{
-       struct object *o;
-       struct commit *old, *new;
-       struct commit_list *list, *used;
-       int found = 0;
-
-       /* Both new and old must be commit-ish and new is descendant of
-        * old.  Otherwise we require --force.
-        */
-       o = deref_tag(parse_object(old_sha1), NULL, 0);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-       old = (struct commit *) o;
-
-       o = deref_tag(parse_object(new_sha1), NULL, 0);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-       new = (struct commit *) o;
 
-       if (parse_commit(new) < 0)
-               return 0;
-
-       used = list = NULL;
-       commit_list_insert(new, &list);
-       while (list) {
-               new = pop_most_recent_commit(&list, 1);
-               commit_list_insert(new, &used);
-               if (new == old) {
-                       found = 1;
-                       break;
+       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;
        }
-       unmark_and_free(list, 1);
-       unmark_and_free(used, 1);
-       return found;
-}
-
-static struct ref *local_refs, **local_tail;
-static struct ref *remote_refs, **remote_tail;
-
-static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct ref *ref;
-       int len;
 
-       /* we already know it starts with refs/ to get here */
-       if (check_ref_format(refname + 5))
-               return 0;
-
-       len = strlen(refname) + 1;
-       ref = xcalloc(1, sizeof(*ref) + len);
-       hashcpy(ref->new_sha1, sha1);
-       memcpy(ref->name, refname, len);
-       *local_tail = ref;
-       local_tail = &ref->next;
+       if (finish_command(&po))
+               return error("pack-objects died with strange error");
        return 0;
 }
 
-static void get_local_heads(void)
-{
-       local_tail = &local_refs;
-       for_each_ref(one_local_ref, NULL);
-}
-
 static int receive_status(int in, struct ref *refs)
 {
        struct ref *hint;
@@ -247,25 +191,15 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref)
        }
 }
 
-static const char *prettify_ref(const struct ref *ref)
-{
-       const char *name = ref->name;
-       return name + (
-               !prefixcmp(name, "refs/heads/") ? 11 :
-               !prefixcmp(name, "refs/tags/") ? 10 :
-               !prefixcmp(name, "refs/remotes/") ? 13 :
-               0);
-}
-
 #define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 
 static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg)
 {
        fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
        if (from)
-               fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to));
+               fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
        else
-               fputs(prettify_ref(to), stderr);
+               fputs(prettify_refname(to->name), stderr);
        if (msg) {
                fputs(" (", stderr);
                fputs(msg, stderr);
@@ -328,7 +262,7 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count)
                break;
        case REF_STATUS_REJECT_NONFASTFORWARD:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
-                               "non-fast forward");
+                               "non-fast-forward");
                break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
@@ -385,47 +319,85 @@ static int refs_pushed(struct ref *ref)
        return 0;
 }
 
-static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec)
+static void print_helper_status(struct ref *ref)
+{
+       struct strbuf buf = STRBUF_INIT;
+
+       for (; ref; ref = ref->next) {
+               const char *msg = NULL;
+               const char *res;
+
+               switch(ref->status) {
+               case REF_STATUS_NONE:
+                       res = "error";
+                       msg = "no match";
+                       break;
+
+               case REF_STATUS_OK:
+                       res = "ok";
+                       break;
+
+               case REF_STATUS_UPTODATE:
+                       res = "ok";
+                       msg = "up to date";
+                       break;
+
+               case REF_STATUS_REJECT_NONFASTFORWARD:
+                       res = "error";
+                       msg = "non-fast forward";
+                       break;
+
+               case REF_STATUS_REJECT_NODELETE:
+               case REF_STATUS_REMOTE_REJECT:
+                       res = "error";
+                       break;
+
+               case REF_STATUS_EXPECTING_REPORT:
+               default:
+                       continue;
+               }
+
+               strbuf_reset(&buf);
+               strbuf_addf(&buf, "%s %s", res, ref->name);
+               if (ref->remote_status)
+                       msg = ref->remote_status;
+               if (msg) {
+                       strbuf_addch(&buf, ' ');
+                       quote_two_c_style(&buf, "", msg, 0);
+               }
+               strbuf_addch(&buf, '\n');
+
+               safe_write(1, buf.buf, buf.len);
+       }
+       strbuf_release(&buf);
+}
+
+int send_pack(struct send_pack_args *args,
+             int fd[], struct child_process *conn,
+             struct ref *remote_refs,
+             struct extra_have_objects *extra_have)
 {
+       int in = fd[0];
+       int out = fd[1];
+       struct strbuf req_buf = STRBUF_INIT;
        struct ref *ref;
        int new_refs;
        int ask_for_status_report = 0;
        int allow_deleting_refs = 0;
        int expect_status_report = 0;
-       int flags = MATCH_REFS_NONE;
        int ret;
-       struct extra_have_objects extra_have;
-
-       memset(&extra_have, 0, sizeof(extra_have));
-       if (args.send_all)
-               flags |= MATCH_REFS_ALL;
-       if (args.send_mirror)
-               flags |= MATCH_REFS_MIRROR;
-
-       /* No funny business with the matcher */
-       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL,
-                                      &extra_have);
-       get_local_heads();
 
        /* 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)
-               remote_tail = &remote_refs;
-       if (match_refs(local_refs, remote_refs, &remote_tail,
-                      nr_refspec, refspec, flags)) {
-               close(out);
-               return -1;
-       }
+       if (server_supports("ofs-delta"))
+               args->use_ofs_delta = 1;
 
        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");
-               close(out);
                return 0;
        }
 
@@ -434,69 +406,39 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
         */
        new_refs = 0;
        for (ref = remote_refs; ref; ref = ref->next) {
-
-               if (ref->peer_ref)
-                       hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               else if (!args.send_mirror)
+               if (!ref->peer_ref && !args->send_mirror)
                        continue;
 
-               ref->deletion = is_null_sha1(ref->new_sha1);
-               if (ref->deletion && !allow_deleting_refs) {
-                       ref->status = REF_STATUS_REJECT_NODELETE;
-                       continue;
-               }
-               if (!ref->deletion &&
-                   !hashcmp(ref->old_sha1, ref->new_sha1)) {
-                       ref->status = REF_STATUS_UPTODATE;
+               /* 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 */
                }
 
-               /* This part determines what can overwrite what.
-                * The rules are:
-                *
-                * (0) you can always use --force or +A:B notation to
-                *     selectively force individual ref pairs.
-                *
-                * (1) if the old thing does not exist, it is OK.
-                *
-                * (2) if you do not have the old thing, you are not allowed
-                *     to overwrite it; you would not know what you are losing
-                *     otherwise.
-                *
-                * (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.
-                */
-
-               ref->nonfastforward =
-                   !ref->deletion &&
-                   !is_null_sha1(ref->old_sha1) &&
-                   (!has_sha1_file(ref->old_sha1)
-                     || !ref_newer(ref->new_sha1, ref->old_sha1));
-
-               if (ref->nonfastforward && !ref->force && !args.force_update) {
-                       ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+               if (ref->deletion && !allow_deleting_refs) {
+                       ref->status = REF_STATUS_REJECT_NODELETE;
                        continue;
                }
 
                if (!ref->deletion)
                        new_refs++;
 
-               if (!args.dry_run) {
+               if (!args->dry_run) {
                        char *old_hex = sha1_to_hex(ref->old_sha1);
                        char *new_hex = sha1_to_hex(ref->new_sha1);
 
                        if (ask_for_status_report) {
-                               packet_write(out, "%s %s %s%c%s",
+                               packet_buf_write(&req_buf, "%s %s %s%c%s",
                                        old_hex, new_hex, ref->name, 0,
                                        "report-status");
                                ask_for_status_report = 0;
                                expect_status_report = 1;
                        }
                        else
-                               packet_write(out, "%s %s %s",
+                               packet_buf_write(&req_buf, "%s %s %s",
                                        old_hex, new_hex, ref->name);
                }
                ref->status = expect_status_report ?
@@ -504,28 +446,34 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
                        REF_STATUS_OK;
        }
 
-       packet_flush(out);
-       if (new_refs && !args.dry_run) {
-               if (pack_objects(out, remote_refs, &extra_have) < 0)
+       if (args->stateless_rpc) {
+               if (!args->dry_run) {
+                       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);
+       }
+       strbuf_release(&req_buf);
+
+       if (new_refs && !args->dry_run) {
+               if (pack_objects(out, remote_refs, extra_have, args) < 0) {
+                       for (ref = remote_refs; ref; ref = ref->next)
+                               ref->status = REF_STATUS_NONE;
                        return -1;
+               }
        }
-       else
-               close(out);
+       if (args->stateless_rpc && !args->dry_run)
+               packet_flush(out);
 
        if (expect_status_report)
                ret = receive_status(in, remote_refs);
        else
                ret = 0;
+       if (args->stateless_rpc)
+               packet_flush(out);
 
-       print_push_status(dest, remote_refs);
-
-       if (!args.dry_run && remote) {
-               for (ref = remote_refs; ref; ref = ref->next)
-                       update_tracking_ref(remote, ref);
-       }
-
-       if (!refs_pushed(remote_refs))
-               fprintf(stderr, "Everything up-to-date\n");
        if (ret < 0)
                return ret;
        for (ref = remote_refs; ref; ref = ref->next) {
@@ -574,11 +522,20 @@ static void verify_remote_names(int nr_heads, const char **heads)
 
 int cmd_send_pack(int argc, const char **argv, const char *prefix)
 {
-       int i, nr_heads = 0;
-       const char **heads = NULL;
+       int i, nr_refspecs = 0;
+       const char **refspecs = NULL;
        const char *remote_name = NULL;
        struct remote *remote = NULL;
        const char *dest = NULL;
+       int fd[2];
+       struct child_process *conn;
+       struct extra_have_objects extra_have;
+       struct ref *remote_refs, *local_refs;
+       int ret;
+       int helper_status = 0;
+       int send_all = 0;
+       const char *receivepack = "git-receive-pack";
+       int flags;
 
        argv++;
        for (i = 1; i < argc; i++, argv++) {
@@ -586,11 +543,11 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 
                if (*arg == '-') {
                        if (!prefixcmp(arg, "--receive-pack=")) {
-                               args.receivepack = arg + 15;
+                               receivepack = arg + 15;
                                continue;
                        }
                        if (!prefixcmp(arg, "--exec=")) {
-                               args.receivepack = arg + 7;
+                               receivepack = arg + 7;
                                continue;
                        }
                        if (!prefixcmp(arg, "--remote=")) {
@@ -598,7 +555,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                continue;
                        }
                        if (!strcmp(arg, "--all")) {
-                               args.send_all = 1;
+                               send_all = 1;
                                continue;
                        }
                        if (!strcmp(arg, "--dry-run")) {
@@ -621,14 +578,22 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.use_thin_pack = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--stateless-rpc")) {
+                               args.stateless_rpc = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--helper-status")) {
+                               helper_status = 1;
+                               continue;
+                       }
                        usage(send_pack_usage);
                }
                if (!dest) {
                        dest = arg;
                        continue;
                }
-               heads = (const char **) argv;
-               nr_heads = argc - i;
+               refspecs = (const char **) argv;
+               nr_refspecs = argc - i;
                break;
        }
        if (!dest)
@@ -637,8 +602,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
         * --all and --mirror are incompatible; neither makes sense
         * with any refspecs.
         */
-       if ((heads && (args.send_all || args.send_mirror)) ||
-                                       (args.send_all && args.send_mirror))
+       if ((refspecs && (send_all || args.send_mirror)) ||
+           (send_all && args.send_mirror))
                usage(send_pack_usage);
 
        if (remote_name) {
@@ -649,24 +614,59 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                }
        }
 
-       return send_pack(&args, dest, remote, nr_heads, heads);
-}
+       if (args.stateless_rpc) {
+               conn = NULL;
+               fd[0] = 0;
+               fd[1] = 1;
+       } else {
+               conn = git_connect(fd, dest, receivepack,
+                       args.verbose ? CONNECT_VERBOSE : 0);
+       }
 
-int send_pack(struct send_pack_args *my_args,
-             const char *dest, struct remote *remote,
-             int nr_heads, const char **heads)
-{
-       int fd[2], ret;
-       struct child_process *conn;
+       memset(&extra_have, 0, sizeof(extra_have));
+
+       get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
+                        &extra_have);
+
+       verify_remote_names(nr_refspecs, refspecs);
+
+       local_refs = get_local_heads();
+
+       flags = MATCH_REFS_NONE;
+
+       if (send_all)
+               flags |= MATCH_REFS_ALL;
+       if (args.send_mirror)
+               flags |= MATCH_REFS_MIRROR;
+
+       /* match them up */
+       if (match_refs(local_refs, &remote_refs, nr_refspecs, refspecs, flags))
+               return -1;
 
-       memcpy(&args, my_args, sizeof(args));
+       set_ref_status_for_push(remote_refs, args.send_mirror,
+               args.force_update);
 
-       verify_remote_names(nr_heads, heads);
+       ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
 
-       conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0);
-       ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads);
+       if (helper_status)
+               print_helper_status(remote_refs);
+
+       close(fd[1]);
        close(fd[0]);
-       /* do_send_pack always closes fd[1] */
+
        ret |= finish_connect(conn);
-       return !!ret;
+
+       if (!helper_status)
+               print_push_status(dest, remote_refs);
+
+       if (!args.dry_run && remote) {
+               struct ref *ref;
+               for (ref = remote_refs; ref; ref = ref->next)
+                       update_tracking_ref(remote, ref);
+       }
+
+       if (!ret && !refs_pushed(remote_refs))
+               fprintf(stderr, "Everything up-to-date\n");
+
+       return ret;
 }