提交 39fa6112 编写于 作者: J Junio C Hamano

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
......@@ -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 (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")) {
......
......@@ -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 */
......
......@@ -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 @@ static int generate_push_cert(struct strbuf *req_buf,
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,
......
#!/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)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册