Skip to content

Commit

Permalink
Merge branch 'sb/atomic-push'
Browse files Browse the repository at this point in the history
"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
  • Loading branch information
gitster committed Feb 11, 2015
2 parents 4d5c4e4 + 04b39f1 commit 39fa611
Show file tree
Hide file tree
Showing 13 changed files with 429 additions and 50 deletions.
5 changes: 5 additions & 0 deletions Documentation/config.txt
Expand Up @@ -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

This comment has been minimized.

Copy link
@Meskalin12

Meskalin12 Jul 12, 2017

capability to its clients. If you don't want to this capability

This comment has been minimized.

Copy link
@Meskalin12
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
Expand Down
7 changes: 6 additions & 1 deletion Documentation/git-push.txt
Expand Up @@ -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>]]]
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion Documentation/git-send-pack.txt
Expand Up @@ -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
-----------
Expand Down Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions Documentation/technical/protocol-capabilities.txt
Expand Up @@ -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
Expand Down Expand Up @@ -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
----------------------

Expand Down
5 changes: 5 additions & 0 deletions builtin/push.c
Expand Up @@ -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),
Expand Down Expand Up @@ -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()
};

Expand All @@ -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);
Expand Down
165 changes: 130 additions & 35 deletions builtin/receive-pack.c
Expand Up @@ -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;
Expand All @@ -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)
{
Expand Down Expand Up @@ -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);
}

Expand All @@ -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)
Expand Down Expand Up @@ -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)) {
Expand All @@ -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 */
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")) {
Expand Down
6 changes: 5 additions & 1 deletion builtin/send-pack.c
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion remote.h
Expand Up @@ -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 */
Expand Down

0 comments on commit 39fa611

Please sign in to comment.