Skip to content

Commit

Permalink
send-pack.c: add --atomic command line argument
Browse files Browse the repository at this point in the history
This adds support to send-pack to negotiate and use atomic pushes
iff the server supports it. Atomic pushes are activated by a new command
line flag --atomic.

In order to do this we also need to change the semantics for send_pack()
slightly. The existing send_pack() function actually doesn't send all the
refs back to the server when multiple refs are involved, for example
when using --all. Several of the failure modes for pushes can already be
detected locally in the send_pack client based on the information from the
initial server side list of all the refs as generated by receive-pack.
Any such refs that we thus know would fail to push are thus pruned from
the list of refs we send to the server to update.

For atomic pushes, we have to deal thus with both failures that are detected
locally as well as failures that are reported back from the server. In order
to do so we treat all local failures as push failures too.

We introduce a new status code REF_STATUS_ATOMIC_PUSH_FAILED so we can
flag all refs that we would normally have tried to push to the server
but we did not due to local failures. This is to improve the error message
back to the end user to flag that "these refs failed to update since the
atomic push operation failed."

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
rsahlberg authored and gitster committed Jan 8, 2015
1 parent 7582e93 commit 4ff17f1
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 6 deletions.
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
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
49 changes: 47 additions & 2 deletions send-pack.c
Expand Up @@ -282,6 +282,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,
Expand All @@ -298,6 +321,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;
Expand All @@ -318,6 +343,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;

Expand All @@ -332,13 +359,19 @@ 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");
if (use_sideband)
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());

Expand All @@ -363,9 +396,21 @@ int send_pack(struct send_pack_args *args,
* the pack data.
*/
for (ref = remote_refs; ref; ref = ref->next) {
if (check_to_send_update(ref, args) < 0)
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;

Expand Down
3 changes: 2 additions & 1 deletion send-pack.h
Expand Up @@ -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,
Expand Down
4 changes: 4 additions & 0 deletions transport.c
Expand Up @@ -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;
Expand Down

0 comments on commit 4ff17f1

Please sign in to comment.