Skip to content

Commit 507d6aa

Browse files
committed
Merge branch 'sb/atomic-push' into mh/ref-trans-value-check
* sb/atomic-push: Document receive.advertiseatomic t5543-atomic-push.sh: add basic tests for atomic pushes push.c: add an --atomic argument send-pack.c: add --atomic command line argument send-pack: rename ref_update_to_be_sent to check_to_send_update receive-pack.c: negotiate atomic push support receive-pack.c: add execute_commands_atomic function receive-pack.c: move transaction handling in a central place receive-pack.c: move iterating over all commands outside execute_commands receive-pack.c: die instead of error in case of possible future bug receive-pack.c: shorten the execute_commands loop over all commands
2 parents 61c9475 + 04b39f1 commit 507d6aa

File tree

13 files changed

+429
-50
lines changed

13 files changed

+429
-50
lines changed

Documentation/config.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2094,6 +2094,11 @@ rebase.autostash::
20942094
successful rebase might result in non-trivial conflicts.
20952095
Defaults to false.
20962096

2097+
receive.advertiseatomic::
2098+
By default, git-receive-pack will advertise the atomic push
2099+
capability to its clients. If you don't want to this capability
2100+
to be advertised, set this variable to false.
2101+
20972102
receive.autogc::
20982103
By default, git-receive-pack will run "git-gc --auto" after
20992104
receiving data from git-push and updating refs. You can stop

Documentation/git-push.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
99
SYNOPSIS
1010
--------
1111
[verse]
12-
'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
12+
'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
1313
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose]
1414
[-u | --set-upstream] [--signed]
1515
[--force-with-lease[=<refname>[:<expect>]]]
@@ -136,6 +136,11 @@ already exists on the remote side.
136136
logged. See linkgit:git-receive-pack[1] for the details
137137
on the receiving end.
138138

139+
--[no-]atomic::
140+
Use an atomic transaction on the remote side if available.
141+
Either all refs are updated, or on error, no refs are updated.
142+
If the server does not support atomic pushes the push will fail.
143+
139144
--receive-pack=<git-receive-pack>::
140145
--exec=<git-receive-pack>::
141146
Path to the 'git-receive-pack' program on the remote

Documentation/git-send-pack.txt

Lines changed: 6 additions & 1 deletion
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

Documentation/technical/protocol-capabilities.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ was sent. Server MUST NOT ignore capabilities that client requested
1818
and server advertised. As a consequence of these rules, server MUST
1919
NOT advertise capabilities it does not understand.
2020

21-
The 'report-status', 'delete-refs', 'quiet', and 'push-cert' capabilities
22-
are sent and recognized by the receive-pack (push to server) process.
21+
The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert'
22+
capabilities are sent and recognized by the receive-pack (push to server)
23+
process.
2324

2425
The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized
2526
by both upload-pack and receive-pack protocols. The 'agent' capability
@@ -244,6 +245,14 @@ respond with the 'quiet' capability to suppress server-side progress
244245
reporting if the local progress reporting is also being suppressed
245246
(e.g., via `push -q`, or if stderr does not go to a tty).
246247

248+
atomic
249+
------
250+
251+
If the server sends the 'atomic' capability it is capable of accepting
252+
atomic pushes. If the pushing client requests this capability, the server
253+
will update the refs in one atomic transaction. Either all refs are
254+
updated or none.
255+
247256
allow-tip-sha1-in-want
248257
----------------------
249258

builtin/push.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
487487
int flags = 0;
488488
int tags = 0;
489489
int rc;
490+
int atomic = 0;
490491
const char *repo = NULL; /* default repository */
491492
struct option options[] = {
492493
OPT__VERBOSITY(&verbosity),
@@ -518,6 +519,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
518519
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
519520
TRANSPORT_PUSH_FOLLOW_TAGS),
520521
OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT),
522+
OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")),
521523
OPT_END()
522524
};
523525

@@ -533,6 +535,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
533535
if (tags)
534536
add_refspec("refs/tags/*");
535537

538+
if (atomic)
539+
flags |= TRANSPORT_PUSH_ATOMIC;
540+
536541
if (argc > 0) {
537542
repo = argv[0];
538543
set_refspecs(argv + 1, argc - 1, repo);

builtin/receive-pack.c

Lines changed: 130 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ static int receive_fsck_objects = -1;
3838
static int transfer_fsck_objects = -1;
3939
static int receive_unpack_limit = -1;
4040
static int transfer_unpack_limit = -1;
41+
static int advertise_atomic_push = 1;
4142
static int unpack_limit = 100;
4243
static int report_status;
4344
static int use_sideband;
45+
static int use_atomic;
4446
static int quiet;
4547
static int prefer_ofs_delta = 1;
4648
static int auto_update_server_info;
@@ -67,6 +69,7 @@ static const char *NONCE_SLOP = "SLOP";
6769
static const char *nonce_status;
6870
static long nonce_stamp_slop;
6971
static unsigned long nonce_stamp_slop_limit;
72+
static struct ref_transaction *transaction;
7073

7174
static enum deny_action parse_deny_action(const char *var, const char *value)
7275
{
@@ -160,6 +163,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
160163
return 0;
161164
}
162165

166+
if (strcmp(var, "receive.advertiseatomic") == 0) {
167+
advertise_atomic_push = git_config_bool(var, value);
168+
return 0;
169+
}
170+
163171
return git_default_config(var, value, cb);
164172
}
165173

@@ -175,6 +183,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
175183

176184
strbuf_addstr(&cap,
177185
"report-status delete-refs side-band-64k quiet");
186+
if (advertise_atomic_push)
187+
strbuf_addstr(&cap, " atomic");
178188
if (prefer_ofs_delta)
179189
strbuf_addstr(&cap, " ofs-delta");
180190
if (push_cert_nonce)
@@ -910,6 +920,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
910920
}
911921

912922
if (is_null_sha1(new_sha1)) {
923+
struct strbuf err = STRBUF_INIT;
913924
if (!parse_object(old_sha1)) {
914925
old_sha1 = NULL;
915926
if (ref_exists(name)) {
@@ -919,35 +930,36 @@ static const char *update(struct command *cmd, struct shallow_info *si)
919930
cmd->did_not_exist = 1;
920931
}
921932
}
922-
if (delete_ref(namespaced_name, old_sha1, 0)) {
923-
rp_error("failed to delete %s", name);
933+
if (ref_transaction_delete(transaction,
934+
namespaced_name,
935+
old_sha1,
936+
0, old_sha1 != NULL,
937+
"push", &err)) {
938+
rp_error("%s", err.buf);
939+
strbuf_release(&err);
924940
return "failed to delete";
925941
}
942+
strbuf_release(&err);
926943
return NULL; /* good */
927944
}
928945
else {
929946
struct strbuf err = STRBUF_INIT;
930-
struct ref_transaction *transaction;
931-
932947
if (shallow_update && si->shallow_ref[cmd->index] &&
933948
update_shallow_ref(cmd, si))
934949
return "shallow error";
935950

936-
transaction = ref_transaction_begin(&err);
937-
if (!transaction ||
938-
ref_transaction_update(transaction, namespaced_name,
939-
new_sha1, old_sha1, 0, 1, "push",
940-
&err) ||
941-
ref_transaction_commit(transaction, &err)) {
942-
ref_transaction_free(transaction);
943-
951+
if (ref_transaction_update(transaction,
952+
namespaced_name,
953+
new_sha1, old_sha1,
954+
0, 1, "push",
955+
&err)) {
944956
rp_error("%s", err.buf);
945957
strbuf_release(&err);
958+
946959
return "failed to update ref";
947960
}
948-
949-
ref_transaction_free(transaction);
950961
strbuf_release(&err);
962+
951963
return NULL; /* good */
952964
}
953965
}
@@ -1131,11 +1143,105 @@ static void reject_updates_to_hidden(struct command *commands)
11311143
}
11321144
}
11331145

1146+
static int should_process_cmd(struct command *cmd)
1147+
{
1148+
return !cmd->error_string && !cmd->skip_update;
1149+
}
1150+
1151+
static void warn_if_skipped_connectivity_check(struct command *commands,
1152+
struct shallow_info *si)
1153+
{
1154+
struct command *cmd;
1155+
int checked_connectivity = 1;
1156+
1157+
for (cmd = commands; cmd; cmd = cmd->next) {
1158+
if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) {
1159+
error("BUG: connectivity check has not been run on ref %s",
1160+
cmd->ref_name);
1161+
checked_connectivity = 0;
1162+
}
1163+
}
1164+
if (!checked_connectivity)
1165+
die("BUG: connectivity check skipped???");
1166+
}
1167+
1168+
static void execute_commands_non_atomic(struct command *commands,
1169+
struct shallow_info *si)
1170+
{
1171+
struct command *cmd;
1172+
struct strbuf err = STRBUF_INIT;
1173+
1174+
for (cmd = commands; cmd; cmd = cmd->next) {
1175+
if (!should_process_cmd(cmd))
1176+
continue;
1177+
1178+
transaction = ref_transaction_begin(&err);
1179+
if (!transaction) {
1180+
rp_error("%s", err.buf);
1181+
strbuf_reset(&err);
1182+
cmd->error_string = "transaction failed to start";
1183+
continue;
1184+
}
1185+
1186+
cmd->error_string = update(cmd, si);
1187+
1188+
if (!cmd->error_string
1189+
&& ref_transaction_commit(transaction, &err)) {
1190+
rp_error("%s", err.buf);
1191+
strbuf_reset(&err);
1192+
cmd->error_string = "failed to update ref";
1193+
}
1194+
ref_transaction_free(transaction);
1195+
}
1196+
strbuf_release(&err);
1197+
}
1198+
1199+
static void execute_commands_atomic(struct command *commands,
1200+
struct shallow_info *si)
1201+
{
1202+
struct command *cmd;
1203+
struct strbuf err = STRBUF_INIT;
1204+
const char *reported_error = "atomic push failure";
1205+
1206+
transaction = ref_transaction_begin(&err);
1207+
if (!transaction) {
1208+
rp_error("%s", err.buf);
1209+
strbuf_reset(&err);
1210+
reported_error = "transaction failed to start";
1211+
goto failure;
1212+
}
1213+
1214+
for (cmd = commands; cmd; cmd = cmd->next) {
1215+
if (!should_process_cmd(cmd))
1216+
continue;
1217+
1218+
cmd->error_string = update(cmd, si);
1219+
1220+
if (cmd->error_string)
1221+
goto failure;
1222+
}
1223+
1224+
if (ref_transaction_commit(transaction, &err)) {
1225+
rp_error("%s", err.buf);
1226+
reported_error = "atomic transaction failed";
1227+
goto failure;
1228+
}
1229+
goto cleanup;
1230+
1231+
failure:
1232+
for (cmd = commands; cmd; cmd = cmd->next)
1233+
if (!cmd->error_string)
1234+
cmd->error_string = reported_error;
1235+
1236+
cleanup:
1237+
ref_transaction_free(transaction);
1238+
strbuf_release(&err);
1239+
}
1240+
11341241
static void execute_commands(struct command *commands,
11351242
const char *unpacker_error,
11361243
struct shallow_info *si)
11371244
{
1138-
int checked_connectivity;
11391245
struct command *cmd;
11401246
unsigned char sha1[20];
11411247
struct iterate_data data;
@@ -1166,27 +1272,13 @@ static void execute_commands(struct command *commands,
11661272
free(head_name_to_free);
11671273
head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
11681274

1169-
checked_connectivity = 1;
1170-
for (cmd = commands; cmd; cmd = cmd->next) {
1171-
if (cmd->error_string)
1172-
continue;
1173-
1174-
if (cmd->skip_update)
1175-
continue;
1176-
1177-
cmd->error_string = update(cmd, si);
1178-
if (shallow_update && !cmd->error_string &&
1179-
si->shallow_ref[cmd->index]) {
1180-
error("BUG: connectivity check has not been run on ref %s",
1181-
cmd->ref_name);
1182-
checked_connectivity = 0;
1183-
}
1184-
}
1275+
if (use_atomic)
1276+
execute_commands_atomic(commands, si);
1277+
else
1278+
execute_commands_non_atomic(commands, si);
11851279

1186-
if (shallow_update && !checked_connectivity)
1187-
error("BUG: run 'git fsck' for safety.\n"
1188-
"If there are errors, try to remove "
1189-
"the reported refs above");
1280+
if (shallow_update)
1281+
warn_if_skipped_connectivity_check(commands, si);
11901282
}
11911283

11921284
static struct command **queue_command(struct command **tail,
@@ -1268,6 +1360,9 @@ static struct command *read_head_info(struct sha1_array *shallow)
12681360
use_sideband = LARGE_PACKET_MAX;
12691361
if (parse_feature_request(feature_list, "quiet"))
12701362
quiet = 1;
1363+
if (advertise_atomic_push
1364+
&& parse_feature_request(feature_list, "atomic"))
1365+
use_atomic = 1;
12711366
}
12721367

12731368
if (!strcmp(line, "push-cert")) {

builtin/send-pack.c

Lines changed: 5 additions & 1 deletion
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

Lines changed: 2 additions & 1 deletion
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 */

0 commit comments

Comments
 (0)