mirror of
https://github.com/git/git.git
synced 2025-01-19 22:13:32 +08:00
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.
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
----------------------
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 (use_atomic)
|
||||
execute_commands_atomic(commands, si);
|
||||
else
|
||||
execute_commands_non_atomic(commands, si);
|
||||
|
||||
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 (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")) {
|
||||
|
@ -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;
|
||||
|
3
remote.h
3
remote.h
@ -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 */
|
||||
|
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
@ -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,
|
||||
|
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,
|
||||
"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,
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user