Skip to content

Commit 4ff17f1

Browse files
rsahlberggitster
authored andcommitted
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 <[email protected]> Signed-off-by: Stefan Beller <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7582e93 commit 4ff17f1

File tree

6 files changed

+66
-6
lines changed

6 files changed

+66
-6
lines changed

Documentation/git-send-pack.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-send-pack - Push objects over Git protocol to another repository
99
SYNOPSIS
1010
--------
1111
[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>...]
1313

1414
DESCRIPTION
1515
-----------
@@ -62,6 +62,11 @@ be in a separate packet, and the list must end with a flush packet.
6262
Send a "thin" pack, which records objects in deltified form based
6363
on objects not included in the pack to reduce network traffic.
6464

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+
6570
<host>::
6671
A remote host to house the repository. When this
6772
part is specified, 'git-receive-pack' is invoked via

builtin/send-pack.c

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
#include "sha1-array.h"
1414

1515
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"
1717
" --all and explicit <ref> specification are mutually exclusive.";
1818

1919
static struct send_pack_args args;
@@ -170,6 +170,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
170170
args.use_thin_pack = 1;
171171
continue;
172172
}
173+
if (!strcmp(arg, "--atomic")) {
174+
args.atomic = 1;
175+
continue;
176+
}
173177
if (!strcmp(arg, "--stateless-rpc")) {
174178
args.stateless_rpc = 1;
175179
continue;

remote.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ struct ref {
115115
REF_STATUS_REJECT_SHALLOW,
116116
REF_STATUS_UPTODATE,
117117
REF_STATUS_REMOTE_REJECT,
118-
REF_STATUS_EXPECTING_REPORT
118+
REF_STATUS_EXPECTING_REPORT,
119+
REF_STATUS_ATOMIC_PUSH_FAILED
119120
} status;
120121
char *remote_status;
121122
struct ref *peer_ref; /* when renaming */

send-pack.c

+47-2
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,29 @@ static int generate_push_cert(struct strbuf *req_buf,
282282
return update_seen;
283283
}
284284

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+
285308
int send_pack(struct send_pack_args *args,
286309
int fd[], struct child_process *conn,
287310
struct ref *remote_refs,
@@ -298,6 +321,8 @@ int send_pack(struct send_pack_args *args,
298321
int use_sideband = 0;
299322
int quiet_supported = 0;
300323
int agent_supported = 0;
324+
int use_atomic = 0;
325+
int atomic_supported = 0;
301326
unsigned cmds_sent = 0;
302327
int ret;
303328
struct async demux;
@@ -318,6 +343,8 @@ int send_pack(struct send_pack_args *args,
318343
agent_supported = 1;
319344
if (server_supports("no-thin"))
320345
args->use_thin_pack = 0;
346+
if (server_supports("atomic"))
347+
atomic_supported = 1;
321348
if (args->push_cert) {
322349
int len;
323350

@@ -332,13 +359,19 @@ int send_pack(struct send_pack_args *args,
332359
"Perhaps you should specify a branch such as 'master'.\n");
333360
return 0;
334361
}
362+
if (args->atomic && !atomic_supported)
363+
die(_("server does not support --atomic push"));
364+
365+
use_atomic = atomic_supported && args->atomic;
335366

336367
if (status_report)
337368
strbuf_addstr(&cap_buf, " report-status");
338369
if (use_sideband)
339370
strbuf_addstr(&cap_buf, " side-band-64k");
340371
if (quiet_supported && (args->quiet || !args->progress))
341372
strbuf_addstr(&cap_buf, " quiet");
373+
if (use_atomic)
374+
strbuf_addstr(&cap_buf, " atomic");
342375
if (agent_supported)
343376
strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
344377

@@ -363,9 +396,21 @@ int send_pack(struct send_pack_args *args,
363396
* the pack data.
364397
*/
365398
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:
367412
continue;
368-
413+
}
369414
if (!ref->deletion)
370415
need_pack_data = 1;
371416

send-pack.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ struct send_pack_args {
1313
use_ofs_delta:1,
1414
dry_run:1,
1515
push_cert:1,
16-
stateless_rpc:1;
16+
stateless_rpc:1,
17+
atomic:1;
1718
};
1819

1920
int send_pack(struct send_pack_args *args,

transport.c

+4
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,10 @@ static int print_one_push_status(struct ref *ref, const char *dest, int count, i
728728
ref->deletion ? NULL : ref->peer_ref,
729729
"remote failed to report status", porcelain);
730730
break;
731+
case REF_STATUS_ATOMIC_PUSH_FAILED:
732+
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
733+
"atomic push failed", porcelain);
734+
break;
731735
case REF_STATUS_OK:
732736
print_ok_ref_status(ref, porcelain);
733737
break;

0 commit comments

Comments
 (0)