All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/5] Add a flag to push atomically
@ 2014-12-15 19:56 Stefan Beller
  2014-12-15 19:56 ` [PATCH 1/5] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
                   ` (5 more replies)
  0 siblings, 6 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-15 19:56 UTC (permalink / raw)
  To: git; +Cc: mhagger, jrnieder, gitster, ronniesahlberg, Stefan Beller

This patch series adds a flag to git push to update the remote refs atomically.

It was part of a longer patch series[1].
This series applies on top of origin/mh/reflog-expire
It can also be found at [2].

Change since picking the series up from Ronnie.
  * other anchor point (i.e. where the series applies)
  * more tests for this feature, specially testing failures
  * drop the patch to rename ref_transaction_* to transaction_*_ref
  * slight rewording of the additional documentation

[1] http://www.spinics.net/lists/git/msg241214.html
[2] https://github.com/stefanbeller/git/tree/atomic-push-v1

Ronnie Sahlberg (4):
  receive-pack.c: add protocol support to negotiate atomic-push
  send-pack.c: add an --atomic-push command line argument
  receive-pack.c: use a single transaction when atomic-push is
    negotiated
  push.c: add an --atomic-push argument

Stefan Beller (1):
  t5543-atomic-push.sh: add basic tests for atomic pushes

 Documentation/git-push.txt                        |   8 +-
 Documentation/git-send-pack.txt                   |   7 +-
 Documentation/technical/protocol-capabilities.txt |  12 +-
 builtin/push.c                                    |   2 +
 builtin/receive-pack.c                            |  79 +++++++--
 builtin/send-pack.c                               |   6 +-
 remote.h                                          |   3 +-
 send-pack.c                                       |  45 +++++-
 send-pack.h                                       |   1 +
 t/t5543-atomic-push.sh                            | 185 ++++++++++++++++++++++
 transport.c                                       |   5 +
 transport.h                                       |   1 +
 12 files changed, 327 insertions(+), 27 deletions(-)
 create mode 100755 t/t5543-atomic-push.sh

-- 
2.2.0.33.gc2219e3.dirty

^ permalink raw reply	[flat|nested] 56+ messages in thread

* [PATCH 1/5] receive-pack.c: add protocol support to negotiate atomic-push
  2014-12-15 19:56 [PATCH 0/5] Add a flag to push atomically Stefan Beller
@ 2014-12-15 19:56 ` Stefan Beller
  2014-12-15 20:53   ` Junio C Hamano
  2014-12-15 19:56 ` [PATCH 2/5] send-pack.c: add an --atomic-push command line argument Stefan Beller
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-15 19:56 UTC (permalink / raw)
  To: git
  Cc: mhagger, jrnieder, gitster, ronniesahlberg, Ronnie Sahlberg,
	Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

This adds support to the protocol between send-pack and receive-pack to
* allow receive-pack to inform the client that it has atomic push capability
* allow send-pack to request atomic push back.

There is currently no setting in send-pack to actually request that atomic
pushes are to be used yet. This only adds protocol capability not ability
for the user to activate it.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---
 Documentation/technical/protocol-capabilities.txt | 12 ++++++++++--
 builtin/receive-pack.c                            |  6 +++++-
 send-pack.c                                       |  6 ++++++
 3 files changed, 21 insertions(+), 3 deletions(-)

diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index 6d5424c..763120c 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -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-push', '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,13 @@ 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-push
+-----------
+
+If the server sends the 'atomic-push' capability, it means it is
+capable of accepting atomic pushes. If the pushing client requests this
+capability, the server will update the refs in one single atomic transaction.
+
 allow-tip-sha1-in-want
 ----------------------
 
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 32fc540..0c642ab 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -40,6 +40,7 @@ static int transfer_unpack_limit = -1;
 static int unpack_limit = 100;
 static int report_status;
 static int use_sideband;
+static int use_atomic_push;
 static int quiet;
 static int prefer_ofs_delta = 1;
 static int auto_update_server_info;
@@ -171,7 +172,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
 		struct strbuf cap = STRBUF_INIT;
 
 		strbuf_addstr(&cap,
-			      "report-status delete-refs side-band-64k quiet");
+			      "report-status delete-refs side-band-64k quiet "
+			      "atomic-push");
 		if (prefer_ofs_delta)
 			strbuf_addstr(&cap, " ofs-delta");
 		if (push_cert_nonce)
@@ -1179,6 +1181,8 @@ 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 (parse_feature_request(feature_list, "atomic-push"))
+				use_atomic_push = 1;
 		}
 
 		if (!strcmp(line, "push-cert")) {
diff --git a/send-pack.c b/send-pack.c
index 949cb61..1ccc84c 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -294,6 +294,8 @@ int send_pack(struct send_pack_args *args,
 	int use_sideband = 0;
 	int quiet_supported = 0;
 	int agent_supported = 0;
+	int atomic_push_supported = 0;
+	int atomic_push = 0;
 	unsigned cmds_sent = 0;
 	int ret;
 	struct async demux;
@@ -314,6 +316,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-push"))
+		atomic_push_supported = 1;
 	if (args->push_cert) {
 		int len;
 
@@ -335,6 +339,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 (atomic_push)
+		strbuf_addstr(&cap_buf, " atomic-push");
 	if (agent_supported)
 		strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
 
-- 
2.2.0.33.gc2219e3.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH 2/5] send-pack.c: add an --atomic-push command line argument
  2014-12-15 19:56 [PATCH 0/5] Add a flag to push atomically Stefan Beller
  2014-12-15 19:56 ` [PATCH 1/5] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
@ 2014-12-15 19:56 ` Stefan Beller
  2014-12-15 21:01   ` Junio C Hamano
  2014-12-15 19:56 ` [PATCH 3/5] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-15 19:56 UTC (permalink / raw)
  To: git
  Cc: mhagger, jrnieder, gitster, ronniesahlberg, Ronnie Sahlberg,
	Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

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-push.

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>
---
 Documentation/git-send-pack.txt |  7 ++++++-
 builtin/send-pack.c             |  6 +++++-
 remote.h                        |  3 ++-
 send-pack.c                     | 39 ++++++++++++++++++++++++++++++++++-----
 send-pack.h                     |  1 +
 transport.c                     |  4 ++++
 6 files changed, 52 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 2a0de42..9296587 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -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-push] [<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-push::
+	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
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index b564a77..93cb17c 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -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-push] [<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-push")) {
+				args.use_atomic_push = 1;
+				continue;
+			}
 			if (!strcmp(arg, "--stateless-rpc")) {
 				args.stateless_rpc = 1;
 				continue;
diff --git a/remote.h b/remote.h
index 8b62efd..f346524 100644
--- a/remote.h
+++ b/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 */
diff --git a/send-pack.c b/send-pack.c
index 1ccc84c..08602a8 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -190,7 +190,7 @@ 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)
+static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args, int *atomic_push_failed)
 {
 	if (!ref->peer_ref && !args->send_mirror)
 		return 0;
@@ -203,6 +203,12 @@ 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:
+		if (atomic_push_failed) {
+			fprintf(stderr, "Atomic push failed for ref %s. "
+				"Status:%d\n", ref->name, ref->status);
+			*atomic_push_failed = 1;
+		}
+		/* fallthrough */
 	case REF_STATUS_UPTODATE:
 		return 0;
 	default:
@@ -250,7 +256,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 (!ref_update_to_be_sent(ref, args, NULL))
 			continue;
 		update_seen = 1;
 		strbuf_addf(&cert, "%s %s %s\n",
@@ -297,7 +303,7 @@ int send_pack(struct send_pack_args *args,
 	int atomic_push_supported = 0;
 	int atomic_push = 0;
 	unsigned cmds_sent = 0;
-	int ret;
+	int ret, atomic_push_failed = 0;
 	struct async demux;
 	const char *push_cert_nonce = NULL;
 
@@ -332,6 +338,11 @@ int send_pack(struct send_pack_args *args,
 			"Perhaps you should specify a branch such as 'master'.\n");
 		return 0;
 	}
+	if (args->use_atomic_push && !atomic_push_supported) {
+		fprintf(stderr, "Server does not support atomic-push.");
+		return -1;
+	}
+	atomic_push = atomic_push_supported && args->use_atomic_push;
 
 	if (status_report)
 		strbuf_addstr(&cap_buf, " report-status");
@@ -365,7 +376,8 @@ 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))
+		if (!ref_update_to_be_sent(ref, args,
+			args->use_atomic_push ? &atomic_push_failed : NULL))
 			continue;
 
 		if (!ref->deletion)
@@ -377,6 +389,23 @@ int send_pack(struct send_pack_args *args,
 			ref->status = REF_STATUS_EXPECTING_REPORT;
 	}
 
+	if (atomic_push_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:
+				; /* do nothing */
+			}
+		}
+		fprintf(stderr, "Atomic push failed.");
+		return -1;
+	}
+
 	/*
 	 * Finally, tell the other end!
 	 */
@@ -386,7 +415,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 (!ref_update_to_be_sent(ref, args, NULL))
 			continue;
 
 		old_hex = sha1_to_hex(ref->old_sha1);
diff --git a/send-pack.h b/send-pack.h
index 5635457..7486e65 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -11,6 +11,7 @@ struct send_pack_args {
 		force_update:1,
 		use_thin_pack:1,
 		use_ofs_delta:1,
+		use_atomic_push:1,
 		dry_run:1,
 		push_cert:1,
 		stateless_rpc:1;
diff --git a/transport.c b/transport.c
index 70d38e4..e87481d 100644
--- a/transport.c
+++ b/transport.c
@@ -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;
-- 
2.2.0.33.gc2219e3.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH 3/5] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-15 19:56 [PATCH 0/5] Add a flag to push atomically Stefan Beller
  2014-12-15 19:56 ` [PATCH 1/5] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
  2014-12-15 19:56 ` [PATCH 2/5] send-pack.c: add an --atomic-push command line argument Stefan Beller
@ 2014-12-15 19:56 ` Stefan Beller
  2014-12-15 21:37   ` Junio C Hamano
  2014-12-15 19:56 ` [PATCH 4/5] push.c: add an --atomic-push argument Stefan Beller
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-15 19:56 UTC (permalink / raw)
  To: git
  Cc: mhagger, jrnieder, gitster, ronniesahlberg, Ronnie Sahlberg,
	Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

Update receive-pack to use an atomic transaction iff the client negotiated
that it wanted atomic-push. This leaves the default behavior to be the old
non-atomic one ref at a time update. This is to cause as little disruption
as possible to existing clients. It is unknown if there are client scripts
that depend on the old non-atomic behavior so we make it opt-in for now.

If it turns out over time that there are no client scripts that depend on the
old behavior we can change git to default to use atomic pushes and instead
offer an opt-out argument for people that do not want atomic pushes.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---
 builtin/receive-pack.c | 73 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 58 insertions(+), 15 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 0c642ab..e6cf174 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -67,6 +67,8 @@ static const char *NONCE_SLOP = "SLOP";
 static const char *nonce_status;
 static long nonce_stamp_slop;
 static unsigned long nonce_stamp_slop_limit;
+struct strbuf err = STRBUF_INIT;
+struct ref_transaction *transaction;
 
 static enum deny_action parse_deny_action(const char *var, const char *value)
 {
@@ -832,34 +834,56 @@ 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);
-			return "failed to delete";
+		if (!use_atomic_push) {
+			if (delete_ref(namespaced_name, old_sha1, 0)) {
+				rp_error("failed to delete %s", name);
+				return "failed to delete";
+			}
+		} else {
+			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";
+			}
 		}
 		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 (!use_atomic_push) {
+			transaction = ref_transaction_begin(&err);
+			if (!transaction) {
+				rp_error("%s", err.buf);
+				strbuf_release(&err);
+				return "failed to start 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);
+		if (!use_atomic_push) {
+			if (ref_transaction_commit(transaction, &err)) {
+				ref_transaction_free(transaction);
+				rp_error("%s", err.buf);
+				strbuf_release(&err);
+				return "failed to update ref";
+			}
+			ref_transaction_free(transaction);
+		}
 		strbuf_release(&err);
 		return NULL; /* good */
 	}
@@ -1059,6 +1083,16 @@ static void execute_commands(struct command *commands,
 		return;
 	}
 
+	if (use_atomic_push) {
+		transaction = ref_transaction_begin(&err);
+		if (!transaction) {
+			error("%s", err.buf);
+			strbuf_release(&err);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = "transaction error";
+			return;
+		}
+	}
 	data.cmds = commands;
 	data.si = si;
 	if (check_everything_connected(iterate_receive_command_list, 0, &data))
@@ -1096,6 +1130,14 @@ static void execute_commands(struct command *commands,
 		}
 	}
 
+	if (use_atomic_push) {
+		if (ref_transaction_commit(transaction, &err)) {
+			rp_error("%s", err.buf);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = err.buf;
+		}
+		ref_transaction_free(transaction);
+	}
 	if (shallow_update && !checked_connectivity)
 		error("BUG: run 'git fsck' for safety.\n"
 		      "If there are errors, try to remove "
@@ -1543,5 +1585,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 	sha1_array_clear(&shallow);
 	sha1_array_clear(&ref);
 	free((void *)push_cert_nonce);
+	strbuf_release(&err);
 	return 0;
 }
-- 
2.2.0.33.gc2219e3.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH 4/5] push.c: add an --atomic-push argument
  2014-12-15 19:56 [PATCH 0/5] Add a flag to push atomically Stefan Beller
                   ` (2 preceding siblings ...)
  2014-12-15 19:56 ` [PATCH 3/5] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
@ 2014-12-15 19:56 ` Stefan Beller
  2014-12-15 21:50   ` Junio C Hamano
  2014-12-15 19:56 ` [PATCH 5/5] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
  2014-12-15 22:33 ` [PATCH 0/5] Add a flag to push atomically Junio C Hamano
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-15 19:56 UTC (permalink / raw)
  To: git
  Cc: mhagger, jrnieder, gitster, ronniesahlberg, Ronnie Sahlberg,
	Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

Add a command line argument to the git push command to request atomic
pushes.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---
 Documentation/git-push.txt | 8 +++++++-
 builtin/push.c             | 2 ++
 transport.c                | 1 +
 transport.h                | 1 +
 4 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 21b3f29..3e0cb6e 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -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-push] [-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,12 @@ already exists on the remote side.
 	logged.  See linkgit:git-receive-pack[1] for the details
 	on the receiving end.
 
+--atomic-push::
+	Using atomic push. If atomic push is negotiated with the server
+	then any push covering multiple refs will be atomic. 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
diff --git a/builtin/push.c b/builtin/push.c
index a076b19..ae393c5 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -518,6 +518,8 @@ 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_BIT(0, "atomic-push", &flags, N_("use atomic push, if available"),
+			TRANSPORT_ATOMIC_PUSH),
 		OPT_END()
 	};
 
diff --git a/transport.c b/transport.c
index e87481d..ba1499e 100644
--- a/transport.c
+++ b/transport.c
@@ -830,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.use_atomic_push = !!(flags & TRANSPORT_ATOMIC_PUSH);
 	args.url = transport->url;
 
 	ret = send_pack(&args, data->fd, data->conn, remote_refs,
diff --git a/transport.h b/transport.h
index 3e0091e..25fa1da 100644
--- a/transport.h
+++ b/transport.h
@@ -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_ATOMIC_PUSH 4096
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
-- 
2.2.0.33.gc2219e3.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCH 5/5] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-15 19:56 [PATCH 0/5] Add a flag to push atomically Stefan Beller
                   ` (3 preceding siblings ...)
  2014-12-15 19:56 ` [PATCH 4/5] push.c: add an --atomic-push argument Stefan Beller
@ 2014-12-15 19:56 ` Stefan Beller
  2014-12-15 22:29   ` Junio C Hamano
  2014-12-15 22:33 ` [PATCH 0/5] Add a flag to push atomically Junio C Hamano
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-15 19:56 UTC (permalink / raw)
  To: git; +Cc: mhagger, jrnieder, gitster, ronniesahlberg, Stefan Beller

This adds tests for the atomic push option.
The first four tests check if the atomic option works in
good conditions and the last three patches check if the atomic
option prevents any change to be pushed if just one ref cannot
be updated.

Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Originally Ronnie had a similar patch prepared. But as I added
    more tests and cleaned up the existing tests (e.g. use test_commit
    instead of "echo one >file && gitadd file && git commit -a -m 'one'",
    removal of dead code), the file has changed so much that I'd rather
    take ownership.

 t/t5543-atomic-push.sh | 185 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 185 insertions(+)
 create mode 100755 t/t5543-atomic-push.sh

diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh
new file mode 100755
index 0000000..6cbedc5
--- /dev/null
+++ b/t/t5543-atomic-push.sh
@@ -0,0 +1,185 @@
+#!/bin/sh
+
+test_description='pushing to a repository using the atomic push option'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+mk_repo_pair () {
+	rm -rf workbench upstream thirdparty &&
+	mkdir upstream &&
+	(
+		cd upstream &&
+		git init --bare #&&
+		#git config receive.denyCurrentBranch warn
+	) &&
+	mkdir workbench &&
+	(
+		cd workbench &&
+		git init &&
+		git remote add up ../upstream
+	)
+}
+
+test_push_failed () {
+	workbench_master=$(cd workbench && git show-ref -s --verify refs/heads/master) &&
+	upstream_master=$(cd upstream && git show-ref -s --verify refs/heads/master) &&
+	test "$workbench_master" != "$upstream_master" &&
+
+	workbench_second=$(cd workbench && git show-ref -s --verify refs/heads/second) &&
+	upstream_second=$(cd upstream && git show-ref -s --verify refs/heads/second) &&
+	test "$workbench_second" != "$upstream_second"
+}
+
+test_push_succeeded () {
+	workbench_master=$(cd workbench && git show-ref -s --verify refs/heads/master) &&
+	upstream_master=$(cd upstream && git show-ref -s --verify refs/heads/master) &&
+	test "$workbench_master" = "$upstream_master"
+
+	workbench_second=$(cd workbench && git show-ref -s --verify refs/heads/second) &&
+	upstream_second=$(cd upstream && git show-ref -s --verify refs/heads/second) &&
+	test "$workbench_second" = "$upstream_second"
+}
+
+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-push --mirror up
+	) &&
+	workbench_master=$(cd workbench && git show-ref -s --verify refs/heads/master) &&
+	upstream_master=$(cd upstream && git show-ref -s --verify refs/heads/master) &&
+	test "$workbench_master" = "$upstream_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-push --mirror up
+	) &&
+	test_push_succeeded
+'
+
+test_expect_success 'atomic push works in combination with --mirror' '
+	mk_repo_pair &&
+	ls workbench &&
+	(
+		cd workbench &&
+		test_commit one &&
+		git checkout -b second &&
+		test_commit two &&
+		git push --atomic-push --mirror up
+	) &&
+	test_push_succeeded
+'
+
+test_expect_success 'atomic push works in combination with --force' '
+	mk_repo_pair &&
+	(
+		cd workbench &&
+		test_commit one &&
+		git checkout -b second &&
+		test_commit two &&
+		test_commit three &&
+		test_commit four &&
+		git push --mirror up &&
+		git reset --hard HEAD^ &&
+		git push --force --atomic-push up master second
+	) &&
+	test_push_succeeded
+'
+
+# 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 locally' '
+	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 &&
+		git checkout master &&
+		test_commit five &&
+		! git push --atomic-push --all up
+	) &&
+	test_push_failed
+'
+
+test_expect_success 'atomic push fails if one branch 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:
+	(
+		git clone upstream thirdparty &&
+		cd thirdparty
+		git checkout second
+		test_commit unrelated_changes &&
+		git push origin second
+	) &&
+	# see if we can now push both branches.
+	(
+		cd workbench &&
+		git checkout master &&
+		test_commit three &&
+		git checkout second &&
+		test_commit four &&
+		! git push --atomic-push up master second
+	) &&
+	test_push_failed
+'
+
+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:
+	(
+		git clone upstream thirdparty &&
+		cd thirdparty
+		git checkout second
+		git tag test_tag &&
+		git push --tags origin 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 &&
+		! git push --tags --atomic-push up master second
+	) &&
+	test_push_failed
+'
+
+test_done
-- 
2.2.0.33.gc2219e3.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* Re: [PATCH 1/5] receive-pack.c: add protocol support to negotiate atomic-push
  2014-12-15 19:56 ` [PATCH 1/5] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
@ 2014-12-15 20:53   ` Junio C Hamano
  2014-12-15 22:30     ` Stefan Beller
  0 siblings, 1 reply; 56+ messages in thread
From: Junio C Hamano @ 2014-12-15 20:53 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

> From: Ronnie Sahlberg <sahlberg@google.com>
>
> This adds support to the protocol between send-pack and receive-pack to
> * allow receive-pack to inform the client that it has atomic push capability
> * allow send-pack to request atomic push back.
>
> There is currently no setting in send-pack to actually request that atomic
> pushes are to be used yet. This only adds protocol capability not ability
> for the user to activate it.

Hmph, am I reading the patch to send-pack.c correctly?

It detects if the other side supports the capability and leaves it
in atomic_push_supported variable for later use, and also requests
the feature to be activated when atomic_push is set, but I see no
logic to link these two together, e.g. error out when atomic_push
is true and atomic_push_supported is false (or turn it off with a
warning, or whatever).

> diff --git a/send-pack.c b/send-pack.c
> index 949cb61..1ccc84c 100644
> --- a/send-pack.c
> +++ b/send-pack.c
> @@ -294,6 +294,8 @@ int send_pack(struct send_pack_args *args,
>  	int use_sideband = 0;
>  	int quiet_supported = 0;
>  	int agent_supported = 0;
> +	int atomic_push_supported = 0;
> +	int atomic_push = 0;
>  	unsigned cmds_sent = 0;
>  	int ret;
>  	struct async demux;
> @@ -314,6 +316,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-push"))
> +		atomic_push_supported = 1;
>  	if (args->push_cert) {
>  		int len;
>  
> @@ -335,6 +339,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 (atomic_push)
> +		strbuf_addstr(&cap_buf, " atomic-push");
>  	if (agent_supported)
>  		strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH 2/5] send-pack.c: add an --atomic-push command line argument
  2014-12-15 19:56 ` [PATCH 2/5] send-pack.c: add an --atomic-push command line argument Stefan Beller
@ 2014-12-15 21:01   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-15 21:01 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

> -static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args)
> +static int ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args, int *atomic_push_failed)

Hmph.  Is "atomic push" so special that it deserves a separate
parameter?  When we come up with yet another mode of failure, would
we add another parameter to the callers to this function?

> @@ -203,6 +203,12 @@ 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:
> +		if (atomic_push_failed) {
> +			fprintf(stderr, "Atomic push failed for ref %s. "
> +				"Status:%d\n", ref->name, ref->status);
> +			*atomic_push_failed = 1;

All other error message that come from the codepaths around here
seem to avoid uppercase.  Also maybe you want to use error() here?

> +	if (args->use_atomic_push && !atomic_push_supported) {
> +		fprintf(stderr, "Server does not support atomic-push.");
> +		return -1;
> +	}

This check logically belongs to the previous step, no?

> +	atomic_push = atomic_push_supported && args->use_atomic_push;

>  
>  	if (status_report)
>  		strbuf_addstr(&cap_buf, " report-status");
> @@ -365,7 +376,8 @@ 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))
> +		if (!ref_update_to_be_sent(ref, args,
> +			args->use_atomic_push ? &atomic_push_failed : NULL))
>  			continue;
>  
>  		if (!ref->deletion)
> @@ -377,6 +389,23 @@ int send_pack(struct send_pack_args *args,
>  			ref->status = REF_STATUS_EXPECTING_REPORT;
>  	}
>  
> +	if (atomic_push_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:
> +				; /* do nothing */
> +			}
> +		}
> +		fprintf(stderr, "Atomic push failed.");
> +		return -1;

The same comment as the other fprintf() applies here.

> @@ -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);

Why dashed-words-here?

> +		break;
>  	case REF_STATUS_OK:
>  		print_ok_ref_status(ref, porcelain);
>  		break;

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH 3/5] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-15 19:56 ` [PATCH 3/5] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
@ 2014-12-15 21:37   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-15 21:37 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg, Michael Haggerty

Stefan Beller <sbeller@google.com> writes:

> @@ -832,34 +834,56 @@ 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);
> -			return "failed to delete";
> +		if (!use_atomic_push) {
> +			if (delete_ref(namespaced_name, old_sha1, 0)) {
> +				rp_error("failed to delete %s", name);
> +				return "failed to delete";
> +			}
> +		} else {
> +			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";
> +			}

Doesn't the asymmetry between the above (if transaction is there,
use it, otherwise call delete_ref() which conceptually has its sole
operation inside a single transaction by itself) and below (if
transaction is not there, create it and do its thing, and close the
transaction if we created it) bother you?

The above look much simpler, and if it does not switch on
use_atomic_push but on the presense of transaction, it would have
been even better, i.e.

	if (transaction
            ? ref_transaction_delete(transaction, ...)
            : delete_ref(...)) {
		error(...);
                return "failed to delete";
	}

I think it makes the code harder to read and maintain if you forced
a caller of the ref API that happen to touch only a single ref to
make three calls to

	ref_transaction_begin();
        ref_transaction_do_one_thing();
        ref_transaction_commit();

instead of making a single call to a simple wrapper

	ref_do_one_thing();

I think that I saw Michael make a similar observation in a near-by
thread.

Even if you insist using transactions explicitly in the user of the
ref API, I think a better code organization is possible in this
particular codepath.  Because execute_commands() has the loop over
all the proposed updates, why should the update() even need to know
how to open a new transaction and when?  In other words, can't the
code be more like this?

	static update(transaction, ...)
        {
		/* do my thing in the transaction given to me */
                compute what kind of update is needed;
                switch (kind) {
		case delete:
			ref_transaction_delete(transaction, ...);
			break;
		case update:
			ref_transaction_update(transaction, ...);
			break;
		...
		}
	}

	execute_commands(...)
        {
		if (atomic)
                	transaction = ref_transaction_begin(...);
        	for (cmd = commands; cmd; cmd = cmd->next) {
			if (!atomic)        	
                        	transaction = ref_transaction_begin(...);
			update(transaction, ...);
			if (!atomic)
                        	ref_transaction_commit(transaction);
		}
		if (atomic)
                       	ref_transaction_commit(transaction);
	}

That is, update() assumes it is always in _some_ transaction, and
execute_commands(), which is what drives multi-ref updates, knows
if it wants its repeated calls to update() to be in a single
transaction or separate transactions.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH 4/5] push.c: add an --atomic-push argument
  2014-12-15 19:56 ` [PATCH 4/5] push.c: add an --atomic-push argument Stefan Beller
@ 2014-12-15 21:50   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-15 21:50 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

> From: Ronnie Sahlberg <sahlberg@google.com>
>
> Add a command line argument to the git push command to request atomic
> pushes.
>
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---

Makes sense, modulo a nit.

> +--atomic-push::
> +	Using atomic push. If atomic push is negotiated with the server
> +	then any push covering multiple refs will be atomic. Either all
> +	refs are updated, or on error, no refs are updated. If the server
> +	does not support atomic pushes the push will fail.

"git push --atomic-push"?  Why should one need to repeat the word?

>  		OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT),
> +		OPT_BIT(0, "atomic-push", &flags, N_("use atomic push, if available"),
> +			TRANSPORT_ATOMIC_PUSH),

Contrast between the two.  It isn't "git push --signed-push", either ;-)

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH 5/5] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-15 19:56 ` [PATCH 5/5] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
@ 2014-12-15 22:29   ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-15 22:29 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg

Stefan Beller <sbeller@google.com> writes:

> This adds tests for the atomic push option.
> The first four tests check if the atomic option works in
> good conditions and the last three patches check if the atomic
> option prevents any change to be pushed if just one ref cannot
> be updated.
>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
>
> Notes:
>     Originally Ronnie had a similar patch prepared. But as I added
>     more tests and cleaned up the existing tests (e.g. use test_commit
>     instead of "echo one >file && gitadd file && git commit -a -m 'one'",
>     removal of dead code), the file has changed so much that I'd rather
>     take ownership.
>
>  t/t5543-atomic-push.sh | 185 +++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 185 insertions(+)
>  create mode 100755 t/t5543-atomic-push.sh
>
> diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh
> new file mode 100755
> index 0000000..6cbedc5
> --- /dev/null
> +++ b/t/t5543-atomic-push.sh
> @@ -0,0 +1,185 @@
> +#!/bin/sh
> +
> +test_description='pushing to a repository using the atomic push option'
> +
> +. ./test-lib.sh
> +
> +D=`pwd`

$(pwd)???

> +mk_repo_pair () {
> +	rm -rf workbench upstream thirdparty &&
> +	mkdir upstream &&
> +	(
> +		cd upstream &&
> +		git init --bare #&&
> +		#git config receive.denyCurrentBranch warn

Please drop unused comments; they are distracting.

> +	) &&
> +	mkdir workbench &&
> +	(
> +		cd workbench &&
> +		git init &&
> +		git remote add up ../upstream
> +	)
> +}

Hmph.  Shouldn't you be using test_create_repo to create these, so
that templates are used from the build (not install) location, and
their hooks are disabled?

> +test_push_failed () {

Does that mean "test_push_must_fail"?

> +	workbench_master=$(cd workbench && git show-ref -s --verify refs/heads/master) &&
> +	upstream_master=$(cd upstream && git show-ref -s --verify refs/heads/master) &&
> +	test "$workbench_master" != "$upstream_master" &&
> +
> +	workbench_second=$(cd workbench && git show-ref -s --verify refs/heads/second) &&
> +	upstream_second=$(cd upstream && git show-ref -s --verify refs/heads/second) &&
> +	test "$workbench_second" != "$upstream_second"

So the tests that use this helper always try to update master and
second?  Is "they are different" the only thing that matters?  Or
should you be verifying "They are left as the same value they used
to have before the attempted push that must have failed"?

It might be a good time to use "-C" option, e.g. "git -C workbench show-ref ...",
a bit more in our tests.

> +}
> +
> +test_push_succeeded () {
> +	workbench_master=$(cd workbench && git show-ref -s --verify refs/heads/master) &&
> +	upstream_master=$(cd upstream && git show-ref -s --verify refs/heads/master) &&
> +	test "$workbench_master" = "$upstream_master"

Broken &&-chain?

> +
> +	workbench_second=$(cd workbench && git show-ref -s --verify refs/heads/second) &&
> +	upstream_second=$(cd upstream && git show-ref -s --verify refs/heads/second) &&
> +	test "$workbench_second" = "$upstream_second"
> +}
> +
> +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-push --mirror up
> +	) &&
> +	workbench_master=$(cd workbench && git show-ref -s --verify refs/heads/master) &&
> +	upstream_master=$(cd upstream && git show-ref -s --verify refs/heads/master) &&
> +	test "$workbench_master" = "$upstream_master"

Hmph.  It is a shame that the nice helper you created cannot be used
here.

> +'
> +
> +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-push --mirror up
> +	) &&
> +	test_push_succeeded
> +'

OK.

> +test_expect_success 'atomic push works in combination with --mirror' '
> +	mk_repo_pair &&
> +	ls workbench &&

Debugging?

> +	(
> +		cd workbench &&
> +		test_commit one &&
> +		git checkout -b second &&
> +		test_commit two &&
> +		git push --atomic-push --mirror up

It is not wrong per-se, but haven't you already tested in
combination with --mirror in the previous test?

> +	) &&
> +	test_push_succeeded
> +'
> +
> +test_expect_success 'atomic push works in combination with --force' '
> +	mk_repo_pair &&
> +	(
> +		cd workbench &&
> +		test_commit one &&
> +		git checkout -b second &&
> +		test_commit two &&
> +		test_commit three &&
> +		test_commit four &&
> +		git push --mirror up &&
> +		git reset --hard HEAD^ &&
> +		git push --force --atomic-push up master second
> +	) &&
> +	test_push_succeeded
> +'

OK.

> +# 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 locally' '
> +	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

Broken &&-chain.

> +		git reset --hard HEAD~2 &&
> +		git checkout master &&
> +		test_commit five &&
> +		! git push --atomic-push --all up

test_must_fail?

> +	) &&
> +	test_push_failed

You not only want to see master and second in upstream different from 
those in workbench, but they point at specific commits that the previous
mirror push updated to.

Instead of test_push_failed / test_push_succeeded, how about a
single helper that takes the two commit object names you expect
these two branches point at?  E.g.

	check_branches upstream master HEAD@{2} second HEAD~

(I am probably miscounting HEAD@{$offset} here; this is for
illustration only) that roughly does

	check_branches () {
		target=$1
                shift
                while test $# -ne 0
                do
			git -C "$target" rev-parse --verify "refs/heads/$1" >actual &&
			git rev-parse "$2" >expect &&
			test_cmp expect actual || return 1
			shift
                        shift
		done
	}

or something like that?

> +test_expect_success 'atomic push fails if one branch 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:
> +	(
> +		git clone upstream thirdparty &&
> +		cd thirdparty
> +		git checkout second
> +		test_commit unrelated_changes &&
> +		git push origin second
> +	) &&
> +	# see if we can now push both branches.
> +	(
> +		cd workbench &&
> +		git checkout master &&
> +		test_commit three &&
> +		git checkout second &&
> +		test_commit four &&
> +		! git push --atomic-push up master second
> +	) &&
> +	test_push_failed
> +'

What's the value of this test?  Isn't it a non-fast-forward check
you already tested in the previous one?

> +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:
> +	(
> +		git clone upstream thirdparty &&
> +		cd thirdparty
> +		git checkout second
> +		git tag test_tag &&
> +		git push --tags origin second
> +	) &&

Broken &&-chain aside, you do not need thirdparty.  Doing the "git
tag" inside upstream itself should suffice.

> +	# 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 &&
> +		! git push --tags --atomic-push up master second

test_must_fail?

> +	) &&
> +	test_push_failed
> +'

One more failure mode you would want to check is what if "update"
hook in the receiving repository rejected one update (but not the
other).  Something along the lines of:

	... setup ...
	git -C workbench push up &&
        write_script upstream/hooks/update <<-\EOF &&
	# only allow update to master from now on
        test "$1" = "refs/heads/master"
        EOF
        ... further update to master and second ...
        test_must_fail git -C workbench push up
	check_branches upstream master ... second ...


perhaps?

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH 1/5] receive-pack.c: add protocol support to negotiate atomic-push
  2014-12-15 20:53   ` Junio C Hamano
@ 2014-12-15 22:30     ` Stefan Beller
  0 siblings, 0 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-15 22:30 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, Michael Haggerty, Jonathan Nieder, ronnie sahlberg, Ronnie Sahlberg

On Mon, Dec 15, 2014 at 12:53 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Hmph, am I reading the patch to send-pack.c correctly?
>
> It detects if the other side supports the capability and leaves it
> in atomic_push_supported variable for later use, and also requests
> the feature to be activated when atomic_push is set, but I see no
> logic to link these two together, e.g. error out when atomic_push
> is true and atomic_push_supported is false (or turn it off with a
> warning, or whatever).


This is what you mean by

>
>> +     if (args->use_atomic_push && !atomic_push_supported) {
>> +             fprintf(stderr, "Server does not support atomic-push.");
>> +             return -1;
>> +     }
>
> This check logically belongs to the previous step, no?

from the next patch? If so it will be part of the next reroll.

Thanks,
Stefan

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH 0/5] Add a flag to push atomically
  2014-12-15 19:56 [PATCH 0/5] Add a flag to push atomically Stefan Beller
                   ` (4 preceding siblings ...)
  2014-12-15 19:56 ` [PATCH 5/5] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
@ 2014-12-15 22:33 ` Junio C Hamano
  2014-12-16 18:49   ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
  2014-12-17 18:32   ` [PATCHv3 0/6] atomic pushes Stefan Beller
  5 siblings, 2 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-15 22:33 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg

Stefan Beller <sbeller@google.com> writes:

> This patch series adds a flag to git push to update the remote refs atomically.
>
> It was part of a longer patch series[1].
> This series applies on top of origin/mh/reflog-expire
> It can also be found at [2].
>
> Change since picking the series up from Ronnie.
>   * other anchor point (i.e. where the series applies)
>   * more tests for this feature, specially testing failures
>   * drop the patch to rename ref_transaction_* to transaction_*_ref
>   * slight rewording of the additional documentation
>
> [1] http://www.spinics.net/lists/git/msg241214.html
> [2] https://github.com/stefanbeller/git/tree/atomic-push-v1
>
> Ronnie Sahlberg (4):
>   receive-pack.c: add protocol support to negotiate atomic-push
>   send-pack.c: add an --atomic-push command line argument
>   receive-pack.c: use a single transaction when atomic-push is
>     negotiated
>   push.c: add an --atomic-push argument
>
> Stefan Beller (1):
>   t5543-atomic-push.sh: add basic tests for atomic pushes

Thanks.  I think I like where it is going.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push
  2014-12-15 22:33 ` [PATCH 0/5] Add a flag to push atomically Junio C Hamano
@ 2014-12-16 18:49   ` Stefan Beller
  2014-12-16 18:49     ` [PATCHv2 2/6] send-pack: Invert the return value of ref_update_to_be_sent Stefan Beller
                       ` (5 more replies)
  2014-12-17 18:32   ` [PATCHv3 0/6] atomic pushes Stefan Beller
  1 sibling, 6 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 18:49 UTC (permalink / raw)
  To: gitster, git, mhagger, jrnieder, ronniesahlberg
  Cc: Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

This adds support to the protocol between send-pack and receive-pack to
* allow receive-pack to inform the client that it has atomic push capability
* allow send-pack to request atomic push back.

There is currently no setting in send-pack to actually request that atomic
pushes are to be used yet. This only adds protocol capability not ability
for the user to activate it.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Changes v1 -> v2:
    	* Name it atomic instead of atomic-push. The name changes affects
    	  the flags send over the wire as well as the flags in
    	  struct send_pack_args
    	* Add check, which was part of the later patch here:
    	if (args->atomic && !atomic_supported) {
    		fprintf(stderr, "Server does not support atomic push.");
    		return -1;
    	}

 Documentation/technical/protocol-capabilities.txt | 13 +++++++++++--
 builtin/receive-pack.c                            |  6 +++++-
 send-pack.c                                       | 11 +++++++++++
 send-pack.h                                       |  3 ++-
 4 files changed, 29 insertions(+), 4 deletions(-)

diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index 6d5424c..68ec23d 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -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 single atomic transaction. Either all refs are
+updated or none.
+
 allow-tip-sha1-in-want
 ----------------------
 
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 32fc540..e76e5d5 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -40,6 +40,7 @@ static int transfer_unpack_limit = -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;
@@ -171,7 +172,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
 		struct strbuf cap = STRBUF_INIT;
 
 		strbuf_addstr(&cap,
-			      "report-status delete-refs side-band-64k quiet");
+			      "report-status delete-refs side-band-64k quiet "
+			      "atomic");
 		if (prefer_ofs_delta)
 			strbuf_addstr(&cap, " ofs-delta");
 		if (push_cert_nonce)
@@ -1179,6 +1181,8 @@ 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 (parse_feature_request(feature_list, "atomic"))
+				use_atomic = 1;
 		}
 
 		if (!strcmp(line, "push-cert")) {
diff --git a/send-pack.c b/send-pack.c
index 949cb61..2a513f4 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -294,6 +294,8 @@ int send_pack(struct send_pack_args *args,
 	int use_sideband = 0;
 	int quiet_supported = 0;
 	int agent_supported = 0;
+	int use_atomic;
+	int atomic_supported = 0;
 	unsigned cmds_sent = 0;
 	int ret;
 	struct async demux;
@@ -314,6 +316,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;
 
@@ -328,6 +332,11 @@ 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) {
+		fprintf(stderr, "Server does not support atomic push.");
+		return -1;
+	}
+	use_atomic = atomic_supported && args->atomic;
 
 	if (status_report)
 		strbuf_addstr(&cap_buf, " report-status");
@@ -335,6 +344,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());
 
diff --git a/send-pack.h b/send-pack.h
index 5635457..b664648 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -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,
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv2 2/6] send-pack: Invert the return value of ref_update_to_be_sent
  2014-12-16 18:49   ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
@ 2014-12-16 18:49     ` Stefan Beller
  2014-12-16 19:14       ` Junio C Hamano
  2014-12-16 18:49     ` [PATCHv2 3/6] send-pack.c: add --atomic command line argument Stefan Beller
                       ` (4 subsequent siblings)
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 18:49 UTC (permalink / raw)
  To: gitster, git, mhagger, jrnieder, ronniesahlberg; +Cc: Stefan Beller

Having the return value inverted we can have different values for
the error codes. This is useful in a later patch when we want to
know if we hit the REF_STATUS_REJECT_* case.

Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    New in the series. For consistency with all the other patches
    it also reads v2.

 send-pack.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/send-pack.c b/send-pack.c
index 2a513f4..1c4ac75 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -190,10 +190,10 @@ 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)
+static int no_ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args)
 {
 	if (!ref->peer_ref && !args->send_mirror)
-		return 0;
+		return 1;
 
 	/* Check for statuses set by set_ref_status_for_push() */
 	switch (ref->status) {
@@ -203,10 +203,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 2;
 	case REF_STATUS_UPTODATE:
-		return 0;
+		return 3;
 	default:
-		return 1;
+		return 0;
 	}
 }
 
@@ -250,7 +251,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 (no_ref_update_to_be_sent(ref, args))
 			continue;
 		update_seen = 1;
 		strbuf_addf(&cert, "%s %s %s\n",
@@ -370,7 +371,7 @@ 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))
+		if (no_ref_update_to_be_sent(ref, args))
 			continue;
 
 		if (!ref->deletion)
@@ -391,7 +392,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 (no_ref_update_to_be_sent(ref, args))
 			continue;
 
 		old_hex = sha1_to_hex(ref->old_sha1);
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv2 3/6] send-pack.c: add --atomic command line argument
  2014-12-16 18:49   ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
  2014-12-16 18:49     ` [PATCHv2 2/6] send-pack: Invert the return value of ref_update_to_be_sent Stefan Beller
@ 2014-12-16 18:49     ` Stefan Beller
  2014-12-16 19:31       ` Junio C Hamano
  2014-12-16 18:49     ` [PATCHv2 4/6] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
                       ` (3 subsequent siblings)
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 18:49 UTC (permalink / raw)
  To: gitster, git, mhagger, jrnieder, ronniesahlberg
  Cc: Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

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>
---

Notes:
    Changes v1 -> v2:
     * Now we only need a very small change in the existing code and have
       a new static function, which cares about error reporting.
    	  Junio wrote:
    	  > Hmph.  Is "atomic push" so special that it deserves a separate
    	  > parameter?  When we come up with yet another mode of failure, would
    	  > we add another parameter to the callers to this function?
     * error messages are worded differently (lower case!),
     * use of error function instead of fprintf
    
     * undashed the printed error message ("atomic push failed");

 Documentation/git-send-pack.txt |  7 ++++++-
 builtin/send-pack.c             |  6 +++++-
 remote.h                        |  3 ++-
 send-pack.c                     | 36 ++++++++++++++++++++++++++++++++++--
 transport.c                     |  4 ++++
 5 files changed, 51 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 2a0de42..45c7725 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -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
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index b564a77..b961e5a 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -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;
diff --git a/remote.h b/remote.h
index 8b62efd..f346524 100644
--- a/remote.h
+++ b/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 */
diff --git a/send-pack.c b/send-pack.c
index 1c4ac75..71b1915 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -279,6 +279,30 @@ 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:
+			; /* do nothing */
+		}
+	}
+	error("atomic push failed for ref %s. "
+	      "status: %d\n", failing_ref->name, failing_ref->status);
+	return -1;
+}
+
 int send_pack(struct send_pack_args *args,
 	      int fd[], struct child_process *conn,
 	      struct ref *remote_refs,
@@ -371,9 +395,17 @@ int send_pack(struct send_pack_args *args,
 	 * the pack data.
 	 */
 	for (ref = remote_refs; ref; ref = ref->next) {
-		if (no_ref_update_to_be_sent(ref, args))
+		int reject_reason;
+		if ((reject_reason = no_ref_update_to_be_sent(ref, args))) {
+			/* 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 && reject_reason == 2)
+				return atomic_push_failure(args,
+							   remote_refs,
+							   ref);
 			continue;
-
+		}
 		if (!ref->deletion)
 			need_pack_data = 1;
 
diff --git a/transport.c b/transport.c
index 70d38e4..c67feee 100644
--- a/transport.c
+++ b/transport.c
@@ -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;
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv2 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-16 18:49   ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
  2014-12-16 18:49     ` [PATCHv2 2/6] send-pack: Invert the return value of ref_update_to_be_sent Stefan Beller
  2014-12-16 18:49     ` [PATCHv2 3/6] send-pack.c: add --atomic command line argument Stefan Beller
@ 2014-12-16 18:49     ` Stefan Beller
  2014-12-16 19:29       ` Eric Sunshine
  2014-12-16 19:35       ` Junio C Hamano
  2014-12-16 18:49     ` [PATCHv2 5/6] push.c: add an --atomic-push argument Stefan Beller
                       ` (2 subsequent siblings)
  5 siblings, 2 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 18:49 UTC (permalink / raw)
  To: gitster, git, mhagger, jrnieder, ronniesahlberg
  Cc: Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

Update receive-pack to use an atomic transaction iff the client negotiated
that it wanted atomic-push. This leaves the default behavior to be the old
non-atomic one ref at a time update. This is to cause as little disruption
as possible to existing clients. It is unknown if there are client scripts
that depend on the old non-atomic behavior so we make it opt-in for now.

If it turns out over time that there are no client scripts that depend on the
old behavior we can change git to default to use atomic pushes and instead
offer an opt-out argument for people that do not want atomic pushes.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Changes v1 -> v2:
    	* update(...) assumes to be always in a transaction
    	* Caring about when to begin/commit transactions is put
    	  into execute_commands

 builtin/receive-pack.c | 64 ++++++++++++++++++++++++++++++++++++++------------
 1 file changed, 49 insertions(+), 15 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e76e5d5..0803fd2 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -67,6 +67,8 @@ static const char *NONCE_SLOP = "SLOP";
 static const char *nonce_status;
 static long nonce_stamp_slop;
 static unsigned long nonce_stamp_slop_limit;
+struct strbuf err = STRBUF_INIT;
+struct ref_transaction *transaction;
 
 static enum deny_action parse_deny_action(const char *var, const char *value)
 {
@@ -832,34 +834,32 @@ 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";
 		}
 		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 */
 	}
@@ -1059,6 +1059,16 @@ static void execute_commands(struct command *commands,
 		return;
 	}
 
+	if (use_atomic) {
+		transaction = ref_transaction_begin(&err);
+		if (!transaction) {
+			error("%s", err.buf);
+			strbuf_release(&err);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = "transaction error";
+			return;
+		}
+	}
 	data.cmds = commands;
 	data.si = si;
 	if (check_everything_connected(iterate_receive_command_list, 0, &data))
@@ -1086,8 +1096,23 @@ static void execute_commands(struct command *commands,
 
 		if (cmd->skip_update)
 			continue;
-
+		if (!use_atomic) {
+			transaction = ref_transaction_begin(&err);
+			if (!transaction) {
+				rp_error("%s", err.buf);
+				strbuf_release(&err);
+				cmd->error_string = "failed to start transaction";
+			}
+		}
 		cmd->error_string = update(cmd, si);
+		if (!use_atomic)
+			if (ref_transaction_commit(transaction, &err)) {
+				ref_transaction_free(transaction);
+				rp_error("%s", err.buf);
+				strbuf_release(&err);
+				cmd->error_string = "failed to update ref";
+			}
+
 		if (shallow_update && !cmd->error_string &&
 		    si->shallow_ref[cmd->index]) {
 			error("BUG: connectivity check has not been run on ref %s",
@@ -1096,6 +1121,14 @@ static void execute_commands(struct command *commands,
 		}
 	}
 
+	if (use_atomic) {
+		if (ref_transaction_commit(transaction, &err)) {
+			rp_error("%s", err.buf);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = err.buf;
+		}
+		ref_transaction_free(transaction);
+	}
 	if (shallow_update && !checked_connectivity)
 		error("BUG: run 'git fsck' for safety.\n"
 		      "If there are errors, try to remove "
@@ -1543,5 +1576,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
 	sha1_array_clear(&shallow);
 	sha1_array_clear(&ref);
 	free((void *)push_cert_nonce);
+	strbuf_release(&err);
 	return 0;
 }
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv2 5/6] push.c: add an --atomic-push argument
  2014-12-16 18:49   ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
                       ` (2 preceding siblings ...)
  2014-12-16 18:49     ` [PATCHv2 4/6] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
@ 2014-12-16 18:49     ` Stefan Beller
  2014-12-16 19:33       ` Eric Sunshine
  2014-12-16 19:36       ` Junio C Hamano
  2014-12-16 18:49     ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
  2014-12-16 19:05     ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Junio C Hamano
  5 siblings, 2 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 18:49 UTC (permalink / raw)
  To: gitster, git, mhagger, jrnieder, ronniesahlberg
  Cc: Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

Add a command line argument to the git push command to request atomic
pushes.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Changes v1 -> v2
    	It's --atomic now! (dropping the -push)

 Documentation/git-push.txt | 7 ++++++-
 builtin/push.c             | 2 ++
 transport.c                | 1 +
 transport.h                | 1 +
 4 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 21b3f29..3feacc5 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -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-push] [-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.
 
+--atomic::
+	Use the an atomic transaction on the server 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
diff --git a/builtin/push.c b/builtin/push.c
index a076b19..fe0b8cc 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -518,6 +518,8 @@ 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_BIT(0, "atomic", &flags, N_("use a single atomic transaction at the serverside, if available"),
+			TRANSPORT_ATOMIC_PUSH),
 		OPT_END()
 	};
 
diff --git a/transport.c b/transport.c
index c67feee..5b29514 100644
--- a/transport.c
+++ b/transport.c
@@ -830,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_ATOMIC_PUSH);
 	args.url = transport->url;
 
 	ret = send_pack(&args, data->fd, data->conn, remote_refs,
diff --git a/transport.h b/transport.h
index 3e0091e..25fa1da 100644
--- a/transport.h
+++ b/transport.h
@@ -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_ATOMIC_PUSH 4096
 
 #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-16 18:49   ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
                       ` (3 preceding siblings ...)
  2014-12-16 18:49     ` [PATCHv2 5/6] push.c: add an --atomic-push argument Stefan Beller
@ 2014-12-16 18:49     ` Stefan Beller
  2014-12-16 19:14       ` [PATCH] receive-pack: refuse all commands if one fails in atomic mode Stefan Beller
                         ` (3 more replies)
  2014-12-16 19:05     ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Junio C Hamano
  5 siblings, 4 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 18:49 UTC (permalink / raw)
  To: gitster, git, mhagger, jrnieder, ronniesahlberg; +Cc: Stefan Beller

This adds tests for the atomic push option.
The first four tests check if the atomic option works in
good conditions and the last three patches check if the atomic
option prevents any change to be pushed if just one ref cannot
be updated.

Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Changes v1 -> v2:
    > Please drop unused comments; they are distracting.
    
    ok
    
    > It is not wrong per-se, but haven't you already tested in
    > combination with --mirror in the previous test?
    
    I fixed the previous tests, so that there is no --mirror
    and --atomic together. There is still a first --mirror push
    for setup and a second with --atomic <branchnames> though
    
    > check_branches upstream master HEAD@{2} second HEAD~
    
    A similar function test_ref_upstream is introduced.
    
    > What's the value of this test?  Isn't it a non-fast-forward check
    > you already tested in the previous one?
    
    I messed up there. Originally I wanted to test the 2 different
    stages of rejection. A non-fast-forward check is done locally and
    we don't even try pushing. But I also want to test if we locally
    thing all is good, but the server refuses a ref to update.
    This is now done with the last test named 'atomic push obeys
    update hook preventing a branch to be pushed'. And that still fails.
    
    I'll investigate that, while still sending out the series for another
    review though.
    
    * Redone the test helper, there is test_ref_upstream now.
      This tests explicitely for SHA1 values of the ref.
      (It's needed in the last test for example. The git push fails,
      but still modifies the ref :/ )
    * checked all && chains and repaired them
    * sometimes make use of git -C <workdir>
    
    Notes v1:
    Originally Ronnie had a similar patch prepared. But as I added
    more tests and cleaned up the existing tests (e.g. use test_commit
    instead of "echo one >file && gitadd file && git commit -a -m 'one'",
    removal of dead code), the file has changed so much that I'd rather
    take ownership.

 t/t5543-atomic-push.sh | 176 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 176 insertions(+)
 create mode 100755 t/t5543-atomic-push.sh

diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh
new file mode 100755
index 0000000..6354fc0
--- /dev/null
+++ b/t/t5543-atomic-push.sh
@@ -0,0 +1,176 @@
+#!/bin/sh
+
+test_description='pushing to a repository using the atomic push option'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+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
+	)
+}
+
+# refname, expected value, e.g.
+# test_ref_upstream refs/heads/master HEADS{0}
+test_ref_upstream () {
+	test "$#" == "2" # if this fails, we have a bug in this script.
+	test "$(git -C upstream rev-parse --verify $1)" == "$2"
+}
+
+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_ref_upstream master $(git -C workbench rev-parse --verify 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_ref_upstream master $(git -C workbench rev-parse --verify master) &&
+	test_ref_upstream second $(git -C workbench rev-parse --verify 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_ref_upstream master $(git -C workbench rev-parse --verify master) &&
+	test_ref_upstream second $(git -C workbench rev-parse --verify 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_ref_upstream master $(git -C workbench rev-parse --verify master) &&
+	test_ref_upstream second $(git -C workbench rev-parse --verify 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 &&
+		! git push --atomic --all up
+	) &&
+	test_ref_upstream master $(git -C workbench rev-parse --verify HEAD@{7}) &&
+	test_ref_upstream second $(git -C workbench rev-parse --verify 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_ref_upstream master $(git -C workbench rev-parse --verify HEAD@{3}) &&
+	test_ref_upstream second $(git -C workbench rev-parse --verify HEAD@{1})
+'
+
+test_expect_failure '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_ref_upstream master $(git -C workbench rev-parse --verify HEAD@{3}) &&
+	test_ref_upstream second $(git -C workbench rev-parse --verify HEAD@{1})
+'
+
+test_done
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push
  2014-12-16 18:49   ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
                       ` (4 preceding siblings ...)
  2014-12-16 18:49     ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
@ 2014-12-16 19:05     ` Junio C Hamano
  5 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 19:05 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

> diff --git a/send-pack.c b/send-pack.c
> index 949cb61..2a513f4 100644
> --- a/send-pack.c
> +++ b/send-pack.c
> @@ -294,6 +294,8 @@ int send_pack(struct send_pack_args *args,
>  	int use_sideband = 0;
>  	int quiet_supported = 0;
>  	int agent_supported = 0;
> +	int use_atomic;
> +	int atomic_supported = 0;
>  	unsigned cmds_sent = 0;
>  	int ret;
>  	struct async demux;
> @@ -314,6 +316,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;
>  
> @@ -328,6 +332,11 @@ 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) {
> +		fprintf(stderr, "Server does not support atomic push.");
> +		return -1;

I'd tweak this to

	return error("server does not support atomic push.");

to (0) shorten, (1) make sure the message is terminated with LF,
and (2) match the other error messages in the program.

Other than that looks good.

Thanks.

> +	}
> +	use_atomic = atomic_supported && args->atomic;
>  
>  	if (status_report)
>  		strbuf_addstr(&cap_buf, " report-status");
> @@ -335,6 +344,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());
>  
> diff --git a/send-pack.h b/send-pack.h
> index 5635457..b664648 100644
> --- a/send-pack.h
> +++ b/send-pack.h
> @@ -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,

^ permalink raw reply	[flat|nested] 56+ messages in thread

* [PATCH] receive-pack: refuse all commands if one fails in atomic mode
  2014-12-16 18:49     ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
@ 2014-12-16 19:14       ` Stefan Beller
  2014-12-16 20:32         ` Junio C Hamano
  2014-12-16 19:37       ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Eric Sunshine
                         ` (2 subsequent siblings)
  3 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 19:14 UTC (permalink / raw)
  To: ronniesahlberg, mhagger, jrnieder, gitster; +Cc: git, Stefan Beller

This patch will be incorporated into the right places in v3 of the series.

Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    I don't want to resend the patch series today to accumulate comments,
    but this makes the last test pass, which is the whole point of the series.
    
    I'll put it into the right places in a reroll.

 builtin/receive-pack.c | 13 ++++++++++++-
 t/t5543-atomic-push.sh |  2 +-
 2 files changed, 13 insertions(+), 2 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 0803fd2..3477f7c 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1122,7 +1122,18 @@ static void execute_commands(struct command *commands,
 	}
 
 	if (use_atomic) {
-		if (ref_transaction_commit(transaction, &err)) {
+		/* update(...) may abort early (i.e. because the hook refused to
+		 * update that ref), which then doesn't even record a transaction
+		 * regarding that ref. Make sure all commands are without error
+		 * and then commit atomically. */
+		for (cmd = commands; cmd; cmd = cmd->next)
+			if (cmd->error_string)
+				break;
+		if (cmd) {
+			for (cmd = commands; cmd; cmd = cmd->next)
+				if (!cmd->error_string)
+					cmd->error_string = "atomic push failure";
+		} else if (ref_transaction_commit(transaction, &err)) {
 			rp_error("%s", err.buf);
 			for (cmd = commands; cmd; cmd = cmd->next)
 				cmd->error_string = err.buf;
diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh
index 6354fc0..f0e54d9 100755
--- a/t/t5543-atomic-push.sh
+++ b/t/t5543-atomic-push.sh
@@ -142,7 +142,7 @@ test_expect_success 'atomic push fails if one tag fails remotely' '
 	test_ref_upstream second $(git -C workbench rev-parse --verify HEAD@{1})
 '
 
-test_expect_failure 'atomic push obeys update hook preventing a branch to be pushed' '
+test_expect_success 'atomic push obeys update hook preventing a branch to be pushed' '
 	mk_repo_pair &&
 	(
 		cd workbench &&
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 2/6] send-pack: Invert the return value of ref_update_to_be_sent
  2014-12-16 18:49     ` [PATCHv2 2/6] send-pack: Invert the return value of ref_update_to_be_sent Stefan Beller
@ 2014-12-16 19:14       ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 19:14 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg

Stefan Beller <sbeller@google.com> writes:

> Having the return value inverted we can have different values for
> the error codes. This is useful in a later patch when we want to
> know if we hit the REF_STATUS_REJECT_* case.
>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
>
> Notes:
>     New in the series. For consistency with all the other patches
>     it also reads v2.

Sorry, but ECANNOTPARSE especially "it also read v2" part.

>  send-pack.c | 15 ++++++++-------
>  1 file changed, 8 insertions(+), 7 deletions(-)
>
> diff --git a/send-pack.c b/send-pack.c
> index 2a513f4..1c4ac75 100644
> --- a/send-pack.c
> +++ b/send-pack.c
> @@ -190,10 +190,10 @@ 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)
> +static int no_ref_update_to_be_sent(const struct ref *ref, const struct send_pack_args *args)
>  {
>  	if (!ref->peer_ref && !args->send_mirror)
> -		return 0;
> +		return 1;
>  
>  	/* Check for statuses set by set_ref_status_for_push() */
>  	switch (ref->status) {
> @@ -203,10 +203,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 2;
>  	case REF_STATUS_UPTODATE:
> -		return 0;
> +		return 3;
>  	default:
> -		return 1;
> +		return 0;

Hmmm.  It used to be "will we send this one?"  It is fine if the
function wants to differenciate various kinds of error, but

 (1) unexplained 1, 2 and 3 looks cryptic and unmaintainable;

 (2) no_ prefix in the function name is usually a bad idea, because
     it easily invites "if (!no_foo(...))" double negation; and

 (3) unless there is a good reason to do otherwise, a function that
     returns 0 on success should signal an error with a negative
     return.

"static int check_to_send_update()" or something, perhaps?

	if (check_to_send_update() < 0)
        	/* skip as this is an error */
                continue;

does not look too bad.

>  }
>  
> @@ -250,7 +251,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 (no_ref_update_to_be_sent(ref, args))
>  			continue;
>  		update_seen = 1;
>  		strbuf_addf(&cert, "%s %s %s\n",
> @@ -370,7 +371,7 @@ 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))
> +		if (no_ref_update_to_be_sent(ref, args))
>  			continue;
>  
>  		if (!ref->deletion)
> @@ -391,7 +392,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 (no_ref_update_to_be_sent(ref, args))
>  			continue;
>  
>  		old_hex = sha1_to_hex(ref->old_sha1);

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-16 18:49     ` [PATCHv2 4/6] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
@ 2014-12-16 19:29       ` Eric Sunshine
  2014-12-16 20:30         ` Eric Sunshine
  2014-12-16 19:35       ` Junio C Hamano
  1 sibling, 1 reply; 56+ messages in thread
From: Eric Sunshine @ 2014-12-16 19:29 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Junio C Hamano, Git List, Michael Haggerty, Jonathan Nieder,
	ronniesahlberg, Ronnie Sahlberg

On Tue, Dec 16, 2014 at 1:49 PM, Stefan Beller <sbeller@google.com> wrote:
> From: Ronnie Sahlberg <sahlberg@google.com>
>
> Update receive-pack to use an atomic transaction iff the client negotiated
> that it wanted atomic-push. This leaves the default behavior to be the old
> non-atomic one ref at a time update. This is to cause as little disruption
> as possible to existing clients. It is unknown if there are client scripts
> that depend on the old non-atomic behavior so we make it opt-in for now.
>
> If it turns out over time that there are no client scripts that depend on the
> old behavior we can change git to default to use atomic pushes and instead
> offer an opt-out argument for people that do not want atomic pushes.
>
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index e76e5d5..0803fd2 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -67,6 +67,8 @@ static const char *NONCE_SLOP = "SLOP";
>  static const char *nonce_status;
>  static long nonce_stamp_slop;
>  static unsigned long nonce_stamp_slop_limit;
> +struct strbuf err = STRBUF_INIT;
> +struct ref_transaction *transaction;

Should these be static?

>  static enum deny_action parse_deny_action(const char *var, const char *value)
>  {
> @@ -1059,6 +1059,16 @@ static void execute_commands(struct command *commands,
>                 return;
>         }
>
> +       if (use_atomic) {
> +               transaction = ref_transaction_begin(&err);
> +               if (!transaction) {
> +                       error("%s", err.buf);
> +                       strbuf_release(&err);
> +                       for (cmd = commands; cmd; cmd = cmd->next)
> +                               cmd->error_string = "transaction error";
> +                       return;
> +               }
> +       }

Not seen in this diff, but just below this point, the pre-receive hook
is invoked. If it "declines", then execute_commands() returns without
releasing the transaction which was begun here. Is that correct
behavior?

For robustness, it might also be sane to release the 'err' strbuf at
this early return (though the current code does not strictly leak it).

>         data.cmds = commands;
>         data.si = si;
>         if (check_everything_connected(iterate_receive_command_list, 0, &data))
> @@ -1086,8 +1096,23 @@ static void execute_commands(struct command *commands,
>
>                 if (cmd->skip_update)
>                         continue;
> -
> +               if (!use_atomic) {
> +                       transaction = ref_transaction_begin(&err);
> +                       if (!transaction) {
> +                               rp_error("%s", err.buf);
> +                               strbuf_release(&err);
> +                               cmd->error_string = "failed to start transaction";

Here, you assign cmd->error_string...

> +                       }
> +               }
>                 cmd->error_string = update(cmd, si);

and then immediately overwrite it here. Is it correct to invoke
update() when ref_transaction_begin() has failed? Seems fishy.

> +               if (!use_atomic)
> +                       if (ref_transaction_commit(transaction, &err)) {
> +                               ref_transaction_free(transaction);

Shouldn't you be freeing the transaction outside of this conditional
rather than only in case of failure?

> +                               rp_error("%s", err.buf);
> +                               strbuf_release(&err);
> +                               cmd->error_string = "failed to update ref";
> +                       }
> +
>                 if (shallow_update && !cmd->error_string &&
>                     si->shallow_ref[cmd->index]) {
>                         error("BUG: connectivity check has not been run on ref %s",
> @@ -1096,6 +1121,14 @@ static void execute_commands(struct command *commands,
>                 }
>         }
>
> +       if (use_atomic) {
> +               if (ref_transaction_commit(transaction, &err)) {
> +                       rp_error("%s", err.buf);
> +                       for (cmd = commands; cmd; cmd = cmd->next)
> +                               cmd->error_string = err.buf;
> +               }
> +               ref_transaction_free(transaction);
> +       }
>         if (shallow_update && !checked_connectivity)
>                 error("BUG: run 'git fsck' for safety.\n"
>                       "If there are errors, try to remove "
> @@ -1543,5 +1576,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
>         sha1_array_clear(&shallow);
>         sha1_array_clear(&ref);
>         free((void *)push_cert_nonce);
> +       strbuf_release(&err);
>         return 0;
>  }
> --
> 2.2.0.31.gad78000.dirty

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 3/6] send-pack.c: add --atomic command line argument
  2014-12-16 18:49     ` [PATCHv2 3/6] send-pack.c: add --atomic command line argument Stefan Beller
@ 2014-12-16 19:31       ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 19:31 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

>      * Now we only need a very small change in the existing code and have
>        a new static function, which cares about error reporting.
>     	  Junio wrote:
>     	  > Hmph.  Is "atomic push" so special that it deserves a separate
>     	  > parameter?  When we come up with yet another mode of failure, would
>     	  > we add another parameter to the callers to this function?

I suspect that you completely mis-read me.

If you add an extra arg that is *specifically* for atomic push, like
this:

    -static int update_to_send(...);
    +static int update_to_send(..., int *atomic_push_failed);
    
what signal does it send to the next person who may want to add
a new "nuclear push" option?  Should the patch look like

    -static int update_to_send(..., int *atomic_push_failed);
    +static int update_to_send(..., int *atomic_push_failed, int *nuclear_push_failed);

by adding yet another separate variable for error reporting?

I would have understood if the new variable was named something like
"failure_reason", which may be set to PUSH_FAILURE_ATOMIC when an
atomic push failure was detected.  Making the helper return the
reason why the push failed would be another way, like you did in the
2/6 patch in this round.

Either way, the next person would know to add a code to do his
"nuclear push" and set the reason to PUSH_FAILURE_NUCLEAR when it
fails, instead of piling yet another error reporting variable that
is specific to the feature.

This is all about code maintainability, which is very different from
"who cares about error reporting".

> diff --git a/remote.h b/remote.h
> index 8b62efd..f346524 100644
> --- a/remote.h
> +++ b/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;
> ...
>  	for (ref = remote_refs; ref; ref = ref->next) {
> -		if (no_ref_update_to_be_sent(ref, args))
> +		int reject_reason;
> +		if ((reject_reason = no_ref_update_to_be_sent(ref, args))) {

We avoid assignment inside a conditional.

> +			/* 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 && reject_reason == 2)
> +				return atomic_push_failure(args,
> +							   remote_refs,
> +							   ref);
>  			continue;

Perhaps

		switch (check_to_send_update(...)) {
                case 0: /* no error */
                	break;
		case -REF_STATUS_ATOMIC_PUSH_FAILED:                        
                	return atomic_push_failure(args, remote_refs, ref);
                	break;
		default:
			continue;
		}

or something?

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 5/6] push.c: add an --atomic-push argument
  2014-12-16 18:49     ` [PATCHv2 5/6] push.c: add an --atomic-push argument Stefan Beller
@ 2014-12-16 19:33       ` Eric Sunshine
  2014-12-16 20:43         ` Junio C Hamano
  2014-12-16 19:36       ` Junio C Hamano
  1 sibling, 1 reply; 56+ messages in thread
From: Eric Sunshine @ 2014-12-16 19:33 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Junio C Hamano, Git List, Michael Haggerty, Jonathan Nieder,
	ronniesahlberg, Ronnie Sahlberg

On Tue, Dec 16, 2014 at 1:49 PM, Stefan Beller <sbeller@google.com> wrote:
> From: Ronnie Sahlberg <sahlberg@google.com>
>
> Add a command line argument to the git push command to request atomic
> pushes.
>
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
>
> Notes:
>     Changes v1 -> v2
>         It's --atomic now! (dropping the -push)
>
> diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
> index 21b3f29..3feacc5 100644
> --- a/Documentation/git-push.txt
> +++ b/Documentation/git-push.txt
> @@ -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-push] [-n | --dry-run] [--receive-pack=<git-receive-pack>]

s/atomic-push/atomic/

>            [--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.
>
> +--atomic::
> +       Use the an atomic transaction on the server side if available.

s/the an/an/

> +       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
> diff --git a/builtin/push.c b/builtin/push.c
> index a076b19..fe0b8cc 100644
> --- a/builtin/push.c
> +++ b/builtin/push.c
> @@ -518,6 +518,8 @@ 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_BIT(0, "atomic", &flags, N_("use a single atomic transaction at the serverside, if available"),

"single atomic" sounds awfully redundant.

"serverside" is odd. Perhaps "server side" or merely "remote" or "remote side".

> +                       TRANSPORT_ATOMIC_PUSH),
>                 OPT_END()
>         };
>
> diff --git a/transport.c b/transport.c
> index c67feee..5b29514 100644
> --- a/transport.c
> +++ b/transport.c
> @@ -830,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_ATOMIC_PUSH);
>         args.url = transport->url;
>
>         ret = send_pack(&args, data->fd, data->conn, remote_refs,
> diff --git a/transport.h b/transport.h
> index 3e0091e..25fa1da 100644
> --- a/transport.h
> +++ b/transport.h
> @@ -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_ATOMIC_PUSH 4096

For consistency with existing names, should this be TRANSPORT_PUSH_ATOMIC?

>  #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
>  #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
> --
> 2.2.0.31.gad78000.dirty

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-16 18:49     ` [PATCHv2 4/6] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
  2014-12-16 19:29       ` Eric Sunshine
@ 2014-12-16 19:35       ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 19:35 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

>     	* update(...) assumes to be always in a transaction
>     	* Caring about when to begin/commit transactions is put
>     	  into execute_commands

I am obviously biased, but I find that the new code structure and
separation of responsibility between update() and execute()
functions a lot clearer than the previous one.

Thanks.

>  builtin/receive-pack.c | 64 ++++++++++++++++++++++++++++++++++++++------------
>  1 file changed, 49 insertions(+), 15 deletions(-)
>
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index e76e5d5..0803fd2 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -67,6 +67,8 @@ static const char *NONCE_SLOP = "SLOP";
>  static const char *nonce_status;
>  static long nonce_stamp_slop;
>  static unsigned long nonce_stamp_slop_limit;
> +struct strbuf err = STRBUF_INIT;
> +struct ref_transaction *transaction;
>  
>  static enum deny_action parse_deny_action(const char *var, const char *value)
>  {
> @@ -832,34 +834,32 @@ 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";
>  		}
>  		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 */
>  	}
> @@ -1059,6 +1059,16 @@ static void execute_commands(struct command *commands,
>  		return;
>  	}
>  
> +	if (use_atomic) {
> +		transaction = ref_transaction_begin(&err);
> +		if (!transaction) {
> +			error("%s", err.buf);
> +			strbuf_release(&err);
> +			for (cmd = commands; cmd; cmd = cmd->next)
> +				cmd->error_string = "transaction error";
> +			return;
> +		}
> +	}
>  	data.cmds = commands;
>  	data.si = si;
>  	if (check_everything_connected(iterate_receive_command_list, 0, &data))
> @@ -1086,8 +1096,23 @@ static void execute_commands(struct command *commands,
>  
>  		if (cmd->skip_update)
>  			continue;
> -
> +		if (!use_atomic) {
> +			transaction = ref_transaction_begin(&err);
> +			if (!transaction) {
> +				rp_error("%s", err.buf);
> +				strbuf_release(&err);
> +				cmd->error_string = "failed to start transaction";
> +			}
> +		}
>  		cmd->error_string = update(cmd, si);
> +		if (!use_atomic)
> +			if (ref_transaction_commit(transaction, &err)) {
> +				ref_transaction_free(transaction);
> +				rp_error("%s", err.buf);
> +				strbuf_release(&err);
> +				cmd->error_string = "failed to update ref";
> +			}
> +
>  		if (shallow_update && !cmd->error_string &&
>  		    si->shallow_ref[cmd->index]) {
>  			error("BUG: connectivity check has not been run on ref %s",
> @@ -1096,6 +1121,14 @@ static void execute_commands(struct command *commands,
>  		}
>  	}
>  
> +	if (use_atomic) {
> +		if (ref_transaction_commit(transaction, &err)) {
> +			rp_error("%s", err.buf);
> +			for (cmd = commands; cmd; cmd = cmd->next)
> +				cmd->error_string = err.buf;
> +		}
> +		ref_transaction_free(transaction);
> +	}
>  	if (shallow_update && !checked_connectivity)
>  		error("BUG: run 'git fsck' for safety.\n"
>  		      "If there are errors, try to remove "
> @@ -1543,5 +1576,6 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
>  	sha1_array_clear(&shallow);
>  	sha1_array_clear(&ref);
>  	free((void *)push_cert_nonce);
> +	strbuf_release(&err);
>  	return 0;
>  }

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 5/6] push.c: add an --atomic-push argument
  2014-12-16 18:49     ` [PATCHv2 5/6] push.c: add an --atomic-push argument Stefan Beller
  2014-12-16 19:33       ` Eric Sunshine
@ 2014-12-16 19:36       ` Junio C Hamano
  1 sibling, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 19:36 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

> From: Ronnie Sahlberg <sahlberg@google.com>
>
> Add a command line argument to the git push command to request atomic
> pushes.
>
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
>
> Notes:
>     Changes v1 -> v2
>     	It's --atomic now! (dropping the -push)

Also from the subject line ;-)

>
>  Documentation/git-push.txt | 7 ++++++-
>  builtin/push.c             | 2 ++
>  transport.c                | 1 +
>  transport.h                | 1 +
>  4 files changed, 10 insertions(+), 1 deletion(-)
>
> diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
> index 21b3f29..3feacc5 100644
> --- a/Documentation/git-push.txt
> +++ b/Documentation/git-push.txt
> @@ -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-push] [-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.
>  
> +--atomic::
> +	Use the an atomic transaction on the server 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
> diff --git a/builtin/push.c b/builtin/push.c
> index a076b19..fe0b8cc 100644
> --- a/builtin/push.c
> +++ b/builtin/push.c
> @@ -518,6 +518,8 @@ 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_BIT(0, "atomic", &flags, N_("use a single atomic transaction at the serverside, if available"),
> +			TRANSPORT_ATOMIC_PUSH),
>  		OPT_END()
>  	};
>  
> diff --git a/transport.c b/transport.c
> index c67feee..5b29514 100644
> --- a/transport.c
> +++ b/transport.c
> @@ -830,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_ATOMIC_PUSH);
>  	args.url = transport->url;
>  
>  	ret = send_pack(&args, data->fd, data->conn, remote_refs,
> diff --git a/transport.h b/transport.h
> index 3e0091e..25fa1da 100644
> --- a/transport.h
> +++ b/transport.h
> @@ -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_ATOMIC_PUSH 4096
>  
>  #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
>  #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-16 18:49     ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
  2014-12-16 19:14       ` [PATCH] receive-pack: refuse all commands if one fails in atomic mode Stefan Beller
@ 2014-12-16 19:37       ` Eric Sunshine
  2014-12-16 19:46       ` Junio C Hamano
  2014-12-16 20:30       ` Junio C Hamano
  3 siblings, 0 replies; 56+ messages in thread
From: Eric Sunshine @ 2014-12-16 19:37 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Junio C Hamano, Git List, Michael Haggerty, Jonathan Nieder,
	ronniesahlberg

On Tue, Dec 16, 2014 at 1:49 PM, Stefan Beller <sbeller@google.com> wrote:
> This adds tests for the atomic push option.
> The first four tests check if the atomic option works in
> good conditions and the last three patches check if the atomic
> option prevents any change to be pushed if just one ref cannot
> be updated.
>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
> diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh
> new file mode 100755
> index 0000000..6354fc0
> --- /dev/null
> +++ b/t/t5543-atomic-push.sh
> @@ -0,0 +1,176 @@
> +#!/bin/sh
> +
> +test_description='pushing to a repository using the atomic push option'
> +
> +. ./test-lib.sh
> +
> +D=`pwd`

Junio already mentioned this previously, but this should be $(pwd)
rather than `pwd`.

However, more importantly, why is this variable declared at all since
it is never used by the script?

> +mk_repo_pair () {
> +       rm -rf workbench upstream &&
> +       test_create_repo upstream &&
> +       test_create_repo workbench &&
> +       (
> +               cd upstream && git config receive.denyCurrentBranch warn

This would be slightly easier to read if split over two lines.

> +       ) &&
> +       (
> +               cd workbench && git remote add up ../upstream

Ditto.

> +       )
> +}
> +

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-16 18:49     ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
  2014-12-16 19:14       ` [PATCH] receive-pack: refuse all commands if one fails in atomic mode Stefan Beller
  2014-12-16 19:37       ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Eric Sunshine
@ 2014-12-16 19:46       ` Junio C Hamano
  2014-12-16 19:57         ` Stefan Beller
  2014-12-16 20:30       ` Junio C Hamano
  3 siblings, 1 reply; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 19:46 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg

Stefan Beller <sbeller@google.com> writes:

> +test_description='pushing to a repository using the atomic push option'
> +
> +. ./test-lib.sh
> +
> +D=`pwd`

$(pwd)?

> +mk_repo_pair () {
> +	rm -rf workbench upstream &&
> +	test_create_repo upstream &&
> +	test_create_repo workbench &&
> +	(
> +		cd upstream && git config receive.denyCurrentBranch warn
> +	) &&

I was wondering how you would do this part after suggesting use of
test_create_repo, knowing very well that one of them was a bare one
;-).

We might want to extend test_create_repo to allow creating a bare
repository, but this is also OK.

> +	(
> +		cd workbench && git remote add up ../upstream
> +	)
> +}

> +# refname, expected value, e.g.
> +# test_ref_upstream refs/heads/master HEADS{0}
> +test_ref_upstream () {
> +	test "$#" == "2" # if this fails, we have a bug in this script.

This is not C; "test $# = 2" (notice a single equal sign).  And you
do not need dq-pair around these.

> +	test "$(git -C upstream rev-parse --verify $1)" == "$2"
> +}

Seeing that all callers of test_ref_upstream computes $2 as

	git -C workbench rev-parse --verify <something>

I have a feeling that

> +	test_ref_upstream second second

would be easier for them to write than

> +	test_ref_upstream second $(git -C workbench rev-parse --verify second)

That is

# refname in upstream and expected value from workbench
# E.g. "test_ref_upstream master HEAD" makes sure that HEAD in
# workbench matches the master branch in upstream repository.
test_ref_upstream () {
	test $# = 2 &&
        test "$(git -C upstream rev-parse --verify "$1")" == \
		"$(git -C workbench rev-parse --verify "$2")"
}

or something.  We may however want to do the usual

	test $# = 2 &&
	git -C upstream rev-parse --verify "$1" >expect &&
	git -C workbench rev-parse --verify "$2" >actual &&
        test_cmp expect actual

though.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-16 19:46       ` Junio C Hamano
@ 2014-12-16 19:57         ` Stefan Beller
  2014-12-16 20:46           ` Junio C Hamano
  0 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 19:57 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Michael Haggerty, Jonathan Nieder, ronnie sahlberg

On Tue, Dec 16, 2014 at 11:46 AM, Junio C Hamano <gitster@pobox.com> wrote:

>
> Seeing that all callers of test_ref_upstream computes $2 as
>
>         git -C workbench rev-parse --verify <something>
>

Only in the first tests, where this should be the case after push.
In the failure tests, we go with HEAD@{N} which needs to be computed
inside the workbench repo.

Alternatively we could check if the reflog of upstream has a certain
number of entries
which would indicate the push was not recorded (i.e. not performed?)

I think we should keep it similar to this one.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-16 18:49     ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
                         ` (2 preceding siblings ...)
  2014-12-16 19:46       ` Junio C Hamano
@ 2014-12-16 20:30       ` Junio C Hamano
  2014-12-16 20:36         ` Stefan Beller
  3 siblings, 1 reply; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 20:30 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, mhagger, jrnieder, ronniesahlberg

Stefan Beller <sbeller@google.com> writes:

>     > What's the value of this test?  Isn't it a non-fast-forward check
>     > you already tested in the previous one?
>     
>     I messed up there. Originally I wanted to test the 2 different
>     stages of rejection. A non-fast-forward check is done locally and
>     we don't even try pushing. But I also want to test if we locally
>     thing all is good, but the server refuses a ref to update.

It is tricky to arrange a test to fail a fast-forward check on the
receiver side, because the local test is done by reading from the
other side, not relying on your remote tracking branches.  The usual
flow is:

    pusher says to the receiver "I want to push"

    receiver says to the pusher "Here are the tips of my refs"

    pusher thinks: "Ah, I was about to update their master branch
		with this commit, but what they have is (1) not
		known to me so by definition I cannot fast-forward,
		or (2) known to me and I can definitely tell I am
		not fast-forwarding it, so I'd locally fail this
		push".

You need to invent a way to successfully race with an ongoing push.
After receiver gives the tips of its refs (all of which are
ancestors of what is going to be pushed) but before the pusher
finishes the "thinking" above, you would somehow update the refs at
the receiver so that the push will not fast-forward.  

Such a raced flow would look like:

    pusher says to the receiver "I want to push"

    receiver says to the pusher "Here are the tips of my refs"

    pusher thinks: "OK, everything I'll send will fast-forward"
    pusher thinks: "Let's start generating a packfile"

    you intervene and update receiver's 'master' at this point.

    pusher send a pack and tells the receiver "I want to update your
		master from OLD to NEW".

    receiver thinks: "Huh, that OLD is not where my 'master' is"

    recevier says to the pusher "No fast-forward".

But I do not think it is practical to run such a test.

Rejecting on the receiver's end using pre-receive or update hook
should be testable and should be tested.

Thanks.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-16 19:29       ` Eric Sunshine
@ 2014-12-16 20:30         ` Eric Sunshine
  0 siblings, 0 replies; 56+ messages in thread
From: Eric Sunshine @ 2014-12-16 20:30 UTC (permalink / raw)
  To: Stefan Beller
  Cc: Junio C Hamano, Git List, Michael Haggerty, Jonathan Nieder,
	ronniesahlberg, Ronnie Sahlberg

On Tue, Dec 16, 2014 at 2:29 PM, Eric Sunshine <sunshine@sunshineco.com> wrote:
> On Tue, Dec 16, 2014 at 1:49 PM, Stefan Beller <sbeller@google.com> wrote:
>> ---
>> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
>> index e76e5d5..0803fd2 100644
>> --- a/builtin/receive-pack.c
>> +++ b/builtin/receive-pack.c
>> @@ -1059,6 +1059,16 @@ static void execute_commands(struct command *commands,
>>                 return;
>>         }
>>
>> +       if (use_atomic) {
>> +               transaction = ref_transaction_begin(&err);
>> +               if (!transaction) {
>> +                       error("%s", err.buf);
>> +                       strbuf_release(&err);
>> +                       for (cmd = commands; cmd; cmd = cmd->next)
>> +                               cmd->error_string = "transaction error";
>> +                       return;
>> +               }
>> +       }
>
> Not seen in this diff, but just below this point, the pre-receive hook
> is invoked. If it "declines", then execute_commands() returns without
> releasing the transaction which was begun here. Is that correct
> behavior?
>
> For robustness, it might also be sane to release the 'err' strbuf at
> this early return (though the current code does not strictly leak it).

To clarify: By "this early return", I mean the early return taken when
pre-receive hook declines.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCH] receive-pack: refuse all commands if one fails in atomic mode
  2014-12-16 19:14       ` [PATCH] receive-pack: refuse all commands if one fails in atomic mode Stefan Beller
@ 2014-12-16 20:32         ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 20:32 UTC (permalink / raw)
  To: Stefan Beller; +Cc: ronniesahlberg, mhagger, jrnieder, git

Stefan Beller <sbeller@google.com> writes:

> This patch will be incorporated into the right places in v3 of the series.
>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
>
> Notes:
>     I don't want to resend the patch series today to accumulate comments,
>     but this makes the last test pass, which is the whole point of the series.
>     
>     I'll put it into the right places in a reroll.
>
>  builtin/receive-pack.c | 13 ++++++++++++-
>  t/t5543-atomic-push.sh |  2 +-
>  2 files changed, 13 insertions(+), 2 deletions(-)
>
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index 0803fd2..3477f7c 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -1122,7 +1122,18 @@ static void execute_commands(struct command *commands,
>  	}
>  
>  	if (use_atomic) {
> -		if (ref_transaction_commit(transaction, &err)) {
> +		/* update(...) may abort early (i.e. because the hook refused to
> +		 * update that ref), which then doesn't even record a transaction
> +		 * regarding that ref. Make sure all commands are without error
> +		 * and then commit atomically. */

	/*
         * The first line of our multi-line comment
         * has only opening slash-asterisk and nothing else.
         * The last line has asterisk-slash and nothing else.
         */

> +		for (cmd = commands; cmd; cmd = cmd->next)
> +			if (cmd->error_string)
> +				break;
> +		if (cmd) {
> +			for (cmd = commands; cmd; cmd = cmd->next)
> +				if (!cmd->error_string)
> +					cmd->error_string = "atomic push failure";
> +		} else if (ref_transaction_commit(transaction, &err)) {
>  			rp_error("%s", err.buf);
>  			for (cmd = commands; cmd; cmd = cmd->next)
>  				cmd->error_string = err.buf;

Makes sense.

> diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh
> index 6354fc0..f0e54d9 100755
> --- a/t/t5543-atomic-push.sh
> +++ b/t/t5543-atomic-push.sh
> @@ -142,7 +142,7 @@ test_expect_success 'atomic push fails if one tag fails remotely' '
>  	test_ref_upstream second $(git -C workbench rev-parse --verify HEAD@{1})
>  '
>  
> -test_expect_failure 'atomic push obeys update hook preventing a branch to be pushed' '
> +test_expect_success 'atomic push obeys update hook preventing a branch to be pushed' '
>  	mk_repo_pair &&
>  	(
>  		cd workbench &&

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-16 20:30       ` Junio C Hamano
@ 2014-12-16 20:36         ` Stefan Beller
  0 siblings, 0 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 20:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Michael Haggerty, Jonathan Nieder, ronnie sahlberg

On Tue, Dec 16, 2014 at 12:30 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Stefan Beller <sbeller@google.com> writes:
>
>>     > What's the value of this test?  Isn't it a non-fast-forward check
>>     > you already tested in the previous one?
>>
>>     I messed up there. Originally I wanted to test the 2 different
>>     stages of rejection. A non-fast-forward check is done locally and
>>     we don't even try pushing. But I also want to test if we locally
>>     thing all is good, but the server refuses a ref to update.
>
> It is tricky to arrange a test to fail a fast-forward check on the
> receiver side, because the local test is done by reading from the
> other side, not relying on your remote tracking branches.  The usual
> flow is:
>
>     pusher says to the receiver "I want to push"
>
>     receiver says to the pusher "Here are the tips of my refs"
>
>     pusher thinks: "Ah, I was about to update their master branch
>                 with this commit, but what they have is (1) not
>                 known to me so by definition I cannot fast-forward,
>                 or (2) known to me and I can definitely tell I am
>                 not fast-forwarding it, so I'd locally fail this
>                 push".
>
> You need to invent a way to successfully race with an ongoing push.
> After receiver gives the tips of its refs (all of which are
> ancestors of what is going to be pushed) but before the pusher
> finishes the "thinking" above, you would somehow update the refs at
> the receiver so that the push will not fast-forward.
>
> Such a raced flow would look like:
>
>     pusher says to the receiver "I want to push"
>
>     receiver says to the pusher "Here are the tips of my refs"
>
>     pusher thinks: "OK, everything I'll send will fast-forward"
>     pusher thinks: "Let's start generating a packfile"
>
>     you intervene and update receiver's 'master' at this point.
>
>     pusher send a pack and tells the receiver "I want to update your
>                 master from OLD to NEW".
>
>     receiver thinks: "Huh, that OLD is not where my 'master' is"
>
>     recevier says to the pusher "No fast-forward".
>
> But I do not think it is practical to run such a test.
>
> Rejecting on the receiver's end using pre-receive or update hook
> should be testable and should be tested.
>
> Thanks.
>

Yes, that was my understanding as well. I just messed up mentally.

Now that the update hook test is in place, we do have tests from the local
and the remote side rejecting, so it should be fine now.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 5/6] push.c: add an --atomic-push argument
  2014-12-16 19:33       ` Eric Sunshine
@ 2014-12-16 20:43         ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 20:43 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: Stefan Beller, Git List, Michael Haggerty, Jonathan Nieder,
	ronniesahlberg, Ronnie Sahlberg

Eric Sunshine <sunshine@sunshineco.com> writes:

>> +--atomic::
>> +       Use the an atomic transaction on the server side if available.
>
> s/the an/an/
> ...
>> +               OPT_BIT(0, "atomic", &flags, N_("use a single atomic transaction at the serverside, if available"),
>
> "single atomic" sounds awfully redundant.
>
> "serverside" is odd. Perhaps "server side" or merely "remote" or "remote side".
> ...
>> diff --git a/transport.h b/transport.h
>> index 3e0091e..25fa1da 100644
>> --- a/transport.h
>> +++ b/transport.h
>> @@ -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_ATOMIC_PUSH 4096
>
> For consistency with existing names, should this be TRANSPORT_PUSH_ATOMIC?

As always, thanks for a careful reading.  I missed everything you
pointed out (I agree with you).

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-16 19:57         ` Stefan Beller
@ 2014-12-16 20:46           ` Junio C Hamano
  2014-12-16 20:51             ` Stefan Beller
  0 siblings, 1 reply; 56+ messages in thread
From: Junio C Hamano @ 2014-12-16 20:46 UTC (permalink / raw)
  To: Stefan Beller; +Cc: git, Michael Haggerty, Jonathan Nieder, ronnie sahlberg

Stefan Beller <sbeller@google.com> writes:

> On Tue, Dec 16, 2014 at 11:46 AM, Junio C Hamano <gitster@pobox.com> wrote:
>
>>
>> Seeing that all callers of test_ref_upstream computes $2 as
>>
>>         git -C workbench rev-parse --verify <something>
>>
>
> Only in the first tests, where this should be the case after push.
> In the failure tests, we go with HEAD@{N} which needs to be computed
> inside the workbench repo.

Aren't we saying the same thing?  The suggested alternative is to do
the "git -C workbench rev-parse" bit inside the helper.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-16 20:46           ` Junio C Hamano
@ 2014-12-16 20:51             ` Stefan Beller
  0 siblings, 0 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-16 20:51 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Michael Haggerty, Jonathan Nieder, ronnie sahlberg

On Tue, Dec 16, 2014 at 12:46 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Stefan Beller <sbeller@google.com> writes:
>
>> On Tue, Dec 16, 2014 at 11:46 AM, Junio C Hamano <gitster@pobox.com> wrote:
>>
>>>
>>> Seeing that all callers of test_ref_upstream computes $2 as
>>>
>>>         git -C workbench rev-parse --verify <something>
>>>
>>
>> Only in the first tests, where this should be the case after push.
>> In the failure tests, we go with HEAD@{N} which needs to be computed
>> inside the workbench repo.
>
> Aren't we saying the same thing?  The suggested alternative is to do
> the "git -C workbench rev-parse" bit inside the helper.

Now I am saying the same. (I finally understood what you were saying)

Will incorporate in a reroll.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* [PATCHv3 0/6] atomic pushes
  2014-12-15 22:33 ` [PATCH 0/5] Add a flag to push atomically Junio C Hamano
  2014-12-16 18:49   ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
@ 2014-12-17 18:32   ` Stefan Beller
  2014-12-17 18:32     ` [PATCHv3 1/6] receive-pack.c: add protocol support to negotiate atomic Stefan Beller
                       ` (5 more replies)
  1 sibling, 6 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-17 18:32 UTC (permalink / raw)
  To: ronniesahlberg, mhagger, jrnieder, gitster, sunshine; +Cc: git, Stefan Beller

This patch series adds a flag to git push to update the remote refs atomically.

This series applies on top of origin/mh/reflog-expire
It can also be found at github[2].

Changes v3->v3 are annotated in each patch.

[2] https://github.com/stefanbeller/git/tree/atomic-push-v3

Ronnie Sahlberg (4):
  receive-pack.c: add protocol support to negotiate atomic-push
  send-pack.c: add --atomic command line argument
  receive-pack.c: use a single transaction when atomic-push is
    negotiated
  push.c: add an --atomic argument

Stefan Beller (2):
  send-pack: Rename ref_update_to_be_sent to check_to_send_update
  t5543-atomic-push.sh: add basic tests for atomic pushes

 Documentation/git-push.txt                        |   7 +-
 Documentation/git-send-pack.txt                   |   7 +-
 Documentation/technical/protocol-capabilities.txt |  13 +-
 builtin/push.c                                    |   2 +
 builtin/receive-pack.c                            |  89 +++++++++--
 builtin/send-pack.c                               |   6 +-
 remote.h                                          |   3 +-
 send-pack.c                                       |  66 +++++++-
 send-pack.h                                       |   3 +-
 t/t5543-atomic-push.sh                            | 178 ++++++++++++++++++++++
 transport.c                                       |   5 +
 transport.h                                       |   1 +
 12 files changed, 349 insertions(+), 31 deletions(-)
 create mode 100755 t/t5543-atomic-push.sh

-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply	[flat|nested] 56+ messages in thread

* [PATCHv3 1/6] receive-pack.c: add protocol support to negotiate atomic
  2014-12-17 18:32   ` [PATCHv3 0/6] atomic pushes Stefan Beller
@ 2014-12-17 18:32     ` Stefan Beller
  2014-12-19  1:05       ` Eric Sunshine
  2014-12-17 18:32     ` [PATCHv3 2/6] send-pack: Rename ref_update_to_be_sent to check_to_send_update Stefan Beller
                       ` (4 subsequent siblings)
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-17 18:32 UTC (permalink / raw)
  To: ronniesahlberg, mhagger, jrnieder, gitster, sunshine
  Cc: git, Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

This adds support to the protocol between send-pack and receive-pack to
* allow receive-pack to inform the client that it has atomic push capability
* allow send-pack to request atomic push back.

There is currently no setting in send-pack to actually request that atomic
pushes are to be used yet. This only adds protocol capability not ability
for the user to activate it.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Changes v1 -> v2:
    	* Name it atomic instead of atomic-push. The name changes affects
    	  the flags send over the wire as well as the flags in
    	  struct send_pack_args
    	* Add check, which was part of the later patch here:
    	if (args->atomic && !atomic_supported) {
    		fprintf(stderr, "Server does not support atomic push.");
    		return -1;
    	}
    
    v2 -> v3:
    More concise error reporting as suggested by Junio
    -		fprintf(stderr, "Server does not support atomic push.");
    -		return -1;
    +		return error("server does not support atomic push.");

 Documentation/technical/protocol-capabilities.txt | 13 +++++++++++--
 builtin/receive-pack.c                            |  6 +++++-
 send-pack.c                                       | 10 ++++++++++
 send-pack.h                                       |  3 ++-
 4 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index 6d5424c..68ec23d 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -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 single atomic transaction. Either all refs are
+updated or none.
+
 allow-tip-sha1-in-want
 ----------------------
 
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 32fc540..e76e5d5 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -40,6 +40,7 @@ static int transfer_unpack_limit = -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;
@@ -171,7 +172,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
 		struct strbuf cap = STRBUF_INIT;
 
 		strbuf_addstr(&cap,
-			      "report-status delete-refs side-band-64k quiet");
+			      "report-status delete-refs side-band-64k quiet "
+			      "atomic");
 		if (prefer_ofs_delta)
 			strbuf_addstr(&cap, " ofs-delta");
 		if (push_cert_nonce)
@@ -1179,6 +1181,8 @@ 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 (parse_feature_request(feature_list, "atomic"))
+				use_atomic = 1;
 		}
 
 		if (!strcmp(line, "push-cert")) {
diff --git a/send-pack.c b/send-pack.c
index 949cb61..e2bdd89 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -294,6 +294,8 @@ int send_pack(struct send_pack_args *args,
 	int use_sideband = 0;
 	int quiet_supported = 0;
 	int agent_supported = 0;
+	int use_atomic;
+	int atomic_supported = 0;
 	unsigned cmds_sent = 0;
 	int ret;
 	struct async demux;
@@ -314,6 +316,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;
 
@@ -328,6 +332,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) {
+		return error("server does not support atomic push.");
+	}
+	use_atomic = atomic_supported && args->atomic;
 
 	if (status_report)
 		strbuf_addstr(&cap_buf, " report-status");
@@ -335,6 +343,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());
 
diff --git a/send-pack.h b/send-pack.h
index 5635457..b664648 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -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,
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv3 2/6] send-pack: Rename ref_update_to_be_sent to check_to_send_update
  2014-12-17 18:32   ` [PATCHv3 0/6] atomic pushes Stefan Beller
  2014-12-17 18:32     ` [PATCHv3 1/6] receive-pack.c: add protocol support to negotiate atomic Stefan Beller
@ 2014-12-17 18:32     ` Stefan Beller
  2014-12-17 22:53       ` Junio C Hamano
  2014-12-17 18:32     ` [PATCHv3 3/6] send-pack.c: add --atomic command line argument Stefan Beller
                       ` (3 subsequent siblings)
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-17 18:32 UTC (permalink / raw)
  To: ronniesahlberg, mhagger, jrnieder, gitster, sunshine; +Cc: git, Stefan Beller

This renames ref_update_to_be_sent to check_to_send_update and inverts
the meaning of the return value. Having the return value inverted we
can have different values for the error codes. This is useful in a
later patch when we want to know if we hit the CHECK_REF_STATUS_REJECTED
case.

Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    This was introduced with the [PATCHv2] series.
    Changes v2 -> v3:
    
    * Rename to check_to_send_update
    * Negative error values.
    * errors values are #define'd and not raw numbers.

 send-pack.c | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/send-pack.c b/send-pack.c
index e2bdd89..77e4201 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -190,10 +190,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) {
@@ -203,10 +206,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;
 	}
 }
 
@@ -250,7 +254,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",
@@ -369,7 +373,7 @@ 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))
+		if (check_to_send_update(ref, args) < 0)
 			continue;
 
 		if (!ref->deletion)
@@ -390,7 +394,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);
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv3 3/6] send-pack.c: add --atomic command line argument
  2014-12-17 18:32   ` [PATCHv3 0/6] atomic pushes Stefan Beller
  2014-12-17 18:32     ` [PATCHv3 1/6] receive-pack.c: add protocol support to negotiate atomic Stefan Beller
  2014-12-17 18:32     ` [PATCHv3 2/6] send-pack: Rename ref_update_to_be_sent to check_to_send_update Stefan Beller
@ 2014-12-17 18:32     ` Stefan Beller
  2014-12-17 23:14       ` Junio C Hamano
  2014-12-19  1:22       ` Eric Sunshine
  2014-12-17 18:32     ` [PATCHv3 4/6] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
                       ` (2 subsequent siblings)
  5 siblings, 2 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-17 18:32 UTC (permalink / raw)
  To: ronniesahlberg, mhagger, jrnieder, gitster, sunshine
  Cc: git, Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

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>
---

Notes:
    Notes:
        Changes v1 -> v2:
         * Now we only need a very small change in the existing code and have
           a new static function, which cares about error reporting.
              Junio wrote:
              > Hmph.  Is "atomic push" so special that it deserves a separate
              > parameter?  When we come up with yet another mode of failure, would
              > we add another parameter to the callers to this function?
         * error messages are worded differently (lower case!),
         * use of error function instead of fprintf
    
         * undashed the printed error message ("atomic push failed");
    
    Changes v2 -> v3:
    > We avoid assignment inside a conditional.
    
    Ok I switched to using a switch statement.

 Documentation/git-send-pack.txt |  7 ++++++-
 builtin/send-pack.c             |  6 +++++-
 remote.h                        |  3 ++-
 send-pack.c                     | 40 ++++++++++++++++++++++++++++++++++++++--
 transport.c                     |  4 ++++
 5 files changed, 55 insertions(+), 5 deletions(-)

diff --git a/Documentation/git-send-pack.txt b/Documentation/git-send-pack.txt
index 2a0de42..45c7725 100644
--- a/Documentation/git-send-pack.txt
+++ b/Documentation/git-send-pack.txt
@@ -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
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index b564a77..b961e5a 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -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;
diff --git a/remote.h b/remote.h
index 8b62efd..f346524 100644
--- a/remote.h
+++ b/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 */
diff --git a/send-pack.c b/send-pack.c
index 77e4201..a696d5c 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -282,6 +282,30 @@ 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:
+			; /* do nothing */
+		}
+	}
+	error("atomic push failed for ref %s. "
+	      "status: %d\n", failing_ref->name, failing_ref->status);
+	return -1;
+}
+
 int send_pack(struct send_pack_args *args,
 	      int fd[], struct child_process *conn,
 	      struct ref *remote_refs,
@@ -373,9 +397,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;
 
diff --git a/transport.c b/transport.c
index 70d38e4..c67feee 100644
--- a/transport.c
+++ b/transport.c
@@ -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;
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv3 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-17 18:32   ` [PATCHv3 0/6] atomic pushes Stefan Beller
                       ` (2 preceding siblings ...)
  2014-12-17 18:32     ` [PATCHv3 3/6] send-pack.c: add --atomic command line argument Stefan Beller
@ 2014-12-17 18:32     ` Stefan Beller
  2014-12-17 23:26       ` Junio C Hamano
  2014-12-17 18:32     ` [PATCHv3 5/6] push.c: add an --atomic argument Stefan Beller
  2014-12-17 18:32     ` [PATCHv3 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-17 18:32 UTC (permalink / raw)
  To: ronniesahlberg, mhagger, jrnieder, gitster, sunshine
  Cc: git, Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

Update receive-pack to use an atomic transaction iff the client negotiated
that it wanted atomic-push. This leaves the default behavior to be the old
non-atomic one ref at a time update. This is to cause as little disruption
as possible to existing clients. It is unknown if there are client scripts
that depend on the old non-atomic behavior so we make it opt-in for now.

If it turns out over time that there are no client scripts that depend on the
old behavior we can change git to default to use atomic pushes and instead
offer an opt-out argument for people that do not want atomic pushes.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Changes v1 -> v2:
    	* update(...) assumes to be always in a transaction
    	* Caring about when to begin/commit transactions is put
    	  into execute_commands
    v2->v3:
    	* meditated about the error flow. Now we always construct a local
    	  strbuf err if required. Then the flow is easier to follow and
    	  destruction of it is performed nearby.
    	* early return in execute_commands if transaction_begin fails.

 builtin/receive-pack.c | 83 +++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 68 insertions(+), 15 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e76e5d5..c942a3b 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -67,6 +67,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)
 {
@@ -823,6 +824,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)) {
@@ -832,35 +834,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 */
 	}
 }
@@ -1052,6 +1055,7 @@ static void execute_commands(struct command *commands,
 	struct command *cmd;
 	unsigned char sha1[20];
 	struct iterate_data data;
+	struct strbuf err = STRBUF_INIT;
 
 	if (unpacker_error) {
 		for (cmd = commands; cmd; cmd = cmd->next)
@@ -1059,6 +1063,16 @@ static void execute_commands(struct command *commands,
 		return;
 	}
 
+	if (use_atomic) {
+		transaction = ref_transaction_begin(&err);
+		if (!transaction) {
+			error("%s", err.buf);
+			strbuf_release(&err);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = "transaction error";
+			return;
+		}
+	}
 	data.cmds = commands;
 	data.si = si;
 	if (check_everything_connected(iterate_receive_command_list, 0, &data))
@@ -1086,8 +1100,25 @@ static void execute_commands(struct command *commands,
 
 		if (cmd->skip_update)
 			continue;
-
+		if (!use_atomic) {
+			transaction = ref_transaction_begin(&err);
+			if (!transaction) {
+				rp_error("%s", err.buf);
+				strbuf_release(&err);
+				cmd->error_string = "failed to start transaction";
+				return;
+			}
+		}
 		cmd->error_string = update(cmd, si);
+		if (!use_atomic)
+			if (ref_transaction_commit(transaction, &err)) {
+				ref_transaction_free(transaction);
+				rp_error("%s", err.buf);
+				strbuf_release(&err);
+				cmd->error_string = "failed to update ref";
+				return;
+			}
+
 		if (shallow_update && !cmd->error_string &&
 		    si->shallow_ref[cmd->index]) {
 			error("BUG: connectivity check has not been run on ref %s",
@@ -1096,10 +1127,32 @@ static void execute_commands(struct command *commands,
 		}
 	}
 
+	if (use_atomic) {
+		/*
+		 * update(...) may abort early (i.e. because the hook refused to
+		 * update that ref) which then doesn't even record a transaction
+		 * regarding that ref. Make sure all commands are without error
+		 * and then commit atomically.
+		 */
+		for (cmd = commands; cmd; cmd = cmd->next)
+			if (cmd->error_string)
+				break;
+		if (cmd) {
+			for (cmd = commands; cmd; cmd = cmd->next)
+				if (!cmd->error_string)
+					cmd->error_string = "atomic push failure";
+		} else if (ref_transaction_commit(transaction, &err)) {
+			rp_error("%s", err.buf);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = err.buf;
+		}
+		ref_transaction_free(transaction);
+	}
 	if (shallow_update && !checked_connectivity)
 		error("BUG: run 'git fsck' for safety.\n"
 		      "If there are errors, try to remove "
 		      "the reported refs above");
+	strbuf_release(&err);
 }
 
 static struct command **queue_command(struct command **tail,
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv3 5/6] push.c: add an --atomic argument
  2014-12-17 18:32   ` [PATCHv3 0/6] atomic pushes Stefan Beller
                       ` (3 preceding siblings ...)
  2014-12-17 18:32     ` [PATCHv3 4/6] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
@ 2014-12-17 18:32     ` Stefan Beller
  2014-12-19  1:29       ` Eric Sunshine
  2014-12-17 18:32     ` [PATCHv3 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
  5 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-17 18:32 UTC (permalink / raw)
  To: ronniesahlberg, mhagger, jrnieder, gitster, sunshine
  Cc: git, Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

Add a command line argument to the git push command to request atomic
pushes.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Changes v1 -> v2
    	It's --atomic now! (dropping the -push)
    
    v2->v3:
    	* s/atomic-push/atomic/
    	* s/the an/an/
    	* no serverside, but just remote instead
    	* TRANSPORT_PUSH_ATOMIC instead of TRANSPORT_ATOMIC_PUSH

 Documentation/git-push.txt | 7 ++++++-
 builtin/push.c             | 2 ++
 transport.c                | 1 +
 transport.h                | 1 +
 4 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 21b3f29..da63bdf 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -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.
 
+--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
diff --git a/builtin/push.c b/builtin/push.c
index a076b19..5731a0d 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -518,6 +518,8 @@ 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_BIT(0, "atomic", &flags, N_("use an atomic transaction remote"),
+			TRANSPORT_PUSH_ATOMIC),
 		OPT_END()
 	};
 
diff --git a/transport.c b/transport.c
index c67feee..1373152 100644
--- a/transport.c
+++ b/transport.c
@@ -830,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,
diff --git a/transport.h b/transport.h
index 3e0091e..18d2cf8 100644
--- a/transport.h
+++ b/transport.h
@@ -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)
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* [PATCHv3 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes
  2014-12-17 18:32   ` [PATCHv3 0/6] atomic pushes Stefan Beller
                       ` (4 preceding siblings ...)
  2014-12-17 18:32     ` [PATCHv3 5/6] push.c: add an --atomic argument Stefan Beller
@ 2014-12-17 18:32     ` Stefan Beller
  5 siblings, 0 replies; 56+ messages in thread
From: Stefan Beller @ 2014-12-17 18:32 UTC (permalink / raw)
  To: ronniesahlberg, mhagger, jrnieder, gitster, sunshine; +Cc: git, Stefan Beller

This adds tests for the atomic push option.
The first four tests check if the atomic option works in
good conditions and the last three patches check if the atomic
option prevents any change to be pushed if just one ref cannot
be updated.

Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Changes in v3:
    * test_refs for checking  

    Changes v1 -> v2:
    > Please drop unused comments; they are distracting.
    
    ok
    
    > It is not wrong per-se, but haven't you already tested in
    > combination with --mirror in the previous test?
    
    I fixed the previous tests, so that there is no --mirror
    and --atomic together. There is still a first --mirror push
    for setup and a second with --atomic <branchnames> though
    
    > check_branches upstream master HEAD@{2} second HEAD~
    
    A similar function test_ref_upstream is introduced.
    
    > What's the value of this test?  Isn't it a non-fast-forward check
    > you already tested in the previous one?
    
    I messed up there. Originally I wanted to test the 2 different
    stages of rejection. A non-fast-forward check is done locally and
    we don't even try pushing. But I also want to test if we locally
    thing all is good, but the server refuses a ref to update.
    This is now done with the last test named 'atomic push obeys
    update hook preventing a branch to be pushed'. And that still fails.
    
    I'll investigate that, while still sending out the series for another
    review though.
    
    * Redone the test helper, there is test_ref_upstream now.
      This tests explicitely for SHA1 values of the ref.
      (It's needed in the last test for example. The git push fails,
      but still modifies the ref :/ )
    * checked all && chains and repaired them
    * sometimes make use of git -C <workdir>
    
    Notes v1:
    Originally Ronnie had a similar patch prepared. But as I added
    more tests and cleaned up the existing tests (e.g. use test_commit
    instead of "echo one >file && gitadd file && git commit -a -m 'one'",
    removal of dead code), the file has changed so much that I'd rather
    take ownership.

 t/t5543-atomic-push.sh | 178 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 178 insertions(+)
 create mode 100755 t/t5543-atomic-push.sh

diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh
new file mode 100755
index 0000000..b81a542
--- /dev/null
+++ b/t/t5543-atomic-push.sh
@@ -0,0 +1,178 @@
+#!/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_done
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* Re: [PATCHv3 2/6] send-pack: Rename ref_update_to_be_sent to check_to_send_update
  2014-12-17 18:32     ` [PATCHv3 2/6] send-pack: Rename ref_update_to_be_sent to check_to_send_update Stefan Beller
@ 2014-12-17 22:53       ` Junio C Hamano
  0 siblings, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-17 22:53 UTC (permalink / raw)
  To: Stefan Beller; +Cc: ronniesahlberg, mhagger, jrnieder, sunshine, git

Stefan Beller <sbeller@google.com> writes:

> This renames ref_update_to_be_sent to check_to_send_update and inverts
> the meaning of the return value. Having the return value inverted we
> can have different values for the error codes. This is useful in a
> later patch when we want to know if we hit the CHECK_REF_STATUS_REJECTED
> case.

Makes sense; I would have defined these constants negative, but that
can be cleaned up if somebody cares deeply enough later.

Will queue.  Thanks.

>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
>
> Notes:
>     This was introduced with the [PATCHv2] series.
>     Changes v2 -> v3:
>     
>     * Rename to check_to_send_update
>     * Negative error values.
>     * errors values are #define'd and not raw numbers.
>
>  send-pack.c | 18 +++++++++++-------
>  1 file changed, 11 insertions(+), 7 deletions(-)
>
> diff --git a/send-pack.c b/send-pack.c
> index e2bdd89..77e4201 100644
> --- a/send-pack.c
> +++ b/send-pack.c
> @@ -190,10 +190,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) {
> @@ -203,10 +206,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;
>  	}
>  }
>  
> @@ -250,7 +254,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",
> @@ -369,7 +373,7 @@ 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))
> +		if (check_to_send_update(ref, args) < 0)
>  			continue;
>  
>  		if (!ref->deletion)
> @@ -390,7 +394,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);

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv3 3/6] send-pack.c: add --atomic command line argument
  2014-12-17 18:32     ` [PATCHv3 3/6] send-pack.c: add --atomic command line argument Stefan Beller
@ 2014-12-17 23:14       ` Junio C Hamano
  2014-12-19  1:22       ` Eric Sunshine
  1 sibling, 0 replies; 56+ messages in thread
From: Junio C Hamano @ 2014-12-17 23:14 UTC (permalink / raw)
  To: Stefan Beller
  Cc: ronniesahlberg, mhagger, jrnieder, sunshine, git, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

> From: Ronnie Sahlberg <sahlberg@google.com>
>
> 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.

Looks sensible.  Thanks.

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv3 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-17 18:32     ` [PATCHv3 4/6] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
@ 2014-12-17 23:26       ` Junio C Hamano
  2014-12-17 23:58         ` Stefan Beller
  0 siblings, 1 reply; 56+ messages in thread
From: Junio C Hamano @ 2014-12-17 23:26 UTC (permalink / raw)
  To: Stefan Beller
  Cc: ronniesahlberg, mhagger, jrnieder, sunshine, git, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

> @@ -1086,8 +1100,25 @@ static void execute_commands(struct command *commands,
>  
>  		if (cmd->skip_update)
>  			continue;
> -
> +		if (!use_atomic) {
> +			transaction = ref_transaction_begin(&err);
> +			if (!transaction) {
> +				rp_error("%s", err.buf);
> +				strbuf_release(&err);
> +				cmd->error_string = "failed to start transaction";
> +				return;
> +			}
> +		}
>  		cmd->error_string = update(cmd, si);
> +		if (!use_atomic)
> +			if (ref_transaction_commit(transaction, &err)) {
> +				ref_transaction_free(transaction);
> +				rp_error("%s", err.buf);
> +				strbuf_release(&err);
> +				cmd->error_string = "failed to update ref";
> +				return;
> +			}

Hmm, should the code even attempt to commit if update() returned a
non NULL, signaling a failure?

Or would we want to do this instead?

	if (cmd->error_string)
        	goto transaction_abort;
	else if (!use_atomic) {
		if (ref_transaction_commit(...)) {
			...
                        cmd->error_string = "...";
                        return;
        	}
	}

and then ...

>  		if (shallow_update && !cmd->error_string &&
>  		    si->shallow_ref[cmd->index]) {
>  			error("BUG: connectivity check has not been run on ref %s",
> @@ -1096,10 +1127,32 @@ static void execute_commands(struct command *commands,
>  		}
>  	}
>  
> +	if (use_atomic) {
> +		/*
> +		 * update(...) may abort early (i.e. because the hook refused to
> +		 * update that ref) which then doesn't even record a transaction
> +		 * regarding that ref. Make sure all commands are without error
> +		 * and then commit atomically.
> +		 */
> +		for (cmd = commands; cmd; cmd = cmd->next)
> +			if (cmd->error_string)
> +				break;
> +		if (cmd) {
> +			for (cmd = commands; cmd; cmd = cmd->next)
> +				if (!cmd->error_string)
> +					cmd->error_string = "atomic push failure";
> +		} else if (ref_transaction_commit(transaction, &err)) {
> +			rp_error("%s", err.buf);
> +			for (cmd = commands; cmd; cmd = cmd->next)
> +				cmd->error_string = err.buf;
> +		}

... have the label to jump to here:

	transaction_abort:

> +		ref_transaction_free(transaction);

I was confused by the fact that you did not have any call to
transaction-abort, until I realized that there is no such API
function and ref_transaction_free() serves that "don't commit,
roll it back" purpose.

> +	}
>  	if (shallow_update && !checked_connectivity)
>  		error("BUG: run 'git fsck' for safety.\n"
>  		      "If there are errors, try to remove "
>  		      "the reported refs above");
> +	strbuf_release(&err);
>  }
>  
>  static struct command **queue_command(struct command **tail,

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv3 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-17 23:26       ` Junio C Hamano
@ 2014-12-17 23:58         ` Stefan Beller
  2014-12-18 17:02           ` Junio C Hamano
  0 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-17 23:58 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: ronnie sahlberg, Michael Haggerty, Jonathan Nieder,
	Eric Sunshine, git, Ronnie Sahlberg

On Wed, Dec 17, 2014 at 3:26 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Stefan Beller <sbeller@google.com> writes:
>
>> @@ -1086,8 +1100,25 @@ static void execute_commands(struct command *commands,
>>
>>               if (cmd->skip_update)
>>                       continue;
>> -
>> +             if (!use_atomic) {
>> +                     transaction = ref_transaction_begin(&err);
>> +                     if (!transaction) {
>> +                             rp_error("%s", err.buf);
>> +                             strbuf_release(&err);
>> +                             cmd->error_string = "failed to start transaction";
>> +                             return;
>> +                     }
>> +             }
>>               cmd->error_string = update(cmd, si);
>> +             if (!use_atomic)
>> +                     if (ref_transaction_commit(transaction, &err)) {
>> +                             ref_transaction_free(transaction);
>> +                             rp_error("%s", err.buf);
>> +                             strbuf_release(&err);
>> +                             cmd->error_string = "failed to update ref";
>> +                             return;
>> +                     }
>
> Hmm, should the code even attempt to commit if update() returned a
> non NULL, signaling a failure?
>
> Or would we want to do this instead?

This would change the current behavior. In the case of !atomic we want
to consider all commands and not stop early.

So maybe more
if (!cmd->error_string) {
        if (!use_atomic
            && ref_transaction_commit(...)) {
            ...
        }
} else {
        if (use_atomic)
             goto check_atomic_commit;
}

and the  check_atomic_commit label is replacing the loop to check:
-        for (cmd = commands; cmd; cmd = cmd->next)
-                if (cmd->error_string)
-                        break;
+ check_atomic_commit:

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv3 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-17 23:58         ` Stefan Beller
@ 2014-12-18 17:02           ` Junio C Hamano
  2014-12-18 17:45             ` [PATCHv4 " Stefan Beller
  0 siblings, 1 reply; 56+ messages in thread
From: Junio C Hamano @ 2014-12-18 17:02 UTC (permalink / raw)
  To: Stefan Beller
  Cc: ronnie sahlberg, Michael Haggerty, Jonathan Nieder,
	Eric Sunshine, git, Ronnie Sahlberg

Stefan Beller <sbeller@google.com> writes:

> This would change the current behavior. In the case of !atomic we want
> to consider all commands and not stop early.

Quite right.

> So maybe more
> if (!cmd->error_string) {
>         if (!use_atomic
>             && ref_transaction_commit(...)) {
>             ...
>         }
> } else {
>         if (use_atomic)
>              goto check_atomic_commit;
> }

Don't you need to rollback/abort/free in one-at-a-time mode and did
not commit because error_string is already set, though?

^ permalink raw reply	[flat|nested] 56+ messages in thread

* [PATCHv4 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-18 17:02           ` Junio C Hamano
@ 2014-12-18 17:45             ` Stefan Beller
  2014-12-18 22:26               ` Eric Sunshine
  0 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-18 17:45 UTC (permalink / raw)
  To: ronniesahlberg, mhagger, jrnieder, sunshine, git
  Cc: Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

Update receive-pack to use an atomic transaction iff the client negotiated
that it wanted atomic-push. This leaves the default behavior to be the old
non-atomic one ref at a time update. This is to cause as little disruption
as possible to existing clients. It is unknown if there are client scripts
that depend on the old non-atomic behavior so we make it opt-in for now.

If it turns out over time that there are no client scripts that depend on the
old behavior we can change git to default to use atomic pushes and instead
offer an opt-out argument for people that do not want atomic pushes.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---

v4 of the series only resends this patch as the others did not get any 
feedback to improve.
        
Notes:
    Changes v1 -> v2:
    	* update(...) assumes to be always in a transaction
    	* Caring about when to begin/commit transactions is put
    	  into execute_commands
    v2->v3:
    	* meditated about the error flow. Now we always construct a local
    	  strbuf err if required. Then the flow is easier to follow and
    	  destruction of it is performed nearby.
    	* early return in execute_commands if transaction_begin fails.
    
    v3->v4:

    	* revamp logic again. This should keep the non atomic behavior
    	  as is (in case of error say so, in non error case just free the
    	  transaction). In the atomic case we either do nothing (when no error),
    	  or abort with the goto.
    
    		if (!cmd->error_string) {
    			if (!use_atomic
    			    && ref_transaction_commit(transaction, &err)) {
    				ref_transaction_free(transaction);
    				rp_error("%s", err.buf);
    				strbuf_release(&err);
    				cmd->error_string = "failed to update ref";
    			}
    		} else if (use_atomic) {
    			goto atomic_failure;
    		} else {
    			ref_transaction_free(transaction);
    		}
    
    	 * Having the goto directly there when checking for cmd->error_string,
    	   we don't need to do it again, so the paragraph explaining the error
    	   checking is gone as well. (Previous patch had the following, this is
    	   put at the end of the function, where the goto jumps to and the comment
    	   has been dropped.
    +		/*
    +		 * update(...) may abort early (i.e. because the hook refused to
    +		 * update that ref) which then doesn't even record a transaction
    +		 * regarding that ref. Make sure all commands are without error
    +		 * and then commit atomically.
    +		 */
    +		for (cmd = commands; cmd; cmd = cmd->next)
    +			if (cmd->error_string)
    +				break;

 builtin/receive-pack.c | 83 +++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 69 insertions(+), 14 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e76e5d5..4952d91 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -67,6 +67,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)
 {
@@ -823,6 +824,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)) {
@@ -832,35 +834,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 */
 	}
 }
@@ -1052,6 +1055,7 @@ static void execute_commands(struct command *commands,
 	struct command *cmd;
 	unsigned char sha1[20];
 	struct iterate_data data;
+	struct strbuf err = STRBUF_INIT;
 
 	if (unpacker_error) {
 		for (cmd = commands; cmd; cmd = cmd->next)
@@ -1059,6 +1063,16 @@ static void execute_commands(struct command *commands,
 		return;
 	}
 
+	if (use_atomic) {
+		transaction = ref_transaction_begin(&err);
+		if (!transaction) {
+			error("%s", err.buf);
+			strbuf_release(&err);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = "transaction error";
+			return;
+		}
+	}
 	data.cmds = commands;
 	data.si = si;
 	if (check_everything_connected(iterate_receive_command_list, 0, &data))
@@ -1087,7 +1101,30 @@ static void execute_commands(struct command *commands,
 		if (cmd->skip_update)
 			continue;
 
+		if (!use_atomic) {
+			transaction = ref_transaction_begin(&err);
+			if (!transaction) {
+				rp_error("%s", err.buf);
+				strbuf_release(&err);
+				cmd->error_string = "failed to start transaction";
+				continue;
+			}
+		}
 		cmd->error_string = update(cmd, si);
+		if (!cmd->error_string) {
+			if (!use_atomic
+			    && ref_transaction_commit(transaction, &err)) {
+				ref_transaction_free(transaction);
+				rp_error("%s", err.buf);
+				strbuf_release(&err);
+				cmd->error_string = "failed to update ref";
+			}
+		} else if (use_atomic) {
+			goto atomic_failure;
+		} else {
+			ref_transaction_free(transaction);
+		}
+
 		if (shallow_update && !cmd->error_string &&
 		    si->shallow_ref[cmd->index]) {
 			error("BUG: connectivity check has not been run on ref %s",
@@ -1096,10 +1133,28 @@ static void execute_commands(struct command *commands,
 		}
 	}
 
+	if (use_atomic) {
+		if (ref_transaction_commit(transaction, &err)) {
+			rp_error("%s", err.buf);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = err.buf;
+		}
+		ref_transaction_free(transaction);
+	}
 	if (shallow_update && !checked_connectivity)
 		error("BUG: run 'git fsck' for safety.\n"
 		      "If there are errors, try to remove "
 		      "the reported refs above");
+	strbuf_release(&err);
+
+	return;
+
+atomic_failure:
+	for (cmd = commands; cmd; cmd = cmd->next)
+		if (!cmd->error_string)
+			cmd->error_string = "atomic push failure";
+
+	ref_transaction_free(transaction);
 }
 
 static struct command **queue_command(struct command **tail,
-- 
2.2.0.31.gad78000.dirty

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* Re: [PATCHv4 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-18 17:45             ` [PATCHv4 " Stefan Beller
@ 2014-12-18 22:26               ` Eric Sunshine
  2014-12-19  0:22                 ` [PATCHv5 " Stefan Beller
  0 siblings, 1 reply; 56+ messages in thread
From: Eric Sunshine @ 2014-12-18 22:26 UTC (permalink / raw)
  To: Stefan Beller
  Cc: ronnie sahlberg, Michael Haggerty, Jonathan Nieder, Git List,
	Ronnie Sahlberg

On Thu, Dec 18, 2014 at 12:45 PM, Stefan Beller <sbeller@google.com> wrote:
> From: Ronnie Sahlberg <sahlberg@google.com>
>
> Update receive-pack to use an atomic transaction iff the client negotiated
> that it wanted atomic-push. This leaves the default behavior to be the old
> non-atomic one ref at a time update. This is to cause as little disruption
> as possible to existing clients. It is unknown if there are client scripts
> that depend on the old non-atomic behavior so we make it opt-in for now.
>
> If it turns out over time that there are no client scripts that depend on the
> old behavior we can change git to default to use atomic pushes and instead
> offer an opt-out argument for people that do not want atomic pushes.
>
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index e76e5d5..4952d91 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -1059,6 +1063,16 @@ static void execute_commands(struct command *commands,
>                 return;
>         }
>
> +       if (use_atomic) {
> +               transaction = ref_transaction_begin(&err);

Repeating from my earlier review[1]: If the 'pre-receive' hook
"declines", then this transaction is left dangling (and its resources
leaked).

> +               if (!transaction) {
> +                       error("%s", err.buf);
> +                       strbuf_release(&err);
> +                       for (cmd = commands; cmd; cmd = cmd->next)
> +                               cmd->error_string = "transaction error";

The !use_atomic case (below), calls this error "failed to start
transaction", not merely "transaction error".

Furthermore, in the use_atomic case (also below), when a commit fails,
you assign err.buf to cmd->error_string rather than a generic
"transaction error" message. What differs between these cases which
makes the generic message preferable here over the more specific
err.buf message?

> +                       return;
> +               }
> +       }
>         data.cmds = commands;
>         data.si = si;
>         if (check_everything_connected(iterate_receive_command_list, 0, &data))
> @@ -1087,7 +1101,30 @@ static void execute_commands(struct command *commands,
>                 if (cmd->skip_update)
>                         continue;
>
> +               if (!use_atomic) {
> +                       transaction = ref_transaction_begin(&err);
> +                       if (!transaction) {
> +                               rp_error("%s", err.buf);
> +                               strbuf_release(&err);
> +                               cmd->error_string = "failed to start transaction";
> +                               continue;
> +                       }
> +               }
>                 cmd->error_string = update(cmd, si);
> +               if (!cmd->error_string) {
> +                       if (!use_atomic
> +                           && ref_transaction_commit(transaction, &err)) {
> +                               ref_transaction_free(transaction);

Repeating from my earlier review[1]: This is leaking 'transaction' for
each successful commit (and only freeing it upon commit error).

> +                               rp_error("%s", err.buf);
> +                               strbuf_release(&err);
> +                               cmd->error_string = "failed to update ref";
> +                       }
> +               } else if (use_atomic) {
> +                       goto atomic_failure;

See commentary below.

> +               } else {
> +                       ref_transaction_free(transaction);
> +               }
> +
>                 if (shallow_update && !cmd->error_string &&
>                     si->shallow_ref[cmd->index]) {
>                         error("BUG: connectivity check has not been run on ref %s",
> @@ -1096,10 +1133,28 @@ static void execute_commands(struct command *commands,
>                 }
>         }
>
> +       if (use_atomic) {
> +               if (ref_transaction_commit(transaction, &err)) {
> +                       rp_error("%s", err.buf);
> +                       for (cmd = commands; cmd; cmd = cmd->next)
> +                               cmd->error_string = err.buf;

At the end of this function, strbuf_release(&err) is invoked, which
leaves all these cmd->error_strings dangling.

> +               }
> +               ref_transaction_free(transaction);
> +       }
>         if (shallow_update && !checked_connectivity)
>                 error("BUG: run 'git fsck' for safety.\n"
>                       "If there are errors, try to remove "
>                       "the reported refs above");
> +       strbuf_release(&err);
> +
> +       return;
> +
> +atomic_failure:
> +       for (cmd = commands; cmd; cmd = cmd->next)
> +               if (!cmd->error_string)
> +                       cmd->error_string = "atomic push failure";
> +
> +       ref_transaction_free(transaction);

goto's can help simplify error-handling when multiple conditional
branches need to perform common cleanup, however, this label
corresponds to only a single goto statement. Placing the cleanup code
for that one case so far away from the point where the condition is
detected actually obfuscates the code slightly rather than clarifying
it. It would be a bit easier to understand the logic if this cleanup
(plus a 'return') was inlined directly at the location of the 'goto'.

By the way, you employ the idiom of iterating over 'commands' and
assigning error_string often enough, that it might be worthwhile to
wrap it in a function. In that case, inlining the above cleanup code
in place of the 'goto' would make it even easier to read since it
would be a mere two-liner.

>  }

[1]: http://article.gmane.org/gmane.comp.version-control.git/261455

^ permalink raw reply	[flat|nested] 56+ messages in thread

* [PATCHv5 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-18 22:26               ` Eric Sunshine
@ 2014-12-19  0:22                 ` Stefan Beller
  2014-12-19 10:14                   ` Eric Sunshine
  0 siblings, 1 reply; 56+ messages in thread
From: Stefan Beller @ 2014-12-19  0:22 UTC (permalink / raw)
  To: sunshine, ronniesahlberg, mhagger, jrnieder, git, gitster
  Cc: Ronnie Sahlberg, Stefan Beller

From: Ronnie Sahlberg <sahlberg@google.com>

Update receive-pack to use an atomic transaction iff the client negotiated
that it wanted atomic-push. This leaves the default behavior to be the old
non-atomic one ref at a time update. This is to cause as little disruption
as possible to existing clients. It is unknown if there are client scripts
that depend on the old non-atomic behavior so we make it opt-in for now.

If it turns out over time that there are no client scripts that depend on the
old behavior we can change git to default to use atomic pushes and instead
offer an opt-out argument for people that do not want atomic pushes.

Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
Signed-off-by: Stefan Beller <sbeller@google.com>
---

Notes:
    Changes v1 -> v2:
    	* update(...) assumes to be always in a transaction
    	* Caring about when to begin/commit transactions is put
    	  into execute_commands
    v2->v3:
    	* meditated about the error flow. Now we always construct a local
    	  strbuf err if required. Then the flow is easier to follow and
    	  destruction of it is performed nearby.
    	* early return in execute_commands if transaction_begin fails.
    
    v3->v4:
    	* revamp logic again. This should keep the non atomic behavior
    	  as is (in case of error say so, in non error case just free the
    	  transaction). In the atomic case we either do nothing (when no error),
    	  or abort with the goto.
    
    		if (!cmd->error_string) {
    			if (!use_atomic
    			    && ref_transaction_commit(transaction, &err)) {
    				ref_transaction_free(transaction);
    				rp_error("%s", err.buf);
    				strbuf_release(&err);
    				cmd->error_string = "failed to update ref";
    			}
    		} else if (use_atomic) {
    			goto atomic_failure;
    		} else {
    			ref_transaction_free(transaction);
    		}
    
    	 * Having the goto directly there when checking for cmd->error_string,
    	   we don't need to do it again, so the paragraph explaining the error
    	   checking is gone as well. (Previous patch had the following, this is
    	   put at the end of the function, where the goto jumps to and the comment
    	   has been dropped.
    +		/*
    +		 * update(...) may abort early (i.e. because the hook refused to
    +		 * update that ref) which then doesn't even record a transaction
    +		 * regarding that ref. Make sure all commands are without error
    +		 * and then commit atomically.
    +		 */
    +		for (cmd = commands; cmd; cmd = cmd->next)
    +			if (cmd->error_string)
    +				break;
    
    v4->v5:
    Eric wrote:
    > Repeating from my earlier review[1]: If the 'pre-receive' hook
    > "declines", then this transaction is left dangling (and its resources
    > leaked).
    
    You're right. The initialization of the transaction is now
    near the actual loop after the pre receive hook.
    
    > The !use_atomic case (below), calls this error "failed to start
    > transaction", not merely "transaction error".
    
    ok, now both are "transaction failed to start".
    In all cases where these generic errors are reported,
    we do have a rp_error(...) with details.
    
    > Furthermore, in the use_atomic case (also below), when a commit fails,
    > you assign err.buf to cmd->error_string rather than a generic
    > "transaction error" message. What differs between these cases which
    > makes the generic message preferable here over the more specific
    > err.buf message?
    
    They are the same now.
    
    > Repeating from my earlier review[1]: This is leaking 'transaction' for
    > each successful commit (and only freeing it upon commit error).
    
    Right. I thought I had it covered with the else clause. Of course not.
    
    > At the end of this function, strbuf_release(&err) is invoked, which
    > leaves all these cmd->error_strings dangling.
    
    I removed all assignments of err.buf now.
    
    > goto's can help simplify error-handling when multiple conditional
    > branches need to perform common cleanup, however, this label
    > corresponds to only a single goto statement.
    
    moved up again.

 builtin/receive-pack.c | 81 +++++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 67 insertions(+), 14 deletions(-)

diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e76e5d5..ebce2fa 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -67,6 +67,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)
 {
@@ -823,6 +824,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)) {
@@ -832,35 +834,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 */
 	}
 }
@@ -1052,6 +1055,7 @@ static void execute_commands(struct command *commands,
 	struct command *cmd;
 	unsigned char sha1[20];
 	struct iterate_data data;
+	struct strbuf err = STRBUF_INIT;
 
 	if (unpacker_error) {
 		for (cmd = commands; cmd; cmd = cmd->next)
@@ -1080,6 +1084,17 @@ static void execute_commands(struct command *commands,
 	head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
 
 	checked_connectivity = 1;
+
+	if (use_atomic) {
+		transaction = ref_transaction_begin(&err);
+		if (!transaction) {
+			rp_error("%s", err.buf);
+			strbuf_release(&err);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = "transaction failed to start";
+			return;
+		}
+	}
 	for (cmd = commands; cmd; cmd = cmd->next) {
 		if (cmd->error_string)
 			continue;
@@ -1087,7 +1102,36 @@ static void execute_commands(struct command *commands,
 		if (cmd->skip_update)
 			continue;
 
+		if (!use_atomic) {
+			transaction = ref_transaction_begin(&err);
+			if (!transaction) {
+				rp_error("%s", err.buf);
+				strbuf_release(&err);
+				cmd->error_string = "transaction failed to start";
+				continue;
+			}
+		}
 		cmd->error_string = update(cmd, si);
+		if (!cmd->error_string) {
+			if (!use_atomic) {
+				if (ref_transaction_commit(transaction, &err)) {
+					rp_error("%s", err.buf);
+					strbuf_release(&err);
+					cmd->error_string = "failed to update ref";
+				}
+				ref_transaction_free(transaction);
+			}
+		} else {
+			ref_transaction_free(transaction);
+			if (use_atomic) {
+				for (cmd = commands; cmd; cmd = cmd->next)
+					if (!cmd->error_string)
+						cmd->error_string = "atomic push failure";
+				strbuf_release(&err);
+				return;
+			}
+		}
+
 		if (shallow_update && !cmd->error_string &&
 		    si->shallow_ref[cmd->index]) {
 			error("BUG: connectivity check has not been run on ref %s",
@@ -1096,10 +1140,19 @@ static void execute_commands(struct command *commands,
 		}
 	}
 
+	if (use_atomic) {
+		if (ref_transaction_commit(transaction, &err)) {
+			rp_error("%s", err.buf);
+			for (cmd = commands; cmd; cmd = cmd->next)
+				cmd->error_string = "atomic transaction failed";
+		}
+		ref_transaction_free(transaction);
+	}
 	if (shallow_update && !checked_connectivity)
 		error("BUG: run 'git fsck' for safety.\n"
 		      "If there are errors, try to remove "
 		      "the reported refs above");
+	strbuf_release(&err);
 }
 
 static struct command **queue_command(struct command **tail,
-- 
2.2.1.62.g3f15098

^ permalink raw reply related	[flat|nested] 56+ messages in thread

* Re: [PATCHv3 1/6] receive-pack.c: add protocol support to negotiate atomic
  2014-12-17 18:32     ` [PATCHv3 1/6] receive-pack.c: add protocol support to negotiate atomic Stefan Beller
@ 2014-12-19  1:05       ` Eric Sunshine
  0 siblings, 0 replies; 56+ messages in thread
From: Eric Sunshine @ 2014-12-19  1:05 UTC (permalink / raw)
  To: Stefan Beller
  Cc: ronnie sahlberg, Michael Haggerty, Jonathan Nieder,
	Junio C Hamano, Git List, Ronnie Sahlberg

On Wed, Dec 17, 2014 at 1:32 PM, Stefan Beller <sbeller@google.com> wrote:
> From: Ronnie Sahlberg <sahlberg@google.com>
>
> This adds support to the protocol between send-pack and receive-pack to
> * allow receive-pack to inform the client that it has atomic push capability
> * allow send-pack to request atomic push back.
>
> There is currently no setting in send-pack to actually request that atomic
> pushes are to be used yet. This only adds protocol capability not ability
> for the user to activate it.
>
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
> diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
> index 6d5424c..68ec23d 100644
> --- a/Documentation/technical/protocol-capabilities.txt
> +++ b/Documentation/technical/protocol-capabilities.txt
> @@ -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 single atomic transaction. Either all refs are

"one single atomic" sounds awfully redundant.

> +updated or none.
> +
>  allow-tip-sha1-in-want
>  ----------------------
>
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index 32fc540..e76e5d5 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -328,6 +332,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) {
> +               return error("server does not support atomic push.");

Just above this code, the 'args->push_cert' check uses die() rather
than error() when the remote side fails to support the requested
feature.

> +       }
> +       use_atomic = atomic_supported && args->atomic;
>
>         if (status_report)
>                 strbuf_addstr(&cap_buf, " report-status");

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv3 3/6] send-pack.c: add --atomic command line argument
  2014-12-17 18:32     ` [PATCHv3 3/6] send-pack.c: add --atomic command line argument Stefan Beller
  2014-12-17 23:14       ` Junio C Hamano
@ 2014-12-19  1:22       ` Eric Sunshine
  1 sibling, 0 replies; 56+ messages in thread
From: Eric Sunshine @ 2014-12-19  1:22 UTC (permalink / raw)
  To: Stefan Beller
  Cc: ronnie sahlberg, Michael Haggerty, Jonathan Nieder,
	Junio C Hamano, Git List, Ronnie Sahlberg

On Wed, Dec 17, 2014 at 1:32 PM, Stefan Beller <sbeller@google.com> wrote:
> From: Ronnie Sahlberg <sahlberg@google.com>
>
> 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.
>
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
> diff --git a/send-pack.c b/send-pack.c
> index 77e4201..a696d5c 100644
> --- a/send-pack.c
> +++ b/send-pack.c
> @@ -282,6 +282,30 @@ 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:
> +                       ; /* do nothing */
> +               }
> +       }
> +       error("atomic push failed for ref %s. "
> +             "status: %d\n", failing_ref->name, failing_ref->status);
> +       return -1;

error() returns -1, so:

    return error(...);

Also, why split the string literal like that? It's not warranted by
the indentation level. Instead:

    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,
> @@ -373,9 +397,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:

I realize that Junio said that this could wait for cleanup patch by
someone later, but I'd argue that defining these constants now with
their proper negative values would be beneficial:

First, it is a potential maintenance burden that each person working
on the code later must remember to negate the constants each time they
are used. Forgetting to do so will lead to incorrect behavior.

Second, negating these constants at point-of-use implies incorrectly
that there is some valid use-case for the non-negated forms. It's
misleading and confusing.

> +                       /*
> +                        * 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;

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv3 5/6] push.c: add an --atomic argument
  2014-12-17 18:32     ` [PATCHv3 5/6] push.c: add an --atomic argument Stefan Beller
@ 2014-12-19  1:29       ` Eric Sunshine
  0 siblings, 0 replies; 56+ messages in thread
From: Eric Sunshine @ 2014-12-19  1:29 UTC (permalink / raw)
  To: Stefan Beller
  Cc: ronnie sahlberg, Michael Haggerty, Jonathan Nieder,
	Junio C Hamano, Git List, Ronnie Sahlberg

On Wed, Dec 17, 2014 at 1:32 PM, Stefan Beller <sbeller@google.com> wrote:
> From: Ronnie Sahlberg <sahlberg@google.com>
>
> Add a command line argument to the git push command to request atomic
> pushes.
>
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
> diff --git a/builtin/push.c b/builtin/push.c
> index a076b19..5731a0d 100644
> --- a/builtin/push.c
> +++ b/builtin/push.c
> @@ -518,6 +518,8 @@ 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_BIT(0, "atomic", &flags, N_("use an atomic transaction remote"),

Unable to parse. Perhaps:

    "use an atomic transaction on remote side"

or

    "use an atomic transaction on the remote"

or

    "request atomic transaction on remote side"

> +                       TRANSPORT_PUSH_ATOMIC),
>                 OPT_END()
>         };
>

^ permalink raw reply	[flat|nested] 56+ messages in thread

* Re: [PATCHv5 4/6] receive-pack.c: use a single ref_transaction for atomic pushes
  2014-12-19  0:22                 ` [PATCHv5 " Stefan Beller
@ 2014-12-19 10:14                   ` Eric Sunshine
  0 siblings, 0 replies; 56+ messages in thread
From: Eric Sunshine @ 2014-12-19 10:14 UTC (permalink / raw)
  To: Stefan Beller
  Cc: ronnie sahlberg, Michael Haggerty, Jonathan Nieder, Git List,
	Junio C Hamano, Ronnie Sahlberg

On Thu, Dec 18, 2014 at 7:22 PM, Stefan Beller <sbeller@google.com> wrote:
> From: Ronnie Sahlberg <sahlberg@google.com>
>
> Update receive-pack to use an atomic transaction iff the client negotiated
> that it wanted atomic-push. This leaves the default behavior to be the old
> non-atomic one ref at a time update. This is to cause as little disruption
> as possible to existing clients. It is unknown if there are client scripts
> that depend on the old non-atomic behavior so we make it opt-in for now.
>
> If it turns out over time that there are no client scripts that depend on the
> old behavior we can change git to default to use atomic pushes and instead
> offer an opt-out argument for people that do not want atomic pushes.
>
> Signed-off-by: Ronnie Sahlberg <sahlberg@google.com>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
> index e76e5d5..ebce2fa 100644
> --- a/builtin/receive-pack.c
> +++ b/builtin/receive-pack.c
> @@ -1087,7 +1102,36 @@ static void execute_commands(struct command *commands,
>                 if (cmd->skip_update)
>                         continue;
>
> +               if (!use_atomic) {
> +                       transaction = ref_transaction_begin(&err);
> +                       if (!transaction) {
> +                               rp_error("%s", err.buf);
> +                               strbuf_release(&err);
> +                               cmd->error_string = "transaction failed to start";
> +                               continue;

For this failure, you 'continue' the loop.

> +                       }
> +               }
>                 cmd->error_string = update(cmd, si);
> +               if (!cmd->error_string) {
> +                       if (!use_atomic) {
> +                               if (ref_transaction_commit(transaction, &err)) {
> +                                       rp_error("%s", err.buf);
> +                                       strbuf_release(&err);
> +                                       cmd->error_string = "failed to update ref";

However, for this failure, you fall through...

> +                               }
> +                               ref_transaction_free(transaction);
> +                       }
> +               } else {
> +                       ref_transaction_free(transaction);
> +                       if (use_atomic) {
> +                               for (cmd = commands; cmd; cmd = cmd->next)
> +                                       if (!cmd->error_string)
> +                                               cmd->error_string = "atomic push failure";
> +                               strbuf_release(&err);
> +                               return;
> +                       }
> +               }
> +
>                 if (shallow_update && !cmd->error_string &&

And here must check if an error occurred in some code above.

This is ugly and inconsistent, and feels as if the new code was
plopped into the middle of this function without concern for overall
flow, thus negatively impacting maintainability and readability. It
could be made a bit cleaner by either (1) consistently using
'continue' for the non-atomic error cases, or (2) moving the "shallow"
handling up into the conditional where you _know_ that no error has
occurred (error_string is NULL).

However, this issue is symptomatic of a larger problem. Prior to this
patch, execute_commands() was relatively straight-forward and easy to
read and understand. With the patch, and its interleaved atomic and
non-atomic cases, the logic flow is a mess: it places a high cognitive
load on the reader and is difficult to maintain and to do correctly in
the first place (as already evidenced).

Have you considered refactoring the code to implement the atomic and
non-atomic cases as distinct single-purpose helper functions of
execute_commands()? It should be possible to do so with very little
duplicated code between the two functions, and the result would likely
be much more readable and maintainable.

>                     si->shallow_ref[cmd->index]) {
>                         error("BUG: connectivity check has not been run on ref %s",
> @@ -1096,10 +1140,19 @@ static void execute_commands(struct command *commands,
>                 }
>         }
>
> +       if (use_atomic) {
> +               if (ref_transaction_commit(transaction, &err)) {
> +                       rp_error("%s", err.buf);
> +                       for (cmd = commands; cmd; cmd = cmd->next)
> +                               cmd->error_string = "atomic transaction failed";
> +               }
> +               ref_transaction_free(transaction);
> +       }
>         if (shallow_update && !checked_connectivity)
>                 error("BUG: run 'git fsck' for safety.\n"
>                       "If there are errors, try to remove "
>                       "the reported refs above");
> +       strbuf_release(&err);
>  }
>
>  static struct command **queue_command(struct command **tail,
> --
> 2.2.1.62.g3f15098

^ permalink raw reply	[flat|nested] 56+ messages in thread

end of thread, other threads:[~2014-12-19 10:14 UTC | newest]

Thread overview: 56+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-12-15 19:56 [PATCH 0/5] Add a flag to push atomically Stefan Beller
2014-12-15 19:56 ` [PATCH 1/5] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
2014-12-15 20:53   ` Junio C Hamano
2014-12-15 22:30     ` Stefan Beller
2014-12-15 19:56 ` [PATCH 2/5] send-pack.c: add an --atomic-push command line argument Stefan Beller
2014-12-15 21:01   ` Junio C Hamano
2014-12-15 19:56 ` [PATCH 3/5] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
2014-12-15 21:37   ` Junio C Hamano
2014-12-15 19:56 ` [PATCH 4/5] push.c: add an --atomic-push argument Stefan Beller
2014-12-15 21:50   ` Junio C Hamano
2014-12-15 19:56 ` [PATCH 5/5] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
2014-12-15 22:29   ` Junio C Hamano
2014-12-15 22:33 ` [PATCH 0/5] Add a flag to push atomically Junio C Hamano
2014-12-16 18:49   ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Stefan Beller
2014-12-16 18:49     ` [PATCHv2 2/6] send-pack: Invert the return value of ref_update_to_be_sent Stefan Beller
2014-12-16 19:14       ` Junio C Hamano
2014-12-16 18:49     ` [PATCHv2 3/6] send-pack.c: add --atomic command line argument Stefan Beller
2014-12-16 19:31       ` Junio C Hamano
2014-12-16 18:49     ` [PATCHv2 4/6] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
2014-12-16 19:29       ` Eric Sunshine
2014-12-16 20:30         ` Eric Sunshine
2014-12-16 19:35       ` Junio C Hamano
2014-12-16 18:49     ` [PATCHv2 5/6] push.c: add an --atomic-push argument Stefan Beller
2014-12-16 19:33       ` Eric Sunshine
2014-12-16 20:43         ` Junio C Hamano
2014-12-16 19:36       ` Junio C Hamano
2014-12-16 18:49     ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller
2014-12-16 19:14       ` [PATCH] receive-pack: refuse all commands if one fails in atomic mode Stefan Beller
2014-12-16 20:32         ` Junio C Hamano
2014-12-16 19:37       ` [PATCHv2 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Eric Sunshine
2014-12-16 19:46       ` Junio C Hamano
2014-12-16 19:57         ` Stefan Beller
2014-12-16 20:46           ` Junio C Hamano
2014-12-16 20:51             ` Stefan Beller
2014-12-16 20:30       ` Junio C Hamano
2014-12-16 20:36         ` Stefan Beller
2014-12-16 19:05     ` [PATCHv2 1/6] receive-pack.c: add protocol support to negotiate atomic-push Junio C Hamano
2014-12-17 18:32   ` [PATCHv3 0/6] atomic pushes Stefan Beller
2014-12-17 18:32     ` [PATCHv3 1/6] receive-pack.c: add protocol support to negotiate atomic Stefan Beller
2014-12-19  1:05       ` Eric Sunshine
2014-12-17 18:32     ` [PATCHv3 2/6] send-pack: Rename ref_update_to_be_sent to check_to_send_update Stefan Beller
2014-12-17 22:53       ` Junio C Hamano
2014-12-17 18:32     ` [PATCHv3 3/6] send-pack.c: add --atomic command line argument Stefan Beller
2014-12-17 23:14       ` Junio C Hamano
2014-12-19  1:22       ` Eric Sunshine
2014-12-17 18:32     ` [PATCHv3 4/6] receive-pack.c: use a single ref_transaction for atomic pushes Stefan Beller
2014-12-17 23:26       ` Junio C Hamano
2014-12-17 23:58         ` Stefan Beller
2014-12-18 17:02           ` Junio C Hamano
2014-12-18 17:45             ` [PATCHv4 " Stefan Beller
2014-12-18 22:26               ` Eric Sunshine
2014-12-19  0:22                 ` [PATCHv5 " Stefan Beller
2014-12-19 10:14                   ` Eric Sunshine
2014-12-17 18:32     ` [PATCHv3 5/6] push.c: add an --atomic argument Stefan Beller
2014-12-19  1:29       ` Eric Sunshine
2014-12-17 18:32     ` [PATCHv3 6/6] t5543-atomic-push.sh: add basic tests for atomic pushes Stefan Beller

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.