Merge branch 'jc/push-cas'
authorJunio C Hamano <gitster@pobox.com>
Mon, 9 Sep 2013 21:30:29 +0000 (14:30 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 9 Sep 2013 21:30:29 +0000 (14:30 -0700)
Allow a safer "rewind of the remote tip" push than blind "--force",
by requiring that the overwritten remote ref to be unchanged since
the new history to replace it was prepared.

The machinery is more or less ready. The "--force" option is again
the big red button to override any safety, thanks to J6t's sanity
(the original round allowed --lockref to defeat --force).

The logic to choose the default implemented here is fragile
(e.g. "git fetch" after seeing a failure will update the
remote-tracking branch and will make the next "push" pass,
defeating the safety pretty easily). It is suitable only for the
simplest workflows, and it may hurt users more than it helps them.

* jc/push-cas:
push: teach --force-with-lease to smart-http transport
send-pack: fix parsing of --force-with-lease option
t5540/5541: smart-http does not support "--force-with-lease"
t5533: test "push --force-with-lease"
push --force-with-lease: tie it all together
push --force-with-lease: implement logic to populate old_sha1_expect[]
remote.c: add command line option parser for "--force-with-lease"
builtin/push.c: use OPT_BOOL, not OPT_BOOLEAN
cache.h: move remote/connect API out of it

1  2 
builtin/fetch-pack.c
builtin/push.c
cache.h
fetch-pack.c
refs.c
remote-curl.c
remote.c
remote.h
transport-helper.c
diff --combined builtin/fetch-pack.c
index 3e19d7149e7b15d61cee1220dbb5e1a2277dc290,c6888c66cef22187bce6f4ab1f14836967f734b4..c8e858232a8e7a3536ef4d296efdc618e034c36e
@@@ -1,6 -1,8 +1,8 @@@
  #include "builtin.h"
  #include "pkt-line.h"
  #include "fetch-pack.h"
+ #include "remote.h"
+ #include "connect.h"
  
  static const char fetch_pack_usage[] =
  "git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
@@@ -100,10 -102,6 +102,10 @@@ int cmd_fetch_pack(int argc, const cha
                        pack_lockfile_ptr = &pack_lockfile;
                        continue;
                }
 +              if (!strcmp("--check-self-contained-and-connected", arg)) {
 +                      args.check_self_contained_and_connected = 1;
 +                      continue;
 +              }
                usage(fetch_pack_usage);
        }
  
                printf("lock %s\n", pack_lockfile);
                fflush(stdout);
        }
 +      if (args.check_self_contained_and_connected &&
 +          args.self_contained_and_connected) {
 +              printf("connectivity-ok\n");
 +              fflush(stdout);
 +      }
        close(fd[0]);
        close(fd[1]);
        if (finish_connect(conn))
diff --combined builtin/push.c
index aff507c9f601f4c783ee16a08621695edd9b5ecb,2fd0a70fa807b1cb081ba8cebcf961aa5bfdfd33..50bbfd62b135b6f4454c3cdd4e74cf227c24a1a5
@@@ -21,6 -21,8 +21,8 @@@ static const char *receivepack
  static int verbosity;
  static int progress = -1;
  
+ static struct push_cas_option cas;
  static const char **refspec;
  static int refspec_nr;
  static int refspec_alloc;
@@@ -92,7 -94,7 +94,7 @@@ static NORETURN int die_push_simple(str
        if (!short_upstream)
                short_upstream = branch->merge[0]->src;
        /*
 -       * Don't show advice for people who explicitely set
 +       * Don't show advice for people who explicitly set
         * push.default.
         */
        if (push_default == PUSH_DEFAULT_UNSPECIFIED)
@@@ -120,11 -122,10 +122,11 @@@ static const char message_detached_head
           "\n"
           "    git push %s HEAD:<name-of-remote-branch>\n");
  
 -static void setup_push_upstream(struct remote *remote, int simple)
 +static void setup_push_upstream(struct remote *remote, struct branch *branch,
 +                              int triangular)
  {
        struct strbuf refspec = STRBUF_INIT;
 -      struct branch *branch = branch_get(NULL);
 +
        if (!branch)
                die(_(message_detached_head_die), remote->name);
        if (!branch->merge_nr || !branch->merge || !branch->remote_name)
        if (branch->merge_nr != 1)
                die(_("The current branch %s has multiple upstream branches, "
                    "refusing to push."), branch->name);
 -      if (strcmp(branch->remote_name, remote->name))
 +      if (triangular)
                die(_("You are pushing to remote '%s', which is not the upstream of\n"
                      "your current branch '%s', without telling me what to push\n"
                      "to update which remote branch."),
                    remote->name, branch->name);
 -      if (simple && strcmp(branch->refname, branch->merge[0]->src))
 -              die_push_simple(branch, remote);
 +
 +      if (push_default == PUSH_DEFAULT_SIMPLE) {
 +              /* Additional safety */
 +              if (strcmp(branch->refname, branch->merge[0]->src))
 +                      die_push_simple(branch, remote);
 +      }
  
        strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
        add_refspec(refspec.buf);
  }
  
 +static void setup_push_current(struct remote *remote, struct branch *branch)
 +{
 +      if (!branch)
 +              die(_(message_detached_head_die), remote->name);
 +      add_refspec(branch->name);
 +}
 +
  static char warn_unspecified_push_default_msg[] =
  N_("push.default is unset; its implicit value is changing in\n"
     "Git 2.0 from 'matching' to 'simple'. To squelch this message\n"
@@@ -185,16 -175,9 +187,16 @@@ static void warn_unspecified_push_defau
        warning("%s\n", _(warn_unspecified_push_default_msg));
  }
  
 +static int is_workflow_triangular(struct remote *remote)
 +{
 +      struct remote *fetch_remote = remote_get(NULL);
 +      return (fetch_remote && fetch_remote != remote);
 +}
 +
  static void setup_default_push_refspecs(struct remote *remote)
  {
 -      struct branch *branch;
 +      struct branch *branch = branch_get(NULL);
 +      int triangular = is_workflow_triangular(remote);
  
        switch (push_default) {
        default:
                break;
  
        case PUSH_DEFAULT_SIMPLE:
 -              setup_push_upstream(remote, 1);
 +              if (triangular)
 +                      setup_push_current(remote, branch);
 +              else
 +                      setup_push_upstream(remote, branch, triangular);
                break;
  
        case PUSH_DEFAULT_UPSTREAM:
 -              setup_push_upstream(remote, 0);
 +              setup_push_upstream(remote, branch, triangular);
                break;
  
        case PUSH_DEFAULT_CURRENT:
 -              branch = branch_get(NULL);
 -              if (!branch)
 -                      die(_(message_detached_head_die), remote->name);
 -              add_refspec(branch->name);
 +              setup_push_current(remote, branch);
                break;
  
        case PUSH_DEFAULT_NOTHING:
  
  static const char message_advice_pull_before_push[] =
        N_("Updates were rejected because the tip of your current branch is behind\n"
 -         "its remote counterpart. Merge the remote changes (e.g. 'git pull')\n"
 -         "before pushing again.\n"
 +         "its remote counterpart. Integrate the remote changes (e.g.\n"
 +         "'git pull ...') before pushing again.\n"
           "See the 'Note about fast-forwards' in 'git push --help' for details.");
  
  static const char message_advice_use_upstream[] =
  
  static const char message_advice_checkout_pull_push[] =
        N_("Updates were rejected because a pushed branch tip is behind its remote\n"
 -         "counterpart. Check out this branch and merge the remote changes\n"
 -         "(e.g. 'git pull') before pushing again.\n"
 +         "counterpart. Check out this branch and integrate the remote changes\n"
 +         "(e.g. 'git pull ...') before pushing again.\n"
           "See the 'Note about fast-forwards' in 'git push --help' for details.");
  
  static const char message_advice_ref_fetch_first[] =
        N_("Updates were rejected because the remote contains work that you do\n"
           "not have locally. This is usually caused by another repository pushing\n"
 -         "to the same ref. You may want to first merge the remote changes (e.g.,\n"
 -         "'git pull') before pushing again.\n"
 +         "to the same ref. You may want to first integrate the remote changes\n"
 +         "(e.g., 'git pull ...') before pushing again.\n"
           "See the 'Note about fast-forwards' in 'git push --help' for details.");
  
  static const char message_advice_ref_already_exists[] =
@@@ -316,6 -299,13 +318,13 @@@ static int push_with_options(struct tra
        if (thin)
                transport_set_option(transport, TRANS_OPT_THIN, "yes");
  
+       if (!is_empty_cas(&cas)) {
+               if (!transport->smart_options)
+                       die("underlying transport does not support --%s option",
+                           CAS_OPT_NAME);
+               transport->smart_options->cas = &cas;
+       }
        if (verbosity > 0)
                fprintf(stderr, _("Pushing to %s\n"), transport->url);
        err = transport_push(transport, refspec_nr, refspec, flags,
@@@ -451,6 -441,10 +460,10 @@@ int cmd_push(int argc, const char **arg
                OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN),
                OPT_BIT( 0,  "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN),
                OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE),
+               { OPTION_CALLBACK,
+                 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"),
                        N_("control recursive pushing of submodules"),
                        PARSE_OPT_OPTARG, option_parse_recurse_submodules },
diff --combined cache.h
index 85b544f38d934fe68d1e155c86337f1400eea14d,cb2891d8fa118dc997e690484da3a3c3a76c437d..558ccb91f675e87b079e54bc688706ce8b19fa44
+++ b/cache.h
@@@ -425,8 -425,6 +425,8 @@@ extern int path_inside_repo(const char 
  extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
  extern int init_db(const char *template_dir, unsigned int flags);
  
 +extern void sanitize_stdfds(void);
 +
  #define alloc_nr(x) (((x)+16)*3/2)
  
  /*
@@@ -478,7 -476,7 +478,7 @@@ extern int remove_file_from_index(struc
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
 -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern int index_name_is_other(const struct index_state *, const char *, int);
  extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
  
  extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  
 -#define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
 +#define PATHSPEC_ONESTAR 1    /* the pathspec pattern satisfies GFNM_ONESTAR */
  
  struct pathspec {
        const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
@@@ -577,7 -575,6 +577,7 @@@ extern int assume_unchanged
  extern int prefer_symlink_refs;
  extern int log_all_ref_updates;
  extern int warn_ambiguous_refs;
 +extern int warn_on_object_refname_ambiguity;
  extern int shared_repository;
  extern const char *apply_default_whitespace;
  extern const char *apply_default_ignorewhitespace;
@@@ -761,7 -758,7 +761,7 @@@ int is_directory(const char *)
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
  const char *absolute_path(const char *path);
 -const char *relative_path(const char *abs, const char *base);
 +const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
  int normalize_path_copy(char *dst, const char *src);
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
@@@ -1038,68 -1035,6 +1038,6 @@@ struct pack_entry 
        struct packed_git *p;
  };
  
- struct ref {
-       struct ref *next;
-       unsigned char old_sha1[20];
-       unsigned char new_sha1[20];
-       char *symref;
-       unsigned int
-               force:1,
-               forced_update:1,
-               deletion:1,
-               matched:1;
-       /*
-        * Order is important here, as we write to FETCH_HEAD
-        * in numeric order. And the default NOT_FOR_MERGE
-        * should be 0, so that xcalloc'd structures get it
-        * by default.
-        */
-       enum {
-               FETCH_HEAD_MERGE = -1,
-               FETCH_HEAD_NOT_FOR_MERGE = 0,
-               FETCH_HEAD_IGNORE = 1
-       } fetch_head_status;
-       enum {
-               REF_STATUS_NONE = 0,
-               REF_STATUS_OK,
-               REF_STATUS_REJECT_NONFASTFORWARD,
-               REF_STATUS_REJECT_ALREADY_EXISTS,
-               REF_STATUS_REJECT_NODELETE,
-               REF_STATUS_REJECT_FETCH_FIRST,
-               REF_STATUS_REJECT_NEEDS_FORCE,
-               REF_STATUS_UPTODATE,
-               REF_STATUS_REMOTE_REJECT,
-               REF_STATUS_EXPECTING_REPORT
-       } status;
-       char *remote_status;
-       struct ref *peer_ref; /* when renaming */
-       char name[FLEX_ARRAY]; /* more */
- };
- #define REF_NORMAL    (1u << 0)
- #define REF_HEADS     (1u << 1)
- #define REF_TAGS      (1u << 2)
- extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
- #define CONNECT_VERBOSE       (1u << 0)
- extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
- extern int finish_connect(struct child_process *conn);
- extern int git_connection_is_socket(struct child_process *conn);
- struct extra_have_objects {
-       int nr, alloc;
-       unsigned char (*array)[20];
- };
- extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
-                                    struct ref **list, unsigned int flags,
-                                    struct extra_have_objects *);
- extern int server_supports(const char *feature);
- extern int parse_feature_request(const char *features, const char *feature);
- extern const char *server_feature_value(const char *feature, int *len_ret);
- extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
  extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
  
  /* A hook for count-objects to report invalid files in pack directory */
@@@ -1132,9 -1067,7 +1070,9 @@@ extern int unpack_object_header(struct 
  
  struct object_info {
        /* Request */
 +      enum object_type *typep;
        unsigned long *sizep;
 +      unsigned long *disk_sizep;
  
        /* Response */
        enum {
@@@ -1178,15 -1111,11 +1116,15 @@@ extern int update_server_info(int)
  typedef int (*config_fn_t)(const char *, const char *, void *);
  extern int git_default_config(const char *, const char *, void *);
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
 +extern int git_config_from_buf(config_fn_t fn, const char *name,
 +                             const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern int git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
 -                                 const char *filename, int respect_includes);
 +                                 const char *filename,
 +                                 const char *blob_ref,
 +                                 int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
diff --combined fetch-pack.c
index f5d99c11813b1ae2eee0bb7dfd94eab60c721b64,c2bab42e6d8c7e039009a07cb83392945e61faba..094267fd80cdb09feec75c89aa2f4931f11e583d
@@@ -9,9 -9,9 +9,10 @@@
  #include "fetch-pack.h"
  #include "remote.h"
  #include "run-command.h"
+ #include "connect.h"
  #include "transport.h"
  #include "version.h"
 +#include "prio-queue.h"
  
  static int transfer_unpack_limit = -1;
  static int fetch_unpack_limit = -1;
@@@ -38,7 -38,7 +39,7 @@@ static int marked
   */
  #define MAX_IN_VAIN 256
  
 -static struct commit_list *rev_list;
 +static struct prio_queue rev_list = { compare_commits_by_commit_date };
  static int non_common_revs, multi_ack, use_sideband, allow_tip_sha1_in_want;
  
  static void rev_list_push(struct commit *commit, int mark)
@@@ -50,7 -50,7 +51,7 @@@
                        if (parse_commit(commit))
                                return;
  
 -              commit_list_insert_by_date(commit, &rev_list);
 +              prio_queue_put(&rev_list, commit);
  
                if (!(commit->object.flags & COMMON))
                        non_common_revs++;
@@@ -123,10 -123,10 +124,10 @@@ static const unsigned char *get_rev(voi
                unsigned int mark;
                struct commit_list *parents;
  
 -              if (rev_list == NULL || non_common_revs == 0)
 +              if (rev_list.nr == 0 || non_common_revs == 0)
                        return NULL;
  
 -              commit = rev_list->item;
 +              commit = prio_queue_get(&rev_list);
                if (!commit->object.parsed)
                        parse_commit(commit);
                parents = commit->parents;
                                mark_common(parents->item, 1, 0);
                        parents = parents->next;
                }
 -
 -              rev_list = rev_list->next;
        }
  
        return commit->object.sha1;
@@@ -441,7 -443,7 +442,7 @@@ static int find_common(struct fetch_pac
                                        in_vain = 0;
                                        got_continue = 1;
                                        if (ack == ACK_ready) {
 -                                              rev_list = NULL;
 +                                              clear_prio_queue(&rev_list);
                                                got_ready = 1;
                                        }
                                        break;
@@@ -504,7 -506,7 +505,7 @@@ static int mark_complete(const char *re
                struct commit *commit = (struct commit *)o;
                if (!(commit->object.flags & COMPLETE)) {
                        commit->object.flags |= COMPLETE;
 -                      commit_list_insert_by_date(commit, &complete);
 +                      commit_list_insert(commit, &complete);
                }
        }
        return 0;
@@@ -621,7 -623,6 +622,7 @@@ static int everything_local(struct fetc
        if (!args->depth) {
                for_each_ref(mark_complete, NULL);
                for_each_alternate_ref(mark_alternate_complete, NULL);
 +              commit_list_sort_by_date(&complete);
                if (cutoff)
                        mark_recent_complete_commits(args, cutoff);
        }
@@@ -897,8 -898,6 +898,8 @@@ static struct ref *do_fetch_pack(struc
                packet_flush(fd[1]);
        if (args->depth > 0)
                setup_alternate_shallow();
 +      else
 +              alternate_shallow_file = NULL;
        if (get_pack(args, fd, pack_lockfile))
                die("git fetch-pack: fetch failed.");
  
@@@ -989,7 -988,7 +990,7 @@@ struct ref *fetch_pack(struct fetch_pac
        }
        ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile);
  
 -      if (alternate_shallow_file) {
 +      if (args->depth > 0 && alternate_shallow_file) {
                if (*alternate_shallow_file == '\0') { /* --unshallow */
                        unlink_or_warn(git_path("shallow"));
                        rollback_lock_file(&shallow_lock);
diff --combined refs.c
index 7922261580515859bc89005c4c69e8623c50c728,330060c8665ab761b2d1d2432dce3d5c11ede86c..d78860c46d95ea9785c83c08824355275393ed99
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -72,6 -72,10 +72,6 @@@ int check_refname_format(const char *re
  {
        int component_len, component_count = 0;
  
 -      if (!strcmp(refname, "@"))
 -              /* Refname is a single character '@'. */
 -              return -1;
 -
        while (1) {
                /* We are at the start of a path component. */
                component_len = check_refname_component(refname, flags);
@@@ -630,9 -634,7 +630,9 @@@ struct ref_entry_cb 
  static int do_one_ref(struct ref_entry *entry, void *cb_data)
  {
        struct ref_entry_cb *data = cb_data;
 +      struct ref_entry *old_current_ref;
        int retval;
 +
        if (prefixcmp(entry->name, data->base))
                return 0;
  
              !ref_resolves_to_object(entry))
                return 0;
  
 +      /* Store the old value, in case this is a recursive call: */
 +      old_current_ref = current_ref;
        current_ref = entry;
        retval = data->fn(entry->name + data->trim, entry->u.value.sha1,
                          entry->flag, data->cb_data);
 -      current_ref = NULL;
 +      current_ref = old_current_ref;
        return retval;
  }
  
@@@ -2174,14 -2174,11 +2174,14 @@@ int lock_packed_refs(int flags
  {
        struct packed_ref_cache *packed_ref_cache;
  
 -      /* Discard the old cache because it might be invalid: */
 -      clear_packed_ref_cache(&ref_cache);
        if (hold_lock_file_for_update(&packlock, git_path("packed-refs"), flags) < 0)
                return -1;
 -      /* Read the current packed-refs while holding the lock: */
 +      /*
 +       * Get the current packed-refs while holding the lock.  If the
 +       * packed-refs file has been modified since we last read it,
 +       * this will automatically invalidate the cache and re-read
 +       * the packed-refs file.
 +       */
        packed_ref_cache = get_packed_ref_cache(&ref_cache);
        packed_ref_cache->lock = &packlock;
        /* Increment the reference count to prevent it from being freed: */
@@@ -3196,14 -3193,6 +3196,6 @@@ int update_ref(const char *action, cons
        return 0;
  }
  
- struct ref *find_ref_by_name(const struct ref *list, const char *name)
- {
-       for ( ; list; list = list->next)
-               if (!strcmp(list->name, name))
-                       return (struct ref *)list;
-       return NULL;
- }
  /*
   * generate a format suitable for scanf from a ref_rev_parse_rules
   * rule, that is replace the "%.*s" spec with a "%s" spec
diff --combined remote-curl.c
index 6918668dc24dc67fcb2870a4c1e6c795987ef323,53c8a3d1a3f24436b6b650a439d36dfaae6547f5..b5ebe0180069c1c1f96b769a37c5f3eb9459dfe9
@@@ -6,8 -6,8 +6,9 @@@
  #include "exec_cmd.h"
  #include "run-command.h"
  #include "pkt-line.h"
+ #include "string-list.h"
  #include "sideband.h"
 +#include "argv-array.h"
  
  static struct remote *remote;
  static const char *url; /* always ends with a trailing slash */
@@@ -16,12 -16,12 +17,13 @@@ struct options 
        int verbosity;
        unsigned long depth;
        unsigned progress : 1,
 +              check_self_contained_and_connected : 1,
                followtags : 1,
                dry_run : 1,
                thin : 1;
  };
  static struct options options;
+ static struct string_list cas_options = STRING_LIST_INIT_DUP;
  
  static int set_option(const char *name, const char *value)
  {
                        return -1;
                return 0;
        }
 +      else if (!strcmp(name, "check-connectivity")) {
 +              if (!strcmp(value, "true"))
 +                      options.check_self_contained_and_connected = 1;
 +              else if (!strcmp(value, "false"))
 +                      options.check_self_contained_and_connected = 0;
 +              else
 +                      return -1;
 +              return 0;
 +      }
+       else if (!strcmp(name, "cas")) {
+               struct strbuf val = STRBUF_INIT;
+               strbuf_addf(&val, "--" CAS_OPT_NAME "=%s", value);
+               string_list_append(&cas_options, val.buf);
+               strbuf_release(&val);
+               return 0;
+       }
        else {
                return 1 /* unsupported */;
        }
@@@ -664,7 -662,7 +673,7 @@@ static int fetch_git(struct discovery *
        struct strbuf preamble = STRBUF_INIT;
        char *depth_arg = NULL;
        int argc = 0, i, err;
 -      const char *argv[15];
 +      const char *argv[16];
  
        argv[argc++] = "fetch-pack";
        argv[argc++] = "--stateless-rpc";
                argv[argc++] = "-v";
                argv[argc++] = "-v";
        }
 +      if (options.check_self_contained_and_connected)
 +              argv[argc++] = "--check-self-contained-and-connected";
        if (!options.progress)
                argv[argc++] = "--no-progress";
        if (options.depth) {
@@@ -800,35 -796,41 +809,38 @@@ static int push_dav(int nr_spec, char *
  static int push_git(struct discovery *heads, int nr_spec, char **specs)
  {
        struct rpc_state rpc;
 -      const char **argv;
 -      int argc = 0, i, err;
 +      int i, err;
 +      struct argv_array args;
+       struct string_list_item *cas_option;
  
 -      argv = xmalloc((10 + nr_spec + cas_options.nr) * sizeof(char *));
 -      argv[argc++] = "send-pack";
 -      argv[argc++] = "--stateless-rpc";
 -      argv[argc++] = "--helper-status";
 +      argv_array_init(&args);
 +      argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status",
 +                       NULL);
 +
        if (options.thin)
 -              argv[argc++] = "--thin";
 +              argv_array_push(&args, "--thin");
        if (options.dry_run)
 -              argv[argc++] = "--dry-run";
 +              argv_array_push(&args, "--dry-run");
        if (options.verbosity == 0)
 -              argv[argc++] = "--quiet";
 +              argv_array_push(&args, "--quiet");
        else if (options.verbosity > 1)
 -              argv[argc++] = "--verbose";
 -      argv[argc++] = options.progress ? "--progress" : "--no-progress";
 -
 +              argv_array_push(&args, "--verbose");
 +      argv_array_push(&args, options.progress ? "--progress" : "--no-progress");
+       for_each_string_list_item(cas_option, &cas_options)
 -              argv[argc++] = cas_option->string;
 -
 -      argv[argc++] = url;
++              argv_array_push(&args, cas_option->string);
 +      argv_array_push(&args, url);
        for (i = 0; i < nr_spec; i++)
 -              argv[argc++] = specs[i];
 -      argv[argc++] = NULL;
 +              argv_array_push(&args, specs[i]);
  
        memset(&rpc, 0, sizeof(rpc));
        rpc.service_name = "git-receive-pack",
 -      rpc.argv = argv;
 +      rpc.argv = args.argv;
  
        err = rpc_service(&rpc, heads);
        if (rpc.result.len)
                write_or_die(1, rpc.result.buf, rpc.result.len);
        strbuf_release(&rpc.result);
 -      free(argv);
 +      argv_array_clear(&args);
        return err;
  }
  
@@@ -951,7 -953,6 +963,7 @@@ int main(int argc, const char **argv
                        printf("fetch\n");
                        printf("option\n");
                        printf("push\n");
 +                      printf("check-connectivity\n");
                        printf("\n");
                        fflush(stdout);
                } else {
diff --combined remote.c
index 8f0f2dd10e13ea8bd75e622af89acb02fe494440,922822c3cb5ac383045ee0edcc5ade6731e2e018..24334679e0971e3af5e168d1cf7259da55d21d50
+++ b/remote.c
@@@ -148,7 -148,6 +148,7 @@@ static struct remote *make_remote(cons
        }
  
        ret = xcalloc(1, sizeof(struct remote));
 +      ret->prune = -1;  /* unspecified */
        ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
        remotes[remotes_nr++] = ret;
        if (len)
@@@ -405,8 -404,6 +405,8 @@@ static int handle_config(const char *ke
                remote->skip_default_update = git_config_bool(key, value);
        else if (!strcmp(subkey, ".skipfetchall"))
                remote->skip_default_update = git_config_bool(key, value);
 +      else if (!strcmp(subkey, ".prune"))
 +              remote->prune = git_config_bool(key, value);
        else if (!strcmp(subkey, ".url")) {
                const char *v;
                if (git_config_string(&v, key, value))
@@@ -1305,14 -1302,14 +1305,22 @@@ static void add_missing_tags(struct re
        free(sent_tips.tip);
  }
  
+ struct ref *find_ref_by_name(const struct ref *list, const char *name)
+ {
+       for ( ; list; list = list->next)
+               if (!strcmp(list->name, name))
+                       return (struct ref *)list;
+       return NULL;
+ }
 +static void prepare_ref_index(struct string_list *ref_index, struct ref *ref)
 +{
 +      for ( ; ref; ref = ref->next)
 +              string_list_append_nodup(ref_index, ref->name)->util = ref;
 +
 +      sort_string_list(ref_index);
 +}
 +
  /*
   * Given the set of refs the local repository has, the set of refs the
   * remote repository has, and the refspec used for push, determine
@@@ -1331,7 -1328,6 +1339,7 @@@ int match_push_refs(struct ref *src, st
        int errs;
        static const char *default_refspec[] = { ":", NULL };
        struct ref *ref, **dst_tail = tail_ref(dst);
 +      struct string_list dst_ref_index = STRING_LIST_INIT_NODUP;
  
        if (!nr_refspec) {
                nr_refspec = 1;
  
        /* pick the remainder */
        for (ref = src; ref; ref = ref->next) {
 +              struct string_list_item *dst_item;
                struct ref *dst_peer;
                const struct refspec *pat = NULL;
                char *dst_name;
                if (!dst_name)
                        continue;
  
 -              dst_peer = find_ref_by_name(*dst, dst_name);
 +              if (!dst_ref_index.nr)
 +                      prepare_ref_index(&dst_ref_index, *dst);
 +
 +              dst_item = string_list_lookup(&dst_ref_index, dst_name);
 +              dst_peer = dst_item ? dst_item->util : NULL;
                if (dst_peer) {
                        if (dst_peer->peer_ref)
                                /* We're already sending something to this ref. */
                        /* Create a new one and link it */
                        dst_peer = make_linked_ref(dst_name, &dst_tail);
                        hashcpy(dst_peer->new_sha1, ref->new_sha1);
 +                      string_list_insert(&dst_ref_index,
 +                              dst_peer->name)->util = dst_peer;
                }
                dst_peer->peer_ref = copy_ref(ref);
                dst_peer->force = pat->force;
                free(dst_name);
        }
  
 +      string_list_clear(&dst_ref_index, 0);
 +
        if (flags & MATCH_REFS_FOLLOW_TAGS)
                add_missing_tags(src, dst, &dst_tail);
  
        if (send_prune) {
 +              struct string_list src_ref_index = STRING_LIST_INIT_NODUP;
                /* check for missing refs on the remote */
                for (ref = *dst; ref; ref = ref->next) {
                        char *src_name;
  
                        src_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_DST, NULL);
                        if (src_name) {
 -                              if (!find_ref_by_name(src, src_name))
 +                              if (!src_ref_index.nr)
 +                                      prepare_ref_index(&src_ref_index, src);
 +                              if (!string_list_has_string(&src_ref_index,
 +                                          src_name))
                                        ref->peer_ref = alloc_delete_ref();
                                free(src_name);
                        }
                }
 +              string_list_clear(&src_ref_index, 0);
        }
        if (errs)
                return -1;
  }
  
  void set_ref_status_for_push(struct ref *remote_refs, int send_mirror,
-       int force_update)
+                            int force_update)
  {
        struct ref *ref;
  
        for (ref = remote_refs; ref; ref = ref->next) {
                int force_ref_update = ref->force || force_update;
+               int reject_reason = 0;
  
                if (ref->peer_ref)
                        hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
                }
  
                /*
+                * Bypass the usual "must fast-forward" check but
+                * replace it with a weaker "the old value must be
+                * this value we observed".  If the remote ref has
+                * moved and is now different from what we expect,
+                * reject any push.
+                *
+                * It also is an error if the user told us to check
+                * with the remote-tracking branch to find the value
+                * to expect, but we did not have such a tracking
+                * branch.
+                */
+               if (ref->expect_old_sha1) {
+                       if (ref->expect_old_no_trackback ||
+                           hashcmp(ref->old_sha1, ref->old_sha1_expect))
+                               reject_reason = REF_STATUS_REJECT_STALE;
+               }
+               /*
+                * The usual "must fast-forward" rules.
+                *
                 * Decide whether an individual refspec A:B can be
                 * pushed.  The push will succeed if any of the
                 * following are true:
                 *     passing the --force argument
                 */
  
-               if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
-                       int why = 0; /* why would this push require --force? */
+               else if (!ref->deletion && !is_null_sha1(ref->old_sha1)) {
                        if (!prefixcmp(ref->name, "refs/tags/"))
-                               why = REF_STATUS_REJECT_ALREADY_EXISTS;
+                               reject_reason = REF_STATUS_REJECT_ALREADY_EXISTS;
                        else if (!has_sha1_file(ref->old_sha1))
-                               why = REF_STATUS_REJECT_FETCH_FIRST;
+                               reject_reason = REF_STATUS_REJECT_FETCH_FIRST;
                        else if (!lookup_commit_reference_gently(ref->old_sha1, 1) ||
                                 !lookup_commit_reference_gently(ref->new_sha1, 1))
-                               why = REF_STATUS_REJECT_NEEDS_FORCE;
+                               reject_reason = REF_STATUS_REJECT_NEEDS_FORCE;
                        else if (!ref_newer(ref->new_sha1, ref->old_sha1))
-                               why = REF_STATUS_REJECT_NONFASTFORWARD;
-                       if (!force_ref_update)
-                               ref->status = why;
-                       else if (why)
-                               ref->forced_update = 1;
+                               reject_reason = REF_STATUS_REJECT_NONFASTFORWARD;
                }
+               /*
+                * "--force" will defeat any rejection implemented
+                * by the rules above.
+                */
+               if (!force_ref_update)
+                       ref->status = reject_reason;
+               else if (reject_reason)
+                       ref->forced_update = 1;
        }
  }
  
@@@ -1939,3 -1944,121 +1970,121 @@@ struct ref *get_stale_heads(struct refs
        string_list_clear(&ref_names, 0);
        return stale_refs;
  }
+ /*
+  * Compare-and-swap
+  */
+ void clear_cas_option(struct push_cas_option *cas)
+ {
+       int i;
+       for (i = 0; i < cas->nr; i++)
+               free(cas->entry[i].refname);
+       free(cas->entry);
+       memset(cas, 0, sizeof(*cas));
+ }
+ static struct push_cas *add_cas_entry(struct push_cas_option *cas,
+                                     const char *refname,
+                                     size_t refnamelen)
+ {
+       struct push_cas *entry;
+       ALLOC_GROW(cas->entry, cas->nr + 1, cas->alloc);
+       entry = &cas->entry[cas->nr++];
+       memset(entry, 0, sizeof(*entry));
+       entry->refname = xmemdupz(refname, refnamelen);
+       return entry;
+ }
+ int parse_push_cas_option(struct push_cas_option *cas, const char *arg, int unset)
+ {
+       const char *colon;
+       struct push_cas *entry;
+       if (unset) {
+               /* "--no-<option>" */
+               clear_cas_option(cas);
+               return 0;
+       }
+       if (!arg) {
+               /* just "--<option>" */
+               cas->use_tracking_for_rest = 1;
+               return 0;
+       }
+       /* "--<option>=refname" or "--<option>=refname:value" */
+       colon = strchrnul(arg, ':');
+       entry = add_cas_entry(cas, arg, colon - arg);
+       if (!*colon)
+               entry->use_tracking = 1;
+       else if (get_sha1(colon + 1, entry->expect))
+               return error("cannot parse expected object name '%s'", colon + 1);
+       return 0;
+ }
+ int parseopt_push_cas_option(const struct option *opt, const char *arg, int unset)
+ {
+       return parse_push_cas_option(opt->value, arg, unset);
+ }
+ int is_empty_cas(const struct push_cas_option *cas)
+ {
+       return !cas->use_tracking_for_rest && !cas->nr;
+ }
+ /*
+  * Look at remote.fetch refspec and see if we have a remote
+  * tracking branch for the refname there.  Fill its current
+  * value in sha1[].
+  * If we cannot do so, return negative to signal an error.
+  */
+ static int remote_tracking(struct remote *remote, const char *refname,
+                          unsigned char sha1[20])
+ {
+       char *dst;
+       dst = apply_refspecs(remote->fetch, remote->fetch_refspec_nr, refname);
+       if (!dst)
+               return -1; /* no tracking ref for refname at remote */
+       if (read_ref(dst, sha1))
+               return -1; /* we know what the tracking ref is but we cannot read it */
+       return 0;
+ }
+ static void apply_cas(struct push_cas_option *cas,
+                     struct remote *remote,
+                     struct ref *ref)
+ {
+       int i;
+       /* Find an explicit --<option>=<name>[:<value>] entry */
+       for (i = 0; i < cas->nr; i++) {
+               struct push_cas *entry = &cas->entry[i];
+               if (!refname_match(entry->refname, ref->name, ref_rev_parse_rules))
+                       continue;
+               ref->expect_old_sha1 = 1;
+               if (!entry->use_tracking)
+                       hashcpy(ref->old_sha1_expect, cas->entry[i].expect);
+               else if (remote_tracking(remote, ref->name, ref->old_sha1_expect))
+                       ref->expect_old_no_trackback = 1;
+               return;
+       }
+       /* Are we using "--<option>" to cover all? */
+       if (!cas->use_tracking_for_rest)
+               return;
+       ref->expect_old_sha1 = 1;
+       if (remote_tracking(remote, ref->name, ref->old_sha1_expect))
+               ref->expect_old_no_trackback = 1;
+ }
+ void apply_push_cas(struct push_cas_option *cas,
+                   struct remote *remote,
+                   struct ref *remote_refs)
+ {
+       struct ref *ref;
+       for (ref = remote_refs; ref; ref = ref->next)
+               apply_cas(cas, remote, ref);
+ }
diff --combined remote.h
index 4db34980f6b9f5300cafab58aa8a2d4bea4035db,6c42554cc2c03f637d9bebdb1b462b1c4601dec9..131130a611b55c9f79649037e6dde916b621f578
+++ b/remote.h
@@@ -1,6 -1,8 +1,8 @@@
  #ifndef REMOTE_H
  #define REMOTE_H
  
+ #include "parse-options.h"
  enum {
        REMOTE_CONFIG,
        REMOTE_REMOTES,
@@@ -40,7 -42,6 +42,7 @@@ struct remote 
        int fetch_tags;
        int skip_default_update;
        int mirror;
 +      int prune;
  
        const char *receivepack;
        const char *uploadpack;
@@@ -72,6 -73,56 +74,56 @@@ struct refspec 
  
  extern const struct refspec *tag_refspec;
  
+ struct ref {
+       struct ref *next;
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       unsigned char old_sha1_expect[20]; /* used by expect-old */
+       char *symref;
+       unsigned int
+               force:1,
+               forced_update:1,
+               expect_old_sha1:1,
+               expect_old_no_trackback:1,
+               deletion:1,
+               matched:1;
+       /*
+        * Order is important here, as we write to FETCH_HEAD
+        * in numeric order. And the default NOT_FOR_MERGE
+        * should be 0, so that xcalloc'd structures get it
+        * by default.
+        */
+       enum {
+               FETCH_HEAD_MERGE = -1,
+               FETCH_HEAD_NOT_FOR_MERGE = 0,
+               FETCH_HEAD_IGNORE = 1
+       } fetch_head_status;
+       enum {
+               REF_STATUS_NONE = 0,
+               REF_STATUS_OK,
+               REF_STATUS_REJECT_NONFASTFORWARD,
+               REF_STATUS_REJECT_ALREADY_EXISTS,
+               REF_STATUS_REJECT_NODELETE,
+               REF_STATUS_REJECT_FETCH_FIRST,
+               REF_STATUS_REJECT_NEEDS_FORCE,
+               REF_STATUS_REJECT_STALE,
+               REF_STATUS_UPTODATE,
+               REF_STATUS_REMOTE_REJECT,
+               REF_STATUS_EXPECTING_REPORT
+       } status;
+       char *remote_status;
+       struct ref *peer_ref; /* when renaming */
+       char name[FLEX_ARRAY]; /* more */
+ };
+ #define REF_NORMAL    (1u << 0)
+ #define REF_HEADS     (1u << 1)
+ #define REF_TAGS      (1u << 2)
+ extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
  struct ref *alloc_ref(const char *name);
  struct ref *copy_ref(const struct ref *ref);
  struct ref *copy_ref_list(const struct ref *ref);
@@@ -85,6 -136,14 +137,14 @@@ int check_ref_type(const struct ref *re
   */
  void free_refs(struct ref *ref);
  
+ struct extra_have_objects {
+       int nr, alloc;
+       unsigned char (*array)[20];
+ };
+ extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
+                                    struct ref **list, unsigned int flags,
+                                    struct extra_have_objects *);
  int resolve_remote_symref(struct ref *ref, struct ref *list);
  int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1);
  
@@@ -173,4 -232,27 +233,27 @@@ struct ref *guess_remote_head(const str
  /* Return refs which no longer exist on remote */
  struct ref *get_stale_heads(struct refspec *refs, int ref_count, struct ref *fetch_map);
  
+ /*
+  * Compare-and-swap
+  */
+ #define CAS_OPT_NAME "force-with-lease"
+ struct push_cas_option {
+       unsigned use_tracking_for_rest:1;
+       struct push_cas {
+               unsigned char expect[20];
+               unsigned use_tracking:1;
+               char *refname;
+       } *entry;
+       int nr;
+       int alloc;
+ };
+ extern int parseopt_push_cas_option(const struct option *, const char *arg, int unset);
+ extern int parse_push_cas_option(struct push_cas_option *, const char *arg, int unset);
+ extern void clear_cas_option(struct push_cas_option *);
+ extern int is_empty_cas(const struct push_cas_option *);
+ void apply_push_cas(struct push_cas_option *, struct remote *, struct ref *);
  #endif
diff --combined transport-helper.c
index bec3b721fae56bb3936091ada0b5d5290f3ca4c2,e3a60d777de8bf323a5f45ad3145c75c62d5b907..4c2a39e8ff03df5285657a5c0f9b7746c28d76a6
@@@ -27,7 -27,6 +27,7 @@@ struct helper_data 
                push : 1,
                connect : 1,
                signed_tags : 1,
 +              check_connectivity : 1,
                no_disconnect_req : 1;
        char *export_marks;
        char *import_marks;
@@@ -187,8 -186,6 +187,8 @@@ static struct child_process *get_helper
                        data->bidi_import = 1;
                else if (!strcmp(capname, "export"))
                        data->export = 1;
 +              else if (!strcmp(capname, "check-connectivity"))
 +                      data->check_connectivity = 1;
                else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
                        ALLOC_GROW(refspecs,
                                   refspec_nr + 1,
@@@ -352,9 -349,6 +352,9 @@@ static int fetch_with_fetch(struct tran
        struct strbuf buf = STRBUF_INIT;
  
        standard_options(transport);
 +      if (data->check_connectivity &&
 +          data->transport_options.check_self_contained_and_connected)
 +              set_helper_option(transport, "check-connectivity", "true");
  
        for (i = 0; i < nr_heads; i++) {
                const struct ref *posn = to_fetch[i];
                        else
                                transport->pack_lockfile = xstrdup(name);
                }
 +              else if (data->check_connectivity &&
 +                       data->transport_options.check_self_contained_and_connected &&
 +                       !strcmp(buf.buf, "connectivity-ok"))
 +                      data->transport_options.self_contained_and_connected = 1;
                else if (!buf.len)
                        break;
                else
@@@ -693,6 -683,11 +693,11 @@@ static int push_update_ref_status(struc
                        free(msg);
                        msg = NULL;
                }
+               else if (!strcmp(msg, "stale info")) {
+                       status = REF_STATUS_REJECT_STALE;
+                       free(msg);
+                       msg = NULL;
+               }
        }
  
        if (*ref)
@@@ -747,13 -742,15 +752,15 @@@ static void push_update_refs_status(str
  }
  
  static int push_refs_with_push(struct transport *transport,
-               struct ref *remote_refs, int flags)
+                              struct ref *remote_refs, int flags)
  {
        int force_all = flags & TRANSPORT_PUSH_FORCE;
        int mirror = flags & TRANSPORT_PUSH_MIRROR;
        struct helper_data *data = transport->data;
        struct strbuf buf = STRBUF_INIT;
        struct ref *ref;
+       struct string_list cas_options = STRING_LIST_INIT_DUP;
+       struct string_list_item *cas_option;
  
        get_helper(transport);
        if (!data->push)
                /* Check for statuses set by set_ref_status_for_push() */
                switch (ref->status) {
                case REF_STATUS_REJECT_NONFASTFORWARD:
+               case REF_STATUS_REJECT_STALE:
                case REF_STATUS_REJECT_ALREADY_EXISTS:
                case REF_STATUS_UPTODATE:
                        continue;
                strbuf_addch(&buf, ':');
                strbuf_addstr(&buf, ref->name);
                strbuf_addch(&buf, '\n');
+               /*
+                * The "--force-with-lease" options without explicit
+                * values to expect have already been expanded into
+                * the ref->old_sha1_expect[] field; we can ignore
+                * transport->smart_options->cas altogether and instead
+                * can enumerate them from the refs.
+                */
+               if (ref->expect_old_sha1) {
+                       struct strbuf cas = STRBUF_INIT;
+                       strbuf_addf(&cas, "%s:%s",
+                                   ref->name, sha1_to_hex(ref->old_sha1_expect));
+                       string_list_append(&cas_options, strbuf_detach(&cas, NULL));
+               }
        }
-       if (buf.len == 0)
+       if (buf.len == 0) {
+               string_list_clear(&cas_options, 0);
                return 0;
+       }
  
        standard_options(transport);
+       for_each_string_list_item(cas_option, &cas_options)
+               set_helper_option(transport, "cas", cas_option->string);
  
        if (flags & TRANSPORT_PUSH_DRY_RUN) {
                if (set_helper_option(transport, "dry-run", "true") != 0)
@@@ -992,7 -1008,6 +1018,7 @@@ int transport_helper_init(struct transp
  #define PBUFFERSIZE 8192
  
  /* Print bidirectional transfer loop debug message. */
 +__attribute__((format (printf, 1, 2)))
  static void transfer_debug(const char *fmt, ...)
  {
        va_list args;
@@@ -1078,7 -1093,7 +1104,7 @@@ static int udt_do_read(struct unidirect
                return -1;
        } else if (bytes == 0) {
                transfer_debug("%s EOF (with %i bytes in buffer)",
 -                      t->src_name, t->bufuse);
 +                      t->src_name, (int)t->bufuse);
                t->state = SSTATE_FLUSHING;
        } else if (bytes > 0) {
                t->bufuse += bytes;
@@@ -1142,7 -1157,7 +1168,7 @@@ static void *udt_copy_task_routine(voi
  #ifndef NO_PTHREADS
  
  /*
 - * Join thread, with apporiate errors on failure. Name is name for the
 + * Join thread, with appropriate errors on failure. Name is name for the
   * thread (for error messages). Returns 0 on success, 1 on failure.
   */
  static int tloop_join(pthread_t thread, const char *name)
@@@ -1208,7 -1223,7 +1234,7 @@@ static void udt_kill_transfer(struct un
  }
  
  /*
 - * Join process, with apporiate errors on failure. Name is name for the
 + * Join process, with appropriate errors on failure. Name is name for the
   * process (for error messages). Returns 0 on success, 1 on failure.
   */
  static int tloop_join(pid_t pid, const char *name)