send-pack.c: add --atomic command line argument

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>

Ronnie Sahlberg committed Jan 7, 2015 at 19:23 UTC 4ff17f10c4297ab3d9948d4216016ca367e737e3
6 files changed +66 -6
Documentation/git-send-pack.txt
+6 -1
@@ -9,7 +9,7 @@ git-send-pack - Push objects over Git protocol to another repository
9 SYNOPSIS
10 --------
11 [verse]
12 -'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]
12 +'git send-pack' [--all] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]
13
14 DESCRIPTION
15 -----------
@@ -62,6 +62,11 @@ be in a separate packet, and the list must end with a flush packet.
62 Send a "thin" pack, which records objects in deltified form based
63 on objects not included in the pack to reduce network traffic.
64
65 +--atomic::
66 + Use an atomic transaction for updating the refs. If any of the refs
67 + fails to update then the entire push will fail without changing any
68 + refs.
69 +
70 <host>::
71 A remote host to house the repository. When this
72 part is specified, 'git-receive-pack' is invoked via
builtin/send-pack.c
+5 -1
@@ -13,7 +13,7 @@
13 #include "sha1-array.h"
14
15 static const char send_pack_usage[] =
16 -"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
16 +"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [--atomic] [<host>:]<directory> [<ref>...]\n"
17 " --all and explicit <ref> specification are mutually exclusive.";
18
19 static struct send_pack_args args;
@@ -170,6 +170,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
170 args.use_thin_pack = 1;
171 continue;
172 }
173 + if (!strcmp(arg, "--atomic")) {
174 + args.atomic = 1;
175 + continue;
176 + }
177 if (!strcmp(arg, "--stateless-rpc")) {
178 args.stateless_rpc = 1;
179 continue;
remote.h
+2 -1
@@ -115,7 +115,8 @@ struct ref {
115 REF_STATUS_REJECT_SHALLOW,
116 REF_STATUS_UPTODATE,
117 REF_STATUS_REMOTE_REJECT,
118 - REF_STATUS_EXPECTING_REPORT
118 + REF_STATUS_EXPECTING_REPORT,
119 + REF_STATUS_ATOMIC_PUSH_FAILED
120 } status;
121 char *remote_status;
122 struct ref *peer_ref; /* when renaming */
send-pack.c
+47 -2
@@ -282,6 +282,29 @@ free_return:
282 return update_seen;
283 }
284
285 +
286 +static int atomic_push_failure(struct send_pack_args *args,
287 + struct ref *remote_refs,
288 + struct ref *failing_ref)
289 +{
290 + struct ref *ref;
291 + /* Mark other refs as failed */
292 + for (ref = remote_refs; ref; ref = ref->next) {
293 + if (!ref->peer_ref && !args->send_mirror)
294 + continue;
295 +
296 + switch (ref->status) {
297 + case REF_STATUS_EXPECTING_REPORT:
298 + ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
299 + continue;
300 + default:
301 + break; /* do nothing */
302 + }
303 + }
304 + return error("atomic push failed for ref %s. status: %d\n",
305 + failing_ref->name, failing_ref->status);
306 +}
307 +
308 int send_pack(struct send_pack_args *args,
309 int fd[], struct child_process *conn,
310 struct ref *remote_refs,
@@ -298,6 +321,8 @@ int send_pack(struct send_pack_args *args,
321 int use_sideband = 0;
322 int quiet_supported = 0;
323 int agent_supported = 0;
324 + int use_atomic = 0;
325 + int atomic_supported = 0;
326 unsigned cmds_sent = 0;
327 int ret;
328 struct async demux;
@@ -318,6 +343,8 @@ int send_pack(struct send_pack_args *args,
343 agent_supported = 1;
344 if (server_supports("no-thin"))
345 args->use_thin_pack = 0;
346 + if (server_supports("atomic"))
347 + atomic_supported = 1;
348 if (args->push_cert) {
349 int len;
350
@@ -332,6 +359,10 @@ int send_pack(struct send_pack_args *args,
359 "Perhaps you should specify a branch such as 'master'.\n");
360 return 0;
361 }
362 + if (args->atomic && !atomic_supported)
363 + die(_("server does not support --atomic push"));
364 +
365 + use_atomic = atomic_supported && args->atomic;
366
367 if (status_report)
368 strbuf_addstr(&cap_buf, " report-status");
@@ -339,6 +370,8 @@ int send_pack(struct send_pack_args *args,
370 strbuf_addstr(&cap_buf, " side-band-64k");
371 if (quiet_supported && (args->quiet || !args->progress))
372 strbuf_addstr(&cap_buf, " quiet");
373 + if (use_atomic)
374 + strbuf_addstr(&cap_buf, " atomic");
375 if (agent_supported)
376 strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
377
@@ -363,9 +396,21 @@ int send_pack(struct send_pack_args *args,
396 * the pack data.
397 */
398 for (ref = remote_refs; ref; ref = ref->next) {
366 - if (check_to_send_update(ref, args) < 0)
399 + switch (check_to_send_update(ref, args)) {
400 + case 0: /* no error */
401 + break;
402 + case CHECK_REF_STATUS_REJECTED:
403 + /*
404 + * When we know the server would reject a ref update if
405 + * we were to send it and we're trying to send the refs
406 + * atomically, abort the whole operation.
407 + */
408 + if (use_atomic)
409 + return atomic_push_failure(args, remote_refs, ref);
410 + /* Fallthrough for non atomic case. */
411 + default:
412 continue;
368 -
413 + }
414 if (!ref->deletion)
415 need_pack_data = 1;
416
send-pack.h
+2 -1
@@ -13,7 +13,8 @@ struct send_pack_args {
13 use_ofs_delta:1,
14 dry_run:1,
15 push_cert:1,
16 - stateless_rpc:1;
16 + stateless_rpc:1,
17 + atomic:1;
18 };
19
20 int send_pack(struct send_pack_args *args,
transport.c
+4
@@ -728,6 +728,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
728 ref->deletion ? NULL : ref->peer_ref,
729 "remote failed to report status", porcelain);
730 break;
731 + case REF_STATUS_ATOMIC_PUSH_FAILED:
732 + print_ref_status('!', "[rejected]", ref, ref->peer_ref,
733 + "atomic push failed", porcelain);
734 + break;
735 case REF_STATUS_OK:
736 print_ok_ref_status(ref, porcelain);
737 break;