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
This commit is contained in:
commit
39fa6112ec
@ -2094,6 +2094,11 @@ rebase.autostash::
|
|||||||
successful rebase might result in non-trivial conflicts.
|
successful rebase might result in non-trivial conflicts.
|
||||||
Defaults to false.
|
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::
|
receive.autogc::
|
||||||
By default, git-receive-pack will run "git-gc --auto" after
|
By default, git-receive-pack will run "git-gc --auto" after
|
||||||
receiving data from git-push and updating refs. You can stop
|
receiving data from git-push and updating refs. You can stop
|
||||||
|
@ -9,7 +9,7 @@ git-push - Update remote refs along with associated objects
|
|||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[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]
|
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose]
|
||||||
[-u | --set-upstream] [--signed]
|
[-u | --set-upstream] [--signed]
|
||||||
[--force-with-lease[=<refname>[:<expect>]]]
|
[--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
|
logged. See linkgit:git-receive-pack[1] for the details
|
||||||
on the receiving end.
|
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>::
|
--receive-pack=<git-receive-pack>::
|
||||||
--exec=<git-receive-pack>::
|
--exec=<git-receive-pack>::
|
||||||
Path to the 'git-receive-pack' program on the remote
|
Path to the 'git-receive-pack' program on the remote
|
||||||
|
@ -9,7 +9,7 @@ git-send-pack - Push objects over Git protocol to another repository
|
|||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[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
|
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
|
Send a "thin" pack, which records objects in deltified form based
|
||||||
on objects not included in the pack to reduce network traffic.
|
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>::
|
<host>::
|
||||||
A remote host to house the repository. When this
|
A remote host to house the repository. When this
|
||||||
part is specified, 'git-receive-pack' is invoked via
|
part is specified, 'git-receive-pack' is invoked via
|
||||||
|
@ -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
|
and server advertised. As a consequence of these rules, server MUST
|
||||||
NOT advertise capabilities it does not understand.
|
NOT advertise capabilities it does not understand.
|
||||||
|
|
||||||
The 'report-status', 'delete-refs', 'quiet', and 'push-cert' capabilities
|
The 'atomic', 'report-status', 'delete-refs', 'quiet', and 'push-cert'
|
||||||
are sent and recognized by the receive-pack (push to server) process.
|
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
|
The 'ofs-delta' and 'side-band-64k' capabilities are sent and recognized
|
||||||
by both upload-pack and receive-pack protocols. The 'agent' capability
|
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
|
reporting if the local progress reporting is also being suppressed
|
||||||
(e.g., via `push -q`, or if stderr does not go to a tty).
|
(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
|
allow-tip-sha1-in-want
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
@ -487,6 +487,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
|
|||||||
int flags = 0;
|
int flags = 0;
|
||||||
int tags = 0;
|
int tags = 0;
|
||||||
int rc;
|
int rc;
|
||||||
|
int atomic = 0;
|
||||||
const char *repo = NULL; /* default repository */
|
const char *repo = NULL; /* default repository */
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
OPT__VERBOSITY(&verbosity),
|
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"),
|
OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
|
||||||
TRANSPORT_PUSH_FOLLOW_TAGS),
|
TRANSPORT_PUSH_FOLLOW_TAGS),
|
||||||
OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT),
|
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()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -533,6 +535,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
|
|||||||
if (tags)
|
if (tags)
|
||||||
add_refspec("refs/tags/*");
|
add_refspec("refs/tags/*");
|
||||||
|
|
||||||
|
if (atomic)
|
||||||
|
flags |= TRANSPORT_PUSH_ATOMIC;
|
||||||
|
|
||||||
if (argc > 0) {
|
if (argc > 0) {
|
||||||
repo = argv[0];
|
repo = argv[0];
|
||||||
set_refspecs(argv + 1, argc - 1, repo);
|
set_refspecs(argv + 1, argc - 1, repo);
|
||||||
|
@ -38,9 +38,11 @@ static int receive_fsck_objects = -1;
|
|||||||
static int transfer_fsck_objects = -1;
|
static int transfer_fsck_objects = -1;
|
||||||
static int receive_unpack_limit = -1;
|
static int receive_unpack_limit = -1;
|
||||||
static int transfer_unpack_limit = -1;
|
static int transfer_unpack_limit = -1;
|
||||||
|
static int advertise_atomic_push = 1;
|
||||||
static int unpack_limit = 100;
|
static int unpack_limit = 100;
|
||||||
static int report_status;
|
static int report_status;
|
||||||
static int use_sideband;
|
static int use_sideband;
|
||||||
|
static int use_atomic;
|
||||||
static int quiet;
|
static int quiet;
|
||||||
static int prefer_ofs_delta = 1;
|
static int prefer_ofs_delta = 1;
|
||||||
static int auto_update_server_info;
|
static int auto_update_server_info;
|
||||||
@ -67,6 +69,7 @@ static const char *NONCE_SLOP = "SLOP";
|
|||||||
static const char *nonce_status;
|
static const char *nonce_status;
|
||||||
static long nonce_stamp_slop;
|
static long nonce_stamp_slop;
|
||||||
static unsigned long nonce_stamp_slop_limit;
|
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)
|
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;
|
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);
|
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,
|
strbuf_addstr(&cap,
|
||||||
"report-status delete-refs side-band-64k quiet");
|
"report-status delete-refs side-band-64k quiet");
|
||||||
|
if (advertise_atomic_push)
|
||||||
|
strbuf_addstr(&cap, " atomic");
|
||||||
if (prefer_ofs_delta)
|
if (prefer_ofs_delta)
|
||||||
strbuf_addstr(&cap, " ofs-delta");
|
strbuf_addstr(&cap, " ofs-delta");
|
||||||
if (push_cert_nonce)
|
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)) {
|
if (is_null_sha1(new_sha1)) {
|
||||||
|
struct strbuf err = STRBUF_INIT;
|
||||||
if (!parse_object(old_sha1)) {
|
if (!parse_object(old_sha1)) {
|
||||||
old_sha1 = NULL;
|
old_sha1 = NULL;
|
||||||
if (ref_exists(name)) {
|
if (ref_exists(name)) {
|
||||||
@ -919,35 +930,36 @@ static const char *update(struct command *cmd, struct shallow_info *si)
|
|||||||
cmd->did_not_exist = 1;
|
cmd->did_not_exist = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (delete_ref(namespaced_name, old_sha1, 0)) {
|
if (ref_transaction_delete(transaction,
|
||||||
rp_error("failed to delete %s", name);
|
namespaced_name,
|
||||||
|
old_sha1,
|
||||||
|
0, old_sha1 != NULL,
|
||||||
|
"push", &err)) {
|
||||||
|
rp_error("%s", err.buf);
|
||||||
|
strbuf_release(&err);
|
||||||
return "failed to delete";
|
return "failed to delete";
|
||||||
}
|
}
|
||||||
|
strbuf_release(&err);
|
||||||
return NULL; /* good */
|
return NULL; /* good */
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
struct strbuf err = STRBUF_INIT;
|
struct strbuf err = STRBUF_INIT;
|
||||||
struct ref_transaction *transaction;
|
|
||||||
|
|
||||||
if (shallow_update && si->shallow_ref[cmd->index] &&
|
if (shallow_update && si->shallow_ref[cmd->index] &&
|
||||||
update_shallow_ref(cmd, si))
|
update_shallow_ref(cmd, si))
|
||||||
return "shallow error";
|
return "shallow error";
|
||||||
|
|
||||||
transaction = ref_transaction_begin(&err);
|
if (ref_transaction_update(transaction,
|
||||||
if (!transaction ||
|
namespaced_name,
|
||||||
ref_transaction_update(transaction, namespaced_name,
|
new_sha1, old_sha1,
|
||||||
new_sha1, old_sha1, 0, 1, "push",
|
0, 1, "push",
|
||||||
&err) ||
|
&err)) {
|
||||||
ref_transaction_commit(transaction, &err)) {
|
|
||||||
ref_transaction_free(transaction);
|
|
||||||
|
|
||||||
rp_error("%s", err.buf);
|
rp_error("%s", err.buf);
|
||||||
strbuf_release(&err);
|
strbuf_release(&err);
|
||||||
|
|
||||||
return "failed to update ref";
|
return "failed to update ref";
|
||||||
}
|
}
|
||||||
|
|
||||||
ref_transaction_free(transaction);
|
|
||||||
strbuf_release(&err);
|
strbuf_release(&err);
|
||||||
|
|
||||||
return NULL; /* good */
|
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,
|
static void execute_commands(struct command *commands,
|
||||||
const char *unpacker_error,
|
const char *unpacker_error,
|
||||||
struct shallow_info *si)
|
struct shallow_info *si)
|
||||||
{
|
{
|
||||||
int checked_connectivity;
|
|
||||||
struct command *cmd;
|
struct command *cmd;
|
||||||
unsigned char sha1[20];
|
unsigned char sha1[20];
|
||||||
struct iterate_data data;
|
struct iterate_data data;
|
||||||
@ -1166,27 +1272,13 @@ static void execute_commands(struct command *commands,
|
|||||||
free(head_name_to_free);
|
free(head_name_to_free);
|
||||||
head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
|
head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
|
||||||
|
|
||||||
checked_connectivity = 1;
|
if (use_atomic)
|
||||||
for (cmd = commands; cmd; cmd = cmd->next) {
|
execute_commands_atomic(commands, si);
|
||||||
if (cmd->error_string)
|
else
|
||||||
continue;
|
execute_commands_non_atomic(commands, si);
|
||||||
|
|
||||||
if (cmd->skip_update)
|
if (shallow_update)
|
||||||
continue;
|
warn_if_skipped_connectivity_check(commands, si);
|
||||||
|
|
||||||
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 (shallow_update && !checked_connectivity)
|
|
||||||
error("BUG: run 'git fsck' for safety.\n"
|
|
||||||
"If there are errors, try to remove "
|
|
||||||
"the reported refs above");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct command **queue_command(struct command **tail,
|
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;
|
use_sideband = LARGE_PACKET_MAX;
|
||||||
if (parse_feature_request(feature_list, "quiet"))
|
if (parse_feature_request(feature_list, "quiet"))
|
||||||
quiet = 1;
|
quiet = 1;
|
||||||
|
if (advertise_atomic_push
|
||||||
|
&& parse_feature_request(feature_list, "atomic"))
|
||||||
|
use_atomic = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strcmp(line, "push-cert")) {
|
if (!strcmp(line, "push-cert")) {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
#include "sha1-array.h"
|
#include "sha1-array.h"
|
||||||
|
|
||||||
static const char send_pack_usage[] =
|
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.";
|
" --all and explicit <ref> specification are mutually exclusive.";
|
||||||
|
|
||||||
static struct send_pack_args args;
|
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;
|
args.use_thin_pack = 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!strcmp(arg, "--atomic")) {
|
||||||
|
args.atomic = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (!strcmp(arg, "--stateless-rpc")) {
|
if (!strcmp(arg, "--stateless-rpc")) {
|
||||||
args.stateless_rpc = 1;
|
args.stateless_rpc = 1;
|
||||||
continue;
|
continue;
|
||||||
|
3
remote.h
3
remote.h
@ -115,7 +115,8 @@ struct ref {
|
|||||||
REF_STATUS_REJECT_SHALLOW,
|
REF_STATUS_REJECT_SHALLOW,
|
||||||
REF_STATUS_UPTODATE,
|
REF_STATUS_UPTODATE,
|
||||||
REF_STATUS_REMOTE_REJECT,
|
REF_STATUS_REMOTE_REJECT,
|
||||||
REF_STATUS_EXPECTING_REPORT
|
REF_STATUS_EXPECTING_REPORT,
|
||||||
|
REF_STATUS_ATOMIC_PUSH_FAILED
|
||||||
} status;
|
} status;
|
||||||
char *remote_status;
|
char *remote_status;
|
||||||
struct ref *peer_ref; /* when renaming */
|
struct ref *peer_ref; /* when renaming */
|
||||||
|
65
send-pack.c
65
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);
|
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)
|
if (!ref->peer_ref && !args->send_mirror)
|
||||||
return 0;
|
return CHECK_REF_NO_PUSH;
|
||||||
|
|
||||||
/* Check for statuses set by set_ref_status_for_push() */
|
/* Check for statuses set by set_ref_status_for_push() */
|
||||||
switch (ref->status) {
|
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_NEEDS_FORCE:
|
||||||
case REF_STATUS_REJECT_STALE:
|
case REF_STATUS_REJECT_STALE:
|
||||||
case REF_STATUS_REJECT_NODELETE:
|
case REF_STATUS_REJECT_NODELETE:
|
||||||
|
return CHECK_REF_STATUS_REJECTED;
|
||||||
case REF_STATUS_UPTODATE:
|
case REF_STATUS_UPTODATE:
|
||||||
return 0;
|
return CHECK_REF_UPTODATE;
|
||||||
default:
|
default:
|
||||||
return 1;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +257,7 @@ static int generate_push_cert(struct strbuf *req_buf,
|
|||||||
strbuf_addstr(&cert, "\n");
|
strbuf_addstr(&cert, "\n");
|
||||||
|
|
||||||
for (ref = remote_refs; ref; ref = ref->next) {
|
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;
|
continue;
|
||||||
update_seen = 1;
|
update_seen = 1;
|
||||||
strbuf_addf(&cert, "%s %s %s\n",
|
strbuf_addf(&cert, "%s %s %s\n",
|
||||||
@ -281,6 +285,29 @@ free_return:
|
|||||||
return update_seen;
|
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 send_pack(struct send_pack_args *args,
|
||||||
int fd[], struct child_process *conn,
|
int fd[], struct child_process *conn,
|
||||||
struct ref *remote_refs,
|
struct ref *remote_refs,
|
||||||
@ -297,6 +324,8 @@ int send_pack(struct send_pack_args *args,
|
|||||||
int use_sideband = 0;
|
int use_sideband = 0;
|
||||||
int quiet_supported = 0;
|
int quiet_supported = 0;
|
||||||
int agent_supported = 0;
|
int agent_supported = 0;
|
||||||
|
int use_atomic = 0;
|
||||||
|
int atomic_supported = 0;
|
||||||
unsigned cmds_sent = 0;
|
unsigned cmds_sent = 0;
|
||||||
int ret;
|
int ret;
|
||||||
struct async demux;
|
struct async demux;
|
||||||
@ -317,6 +346,8 @@ int send_pack(struct send_pack_args *args,
|
|||||||
agent_supported = 1;
|
agent_supported = 1;
|
||||||
if (server_supports("no-thin"))
|
if (server_supports("no-thin"))
|
||||||
args->use_thin_pack = 0;
|
args->use_thin_pack = 0;
|
||||||
|
if (server_supports("atomic"))
|
||||||
|
atomic_supported = 1;
|
||||||
if (args->push_cert) {
|
if (args->push_cert) {
|
||||||
int len;
|
int len;
|
||||||
|
|
||||||
@ -331,6 +362,10 @@ int send_pack(struct send_pack_args *args,
|
|||||||
"Perhaps you should specify a branch such as 'master'.\n");
|
"Perhaps you should specify a branch such as 'master'.\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (args->atomic && !atomic_supported)
|
||||||
|
die(_("server does not support --atomic push"));
|
||||||
|
|
||||||
|
use_atomic = atomic_supported && args->atomic;
|
||||||
|
|
||||||
if (status_report)
|
if (status_report)
|
||||||
strbuf_addstr(&cap_buf, " report-status");
|
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");
|
strbuf_addstr(&cap_buf, " side-band-64k");
|
||||||
if (quiet_supported && (args->quiet || !args->progress))
|
if (quiet_supported && (args->quiet || !args->progress))
|
||||||
strbuf_addstr(&cap_buf, " quiet");
|
strbuf_addstr(&cap_buf, " quiet");
|
||||||
|
if (use_atomic)
|
||||||
|
strbuf_addstr(&cap_buf, " atomic");
|
||||||
if (agent_supported)
|
if (agent_supported)
|
||||||
strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
|
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.
|
* the pack data.
|
||||||
*/
|
*/
|
||||||
for (ref = remote_refs; ref; ref = ref->next) {
|
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;
|
continue;
|
||||||
|
}
|
||||||
if (!ref->deletion)
|
if (!ref->deletion)
|
||||||
need_pack_data = 1;
|
need_pack_data = 1;
|
||||||
|
|
||||||
@ -383,7 +432,7 @@ int send_pack(struct send_pack_args *args,
|
|||||||
if (args->dry_run || args->push_cert)
|
if (args->dry_run || args->push_cert)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!ref_update_to_be_sent(ref, args))
|
if (check_to_send_update(ref, args) < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
old_hex = sha1_to_hex(ref->old_sha1);
|
old_hex = sha1_to_hex(ref->old_sha1);
|
||||||
|
@ -13,7 +13,8 @@ struct send_pack_args {
|
|||||||
use_ofs_delta:1,
|
use_ofs_delta:1,
|
||||||
dry_run:1,
|
dry_run:1,
|
||||||
push_cert:1,
|
push_cert:1,
|
||||||
stateless_rpc:1;
|
stateless_rpc:1,
|
||||||
|
atomic:1;
|
||||||
};
|
};
|
||||||
|
|
||||||
int send_pack(struct send_pack_args *args,
|
int send_pack(struct send_pack_args *args,
|
||||||
|
194
t/t5543-atomic-push.sh
Executable file
194
t/t5543-atomic-push.sh
Executable file
@ -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
|
@ -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,
|
ref->deletion ? NULL : ref->peer_ref,
|
||||||
"remote failed to report status", porcelain);
|
"remote failed to report status", porcelain);
|
||||||
break;
|
break;
|
||||||
|
case REF_STATUS_ATOMIC_PUSH_FAILED:
|
||||||
|
print_ref_status('!', "[rejected]", ref, ref->peer_ref,
|
||||||
|
"atomic push failed", porcelain);
|
||||||
|
break;
|
||||||
case REF_STATUS_OK:
|
case REF_STATUS_OK:
|
||||||
print_ok_ref_status(ref, porcelain);
|
print_ok_ref_status(ref, porcelain);
|
||||||
break;
|
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.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
|
||||||
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
|
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
|
||||||
args.push_cert = !!(flags & TRANSPORT_PUSH_CERT);
|
args.push_cert = !!(flags & TRANSPORT_PUSH_CERT);
|
||||||
|
args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
|
||||||
args.url = transport->url;
|
args.url = transport->url;
|
||||||
|
|
||||||
ret = send_pack(&args, data->fd, data->conn, remote_refs,
|
ret = send_pack(&args, data->fd, data->conn, remote_refs,
|
||||||
|
@ -125,6 +125,7 @@ struct transport {
|
|||||||
#define TRANSPORT_PUSH_NO_HOOK 512
|
#define TRANSPORT_PUSH_NO_HOOK 512
|
||||||
#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
|
#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
|
||||||
#define TRANSPORT_PUSH_CERT 2048
|
#define TRANSPORT_PUSH_CERT 2048
|
||||||
|
#define TRANSPORT_PUSH_ATOMIC 4096
|
||||||
|
|
||||||
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
|
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
|
||||||
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
|
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
|
||||||
|
Loading…
Reference in New Issue
Block a user