git rebase --abort: always restore the right commit
[gitweb.git] / builtin-send-pack.c
index 5a0f5c681cb1141576564972c4ad3f7999a1ff27..8afb1d0bca0635dc22f658455477d9fada231290 100644 (file)
@@ -8,7 +8,7 @@
 #include "send-pack.h"
 
 static const char send_pack_usage[] =
-"git-send-pack [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+"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 = {
@@ -146,33 +146,67 @@ static void get_local_heads(void)
        for_each_ref(one_local_ref, NULL);
 }
 
-static int receive_status(int in)
+static int receive_status(int in, struct ref *refs)
 {
+       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)) {
-               fprintf(stderr, "did not receive status back\n");
-               return -1;
-       }
+       if (len < 10 || memcmp(line, "unpack ", 7))
+               return error("did not receive remote status");
        if (memcmp(line, "unpack ok\n", 10)) {
-               fputs(line, stderr);
+               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", 2) && memcmp(line, "ng", 2))) {
+                   (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
                        fprintf(stderr, "protocol error: %s\n", line);
                        ret = -1;
                        break;
                }
-               if (!memcmp(line, "ok", 2))
+
+               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;
-               fputs(line, stderr);
-               ret = -1;
+               }
+
+               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;
        }
        return ret;
 }
@@ -180,24 +214,17 @@ static int receive_status(int in)
 static void update_tracking_ref(struct remote *remote, struct ref *ref)
 {
        struct refspec rs;
-       int will_delete_ref;
 
-       rs.src = ref->name;
-       rs.dst = NULL;
-
-       if (!ref->peer_ref)
+       if (ref->status != REF_STATUS_OK)
                return;
 
-       will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
-
-       if (!will_delete_ref &&
-                       !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1))
-               return;
+       rs.src = ref->name;
+       rs.dst = NULL;
 
        if (!remote_find_tracking(remote, &rs)) {
                if (args.verbose)
                        fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
-               if (is_null_sha1(ref->peer_ref->new_sha1)) {
+               if (ref->deletion) {
                        if (delete_ref(rs.dst, NULL))
                                error("Failed to delete");
                } else
@@ -207,8 +234,9 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref)
        }
 }
 
-static const char *prettify_ref(const char *name)
+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 :
@@ -218,15 +246,148 @@ static const char *prettify_ref(const char *name)
 
 #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));
+       else
+               fputs(prettify_ref(to), stderr);
+       if (msg) {
+               fputs(" (", stderr);
+               fputs(msg, stderr);
+               fputc(')', stderr);
+       }
+       fputc('\n', stderr);
+}
+
+static const char *status_abbrev(unsigned char sha1[20])
+{
+       const char *abbrev;
+       abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
+       return abbrev ? abbrev : sha1_to_hex(sha1);
+}
+
+static void print_ok_ref_status(struct ref *ref)
+{
+       if (ref->deletion)
+               print_ref_status('-', "[deleted]", ref, NULL, NULL);
+       else if (is_null_sha1(ref->old_sha1))
+               print_ref_status('*',
+                       (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
+                         "[new branch]"),
+                       ref, ref->peer_ref, NULL);
+       else {
+               char quickref[84];
+               char type;
+               const char *msg;
+
+               strcpy(quickref, status_abbrev(ref->old_sha1));
+               if (ref->nonfastforward) {
+                       strcat(quickref, "...");
+                       type = '+';
+                       msg = "forced update";
+               } else {
+                       strcat(quickref, "..");
+                       type = ' ';
+                       msg = NULL;
+               }
+               strcat(quickref, status_abbrev(ref->new_sha1));
+
+               print_ref_status(type, quickref, ref, ref->peer_ref, msg);
+       }
+}
+
+static int print_one_push_status(struct ref *ref, const char *dest, int count)
+{
+       if (!count)
+               fprintf(stderr, "To %s\n", dest);
+
+       switch(ref->status) {
+       case REF_STATUS_NONE:
+               print_ref_status('X', "[no match]", ref, NULL, NULL);
+               break;
+       case REF_STATUS_REJECT_NODELETE:
+               print_ref_status('!', "[rejected]", ref, NULL,
+                               "remote does not support deleting refs");
+               break;
+       case REF_STATUS_UPTODATE:
+               print_ref_status('=', "[up to date]", ref,
+                               ref->peer_ref, NULL);
+               break;
+       case REF_STATUS_REJECT_NONFASTFORWARD:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                               "non-fast forward");
+               break;
+       case REF_STATUS_REMOTE_REJECT:
+               print_ref_status('!', "[remote rejected]", ref,
+                               ref->deletion ? NULL : ref->peer_ref,
+                               ref->remote_status);
+               break;
+       case REF_STATUS_EXPECTING_REPORT:
+               print_ref_status('!', "[remote failure]", ref,
+                               ref->deletion ? NULL : ref->peer_ref,
+                               "remote failed to report status");
+               break;
+       case REF_STATUS_OK:
+               print_ok_ref_status(ref);
+               break;
+       }
+
+       return 1;
+}
+
+static void print_push_status(const char *dest, struct ref *refs)
+{
+       struct ref *ref;
+       int n = 0;
+
+       if (args.verbose) {
+               for (ref = refs; ref; ref = ref->next)
+                       if (ref->status == REF_STATUS_UPTODATE)
+                               n += print_one_push_status(ref, dest, n);
+       }
+
+       for (ref = refs; ref; ref = ref->next)
+               if (ref->status == REF_STATUS_OK)
+                       n += print_one_push_status(ref, dest, n);
+
+       for (ref = refs; ref; ref = ref->next) {
+               if (ref->status != REF_STATUS_NONE &&
+                   ref->status != REF_STATUS_UPTODATE &&
+                   ref->status != REF_STATUS_OK)
+                       n += print_one_push_status(ref, dest, n);
+       }
+}
+
+static int refs_pushed(struct ref *ref)
+{
+       for (; ref; ref = ref->next) {
+               switch(ref->status) {
+               case REF_STATUS_NONE:
+               case REF_STATUS_UPTODATE:
+                       break;
+               default:
+                       return 1;
+               }
+       }
+       return 0;
+}
+
 static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec)
 {
        struct ref *ref;
        int new_refs;
-       int ret = 0;
        int ask_for_status_report = 0;
        int allow_deleting_refs = 0;
        int expect_status_report = 0;
-       int shown_dest = 0;
+       int flags = MATCH_REFS_NONE;
+       int ret;
+
+       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);
@@ -242,7 +403,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
        if (!remote_tail)
                remote_tail = &remote_refs;
        if (match_refs(local_refs, remote_refs, &remote_tail,
-                      nr_refspec, refspec, args.send_all))
+                                              nr_refspec, refspec, flags))
                return -1;
 
        if (!remote_refs) {
@@ -256,35 +417,25 @@ 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) {
-               char old_hex[60], *new_hex;
-               int will_delete_ref;
-               const char *pretty_ref;
-               const char *pretty_peer;
-
-               if (!ref->peer_ref)
-                       continue;
+               const unsigned char *new_sha1;
 
-               if (!shown_dest) {
-                       fprintf(stderr, "To %s\n", dest);
-                       shown_dest = 1;
+               if (!ref->peer_ref) {
+                       if (!args.send_mirror)
+                               continue;
+                       new_sha1 = null_sha1;
                }
+               else
+                       new_sha1 = ref->peer_ref->new_sha1;
 
-               pretty_ref = prettify_ref(ref->name);
-               pretty_peer = prettify_ref(ref->peer_ref->name);
 
-               will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
-               if (will_delete_ref && !allow_deleting_refs) {
-                       fprintf(stderr, " ! %-*s %s (remote does not support deleting refs)\n",
-                                       SUMMARY_WIDTH, "[rejected]", pretty_ref);
-                       ret = -2;
+               ref->deletion = is_null_sha1(new_sha1);
+               if (ref->deletion && !allow_deleting_refs) {
+                       ref->status = REF_STATUS_REJECT_NODELETE;
                        continue;
                }
-               if (!will_delete_ref &&
-                   !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
-                       if (args.verbose)
-                               fprintf(stderr, " = %-*s %s -> %s\n",
-                                       SUMMARY_WIDTH, "[up to date]",
-                                       pretty_peer, pretty_ref);
+               if (!ref->deletion &&
+                   !hashcmp(ref->old_sha1, new_sha1)) {
+                       ref->status = REF_STATUS_UPTODATE;
                        continue;
                }
 
@@ -307,34 +458,25 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
                 *     always allowed.
                 */
 
-               if (!args.force_update &&
-                   !will_delete_ref &&
+               ref->nonfastforward =
+                   !ref->deletion &&
                    !is_null_sha1(ref->old_sha1) &&
-                   !ref->force) {
-                       if (!has_sha1_file(ref->old_sha1) ||
-                           !ref_newer(ref->peer_ref->new_sha1,
-                                      ref->old_sha1)) {
-                               /* We do not have the remote ref, or
-                                * we know that the remote ref is not
-                                * an ancestor of what we are trying to
-                                * push.  Either way this can be losing
-                                * commits at the remote end and likely
-                                * we were not up to date to begin with.
-                                */
-                               fprintf(stderr, " ! %-*s %s -> %s (non-fast forward)\n",
-                                               SUMMARY_WIDTH, "[rejected]",
-                                               pretty_peer, pretty_ref);
-                               ret = -2;
-                               continue;
-                       }
+                   (!has_sha1_file(ref->old_sha1)
+                     || !ref_newer(new_sha1, ref->old_sha1));
+
+               if (ref->nonfastforward && !ref->force && !args.force_update) {
+                       ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
+                       continue;
                }
-               hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               if (!will_delete_ref)
+
+               hashcpy(ref->new_sha1, new_sha1);
+               if (!ref->deletion)
                        new_refs++;
-               strcpy(old_hex, sha1_to_hex(ref->old_sha1));
-               new_hex = sha1_to_hex(ref->new_sha1);
 
                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",
                                        old_hex, new_hex, ref->name, 0,
@@ -346,63 +488,47 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
                                packet_write(out, "%s %s %s",
                                        old_hex, new_hex, ref->name);
                }
-               if (will_delete_ref)
-                       fprintf(stderr, " - %-*s %s\n",
-                               SUMMARY_WIDTH, "[deleting]",
-                               pretty_ref);
-               else if (is_null_sha1(ref->old_sha1)) {
-                       const char *msg;
-
-                       if (!prefixcmp(ref->name, "refs/tags/"))
-                               msg = "[new tag]";
-                       else
-                               msg = "[new branch]";
-                       fprintf(stderr, " * %-*s %s -> %s\n",
-                               SUMMARY_WIDTH, msg,
-                               pretty_peer, pretty_ref);
-               }
-               else {
-                       char quickref[83];
-                       char type = ' ';
-                       const char *msg = "";
-                       const char *old_abb;
-                       old_abb = find_unique_abbrev(ref->old_sha1, DEFAULT_ABBREV);
-                       strcpy(quickref, old_abb ? old_abb : old_hex);
-                       if (ref_newer(ref->peer_ref->new_sha1, ref->old_sha1))
-                               strcat(quickref, "..");
-                       else {
-                               strcat(quickref, "...");
-                               type = '+';
-                               msg = " (forced update)";
-                       }
-                       strcat(quickref, find_unique_abbrev(ref->new_sha1, DEFAULT_ABBREV));
-
-                       fprintf(stderr, " %c %-*s %s -> %s%s\n",
-                               type,
-                               SUMMARY_WIDTH, quickref,
-                               pretty_peer, pretty_ref,
-                               msg);
-               }
+               ref->status = expect_status_report ?
+                       REF_STATUS_EXPECTING_REPORT :
+                       REF_STATUS_OK;
        }
 
        packet_flush(out);
-       if (new_refs && !args.dry_run)
-               ret = pack_objects(out, remote_refs);
+       if (new_refs && !args.dry_run) {
+               if (pack_objects(out, remote_refs) < 0) {
+                       close(out);
+                       return -1;
+               }
+       }
        close(out);
 
-       if (expect_status_report) {
-               if (receive_status(in))
-                       ret = -4;
-       }
+       if (expect_status_report)
+               ret = receive_status(in, remote_refs);
+       else
+               ret = 0;
 
-       if (!args.dry_run && remote && ret == 0) {
+       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 (!new_refs && ret == 0)
+       if (!refs_pushed(remote_refs))
                fprintf(stderr, "Everything up-to-date\n");
-       return ret;
+       if (ret < 0)
+               return ret;
+       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;
 }
 
 static void verify_remote_names(int nr_heads, const char **heads)
@@ -415,10 +541,12 @@ static void verify_remote_names(int nr_heads, const char **heads)
                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.
-                         */
-               case -3: /* ok but ends with a pattern-match character */
+               case CHECK_REF_FORMAT_ONELEVEL:
+                       /* ok but a single level -- that is fine for
+                        * a match pattern.
+                        */
+               case CHECK_REF_FORMAT_WILDCARD:
+                       /* ok but ends with a pattern-match character */
                        continue;
                }
                die("remote part of refspec is not a valid name in %s",
@@ -459,6 +587,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.dry_run = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--mirror")) {
+                               args.send_mirror = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--force")) {
                                args.force_update = 1;
                                continue;
@@ -483,7 +615,12 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
        }
        if (!dest)
                usage(send_pack_usage);
-       if (heads && args.send_all)
+       /*
+        * --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))
                usage(send_pack_usage);
 
        if (remote_name) {