send-pack.c: add --atomic command line argument
authorRonnie Sahlberg <sahlberg@google.com>
Thu, 8 Jan 2015 03:23:22 +0000 (19:23 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 8 Jan 2015 03:56:44 +0000 (19:56 -0800)
This adds support to send-pack to negotiate and use atomic pushes
iff the server supports it. Atomic pushes are activated by a new command
line flag --atomic.

In order to do this we also need to change the semantics for send_pack()
slightly. The existing send_pack() function actually doesn't send all the
refs back to the server when multiple refs are involved, for example
when using --all. Several of the failure modes for pushes can already be
detected locally in the send_pack client based on the information from the
initial server side list of all the refs as generated by receive-pack.
Any such refs that we thus know would fail to push are thus pruned from
the list of refs we send to the server to update.

For atomic pushes, we have to deal thus with both failures that are detected
locally as well as failures that are reported back from the server. In order
to do so we treat all local failures as push failures too.

We introduce a new status code REF_STATUS_ATOMIC_PUSH_FAILED so we can
flag all refs that we would normally have tried to push to the server
but we did not due to local failures. This is to improve the error message
back to the end user to flag that "these refs failed to update since the
atomic push operation failed."

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-send-pack.txt
builtin/send-pack.c
remote.h
send-pack.c
send-pack.h
transport.c
index 2a0de42a75d482a4bbafefd19991dad25b3e67f9..45c7725dc303eed198fc0ed370f603a71d1fa8c1 100644 (file)
@@ -9,7 +9,7 @@ git-send-pack - Push objects over Git protocol to another repository
 SYNOPSIS
 --------
 [verse]
-'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]
+'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]
 
 DESCRIPTION
 -----------
@@ -62,6 +62,11 @@ be in a separate packet, and the list must end with a flush packet.
        Send a "thin" pack, which records objects in deltified form based
        on objects not included in the pack to reduce network traffic.
 
+--atomic::
+       Use an atomic transaction for updating the refs. If any of the refs
+       fails to update then the entire push will fail without changing any
+       refs.
+
 <host>::
        A remote host to house the repository.  When this
        part is specified, 'git-receive-pack' is invoked via
index b564a778455c352379127c3f06349e0ebbc81c98..b961e5ae7856626ebc063e94bfeee75383d27aeb 100644 (file)
@@ -13,7 +13,7 @@
 #include "sha1-array.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"
+"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n"
 "  --all and explicit <ref> specification are mutually exclusive.";
 
 static struct send_pack_args args;
@@ -170,6 +170,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.use_thin_pack = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--atomic")) {
+                               args.atomic = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--stateless-rpc")) {
                                args.stateless_rpc = 1;
                                continue;
index 8b62efd2adcb74c7e46d3f918a25658d6f6ce16f..f346524dbd805bca5167337b71e5eff605fcd81b 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -115,7 +115,8 @@ struct ref {
                REF_STATUS_REJECT_SHALLOW,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
-               REF_STATUS_EXPECTING_REPORT
+               REF_STATUS_EXPECTING_REPORT,
+               REF_STATUS_ATOMIC_PUSH_FAILED
        } status;
        char *remote_status;
        struct ref *peer_ref; /* when renaming */
index 49748259234dea7301530251bcb1c86f1bb9f36f..266f37f0605d8aa513e96c17d146b8d52468aaff 100644 (file)
@@ -282,6 +282,29 @@ static int generate_push_cert(struct strbuf *req_buf,
        return update_seen;
 }
 
+
+static int atomic_push_failure(struct send_pack_args *args,
+                              struct ref *remote_refs,
+                              struct ref *failing_ref)
+{
+       struct ref *ref;
+       /* Mark other refs as failed */
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (!ref->peer_ref && !args->send_mirror)
+                       continue;
+
+               switch (ref->status) {
+               case REF_STATUS_EXPECTING_REPORT:
+                       ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+                       continue;
+               default:
+                       break; /* do nothing */
+               }
+       }
+       return error("atomic push failed for ref %s. status: %d\n",
+                    failing_ref->name, failing_ref->status);
+}
+
 int send_pack(struct send_pack_args *args,
              int fd[], struct child_process *conn,
              struct ref *remote_refs,
@@ -298,6 +321,8 @@ int send_pack(struct send_pack_args *args,
        int use_sideband = 0;
        int quiet_supported = 0;
        int agent_supported = 0;
+       int use_atomic = 0;
+       int atomic_supported = 0;
        unsigned cmds_sent = 0;
        int ret;
        struct async demux;
@@ -318,6 +343,8 @@ int send_pack(struct send_pack_args *args,
                agent_supported = 1;
        if (server_supports("no-thin"))
                args->use_thin_pack = 0;
+       if (server_supports("atomic"))
+               atomic_supported = 1;
        if (args->push_cert) {
                int len;
 
@@ -332,6 +359,10 @@ int send_pack(struct send_pack_args *args,
                        "Perhaps you should specify a branch such as 'master'.\n");
                return 0;
        }
+       if (args->atomic && !atomic_supported)
+               die(_("server does not support --atomic push"));
+
+       use_atomic = atomic_supported && args->atomic;
 
        if (status_report)
                strbuf_addstr(&cap_buf, " report-status");
@@ -339,6 +370,8 @@ int send_pack(struct send_pack_args *args,
                strbuf_addstr(&cap_buf, " side-band-64k");
        if (quiet_supported && (args->quiet || !args->progress))
                strbuf_addstr(&cap_buf, " quiet");
+       if (use_atomic)
+               strbuf_addstr(&cap_buf, " atomic");
        if (agent_supported)
                strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
 
@@ -363,9 +396,21 @@ int send_pack(struct send_pack_args *args,
         * the pack data.
         */
        for (ref = remote_refs; ref; ref = ref->next) {
-               if (check_to_send_update(ref, args) < 0)
+               switch (check_to_send_update(ref, args)) {
+               case 0: /* no error */
+                       break;
+               case CHECK_REF_STATUS_REJECTED:
+                       /*
+                        * When we know the server would reject a ref update if
+                        * we were to send it and we're trying to send the refs
+                        * atomically, abort the whole operation.
+                        */
+                       if (use_atomic)
+                               return atomic_push_failure(args, remote_refs, ref);
+                       /* Fallthrough for non atomic case. */
+               default:
                        continue;
-
+               }
                if (!ref->deletion)
                        need_pack_data = 1;
 
index 56354577467acfe1bf98652f6bfad9ad7e5db851..b6646488aaf93814c5053d79528c1b5a4d19bcb5 100644 (file)
@@ -13,7 +13,8 @@ struct send_pack_args {
                use_ofs_delta:1,
                dry_run:1,
                push_cert:1,
-               stateless_rpc:1;
+               stateless_rpc:1,
+               atomic:1;
 };
 
 int send_pack(struct send_pack_args *args,
index 70d38e4c4b35c6207e226ab7cb54095c34bb4766..c67feeeb26805e83195b4d705bf3f9bec2ec8247 100644 (file)
@@ -728,6 +728,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
                                                 ref->deletion ? NULL : ref->peer_ref,
                                                 "remote failed to report status", porcelain);
                break;
+       case REF_STATUS_ATOMIC_PUSH_FAILED:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                                                "atomic push failed", porcelain);
+               break;
        case REF_STATUS_OK:
                print_ok_ref_status(ref, porcelain);
                break;