#include "version.h"
#include "sha1-array.h"
#include "gpg-interface.h"
+#include "cache.h"
+
+int option_parse_push_signed(const struct option *opt,
+ const char *arg, int unset)
+{
+ if (unset) {
+ *(int *)(opt->value) = SEND_PACK_PUSH_CERT_NEVER;
+ return 0;
+ }
+ switch (git_parse_maybe_bool(arg)) {
+ case 1:
+ *(int *)(opt->value) = SEND_PACK_PUSH_CERT_ALWAYS;
+ return 0;
+ case 0:
+ *(int *)(opt->value) = SEND_PACK_PUSH_CERT_NEVER;
+ return 0;
+ }
+ if (!strcasecmp("if-asked", arg)) {
+ *(int *)(opt->value) = SEND_PACK_PUSH_CERT_IF_ASKED;
+ return 0;
+ }
+ die("bad %s argument: %s", opt->long_name, arg);
+}
static int feed_object(const unsigned char *sha1, int fd, int negative)
{
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) {
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;
}
}
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",
return update_seen;
}
+
+static int atomic_push_failure(struct send_pack_args *args,
+ struct ref *remote_refs,
+ struct ref *failing_ref)
+{
+ struct ref *ref;
+ /* Mark other refs as failed */
+ for (ref = remote_refs; ref; ref = ref->next) {
+ if (!ref->peer_ref && !args->send_mirror)
+ continue;
+
+ switch (ref->status) {
+ case REF_STATUS_EXPECTING_REPORT:
+ ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+ continue;
+ default:
+ break; /* do nothing */
+ }
+ }
+ return error("atomic push failed for ref %s. status: %d\n",
+ failing_ref->name, failing_ref->status);
+}
+
#define NONCE_LEN_LIMIT 256
static void reject_invalid_nonce(const char *nonce, int len)
int use_sideband = 0;
int quiet_supported = 0;
int agent_supported = 0;
+ int use_atomic = 0;
+ int atomic_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
agent_supported = 1;
if (server_supports("no-thin"))
args->use_thin_pack = 0;
- if (args->push_cert) {
- int len;
+ if (server_supports("atomic"))
+ atomic_supported = 1;
+ if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
+ int len;
push_cert_nonce = server_feature_value("push-cert", &len);
- if (!push_cert_nonce)
+ if (push_cert_nonce) {
+ reject_invalid_nonce(push_cert_nonce, len);
+ push_cert_nonce = xmemdupz(push_cert_nonce, len);
+ } else if (args->push_cert == SEND_PACK_PUSH_CERT_ALWAYS) {
die(_("the receiving end does not support --signed push"));
- reject_invalid_nonce(push_cert_nonce, len);
- push_cert_nonce = xmemdupz(push_cert_nonce, len);
+ } else if (args->push_cert == SEND_PACK_PUSH_CERT_IF_ASKED) {
+ warning(_("not sending a push certificate since the"
+ " receiving end does not support --signed"
+ " push"));
+ }
}
if (!remote_refs) {
"Perhaps you should specify a branch such as 'master'.\n");
return 0;
}
+ if (args->atomic && !atomic_supported)
+ die(_("the receiving end does not support --atomic push"));
+
+ use_atomic = atomic_supported && args->atomic;
if (status_report)
strbuf_addstr(&cap_buf, " report-status");
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());
if (!args->dry_run)
advertise_shallow_grafts_buf(&req_buf);
- if (!args->dry_run && args->push_cert)
+ if (!args->dry_run && push_cert_nonce)
cmds_sent = generate_push_cert(&req_buf, remote_refs, args,
cap_buf.buf, push_cert_nonce);
* the pack data.
*/
for (ref = remote_refs; ref; ref = ref->next) {
- if (!ref_update_to_be_sent(ref, args))
+ switch (check_to_send_update(ref, args)) {
+ case 0: /* no error */
+ break;
+ case CHECK_REF_STATUS_REJECTED:
+ /*
+ * When we know the server would reject a ref update if
+ * we were to send it and we're trying to send the refs
+ * atomically, abort the whole operation.
+ */
+ if (use_atomic)
+ return atomic_push_failure(args, remote_refs, ref);
+ /* Fallthrough for non atomic case. */
+ default:
continue;
-
+ }
if (!ref->deletion)
need_pack_data = 1;
for (ref = remote_refs; ref; ref = ref->next) {
char *old_hex, *new_hex;
- if (args->dry_run || args->push_cert)
+ if (args->dry_run || push_cert_nonce)
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);