diff options
author | Junio C Hamano <gitster@pobox.com> | 2015-02-11 13:43:50 -0800 |
---|---|---|
committer | Junio C Hamano <gitster@pobox.com> | 2015-02-11 13:43:51 -0800 |
commit | 39fa6112ec8492f9300765e1f71e7c8ecfafc713 (patch) | |
tree | aaf52d72ae2010c4a99d0f0c3ccd1c3f40cdcb91 | |
parent | 4d5c4e498a14af732f3b0b690f8b551d18610bfc (diff) | |
parent | 04b39f195baf95b79c9c28a096011e9fe0a08303 (diff) | |
download | git-39fa6112ec8492f9300765e1f71e7c8ecfafc713.tar.gz |
Merge branch 'sb/atomic-push'
"git push" has been taught a "--atomic" option that makes push to
update more than one ref an "all-or-none" affair.
* 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
-rw-r--r-- | Documentation/config.txt | 5 | ||||
-rw-r--r-- | Documentation/git-push.txt | 7 | ||||
-rw-r--r-- | Documentation/git-send-pack.txt | 7 | ||||
-rw-r--r-- | Documentation/technical/protocol-capabilities.txt | 13 | ||||
-rw-r--r-- | builtin/push.c | 5 | ||||
-rw-r--r-- | builtin/receive-pack.c | 165 | ||||
-rw-r--r-- | builtin/send-pack.c | 6 | ||||
-rw-r--r-- | remote.h | 3 | ||||
-rw-r--r-- | send-pack.c | 65 | ||||
-rw-r--r-- | send-pack.h | 3 | ||||
-rwxr-xr-x | t/t5543-atomic-push.sh | 194 | ||||
-rw-r--r-- | transport.c | 5 | ||||
-rw-r--r-- | transport.h | 1 |
13 files changed, 429 insertions, 50 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 04e2a71687..1a54eae8f8 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -2094,6 +2094,11 @@ rebase.autostash:: successful rebase might result in non-trivial conflicts. Defaults to false. +receive.advertiseatomic:: + By default, git-receive-pack will advertise the atomic push + capability to its clients. If you don't want to this capability + to be advertised, set this variable to false. + receive.autogc:: By default, git-receive-pack will run "git-gc --auto" after receiving data from git-push and updating refs. You can stop diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index b17283ab7a..ea9757692a 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects SYNOPSIS -------- [verse] -'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>] +'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>] [--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream] [--signed] [--force-with-lease[=<refname>[:<expect>]]] @@ -136,6 +136,11 @@ already exists on the remote side. logged. See linkgit:git-receive-pack[1] for the details on the receiving end. +--[no-]atomic:: + Use an atomic transaction on the remote side if available. + Either all refs are updated, or on error, no refs are updated. + If the server does not support atomic pushes the push will fail. + --receive-pack=<git-receive-pack>:: --exec=<git-receive-pack>:: Path to the 'git-receive-pack' program on the remote diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt index 2a0de42a75..45c7725dc3 100644 --- a/Documentation/git-send-pack.txt +++ b/Documentation/git-send-pack.txt @@ -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 diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt index 6d5424c1bd..4f8a7bfb4c 100644 --- a/Documentation/technical/protocol-capabilities.txt +++ b/Documentation/technical/protocol-capabilities.txt @@ -18,8 +18,9 @@ was sent. Server MUST NOT ignore capabilities that client requested and server advertised. As a consequence of these rules, server MUST NOT advertise capabilities it does not understand. -The 'report-status', 'delete-refs', 'quiet', and 'push-cert' capabilities -are sent and recognized by the receive-pack (push to server) process. +The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert' +capabilities are sent and recognized by the receive-pack (push to server) +process. The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized 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 reporting if the local progress reporting is also being suppressed (e.g., via `push -q`, or if stderr does not go to a tty). +atomic +------ + +If the server sends the 'atomic' capability it is capable of accepting +atomic pushes. If the pushing client requests this capability, the server +will update the refs in one atomic transaction. Either all refs are +updated or none. + allow-tip-sha1-in-want ---------------------- diff --git a/builtin/push.c b/builtin/push.c index 12f5e69393..fc771a9f6f 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -487,6 +487,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) int flags = 0; int tags = 0; int rc; + int atomic = 0; const char *repo = NULL; /* default repository */ struct option options[] = { OPT__VERBOSITY(&verbosity), @@ -518,6 +519,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), TRANSPORT_PUSH_FOLLOW_TAGS), OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT), + OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")), OPT_END() }; @@ -533,6 +535,9 @@ int cmd_push(int argc, const char **argv, const char *prefix) if (tags) add_refspec("refs/tags/*"); + if (atomic) + flags |= TRANSPORT_PUSH_ATOMIC; + if (argc > 0) { repo = argv[0]; set_refspecs(argv + 1, argc - 1, repo); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 8266c1fccf..4e85e25d0f 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -38,9 +38,11 @@ static int receive_fsck_objects = -1; static int transfer_fsck_objects = -1; static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; +static int advertise_atomic_push = 1; static int unpack_limit = 100; static int report_status; static int use_sideband; +static int use_atomic; static int quiet; static int prefer_ofs_delta = 1; static int auto_update_server_info; @@ -67,6 +69,7 @@ static const char *NONCE_SLOP = "SLOP"; static const char *nonce_status; static long nonce_stamp_slop; static unsigned long nonce_stamp_slop_limit; +static struct ref_transaction *transaction; static enum deny_action parse_deny_action(const char *var, const char *value) { @@ -160,6 +163,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.advertiseatomic") == 0) { + advertise_atomic_push = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -175,6 +183,8 @@ static void show_ref(const char *path, const unsigned char *sha1) strbuf_addstr(&cap, "report-status delete-refs side-band-64k quiet"); + if (advertise_atomic_push) + strbuf_addstr(&cap, " atomic"); if (prefer_ofs_delta) strbuf_addstr(&cap, " ofs-delta"); if (push_cert_nonce) @@ -910,6 +920,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) } if (is_null_sha1(new_sha1)) { + struct strbuf err = STRBUF_INIT; if (!parse_object(old_sha1)) { old_sha1 = NULL; if (ref_exists(name)) { @@ -919,35 +930,36 @@ static const char *update(struct command *cmd, struct shallow_info *si) cmd->did_not_exist = 1; } } - if (delete_ref(namespaced_name, old_sha1, 0)) { - rp_error("failed to delete %s", name); + if (ref_transaction_delete(transaction, + namespaced_name, + old_sha1, + 0, old_sha1 != NULL, + "push", &err)) { + rp_error("%s", err.buf); + strbuf_release(&err); return "failed to delete"; } + strbuf_release(&err); return NULL; /* good */ } else { struct strbuf err = STRBUF_INIT; - struct ref_transaction *transaction; - if (shallow_update && si->shallow_ref[cmd->index] && update_shallow_ref(cmd, si)) return "shallow error"; - transaction = ref_transaction_begin(&err); - if (!transaction || - ref_transaction_update(transaction, namespaced_name, - new_sha1, old_sha1, 0, 1, "push", - &err) || - ref_transaction_commit(transaction, &err)) { - ref_transaction_free(transaction); - + if (ref_transaction_update(transaction, + namespaced_name, + new_sha1, old_sha1, + 0, 1, "push", + &err)) { rp_error("%s", err.buf); strbuf_release(&err); + return "failed to update ref"; } - - ref_transaction_free(transaction); strbuf_release(&err); + return NULL; /* good */ } } @@ -1131,11 +1143,105 @@ static void reject_updates_to_hidden(struct command *commands) } } +static int should_process_cmd(struct command *cmd) +{ + return !cmd->error_string && !cmd->skip_update; +} + +static void warn_if_skipped_connectivity_check(struct command *commands, + struct shallow_info *si) +{ + struct command *cmd; + int checked_connectivity = 1; + + for (cmd = commands; cmd; cmd = cmd->next) { + if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) { + error("BUG: connectivity check has not been run on ref %s", + cmd->ref_name); + checked_connectivity = 0; + } + } + if (!checked_connectivity) + die("BUG: connectivity check skipped???"); +} + +static void execute_commands_non_atomic(struct command *commands, + struct shallow_info *si) +{ + struct command *cmd; + struct strbuf err = STRBUF_INIT; + + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + transaction = ref_transaction_begin(&err); + if (!transaction) { + rp_error("%s", err.buf); + strbuf_reset(&err); + cmd->error_string = "transaction failed to start"; + continue; + } + + cmd->error_string = update(cmd, si); + + if (!cmd->error_string + && ref_transaction_commit(transaction, &err)) { + rp_error("%s", err.buf); + strbuf_reset(&err); + cmd->error_string = "failed to update ref"; + } + ref_transaction_free(transaction); + } + strbuf_release(&err); +} + +static void execute_commands_atomic(struct command *commands, + struct shallow_info *si) +{ + struct command *cmd; + struct strbuf err = STRBUF_INIT; + const char *reported_error = "atomic push failure"; + + transaction = ref_transaction_begin(&err); + if (!transaction) { + rp_error("%s", err.buf); + strbuf_reset(&err); + reported_error = "transaction failed to start"; + goto failure; + } + + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + cmd->error_string = update(cmd, si); + + if (cmd->error_string) + goto failure; + } + + if (ref_transaction_commit(transaction, &err)) { + rp_error("%s", err.buf); + reported_error = "atomic transaction failed"; + goto failure; + } + goto cleanup; + +failure: + for (cmd = commands; cmd; cmd = cmd->next) + if (!cmd->error_string) + cmd->error_string = reported_error; + +cleanup: + ref_transaction_free(transaction); + strbuf_release(&err); +} + static void execute_commands(struct command *commands, const char *unpacker_error, struct shallow_info *si) { - int checked_connectivity; struct command *cmd; unsigned char sha1[20]; struct iterate_data data; @@ -1166,27 +1272,13 @@ static void execute_commands(struct command *commands, free(head_name_to_free); head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL); - checked_connectivity = 1; - for (cmd = commands; cmd; cmd = cmd->next) { - if (cmd->error_string) - continue; - - if (cmd->skip_update) - continue; - - cmd->error_string = update(cmd, si); - if (shallow_update && !cmd->error_string && - si->shallow_ref[cmd->index]) { - error("BUG: connectivity check has not been run on ref %s", - cmd->ref_name); - checked_connectivity = 0; - } - } + if (use_atomic) + execute_commands_atomic(commands, si); + else + execute_commands_non_atomic(commands, si); - if (shallow_update && !checked_connectivity) - error("BUG: run 'git fsck' for safety.\n" - "If there are errors, try to remove " - "the reported refs above"); + if (shallow_update) + warn_if_skipped_connectivity_check(commands, si); } static struct command **queue_command(struct command **tail, @@ -1268,6 +1360,9 @@ static struct command *read_head_info(struct sha1_array *shallow) use_sideband = LARGE_PACKET_MAX; if (parse_feature_request(feature_list, "quiet")) quiet = 1; + if (advertise_atomic_push + && parse_feature_request(feature_list, "atomic")) + use_atomic = 1; } if (!strcmp(line, "push-cert")) { diff --git a/builtin/send-pack.c b/builtin/send-pack.c index b564a77845..b961e5ae78 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -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; @@ -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 */ diff --git a/send-pack.c b/send-pack.c index 25947d7df9..9d2b0c52ed 100644 --- a/send-pack.c +++ b/send-pack.c @@ -193,10 +193,13 @@ static void advertise_shallow_grafts_buf(struct strbuf *sb) for_each_commit_graft(advertise_shallow_grafts_cb, sb); } -static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args) +#define CHECK_REF_NO_PUSH -1 +#define CHECK_REF_STATUS_REJECTED -2 +#define CHECK_REF_UPTODATE -3 +static int check_to_send_update(const struct ref *ref, const struct send_pack_args *args) { if (!ref->peer_ref && !args->send_mirror) - return 0; + return CHECK_REF_NO_PUSH; /* Check for statuses set by set_ref_status_for_push() */ switch (ref->status) { @@ -206,10 +209,11 @@ static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_a case REF_STATUS_REJECT_NEEDS_FORCE: case REF_STATUS_REJECT_STALE: case REF_STATUS_REJECT_NODELETE: + return CHECK_REF_STATUS_REJECTED; case REF_STATUS_UPTODATE: - return 0; + return CHECK_REF_UPTODATE; default: - return 1; + return 0; } } @@ -253,7 +257,7 @@ static int generate_push_cert(struct strbuf *req_buf, strbuf_addstr(&cert, "\n"); for (ref = remote_refs; ref; ref = ref->next) { - if (!ref_update_to_be_sent(ref, args)) + if (check_to_send_update(ref, args) < 0) continue; update_seen = 1; strbuf_addf(&cert, "%s %s %s\n", @@ -281,6 +285,29 @@ free_return: 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, @@ -297,6 +324,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; @@ -317,6 +346,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; @@ -331,6 +362,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"); @@ -338,6 +373,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()); @@ -362,9 +399,21 @@ int send_pack(struct send_pack_args *args, * the pack data. */ for (ref = remote_refs; ref; ref = ref->next) { - if (!ref_update_to_be_sent(ref, args)) + 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; @@ -383,7 +432,7 @@ int send_pack(struct send_pack_args *args, if (args->dry_run || args->push_cert) continue; - if (!ref_update_to_be_sent(ref, args)) + if (check_to_send_update(ref, args) < 0) continue; old_hex = sha1_to_hex(ref->old_sha1); diff --git a/send-pack.h b/send-pack.h index 5635457746..b6646488aa 100644 --- a/send-pack.h +++ b/send-pack.h @@ -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, diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh new file mode 100755 index 0000000000..3480b33007 --- /dev/null +++ b/t/t5543-atomic-push.sh @@ -0,0 +1,194 @@ +#!/bin/sh + +test_description='pushing to a repository using the atomic push option' + +. ./test-lib.sh + +mk_repo_pair () { + rm -rf workbench upstream && + test_create_repo upstream && + test_create_repo workbench && + ( + cd upstream && + git config receive.denyCurrentBranch warn + ) && + ( + cd workbench && + git remote add up ../upstream + ) +} + +# Compare the ref ($1) in upstream with a ref value from workbench ($2) +# i.e. test_refs second HEAD@{2} +test_refs () { + test $# = 2 && + git -C upstream rev-parse --verify "$1" >expect && + git -C workbench rev-parse --verify "$2" >actual && + test_cmp expect actual +} + +test_expect_success 'atomic push works for a single branch' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + git push --atomic up master + ) && + test_refs master master +' + +test_expect_success 'atomic push works for two branches' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git branch second && + git push --mirror up && + test_commit two && + git checkout second && + test_commit three && + git push --atomic up master second + ) && + test_refs master master && + test_refs second second +' + +test_expect_success 'atomic push works in combination with --mirror' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git checkout -b second && + test_commit two && + git push --atomic --mirror up + ) && + test_refs master master && + test_refs second second +' + +test_expect_success 'atomic push works in combination with --force' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git branch second master && + test_commit two_a && + git checkout second && + test_commit two_b && + test_commit three_b && + test_commit four && + git push --mirror up && + # The actual test is below + git checkout master && + test_commit three_a && + git checkout second && + git reset --hard HEAD^ && + git push --force --atomic up master second + ) && + test_refs master master && + test_refs second second +' + +# set up two branches where master can be pushed but second can not +# (non-fast-forward). Since second can not be pushed the whole operation +# will fail and leave master untouched. +test_expect_success 'atomic push fails if one branch fails' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git checkout -b second master && + test_commit two && + test_commit three && + test_commit four && + git push --mirror up && + git reset --hard HEAD~2 && + test_commit five && + git checkout master && + test_commit six && + test_must_fail git push --atomic --all up + ) && + test_refs master HEAD@{7} && + test_refs second HEAD@{4} +' + +test_expect_success 'atomic push fails if one tag fails remotely' ' + # prepare the repo + mk_repo_pair && + ( + cd workbench && + test_commit one && + git checkout -b second master && + test_commit two && + git push --mirror up + ) && + # a third party modifies the server side: + ( + cd upstream && + git checkout second && + git tag test_tag second + ) && + # see if we can now push both branches. + ( + cd workbench && + git checkout master && + test_commit three && + git checkout second && + test_commit four && + git tag test_tag && + test_must_fail git push --tags --atomic up master second + ) && + test_refs master HEAD@{3} && + test_refs second HEAD@{1} +' + +test_expect_success 'atomic push obeys update hook preventing a branch to be pushed' ' + mk_repo_pair && + ( + cd workbench && + test_commit one && + git checkout -b second master && + test_commit two && + git push --mirror up + ) && + ( + cd upstream && + HOOKDIR="$(git rev-parse --git-dir)/hooks" && + HOOK="$HOOKDIR/update" && + mkdir -p "$HOOKDIR" && + write_script "$HOOK" <<-\EOF + # only allow update to master from now on + test "$1" = "refs/heads/master" + EOF + ) && + ( + cd workbench && + git checkout master && + test_commit three && + git checkout second && + test_commit four && + test_must_fail git push --atomic up master second + ) && + test_refs master HEAD@{3} && + test_refs second HEAD@{1} +' + +test_expect_success 'atomic push is not advertised if configured' ' + mk_repo_pair && + ( + cd upstream + git config receive.advertiseatomic 0 + ) && + ( + cd workbench && + test_commit one && + git push --mirror up && + test_commit two && + test_must_fail git push --atomic up master + ) && + test_refs master HEAD@{1} +' + +test_done diff --git a/transport.c b/transport.c index 08bcd3a4eb..0694a7cf3e 100644 --- a/transport.c +++ b/transport.c @@ -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; @@ -826,6 +830,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN); args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN); args.push_cert = !!(flags & TRANSPORT_PUSH_CERT); + args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC); args.url = transport->url; ret = send_pack(&args, data->fd, data->conn, remote_refs, diff --git a/transport.h b/transport.h index 3e0091eaab..18d2cf8275 100644 --- a/transport.h +++ b/transport.h @@ -125,6 +125,7 @@ struct transport { #define TRANSPORT_PUSH_NO_HOOK 512 #define TRANSPORT_PUSH_FOLLOW_TAGS 1024 #define TRANSPORT_PUSH_CERT 2048 +#define TRANSPORT_PUSH_ATOMIC 4096 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3) #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x) |