Merge branch 'sb/atomic-push' into mh/ref-trans-value-check
authorJunio C Hamano <gitster@pobox.com>
Mon, 9 Feb 2015 22:37:17 +0000 (14:37 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 9 Feb 2015 22:37:17 +0000 (14:37 -0800)
* sb/atomic-push:
Document receive.advertiseatomic
t5543-atomic-push.sh: add basic tests for atomic pushes
push.c: add an --atomic argument
send-pack.c: add --atomic command line argument
send-pack: rename ref_update_to_be_sent to check_to_send_update
receive-pack.c: negotiate atomic push support
receive-pack.c: add execute_commands_atomic function
receive-pack.c: move transaction handling in a central place
receive-pack.c: move iterating over all commands outside execute_commands
receive-pack.c: die instead of error in case of possible future bug
receive-pack.c: shorten the execute_commands loop over all commands

1  2 
Documentation/config.txt
Documentation/git-push.txt
builtin/push.c
builtin/receive-pack.c
send-pack.c
transport.c
diff --combined Documentation/config.txt
index 04e2a71687922bbf78a493df6ca6dc5730359eb1,4f8f4989febd38f8207bdb32488562f122dc2037..1a54eae8f82da26f8b9190d2ce5530bacf09f771
@@@ -246,17 -246,6 +246,17 @@@ core.precomposeunicode:
        When false, file names are handled fully transparent by Git,
        which is backward compatible with older versions of Git.
  
 +core.protectHFS::
 +      If set to true, do not allow checkout of paths that would
 +      be considered equivalent to `.git` on an HFS+ filesystem.
 +      Defaults to `true` on Mac OS, and `false` elsewhere.
 +
 +core.protectNTFS::
 +      If set to true, do not allow checkout of paths that would
 +      cause problems with the NTFS filesystem, e.g. conflict with
 +      8.3 "short" names.
 +      Defaults to `true` on Windows, and `false` elsewhere.
 +
  core.trustctime::
        If false, the ctime differences between the index and the
        working tree are ignored; useful when the inode change time
@@@ -375,19 -364,14 +375,19 @@@ This is useful for excluding servers in
  proxy use, while defaulting to a common proxy for external domains.
  
  core.ignoreStat::
 -      If true, commands which modify both the working tree and the index
 -      will mark the updated paths with the "assume unchanged" bit in the
 -      index. These marked files are then assumed to stay unchanged in the
 -      working tree, until you mark them otherwise manually - Git will not
 -      detect the file changes by lstat() calls. This is useful on systems
 -      where those are very slow, such as Microsoft Windows.
 -      See linkgit:git-update-index[1].
 -      False by default.
 +      If true, Git will avoid using lstat() calls to detect if files have
 +      changed by setting the "assume-unchanged" bit for those tracked files
 +      which it has updated identically in both the index and working tree.
 ++
 +When files are modified outside of Git, the user will need to stage
 +the modified files explicitly (e.g. see 'Examples' section in
 +linkgit:git-update-index[1]).
 +Git will not normally detect changes to those files.
 ++
 +This is useful on systems where lstat() calls are very slow, such as
 +CIFS/Microsoft Windows.
 ++
 +False by default.
  
  core.preferSymlinkRefs::
        Instead of the default "symref" format for HEAD
@@@ -854,13 -838,7 +854,13 @@@ accepted are `normal`, `black`, `red`, 
  `magenta`, `cyan` and `white`; the attributes are `bold`, `dim`, `ul`,
  `blink` and `reverse`.  The first color given is the foreground; the
  second is the background.  The position of the attribute, if any,
 -doesn't matter.
 +doesn't matter. Attributes may be turned off specifically by prefixing
 +them with `no` (e.g., `noreverse`, `noul`, etc).
 ++
 +Colors (foreground and background) may also be given as numbers between
 +0 and 255; these use ANSI 256-color mode (but note that not all
 +terminals may support this).  If your terminal supports it, you may also
 +specify 24-bit RGB values as hex, like `#ff0ab3`.
  
  color.diff::
        Whether to use ANSI escape sequences to add color to patches.
@@@ -2094,6 -2072,11 +2094,11 @@@ rebase.autostash:
        successful rebase might result in non-trivial conflicts.
        Defaults to false.
  
+ receive.advertiseatomic::
+       By default, git-receive-pack will advertise the atomic push
+       capability to its clients. If you don't want to this capability
+       to be advertised, set this variable to false.
  receive.autogc::
        By default, git-receive-pack will run "git-gc --auto" after
        receiving data from git-push and updating refs.  You can stop
@@@ -2151,13 -2134,6 +2156,13 @@@ receive.denyCurrentBranch:
        print a warning of such a push to stderr, but allow the push to
        proceed. If set to false or "ignore", allow such pushes with no
        message. Defaults to "refuse".
 ++
 +Another option is "updateInstead" which will update the working
 +directory (must be clean) if pushing into the current branch. This option is
 +intended for synchronizing working directories when one side is not easily
 +accessible via interactive ssh (e.g. a live web site, hence the requirement
 +that the working directory be clean). This mode also comes in handy when
 +developing inside a VM to test and fix code on different Operating Systems.
  
  receive.denyNonFastForwards::
        If set to true, git-receive-pack will deny a ref update which is
@@@ -2332,9 -2308,7 +2337,9 @@@ sendemail.smtpserverport:
  sendemail.smtpserveroption::
  sendemail.smtpuser::
  sendemail.thread::
 +sendemail.transferencoding::
  sendemail.validate::
 +sendemail.xmailer::
        See linkgit:git-send-email[1] for description.
  
  sendemail.signedoffcc::
index b17283ab7a1cc73c5ec741e0da1128bea2a57a65,4764fcfa7591f48dbe580e24e7347ff9cb93362f..ea9757692ac6fa7f58eb8a150f9eef9e30875333
@@@ -9,7 -9,7 +9,7 @@@ git-push - Update remote refs along wit
  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>]]]
@@@ -34,7 -34,7 +34,7 @@@ When the command line does not specify 
  arguments or `--all`, `--mirror`, `--tags` options, the command finds
  the default `<refspec>` by consulting `remote.*.push` configuration,
  and if it is not found, honors `push.default` configuration to decide
 -what to push (See gitlink:git-config[1] for the meaning of `push.default`).
 +what to push (See linkgit:git-config[1] for the meaning of `push.default`).
  
  
  OPTIONS[[OPTIONS]]
@@@ -136,6 -136,11 +136,11 @@@ already exists on the remote side
        logged.  See linkgit:git-receive-pack[1] for the details
        on the receiving end.
  
+ --[no-]atomic::
+       Use an atomic transaction on the remote side if available.
+       Either all refs are updated, or on error, no refs are updated.
+       If the server does not support atomic pushes the push will fail.
  --receive-pack=<git-receive-pack>::
  --exec=<git-receive-pack>::
        Path to the 'git-receive-pack' program on the remote
diff --combined builtin/push.c
index 12f5e69393bc2981dd5ff1433e34bc4932c0ee22,8f1d945d1a4a7ae51b11519b28660ff48a4c2fb5..fc771a9f6f83a4a8de153c47a2422e82b66adc6f
@@@ -161,7 -161,7 +161,7 @@@ static const char message_detached_head
           "    git push %s HEAD:<name-of-remote-branch>\n");
  
  static void setup_push_upstream(struct remote *remote, struct branch *branch,
 -                              int triangular)
 +                              int triangular, int simple)
  {
        struct strbuf refspec = STRBUF_INIT;
  
                      "to update which remote branch."),
                    remote->name, branch->name);
  
 -      if (push_default == PUSH_DEFAULT_SIMPLE) {
 +      if (simple) {
                /* Additional safety */
                if (strcmp(branch->refname, branch->merge[0]->src))
                        die_push_simple(branch, remote);
@@@ -257,11 -257,11 +257,11 @@@ static void setup_default_push_refspecs
                if (triangular)
                        setup_push_current(remote, branch);
                else
 -                      setup_push_upstream(remote, branch, triangular);
 +                      setup_push_upstream(remote, branch, triangular, 1);
                break;
  
        case PUSH_DEFAULT_UPSTREAM:
 -              setup_push_upstream(remote, branch, triangular);
 +              setup_push_upstream(remote, branch, triangular, 0);
                break;
  
        case PUSH_DEFAULT_CURRENT:
@@@ -487,6 -487,7 +487,7 @@@ int cmd_push(int argc, const char **arg
        int flags = 0;
        int tags = 0;
        int rc;
+       int atomic = 0;
        const char *repo = NULL;        /* default repository */
        struct option options[] = {
                OPT__VERBOSITY(&verbosity),
                  0, CAS_OPT_NAME, &cas, N_("refname>:<expect"),
                  N_("require old value of ref to be at this value"),
                  PARSE_OPT_OPTARG, parseopt_push_cas_option },
 -              { OPTION_CALLBACK, 0, "recurse-submodules", &flags, N_("check"),
 +              { OPTION_CALLBACK, 0, "recurse-submodules", &flags, "check|on-demand",
                        N_("control recursive pushing of submodules"),
                        PARSE_OPT_OPTARG, option_parse_recurse_submodules },
                OPT_BOOL( 0 , "thin", &thin, N_("use thin pack")),
                OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
                        TRANSPORT_PUSH_FOLLOW_TAGS),
                OPT_BIT(0, "signed", &flags, N_("GPG sign the push"), TRANSPORT_PUSH_CERT),
+               OPT_BOOL(0, "atomic", &atomic, N_("request atomic transaction on remote side")),
                OPT_END()
        };
  
        if (tags)
                add_refspec("refs/tags/*");
  
+       if (atomic)
+               flags |= TRANSPORT_PUSH_ATOMIC;
        if (argc > 0) {
                repo = argv[0];
                set_refspecs(argv + 1, argc - 1, repo);
diff --combined builtin/receive-pack.c
index 8266c1fccf0c0b5908c14a726d443dec36a22d58,4c069c53e9ba382954cb44f3c45d3cafcbbdd778..4e85e25d0f3cd3108bd4ad1c3be4dba53a30c1a2
@@@ -26,8 -26,7 +26,8 @@@ enum deny_action 
        DENY_UNCONFIGURED,
        DENY_IGNORE,
        DENY_WARN,
 -      DENY_REFUSE
 +      DENY_REFUSE,
 +      DENY_UPDATE_INSTEAD
  };
  
  static int deny_deletes;
@@@ -38,9 -37,11 +38,11 @@@ static int receive_fsck_objects = -1
  static int transfer_fsck_objects = -1;
  static int receive_unpack_limit = -1;
  static int transfer_unpack_limit = -1;
+ static int advertise_atomic_push = 1;
  static int unpack_limit = 100;
  static int report_status;
  static int use_sideband;
+ static int use_atomic;
  static int quiet;
  static int prefer_ofs_delta = 1;
  static int auto_update_server_info;
@@@ -67,6 -68,7 +69,7 @@@ static const char *NONCE_SLOP = "SLOP"
  static const char *nonce_status;
  static long nonce_stamp_slop;
  static unsigned long nonce_stamp_slop_limit;
+ static struct ref_transaction *transaction;
  
  static enum deny_action parse_deny_action(const char *var, const char *value)
  {
@@@ -77,8 -79,6 +80,8 @@@
                        return DENY_WARN;
                if (!strcasecmp(value, "refuse"))
                        return DENY_REFUSE;
 +              if (!strcasecmp(value, "updateinstead"))
 +                      return DENY_UPDATE_INSTEAD;
        }
        if (git_config_bool(var, value))
                return DENY_REFUSE;
@@@ -160,6 -160,11 +163,11 @@@ static int receive_pack_config(const ch
                return 0;
        }
  
+       if (strcmp(var, "receive.advertiseatomic") == 0) {
+               advertise_atomic_push = git_config_bool(var, value);
+               return 0;
+       }
        return git_default_config(var, value, cb);
  }
  
@@@ -175,6 -180,8 +183,8 @@@ static void show_ref(const char *path, 
  
                strbuf_addstr(&cap,
                              "report-status delete-refs side-band-64k quiet");
+               if (advertise_atomic_push)
+                       strbuf_addstr(&cap, " atomic");
                if (prefer_ofs_delta)
                        strbuf_addstr(&cap, " ofs-delta");
                if (push_cert_nonce)
@@@ -434,7 -441,7 +444,7 @@@ static const char *check_nonce(const ch
        nonce_stamp_slop = (long)ostamp - (long)stamp;
  
        if (nonce_stamp_slop_limit &&
 -          abs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
 +          labs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
                /*
                 * Pretend as if the received nonce (which passes the
                 * HMAC check, so it is not a forged by third-party)
@@@ -733,89 -740,11 +743,89 @@@ static int update_shallow_ref(struct co
        return 0;
  }
  
 +static const char *update_worktree(unsigned char *sha1)
 +{
 +      const char *update_refresh[] = {
 +              "update-index", "-q", "--ignore-submodules", "--refresh", NULL
 +      };
 +      const char *diff_files[] = {
 +              "diff-files", "--quiet", "--ignore-submodules", "--", NULL
 +      };
 +      const char *diff_index[] = {
 +              "diff-index", "--quiet", "--cached", "--ignore-submodules",
 +              "HEAD", "--", NULL
 +      };
 +      const char *read_tree[] = {
 +              "read-tree", "-u", "-m", NULL, NULL
 +      };
 +      const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
 +      struct argv_array env = ARGV_ARRAY_INIT;
 +      struct child_process child = CHILD_PROCESS_INIT;
 +
 +      if (is_bare_repository())
 +              return "denyCurrentBranch = updateInstead needs a worktree";
 +
 +      argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
 +
 +      child.argv = update_refresh;
 +      child.env = env.argv;
 +      child.dir = work_tree;
 +      child.no_stdin = 1;
 +      child.stdout_to_stderr = 1;
 +      child.git_cmd = 1;
 +      if (run_command(&child)) {
 +              argv_array_clear(&env);
 +              return "Up-to-date check failed";
 +      }
 +
 +      /* run_command() does not clean up completely; reinitialize */
 +      child_process_init(&child);
 +      child.argv = diff_files;
 +      child.env = env.argv;
 +      child.dir = work_tree;
 +      child.no_stdin = 1;
 +      child.stdout_to_stderr = 1;
 +      child.git_cmd = 1;
 +      if (run_command(&child)) {
 +              argv_array_clear(&env);
 +              return "Working directory has unstaged changes";
 +      }
 +
 +      child_process_init(&child);
 +      child.argv = diff_index;
 +      child.env = env.argv;
 +      child.no_stdin = 1;
 +      child.no_stdout = 1;
 +      child.stdout_to_stderr = 0;
 +      child.git_cmd = 1;
 +      if (run_command(&child)) {
 +              argv_array_clear(&env);
 +              return "Working directory has staged changes";
 +      }
 +
 +      read_tree[3] = sha1_to_hex(sha1);
 +      child_process_init(&child);
 +      child.argv = read_tree;
 +      child.env = env.argv;
 +      child.dir = work_tree;
 +      child.no_stdin = 1;
 +      child.no_stdout = 1;
 +      child.stdout_to_stderr = 0;
 +      child.git_cmd = 1;
 +      if (run_command(&child)) {
 +              argv_array_clear(&env);
 +              return "Could not update working tree to new HEAD";
 +      }
 +
 +      argv_array_clear(&env);
 +      return NULL;
 +}
 +
  static const char *update(struct command *cmd, struct shallow_info *si)
  {
        const char *name = cmd->ref_name;
        struct strbuf namespaced_name_buf = STRBUF_INIT;
 -      const char *namespaced_name;
 +      const char *namespaced_name, *ret;
        unsigned char *old_sha1 = cmd->old_sha1;
        unsigned char *new_sha1 = cmd->new_sha1;
  
                        if (deny_current_branch == DENY_UNCONFIGURED)
                                refuse_unconfigured_deny();
                        return "branch is currently checked out";
 +              case DENY_UPDATE_INSTEAD:
 +                      ret = update_worktree(new_sha1);
 +                      if (ret)
 +                              return ret;
 +                      break;
                }
        }
  
                                break;
                        case DENY_REFUSE:
                        case DENY_UNCONFIGURED:
 +                      case DENY_UPDATE_INSTEAD:
                                if (deny_delete_current == DENY_UNCONFIGURED)
                                        refuse_unconfigured_deny_delete_current();
                                rp_error("refusing to delete the current branch: %s", name);
                                return "deletion of the current branch prohibited";
 +                      default:
 +                              return "Invalid denyDeleteCurrent setting";
                        }
                }
        }
        }
  
        if (is_null_sha1(new_sha1)) {
+               struct strbuf err = STRBUF_INIT;
                if (!parse_object(old_sha1)) {
                        old_sha1 = NULL;
                        if (ref_exists(name)) {
                                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 */
        }
  }
@@@ -1053,7 -976,7 +1065,7 @@@ static void check_aliased_updates(struc
                        string_list_append(&ref_list, cmd->ref_name);
                item->util = (void *)cmd;
        }
 -      sort_string_list(&ref_list);
 +      string_list_sort(&ref_list);
  
        for (cmd = commands; cmd; cmd = cmd->next) {
                if (!cmd->error_string)
@@@ -1131,11 -1054,105 +1143,105 @@@ static void reject_updates_to_hidden(st
        }
  }
  
+ static int should_process_cmd(struct command *cmd)
+ {
+       return !cmd->error_string && !cmd->skip_update;
+ }
+ static void warn_if_skipped_connectivity_check(struct command *commands,
+                                              struct shallow_info *si)
+ {
+       struct command *cmd;
+       int checked_connectivity = 1;
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) {
+                       error("BUG: connectivity check has not been run on ref %s",
+                             cmd->ref_name);
+                       checked_connectivity = 0;
+               }
+       }
+       if (!checked_connectivity)
+               die("BUG: connectivity check skipped???");
+ }
+ static void execute_commands_non_atomic(struct command *commands,
+                                       struct shallow_info *si)
+ {
+       struct command *cmd;
+       struct strbuf err = STRBUF_INIT;
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!should_process_cmd(cmd))
+                       continue;
+               transaction = ref_transaction_begin(&err);
+               if (!transaction) {
+                       rp_error("%s", err.buf);
+                       strbuf_reset(&err);
+                       cmd->error_string = "transaction failed to start";
+                       continue;
+               }
+               cmd->error_string = update(cmd, si);
+               if (!cmd->error_string
+                   && ref_transaction_commit(transaction, &err)) {
+                       rp_error("%s", err.buf);
+                       strbuf_reset(&err);
+                       cmd->error_string = "failed to update ref";
+               }
+               ref_transaction_free(transaction);
+       }
+       strbuf_release(&err);
+ }
+ static void execute_commands_atomic(struct command *commands,
+                                       struct shallow_info *si)
+ {
+       struct command *cmd;
+       struct strbuf err = STRBUF_INIT;
+       const char *reported_error = "atomic push failure";
+       transaction = ref_transaction_begin(&err);
+       if (!transaction) {
+               rp_error("%s", err.buf);
+               strbuf_reset(&err);
+               reported_error = "transaction failed to start";
+               goto failure;
+       }
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (!should_process_cmd(cmd))
+                       continue;
+               cmd->error_string = update(cmd, si);
+               if (cmd->error_string)
+                       goto failure;
+       }
+       if (ref_transaction_commit(transaction, &err)) {
+               rp_error("%s", err.buf);
+               reported_error = "atomic transaction failed";
+               goto failure;
+       }
+       goto cleanup;
+ failure:
+       for (cmd = commands; cmd; cmd = cmd->next)
+               if (!cmd->error_string)
+                       cmd->error_string = reported_error;
+ cleanup:
+       ref_transaction_free(transaction);
+       strbuf_release(&err);
+ }
  static void execute_commands(struct command *commands,
                             const char *unpacker_error,
                             struct shallow_info *si)
  {
-       int checked_connectivity;
        struct command *cmd;
        unsigned char sha1[20];
        struct iterate_data data;
        free(head_name_to_free);
        head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
  
-       checked_connectivity = 1;
-       for (cmd = commands; cmd; cmd = cmd->next) {
-               if (cmd->error_string)
-                       continue;
-               if (cmd->skip_update)
-                       continue;
-               cmd->error_string = update(cmd, si);
-               if (shallow_update && !cmd->error_string &&
-                   si->shallow_ref[cmd->index]) {
-                       error("BUG: connectivity check has not been run on ref %s",
-                             cmd->ref_name);
-                       checked_connectivity = 0;
-               }
-       }
+       if (use_atomic)
+               execute_commands_atomic(commands, si);
+       else
+               execute_commands_non_atomic(commands, si);
  
-       if (shallow_update && !checked_connectivity)
-               error("BUG: run 'git fsck' for safety.\n"
-                     "If there are errors, try to remove "
-                     "the reported refs above");
+       if (shallow_update)
+               warn_if_skipped_connectivity_check(commands, si);
  }
  
  static struct command **queue_command(struct command **tail,
@@@ -1268,6 -1271,9 +1360,9 @@@ static struct command *read_head_info(s
                                use_sideband = LARGE_PACKET_MAX;
                        if (parse_feature_request(feature_list, "quiet"))
                                quiet = 1;
+                       if (advertise_atomic_push
+                           && parse_feature_request(feature_list, "atomic"))
+                               use_atomic = 1;
                }
  
                if (!strcmp(line, "push-cert")) {
diff --combined send-pack.c
index 25947d7df9dd3af6e665d5d5eb63ec970617aef1,266f37f0605d8aa513e96c17d146b8d52468aaff..9d2b0c52ed8235425795772db99fc14ea9c2bf2b
@@@ -47,7 -47,6 +47,7 @@@ static int pack_objects(int fd, struct 
                NULL,
                NULL,
                NULL,
 +              NULL,
        };
        struct child_process po = CHILD_PROCESS_INIT;
        int i;
@@@ -61,8 -60,6 +61,8 @@@
                argv[i++] = "-q";
        if (args->progress)
                argv[i++] = "--progress";
 +      if (is_repository_shallow())
 +              argv[i++] = "--shallow";
        po.argv = argv;
        po.in = -1;
        po.out = args->stateless_rpc ? -1 : fd;
@@@ -193,10 -190,13 +193,13 @@@ static void advertise_shallow_grafts_bu
        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;
        }
  }
  
@@@ -253,7 -254,7 +257,7 @@@ static int generate_push_cert(struct st
        strbuf_addstr(&cert, "\n");
  
        for (ref = remote_refs; ref; ref = ref->next) {
-               if (!ref_update_to_be_sent(ref, args))
+               if (check_to_send_update(ref, args) < 0)
                        continue;
                update_seen = 1;
                strbuf_addf(&cert, "%s %s %s\n",
@@@ -281,6 -282,29 +285,29 @@@ free_return
        return update_seen;
  }
  
+ static int atomic_push_failure(struct send_pack_args *args,
+                              struct ref *remote_refs,
+                              struct ref *failing_ref)
+ {
+       struct ref *ref;
+       /* Mark other refs as failed */
+       for (ref = remote_refs; ref; ref = ref->next) {
+               if (!ref->peer_ref && !args->send_mirror)
+                       continue;
+               switch (ref->status) {
+               case REF_STATUS_EXPECTING_REPORT:
+                       ref->status = REF_STATUS_ATOMIC_PUSH_FAILED;
+                       continue;
+               default:
+                       break; /* do nothing */
+               }
+       }
+       return error("atomic push failed for ref %s. status: %d\n",
+                    failing_ref->name, failing_ref->status);
+ }
  int send_pack(struct send_pack_args *args,
              int fd[], struct child_process *conn,
              struct ref *remote_refs,
        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 (server_supports("atomic"))
+               atomic_supported = 1;
        if (args->push_cert) {
                int len;
  
                        "Perhaps you should specify a branch such as 'master'.\n");
                return 0;
        }
+       if (args->atomic && !atomic_supported)
+               die(_("server does not support --atomic push"));
+       use_atomic = atomic_supported && args->atomic;
  
        if (status_report)
                strbuf_addstr(&cap_buf, " report-status");
                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());
  
         * 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;
  
                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);
diff --combined transport.c
index 08bcd3a4eba42d2e72b9d84ec89e190c763300fa,1373152a93b281bdaa24b65c458a359d1ddfaf4f..0694a7cf3e4a8bd7bec9fefcd440d65423021a1b
@@@ -728,6 -728,10 +728,10 @@@ static int print_one_push_status(struc
                                                 ref->deletion ? NULL : ref->peer_ref,
                                                 "remote failed to report status", porcelain);
                break;
+       case REF_STATUS_ATOMIC_PUSH_FAILED:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                                                "atomic push failed", porcelain);
+               break;
        case REF_STATUS_OK:
                print_ok_ref_status(ref, porcelain);
                break;
@@@ -826,6 -830,7 +830,7 @@@ static int git_transport_push(struct tr
        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,
@@@ -971,7 -976,9 +976,7 @@@ struct transport *transport_get(struct 
        } else {
                /* Unknown protocol in URL. Pass to external handler. */
                int len = external_specification_len(url);
 -              char *handler = xmalloc(len + 1);
 -              handler[len] = 0;
 -              strncpy(handler, url, len);
 +              char *handler = xmemdupz(url, len);
                transport_helper_init(ret, handler);
        }