Skip to content

Commit 6f11942

Browse files
bk2204gitster
authored andcommitted
remote-curl: pass on atomic capability to remote side
When pushing more than one reference with the --atomic option, the server is supposed to perform a single atomic transaction to update the references, leaving them either all to succeed or all to fail. This works fine when pushing locally or over SSH, but when pushing over HTTP, we fail to pass the atomic capability to the remote side. In fact, we have not reported this capability to any remote helpers during the life of the feature. Now normally, things happen to work nevertheless, since we actually check for most types of failures, such as non-fast-forward updates, on the client side, and just abort the entire attempt. However, if the server side reports a problem, such as the inability to lock a ref, the transaction isn't atomic, because we haven't passed the appropriate capability over and the remote side has no way of knowing that we wanted atomic behavior. Fix this by passing the option from the transport code through to remote helpers, and from the HTTP remote helper down to send-pack. With this change, we can detect if the server side rejects the push and report back appropriately. Note the difference in the messages: the remote side reports "atomic transaction failed", while our own checking rejects pushes with the message "atomic push failed". Document the atomic option in the remote helper documentation, so other implementers can implement it if they like. Signed-off-by: brian m. carlson <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 5fa0f52 commit 6f11942

File tree

5 files changed

+62
-3
lines changed

5 files changed

+62
-3
lines changed

Documentation/gitremote-helpers.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,11 @@ set by Git if the remote helper has the 'option' capability.
505505
Indicate that only the objects wanted need to be fetched, not
506506
their dependents.
507507

508+
'option atomic' {'true'|'false'}::
509+
When pushing, request the remote server to update refs in a single atomic
510+
transaction. If successful, all refs will be updated, or none will. If the
511+
remote side does not support this capability, the push will fail.
512+
508513
SEE ALSO
509514
--------
510515
linkgit:git-remote[1]

remote-curl.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ struct options {
4040
push_cert : 2,
4141
deepen_relative : 1,
4242
from_promisor : 1,
43-
no_dependents : 1;
43+
no_dependents : 1,
44+
atomic : 1;
4445
};
4546
static struct options options;
4647
static struct string_list cas_options = STRING_LIST_INIT_DUP;
@@ -148,6 +149,14 @@ static int set_option(const char *name, const char *value)
148149
else
149150
return -1;
150151
return 0;
152+
} else if (!strcmp(name, "atomic")) {
153+
if (!strcmp(value, "true"))
154+
options.atomic = 1;
155+
else if (!strcmp(value, "false"))
156+
options.atomic = 0;
157+
else
158+
return -1;
159+
return 0;
151160
} else if (!strcmp(name, "push-option")) {
152161
if (*value != '"')
153162
string_list_append(&options.push_options, value);
@@ -1196,6 +1205,8 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
11961205
argv_array_push(&args, "--signed=yes");
11971206
else if (options.push_cert == SEND_PACK_PUSH_CERT_IF_ASKED)
11981207
argv_array_push(&args, "--signed=if-asked");
1208+
if (options.atomic)
1209+
argv_array_push(&args, "--atomic");
11991210
if (options.verbosity == 0)
12001211
argv_array_push(&args, "--quiet");
12011212
else if (options.verbosity > 1)

t/t5541-http-push-smart.sh

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,12 @@ test_expect_success 'push --atomic also prevents branch creation, reports collat
184184
test_config -C "$d" http.receivepack true &&
185185
up="$HTTPD_URL"/smart/atomic-branches.git &&
186186
187-
# Tell "$up" about two branches for now
187+
# Tell "$up" about three branches for now
188188
test_commit atomic1 &&
189189
test_commit atomic2 &&
190190
git branch collateral &&
191-
git push "$up" master collateral &&
191+
git branch other &&
192+
git push "$up" master collateral other &&
192193
193194
# collateral is a valid push, but should be failed by atomic push
194195
git checkout collateral &&
@@ -226,6 +227,41 @@ test_expect_success 'push --atomic also prevents branch creation, reports collat
226227
grep "^ ! .*rejected.* collateral -> collateral .*atomic push failed" output
227228
'
228229

230+
test_expect_success 'push --atomic fails on server-side errors' '
231+
# Use previously set up repository
232+
d=$HTTPD_DOCUMENT_ROOT_PATH/atomic-branches.git &&
233+
test_config -C "$d" http.receivepack true &&
234+
up="$HTTPD_URL"/smart/atomic-branches.git &&
235+
236+
# break ref updates for other on the remote site
237+
mkdir "$d/refs/heads/other.lock" &&
238+
239+
# add the new commit to other
240+
git branch -f other collateral &&
241+
242+
# --atomic should cause entire push to be rejected
243+
test_must_fail git push --atomic "$up" atomic other 2>output &&
244+
245+
# the new branch should not have been created upstream
246+
test_must_fail git -C "$d" show-ref --verify refs/heads/atomic &&
247+
248+
# upstream should still reflect atomic2, the last thing we pushed
249+
# successfully
250+
git rev-parse atomic2 >expected &&
251+
# ...to other.
252+
git -C "$d" rev-parse refs/heads/other >actual &&
253+
test_cmp expected actual &&
254+
255+
# the new branch should not have been created upstream
256+
test_must_fail git -C "$d" show-ref --verify refs/heads/atomic &&
257+
258+
# the failed refs should be indicated to the user
259+
grep "^ ! .*rejected.* other -> other .*atomic transaction failed" output &&
260+
261+
# the collateral failure refs should be indicated to the user
262+
grep "^ ! .*rejected.* atomic -> atomic .*atomic transaction failed" output
263+
'
264+
229265
test_expect_success 'push --all can push to empty repo' '
230266
d=$HTTPD_DOCUMENT_ROOT_PATH/empty-all.git &&
231267
git init --bare "$d" &&

transport-helper.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,10 @@ static void set_common_push_options(struct transport *transport,
840840
die(_("helper %s does not support --signed=if-asked"), name);
841841
}
842842

843+
if (flags & TRANSPORT_PUSH_ATOMIC)
844+
if (set_helper_option(transport, TRANS_OPT_ATOMIC, "true") != 0)
845+
die(_("helper %s does not support --atomic"), name);
846+
843847
if (flags & TRANSPORT_PUSH_OPTIONS) {
844848
struct string_list_item *item;
845849
for_each_string_list_item(item, transport->push_options)

transport.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ void transport_check_allowed(const char *type);
208208
/* Filter objects for partial clone and fetch */
209209
#define TRANS_OPT_LIST_OBJECTS_FILTER "filter"
210210

211+
/* Request atomic (all-or-nothing) updates when pushing */
212+
#define TRANS_OPT_ATOMIC "atomic"
213+
211214
/**
212215
* Returns 0 if the option was used, non-zero otherwise. Prints a
213216
* message to stderr if the option is not used.

0 commit comments

Comments
 (0)