Merge branch 'nd/shallow-clone'
authorJunio C Hamano <gitster@pobox.com>
Fri, 17 Jan 2014 20:21:14 +0000 (12:21 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 17 Jan 2014 20:21:20 +0000 (12:21 -0800)
Fetching from a shallow-cloned repository used to be forbidden,
primarily because the codepaths involved were not carefully vetted
and we did not bother supporting such usage. This attempts to allow
object transfer out of a shallow-cloned repository in a controlled
way (i.e. the receiver become a shallow repository with truncated
history).

* nd/shallow-clone: (31 commits)
t5537: fix incorrect expectation in test case 10
shallow: remove unused code
send-pack.c: mark a file-local function static
git-clone.txt: remove shallow clone limitations
prune: clean .git/shallow after pruning objects
clone: use git protocol for cloning shallow repo locally
send-pack: support pushing from a shallow clone via http
receive-pack: support pushing to a shallow clone via http
smart-http: support shallow fetch/clone
remote-curl: pass ref SHA-1 to fetch-pack as well
send-pack: support pushing to a shallow clone
receive-pack: allow pushes that update .git/shallow
connected.c: add new variant that runs with --shallow-file
add GIT_SHALLOW_FILE to propagate --shallow-file to subprocesses
receive/send-pack: support pushing from a shallow clone
receive-pack: reorder some code in unpack()
fetch: add --update-shallow to accept refs that update .git/shallow
upload-pack: make sure deepening preserves shallow roots
fetch: support fetching from a shallow repository
clone: support remote shallow repository
...

25 files changed:
1  2 
Documentation/config.txt
Documentation/fetch-options.txt
builtin/clone.c
builtin/fetch-pack.c
builtin/fetch.c
builtin/gc.c
builtin/prune.c
builtin/receive-pack.c
builtin/send-pack.c
cache.h
commit.h
connect.c
connected.c
environment.c
fetch-pack.c
fetch-pack.h
git.c
remote-curl.c
remote.h
send-pack.c
shallow.c
t/t5601-clone.sh
transport-helper.c
transport.c
upload-pack.c
diff --combined Documentation/config.txt
index ed5985319089e287b5ada6353d42b1e81568290c,1a0bd0d4edd1eb9863e3d49fabe69f62be26cf42..5f4d7939ed1ec267e7f282624e5bb480e72d7a33
@@@ -567,10 -567,6 +567,10 @@@ be passed to the shell by Git, which wi
  command to `LESS=FRSX less -+S`. The environment tells the command
  to set the `S` option to chop long lines but the command line
  resets it to the default to fold long lines.
 ++
 +Likewise, when the `LV` environment variable is unset, Git sets it
 +to `-c`.  You can override this setting by exporting `LV` with
 +another value or setting `core.pager` to `lv +c`.
  
  core.whitespace::
        A comma separated list of common whitespace problems to
@@@ -2030,6 -2026,10 +2030,10 @@@ receive.updateserverinfo:
        If set to true, git-receive-pack will run git-update-server-info
        after receiving data from git-push and updating refs.
  
+ receive.shallowupdate::
+       If set to true, .git/shallow can be updated when new refs
+       require new shallow roots. Otherwise those refs are rejected.
  remote.pushdefault::
        The remote to push to by default.  Overrides
        `branch.<name>.remote` for all branches, and is overridden by
@@@ -2091,8 -2091,8 +2095,8 @@@ remote.<name>.vcs:
  
  remote.<name>.prune::
        When set to true, fetching from this remote by default will also
 -      remove any remote-tracking branches which no longer exist on the
 -      remote (as if the `--prune` option was give on the command line).
 +      remove any remote-tracking references that no longer exist on the
 +      remote (as if the `--prune` option was given on the command line).
        Overrides `fetch.prune` settings, if any.
  
  remotes.<group>::
index f0ef7d02a5dc6c40eb9aab706e0c43f467b3f8a6,54043e3633af37585e87e80077d4f31a27731d60..92c68c3fdabafd9ac5afe74256dbd2d56218c247
        branch history. Tags for the deepened commits are not fetched.
  
  --unshallow::
-       Convert a shallow repository to a complete one, removing all
-       the limitations imposed by shallow repositories.
+       If the source repository is complete, convert a shallow
+       repository to a complete one, removing all the limitations
+       imposed by shallow repositories.
+ +
+ If the source repository is shallow, fetch as much as possible so that
+ the current repository has the same history as the source repository.
+ --update-shallow::
+       By default when fetching from a shallow repository,
+       `git fetch` refuses refs that require updating
+       .git/shallow. This option updates .git/shallow and accept such
+       refs.
  
  ifndef::git-pull[]
  --dry-run::
@@@ -41,20 -51,17 +51,20 @@@ ifndef::git-pull[
  
  -p::
  --prune::
 -      After fetching, remove any remote-tracking branches which
 -      no longer exist on the remote.
 +      After fetching, remove any remote-tracking references that no
 +      longer exist on the remote.  Tags are not subject to pruning
 +      if they are fetched only because of the default tag
 +      auto-following or due to a --tags option.  However, if tags
 +      are fetched due to an explicit refspec (either on the command
 +      line or in the remote configuration, for example if the remote
 +      was cloned with the --mirror option), then they are also
 +      subject to pruning.
  endif::git-pull[]
  
 -ifdef::git-pull[]
 ---no-tags::
 -endif::git-pull[]
  ifndef::git-pull[]
  -n::
 ---no-tags::
  endif::git-pull[]
 +--no-tags::
        By default, tags that point at objects that are downloaded
        from the remote repository are fetched and stored locally.
        This option disables this automatic tag following. The default
  ifndef::git-pull[]
  -t::
  --tags::
 -      This is a short-hand for giving `refs/tags/*:refs/tags/*`
 -      refspec from the command line, to ask all tags to be fetched
 -      and stored locally.  Because this acts as an explicit
 -      refspec, the default refspecs (configured with the
 -      remote.$name.fetch variable) are overridden and not used.
 +      Fetch all tags from the remote (i.e., fetch remote tags
 +      `refs/tags/*` into local tags with the same name), in addition
 +      to whatever else would otherwise be fetched.  Using this
 +      option alone does not subject tags to pruning, even if --prune
 +      is used (though tags may be pruned anyway if they are also the
 +      destination of an explicit refspec; see '--prune').
  
  --recurse-submodules[=yes|on-demand|no]::
        This option controls if and under what conditions new commits of
diff --combined builtin/clone.c
index f98f52980d3b8f7a4700d37c486c4379a4dd15ed,71ee68b464e52f11f0b2256a2c6e2b6a8927d515..43e772ccdbaba3172f7ee1a969f92b300cb9d5a1
@@@ -252,6 -252,12 +252,12 @@@ static int add_one_reference(struct str
                die(_("reference repository '%s' is not a local repository."),
                    item->string);
  
+       if (!access(mkpath("%s/shallow", ref_git), F_OK))
+               die(_("reference repository '%s' is shallow"), item->string);
+       if (!access(mkpath("%s/info/grafts", ref_git), F_OK))
+               die(_("reference repository '%s' is grafted"), item->string);
        strbuf_addf(&alternate, "%s/objects", ref_git);
        add_to_alternates_file(alternate.buf);
        strbuf_release(&alternate);
@@@ -508,9 -514,9 +514,9 @@@ static void write_followtags(const stru
  {
        const struct ref *ref;
        for (ref = refs; ref; ref = ref->next) {
 -              if (prefixcmp(ref->name, "refs/tags/"))
 +              if (!starts_with(ref->name, "refs/tags/"))
                        continue;
 -              if (!suffixcmp(ref->name, "^{}"))
 +              if (ends_with(ref->name, "^{}"))
                        continue;
                if (!has_sha1_file(ref->old_sha1))
                        continue;
@@@ -578,7 -584,7 +584,7 @@@ static void update_remote_refs(const st
  static void update_head(const struct ref *our, const struct ref *remote,
                        const char *msg)
  {
 -      if (our && !prefixcmp(our->name, "refs/heads/")) {
 +      if (our && starts_with(our->name, "refs/heads/")) {
                /* Local default branch link */
                create_symref("HEAD", our->name, NULL);
                if (!option_bare) {
@@@ -625,7 -631,7 +631,7 @@@ static int checkout(void
                if (advice_detached_head)
                        detach_advice(sha1_to_hex(sha1));
        } else {
 -              if (prefixcmp(head, "refs/heads/"))
 +              if (!starts_with(head, "refs/heads/"))
                        die(_("HEAD not found below refs/heads!"));
        }
        free(head);
@@@ -791,15 -797,18 +797,22 @@@ int cmd_clone(int argc, const char **ar
        else
                repo = repo_name;
        is_local = option_local != 0 && path && !is_bundle;
-       if (is_local && option_depth)
-               warning(_("--depth is ignored in local clones; use file:// instead."));
+       if (is_local) {
+               if (option_depth)
+                       warning(_("--depth is ignored in local clones; use file:// instead."));
+               if (!access(mkpath("%s/shallow", path), F_OK)) {
+                       if (option_local > 0)
+                               warning(_("source repository is shallow, ignoring --local"));
+                       is_local = 0;
+               }
+       }
        if (option_local > 0 && !is_local)
                warning(_("--local is ignored"));
  
 +      /* no need to be strict, transport_set_option() will validate it again */
 +      if (option_depth && atoi(option_depth) < 1)
 +              die(_("depth %s is not a positive number"), option_depth);
 +
        if (argc == 2)
                dir = xstrdup(argv[1]);
        else
  
        remote = remote_get(option_origin);
        transport = transport_get(remote, remote->url[0]);
+       transport->cloning = 1;
  
        if (!transport->get_refs_list || (!is_local && !transport->fetch))
                die(_("Don't know how to clone %s"), transport->url);
diff --combined builtin/fetch-pack.c
index 8b8978a25287bce6c4f937ff52df6cb7ae8e429e,81fae380e8ad8a3c2b32b023fce54cb126b81392..1262b405f8212e71d088f4e090b8121fa304699a
@@@ -3,16 -3,24 +3,24 @@@
  #include "fetch-pack.h"
  #include "remote.h"
  #include "connect.h"
+ #include "sha1-array.h"
  
  static const char fetch_pack_usage[] =
  "git fetch-pack [--all] [--stdin] [--quiet|-q] [--keep|-k] [--thin] "
  "[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
 -"[--no-progress] [-v] [<host>:]<directory> [<refs>...]";
 +"[--no-progress] [--diag-url] [-v] [<host>:]<directory> [<refs>...]";
  
  static void add_sought_entry_mem(struct ref ***sought, int *nr, int *alloc,
                                 const char *name, int namelen)
  {
        struct ref *ref = xcalloc(1, sizeof(*ref) + namelen + 1);
+       unsigned char sha1[20];
+       if (namelen > 41 && name[40] == ' ' && !get_sha1_hex(name, sha1)) {
+               hashcpy(ref->old_sha1, sha1);
+               name += 41;
+               namelen -= 41;
+       }
  
        memcpy(ref->name, name, namelen);
        ref->name[namelen] = '\0';
@@@ -39,6 -47,7 +47,7 @@@ int cmd_fetch_pack(int argc, const cha
        char **pack_lockfile_ptr = NULL;
        struct child_process *conn;
        struct fetch_pack_args args;
+       struct sha1_array shallow = SHA1_ARRAY_INIT;
  
        packet_trace_identity("fetch-pack");
  
        for (i = 1; i < argc && *argv[i] == '-'; i++) {
                const char *arg = argv[i];
  
 -              if (!prefixcmp(arg, "--upload-pack=")) {
 +              if (starts_with(arg, "--upload-pack=")) {
                        args.uploadpack = arg + 14;
                        continue;
                }
 -              if (!prefixcmp(arg, "--exec=")) {
 +              if (starts_with(arg, "--exec=")) {
                        args.uploadpack = arg + 7;
                        continue;
                }
                        args.stdin_refs = 1;
                        continue;
                }
 +              if (!strcmp("--diag-url", arg)) {
 +                      args.diag_url = 1;
 +                      continue;
 +              }
                if (!strcmp("-v", arg)) {
                        args.verbose = 1;
                        continue;
                }
 -              if (!prefixcmp(arg, "--depth=")) {
 +              if (starts_with(arg, "--depth=")) {
                        args.depth = strtol(arg + 8, NULL, 0);
                        continue;
                }
                        args.check_self_contained_and_connected = 1;
                        continue;
                }
+               if (!strcmp("--cloning", arg)) {
+                       args.cloning = 1;
+                       continue;
+               }
+               if (!strcmp("--update-shallow", arg)) {
+                       args.update_shallow = 1;
+                       continue;
+               }
                usage(fetch_pack_usage);
        }
  
                fd[0] = 0;
                fd[1] = 1;
        } else {
 +              int flags = args.verbose ? CONNECT_VERBOSE : 0;
 +              if (args.diag_url)
 +                      flags |= CONNECT_DIAG_URL;
                conn = git_connect(fd, dest, args.uploadpack,
 -                                 args.verbose ? CONNECT_VERBOSE : 0);
 +                                 flags);
 +              if (!conn)
 +                      return args.diag_url ? 0 : 1;
        }
-       get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL);
 -
+       get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL, &shallow);
  
-       ref = fetch_pack(&args, fd, conn, ref, dest,
-                        sought, nr_sought, pack_lockfile_ptr);
+       ref = fetch_pack(&args, fd, conn, ref, dest, sought, nr_sought,
+                        &shallow, pack_lockfile_ptr);
        if (pack_lockfile) {
                printf("lock %s\n", pack_lockfile);
                fflush(stdout);
diff --combined builtin/fetch.c
index 09825c84d731014e075a6f77791f0681b7c4efb0,d2e4fc03d857b8b29bc871c5f983dddb93498ed3..025bc3e38d7d8055699ea15f241648602cfaf267
@@@ -36,7 -36,7 +36,7 @@@ static int prune = -1; /* unspecified *
  
  static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;
  static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
- static int tags = TAGS_DEFAULT, unshallow;
+ static int tags = TAGS_DEFAULT, unshallow, update_shallow;
  static const char *depth;
  static const char *upload_pack;
  static struct strbuf default_rla = STRBUF_INIT;
@@@ -44,7 -44,6 +44,7 @@@ static struct transport *gtransport
  static struct transport *gsecondary;
  static const char *submodule_prefix = "";
  static const char *recurse_submodules_default;
 +static int shown_url = 0;
  
  static int option_parse_recurse_submodules(const struct option *opt,
                                   const char *arg, int unset)
@@@ -105,6 -104,8 +105,8 @@@ static struct option builtin_fetch_opti
        { OPTION_STRING, 0, "recurse-submodules-default",
                   &recurse_submodules_default, NULL,
                   N_("default mode for recursion"), PARSE_OPT_HIDDEN },
+       OPT_BOOL(0, "update-shallow", &update_shallow,
+                N_("accept refs that update .git/shallow")),
        OPT_END()
  };
  
@@@ -161,156 -162,48 +163,156 @@@ static void add_merge_config(struct re
        }
  }
  
 +static int add_existing(const char *refname, const unsigned char *sha1,
 +                      int flag, void *cbdata)
 +{
 +      struct string_list *list = (struct string_list *)cbdata;
 +      struct string_list_item *item = string_list_insert(list, refname);
 +      item->util = xmalloc(20);
 +      hashcpy(item->util, sha1);
 +      return 0;
 +}
 +
 +static int will_fetch(struct ref **head, const unsigned char *sha1)
 +{
 +      struct ref *rm = *head;
 +      while (rm) {
 +              if (!hashcmp(rm->old_sha1, sha1))
 +                      return 1;
 +              rm = rm->next;
 +      }
 +      return 0;
 +}
 +
  static void find_non_local_tags(struct transport *transport,
                        struct ref **head,
 -                      struct ref ***tail);
 +                      struct ref ***tail)
 +{
 +      struct string_list existing_refs = STRING_LIST_INIT_DUP;
 +      struct string_list remote_refs = STRING_LIST_INIT_NODUP;
 +      const struct ref *ref;
 +      struct string_list_item *item = NULL;
 +
 +      for_each_ref(add_existing, &existing_refs);
 +      for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
 +              if (!starts_with(ref->name, "refs/tags/"))
 +                      continue;
 +
 +              /*
 +               * The peeled ref always follows the matching base
 +               * ref, so if we see a peeled ref that we don't want
 +               * to fetch then we can mark the ref entry in the list
 +               * as one to ignore by setting util to NULL.
 +               */
 +              if (ends_with(ref->name, "^{}")) {
 +                      if (item && !has_sha1_file(ref->old_sha1) &&
 +                          !will_fetch(head, ref->old_sha1) &&
 +                          !has_sha1_file(item->util) &&
 +                          !will_fetch(head, item->util))
 +                              item->util = NULL;
 +                      item = NULL;
 +                      continue;
 +              }
 +
 +              /*
 +               * If item is non-NULL here, then we previously saw a
 +               * ref not followed by a peeled reference, so we need
 +               * to check if it is a lightweight tag that we want to
 +               * fetch.
 +               */
 +              if (item && !has_sha1_file(item->util) &&
 +                  !will_fetch(head, item->util))
 +                      item->util = NULL;
 +
 +              item = NULL;
 +
 +              /* skip duplicates and refs that we already have */
 +              if (string_list_has_string(&remote_refs, ref->name) ||
 +                  string_list_has_string(&existing_refs, ref->name))
 +                      continue;
 +
 +              item = string_list_insert(&remote_refs, ref->name);
 +              item->util = (void *)ref->old_sha1;
 +      }
 +      string_list_clear(&existing_refs, 1);
 +
 +      /*
 +       * We may have a final lightweight tag that needs to be
 +       * checked to see if it needs fetching.
 +       */
 +      if (item && !has_sha1_file(item->util) &&
 +          !will_fetch(head, item->util))
 +              item->util = NULL;
 +
 +      /*
 +       * For all the tags in the remote_refs string list,
 +       * add them to the list of refs to be fetched
 +       */
 +      for_each_string_list_item(item, &remote_refs) {
 +              /* Unless we have already decided to ignore this item... */
 +              if (item->util)
 +              {
 +                      struct ref *rm = alloc_ref(item->string);
 +                      rm->peer_ref = alloc_ref(item->string);
 +                      hashcpy(rm->old_sha1, item->util);
 +                      **tail = rm;
 +                      *tail = &rm->next;
 +              }
 +      }
 +
 +      string_list_clear(&remote_refs, 0);
 +}
  
  static struct ref *get_ref_map(struct transport *transport,
 -                             struct refspec *refs, int ref_count, int tags,
 -                             int *autotags)
 +                             struct refspec *refspecs, int refspec_count,
 +                             int tags, int *autotags)
  {
        int i;
        struct ref *rm;
        struct ref *ref_map = NULL;
        struct ref **tail = &ref_map;
  
 -      const struct ref *remote_refs = transport_get_remote_refs(transport);
 +      /* opportunistically-updated references: */
 +      struct ref *orefs = NULL, **oref_tail = &orefs;
  
 -      if (ref_count || tags == TAGS_SET) {
 -              struct ref **old_tail;
 +      const struct ref *remote_refs = transport_get_remote_refs(transport);
  
 -              for (i = 0; i < ref_count; i++) {
 -                      get_fetch_map(remote_refs, &refs[i], &tail, 0);
 -                      if (refs[i].dst && refs[i].dst[0])
 +      if (refspec_count) {
 +              for (i = 0; i < refspec_count; i++) {
 +                      get_fetch_map(remote_refs, &refspecs[i], &tail, 0);
 +                      if (refspecs[i].dst && refspecs[i].dst[0])
                                *autotags = 1;
                }
 -              /* Merge everything on the command line, but not --tags */
 +              /* Merge everything on the command line (but not --tags) */
                for (rm = ref_map; rm; rm = rm->next)
                        rm->fetch_head_status = FETCH_HEAD_MERGE;
 -              if (tags == TAGS_SET)
 -                      get_fetch_map(remote_refs, tag_refspec, &tail, 0);
  
                /*
 -               * For any refs that we happen to be fetching via command-line
 -               * arguments, take the opportunity to update their configured
 -               * counterparts. However, we do not want to mention these
 -               * entries in FETCH_HEAD at all, as they would simply be
 -               * duplicates of existing entries.
 +               * For any refs that we happen to be fetching via
 +               * command-line arguments, the destination ref might
 +               * have been missing or have been different than the
 +               * remote-tracking ref that would be derived from the
 +               * configured refspec.  In these cases, we want to
 +               * take the opportunity to update their configured
 +               * remote-tracking reference.  However, we do not want
 +               * to mention these entries in FETCH_HEAD at all, as
 +               * they would simply be duplicates of existing
 +               * entries, so we set them FETCH_HEAD_IGNORE below.
 +               *
 +               * We compute these entries now, based only on the
 +               * refspecs specified on the command line.  But we add
 +               * them to the list following the refspecs resulting
 +               * from the tags option so that one of the latter,
 +               * which has FETCH_HEAD_NOT_FOR_MERGE, is not removed
 +               * by ref_remove_duplicates() in favor of one of these
 +               * opportunistic entries with FETCH_HEAD_IGNORE.
                 */
 -              old_tail = tail;
                for (i = 0; i < transport->remote->fetch_refspec_nr; i++)
                        get_fetch_map(ref_map, &transport->remote->fetch[i],
 -                                    &tail, 1);
 -              for (rm = *old_tail; rm; rm = rm->next)
 -                      rm->fetch_head_status = FETCH_HEAD_IGNORE;
 +                                    &oref_tail, 1);
 +
 +              if (tags == TAGS_SET)
 +                      get_fetch_map(remote_refs, tag_refspec, &tail, 0);
        } else {
                /* Use the defaults */
                struct remote *remote = transport->remote;
                        tail = &ref_map->next;
                }
        }
 -      if (tags == TAGS_DEFAULT && *autotags)
 +
 +      if (tags == TAGS_SET)
 +              /* also fetch all tags */
 +              get_fetch_map(remote_refs, tag_refspec, &tail, 0);
 +      else if (tags == TAGS_DEFAULT && *autotags)
                find_non_local_tags(transport, &ref_map, &tail);
 -      ref_remove_duplicates(ref_map);
  
 -      return ref_map;
 +      /* Now append any refs to be updated opportunistically: */
 +      *tail = orefs;
 +      for (rm = orefs; rm; rm = rm->next) {
 +              rm->fetch_head_status = FETCH_HEAD_IGNORE;
 +              tail = &rm->next;
 +      }
 +
 +      return ref_remove_duplicates(ref_map);
  }
  
  #define STORE_REF_ERROR_OTHER 1
@@@ -432,7 -315,7 +434,7 @@@ static int update_local_ref(struct ref 
        }
  
        if (!is_null_sha1(ref->old_sha1) &&
 -          !prefixcmp(ref->name, "refs/tags/")) {
 +          starts_with(ref->name, "refs/tags/")) {
                int r;
                r = s_update_ref("updating tag", ref, 0);
                strbuf_addf(display, "%c %-*s %-*s -> %s%s",
                 * more likely to follow a standard layout.
                 */
                const char *name = remote_ref ? remote_ref->name : "";
 -              if (!prefixcmp(name, "refs/tags/")) {
 +              if (starts_with(name, "refs/tags/")) {
                        msg = "storing tag";
                        what = _("[new tag]");
 -              } else if (!prefixcmp(name, "refs/heads/")) {
 +              } else if (starts_with(name, "refs/heads/")) {
                        msg = "storing head";
                        what = _("[new branch]");
                } else {
@@@ -524,6 -407,8 +526,8 @@@ static int iterate_ref_map(void *cb_dat
        struct ref **rm = cb_data;
        struct ref *ref = *rm;
  
+       while (ref && ref->status == REF_STATUS_REJECT_SHALLOW)
+               ref = ref->next;
        if (!ref)
                return -1; /* end of the list */
        *rm = ref->next;
@@@ -536,7 -421,7 +540,7 @@@ static int store_updated_refs(const cha
  {
        FILE *fp;
        struct commit *commit;
 -      int url_len, i, shown_url = 0, rc = 0;
 +      int url_len, i, rc = 0;
        struct strbuf note = STRBUF_INIT;
        const char *what, *kind;
        struct ref *rm;
                        struct ref *ref = NULL;
                        const char *merge_status_marker = "";
  
+                       if (rm->status == REF_STATUS_REJECT_SHALLOW) {
+                               if (want_status == FETCH_HEAD_MERGE)
+                                       warning(_("reject %s because shallow roots are not allowed to be updated"),
+                                               rm->peer_ref ? rm->peer_ref->name : rm->name);
+                               continue;
+                       }
                        commit = lookup_commit_reference_gently(rm->old_sha1, 1);
                        if (!commit)
                                rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE;
                                kind = "";
                                what = "";
                        }
 -                      else if (!prefixcmp(rm->name, "refs/heads/")) {
 +                      else if (starts_with(rm->name, "refs/heads/")) {
                                kind = "branch";
                                what = rm->name + 11;
                        }
 -                      else if (!prefixcmp(rm->name, "refs/tags/")) {
 +                      else if (starts_with(rm->name, "refs/tags/")) {
                                kind = "tag";
                                what = rm->name + 10;
                        }
 -                      else if (!prefixcmp(rm->name, "refs/remotes/")) {
 +                      else if (starts_with(rm->name, "refs/remotes/")) {
                                kind = "remote-tracking branch";
                                what = rm->name + 13;
                        }
@@@ -709,36 -601,17 +720,36 @@@ static int fetch_refs(struct transport 
        return ret;
  }
  
 -static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map)
 +static int prune_refs(struct refspec *refs, int ref_count, struct ref *ref_map,
 +              const char *raw_url)
  {
 -      int result = 0;
 +      int url_len, i, result = 0;
        struct ref *ref, *stale_refs = get_stale_heads(refs, ref_count, ref_map);
 +      char *url;
        const char *dangling_msg = dry_run
                ? _("   (%s will become dangling)")
                : _("   (%s has become dangling)");
  
 +      if (raw_url)
 +              url = transport_anonymize_url(raw_url);
 +      else
 +              url = xstrdup("foreign");
 +
 +      url_len = strlen(url);
 +      for (i = url_len - 1; url[i] == '/' && 0 <= i; i--)
 +              ;
 +
 +      url_len = i + 1;
 +      if (4 < i && !strncmp(".git", url + i - 3, 4))
 +              url_len = i - 3;
 +
        for (ref = stale_refs; ref; ref = ref->next) {
                if (!dry_run)
                        result |= delete_ref(ref->name, NULL, 0);
 +              if (verbosity >= 0 && !shown_url) {
 +                      fprintf(stderr, _("From %.*s\n"), url_len, url);
 +                      shown_url = 1;
 +              }
                if (verbosity >= 0) {
                        fprintf(stderr, " x %-*s %-*s -> %s\n",
                                TRANSPORT_SUMMARY(_("[deleted]")),
                        warn_dangling_symref(stderr, dangling_msg, ref->name);
                }
        }
 +      free(url);
        free_refs(stale_refs);
        return result;
  }
  
 -static int add_existing(const char *refname, const unsigned char *sha1,
 -                      int flag, void *cbdata)
 -{
 -      struct string_list *list = (struct string_list *)cbdata;
 -      struct string_list_item *item = string_list_insert(list, refname);
 -      item->util = xmalloc(20);
 -      hashcpy(item->util, sha1);
 -      return 0;
 -}
 -
 -static int will_fetch(struct ref **head, const unsigned char *sha1)
 -{
 -      struct ref *rm = *head;
 -      while (rm) {
 -              if (!hashcmp(rm->old_sha1, sha1))
 -                      return 1;
 -              rm = rm->next;
 -      }
 -      return 0;
 -}
 -
 -static void find_non_local_tags(struct transport *transport,
 -                      struct ref **head,
 -                      struct ref ***tail)
 -{
 -      struct string_list existing_refs = STRING_LIST_INIT_DUP;
 -      struct string_list remote_refs = STRING_LIST_INIT_NODUP;
 -      const struct ref *ref;
 -      struct string_list_item *item = NULL;
 -
 -      for_each_ref(add_existing, &existing_refs);
 -      for (ref = transport_get_remote_refs(transport); ref; ref = ref->next) {
 -              if (prefixcmp(ref->name, "refs/tags/"))
 -                      continue;
 -
 -              /*
 -               * The peeled ref always follows the matching base
 -               * ref, so if we see a peeled ref that we don't want
 -               * to fetch then we can mark the ref entry in the list
 -               * as one to ignore by setting util to NULL.
 -               */
 -              if (!suffixcmp(ref->name, "^{}")) {
 -                      if (item && !has_sha1_file(ref->old_sha1) &&
 -                          !will_fetch(head, ref->old_sha1) &&
 -                          !has_sha1_file(item->util) &&
 -                          !will_fetch(head, item->util))
 -                              item->util = NULL;
 -                      item = NULL;
 -                      continue;
 -              }
 -
 -              /*
 -               * If item is non-NULL here, then we previously saw a
 -               * ref not followed by a peeled reference, so we need
 -               * to check if it is a lightweight tag that we want to
 -               * fetch.
 -               */
 -              if (item && !has_sha1_file(item->util) &&
 -                  !will_fetch(head, item->util))
 -                      item->util = NULL;
 -
 -              item = NULL;
 -
 -              /* skip duplicates and refs that we already have */
 -              if (string_list_has_string(&remote_refs, ref->name) ||
 -                  string_list_has_string(&existing_refs, ref->name))
 -                      continue;
 -
 -              item = string_list_insert(&remote_refs, ref->name);
 -              item->util = (void *)ref->old_sha1;
 -      }
 -      string_list_clear(&existing_refs, 1);
 -
 -      /*
 -       * We may have a final lightweight tag that needs to be
 -       * checked to see if it needs fetching.
 -       */
 -      if (item && !has_sha1_file(item->util) &&
 -          !will_fetch(head, item->util))
 -              item->util = NULL;
 -
 -      /*
 -       * For all the tags in the remote_refs string list,
 -       * add them to the list of refs to be fetched
 -       */
 -      for_each_string_list_item(item, &remote_refs) {
 -              /* Unless we have already decided to ignore this item... */
 -              if (item->util)
 -              {
 -                      struct ref *rm = alloc_ref(item->string);
 -                      rm->peer_ref = alloc_ref(item->string);
 -                      hashcpy(rm->old_sha1, item->util);
 -                      **tail = rm;
 -                      *tail = &rm->next;
 -              }
 -      }
 -
 -      string_list_clear(&remote_refs, 0);
 -}
 -
  static void check_not_current_branch(struct ref *ref_map)
  {
        struct branch *current_branch = branch_get(NULL);
@@@ -798,6 -770,8 +809,8 @@@ static struct transport *prepare_transp
                set_option(transport, TRANS_OPT_KEEP, "yes");
        if (depth)
                set_option(transport, TRANS_OPT_DEPTH, depth);
+       if (update_shallow)
+               set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
        return transport;
  }
  
@@@ -863,26 -837,39 +876,26 @@@ static int do_fetch(struct transport *t
  
        if (tags == TAGS_DEFAULT && autotags)
                transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
 -      if (fetch_refs(transport, ref_map)) {
 -              free_refs(ref_map);
 -              retcode = 1;
 -              goto cleanup;
 -      }
        if (prune) {
                /*
 -               * If --tags was specified, pretend that the user gave us
 -               * the canonical tags refspec
 +               * We only prune based on refspecs specified
 +               * explicitly (via command line or configuration); we
 +               * don't care whether --tags was specified.
                 */
 -              if (tags == TAGS_SET) {
 -                      const char *tags_str = "refs/tags/*:refs/tags/*";
 -                      struct refspec *tags_refspec, *refspec;
 -
 -                      /* Copy the refspec and add the tags to it */
 -                      refspec = xcalloc(ref_count + 1, sizeof(struct refspec));
 -                      tags_refspec = parse_fetch_refspec(1, &tags_str);
 -                      memcpy(refspec, refs, ref_count * sizeof(struct refspec));
 -                      memcpy(&refspec[ref_count], tags_refspec, sizeof(struct refspec));
 -                      ref_count++;
 -
 -                      prune_refs(refspec, ref_count, ref_map);
 -
 -                      ref_count--;
 -                      /* The rest of the strings belong to fetch_one */
 -                      free_refspec(1, tags_refspec);
 -                      free(refspec);
 -              } else if (ref_count) {
 -                      prune_refs(refs, ref_count, ref_map);
 +              if (ref_count) {
 +                      prune_refs(refs, ref_count, ref_map, transport->url);
                } else {
 -                      prune_refs(transport->remote->fetch, transport->remote->fetch_refspec_nr, ref_map);
 +                      prune_refs(transport->remote->fetch,
 +                                 transport->remote->fetch_refspec_nr,
 +                                 ref_map,
 +                                 transport->url);
                }
        }
 +      if (fetch_refs(transport, ref_map)) {
 +              free_refs(ref_map);
 +              retcode = 1;
 +              goto cleanup;
 +      }
        free_refs(ref_map);
  
        /* if neither --no-tags nor --tags was specified, do automated tag
@@@ -918,7 -905,7 +931,7 @@@ static int get_remote_group(const char 
  {
        struct remote_group_data *g = priv;
  
 -      if (!prefixcmp(key, "remotes.") &&
 +      if (starts_with(key, "remotes.") &&
                        !strcmp(key + 8, g->name)) {
                /* split list by white space */
                int space = strcspn(value, " \t\n");
@@@ -956,8 -943,8 +969,8 @@@ static void add_options_to_argv(struct 
  {
        if (dry_run)
                argv_array_push(argv, "--dry-run");
 -      if (prune > 0)
 -              argv_array_push(argv, "--prune");
 +      if (prune != -1)
 +              argv_array_push(argv, prune ? "--prune" : "--no-prune");
        if (update_head_ok)
                argv_array_push(argv, "--update-head-ok");
        if (force)
@@@ -1101,10 -1088,6 +1114,10 @@@ int cmd_fetch(int argc, const char **ar
                }
        }
  
 +      /* no need to be strict, transport_set_option() will validate it again */
 +      if (depth && atoi(depth) < 1)
 +              die(_("depth %s is not a positive number"), depth);
 +
        if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
                if (recurse_submodules_default) {
                        int arg = parse_fetch_recurse_submodules_arg("--recurse-submodules-default", recurse_submodules_default);
diff --combined builtin/gc.c
index 25f2237c08f3b8fbc8f3554f3e22bf7e59e74b1c,cec8ecd75442e1721ab49be8a4d72b43ce244008..c19545d49e217400ee1736183c870557b9c9d54b
@@@ -16,6 -16,7 +16,7 @@@
  #include "run-command.h"
  #include "sigchain.h"
  #include "argv-array.h"
+ #include "commit.h"
  
  #define FAILED_RUN "failed to run %s"
  
@@@ -222,7 -223,7 +223,7 @@@ static const char *lock_repo_for_gc(in
                        time(NULL) - st.st_mtime <= 12 * 3600 &&
                        fscanf(fp, "%"PRIuMAX" %127c", &pid, locking_host) == 2 &&
                        /* be gentle to concurrent "gc" on remote hosts */
 -                      (strcmp(locking_host, my_host) || !kill(pid, 0));
 +                      (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM);
                if (fp != NULL)
                        fclose(fp);
                if (should_exit) {
diff --combined builtin/prune.c
index beb0dc942c1263ecf4aa5ab87b10520f481ebe43,221404034995ba2fa4bb113c4015cc7eb5b01c75..de43b26cfe9c7610c13e3320bdac9fa2049e4db6
@@@ -17,8 -17,9 +17,8 @@@ static int verbose
  static unsigned long expire;
  static int show_progress = -1;
  
 -static int prune_tmp_object(const char *path, const char *filename)
 +static int prune_tmp_file(const char *fullpath)
  {
 -      const char *fullpath = mkpath("%s/%s", path, filename);
        struct stat st;
        if (lstat(fullpath, &st))
                return error("Could not stat '%s'", fullpath);
@@@ -31,8 -32,9 +31,8 @@@
        return 0;
  }
  
 -static int prune_object(char *path, const char *filename, const unsigned char *sha1)
 +static int prune_object(const char *fullpath, const unsigned char *sha1)
  {
 -      const char *fullpath = mkpath("%s/%s", path, filename);
        struct stat st;
        if (lstat(fullpath, &st))
                return error("Could not stat '%s'", fullpath);
        return 0;
  }
  
 -static int prune_dir(int i, char *path)
 +static int prune_dir(int i, struct strbuf *path)
  {
 -      DIR *dir = opendir(path);
 +      size_t baselen = path->len;
 +      DIR *dir = opendir(path->buf);
        struct dirent *de;
  
        if (!dir)
                        if (lookup_object(sha1))
                                continue;
  
 -                      prune_object(path, de->d_name, sha1);
 +                      strbuf_addf(path, "/%s", de->d_name);
 +                      prune_object(path->buf, sha1);
 +                      strbuf_setlen(path, baselen);
                        continue;
                }
 -              if (!prefixcmp(de->d_name, "tmp_obj_")) {
 -                      prune_tmp_object(path, de->d_name);
 +              if (starts_with(de->d_name, "tmp_obj_")) {
 +                      strbuf_addf(path, "/%s", de->d_name);
 +                      prune_tmp_file(path->buf);
 +                      strbuf_setlen(path, baselen);
                        continue;
                }
 -              fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name);
 +              fprintf(stderr, "bad sha1 file: %s/%s\n", path->buf, de->d_name);
        }
        closedir(dir);
        if (!show_only)
 -              rmdir(path);
 +              rmdir(path->buf);
        return 0;
  }
  
  static void prune_object_dir(const char *path)
  {
 +      struct strbuf buf = STRBUF_INIT;
 +      size_t baselen;
        int i;
 +
 +      strbuf_addstr(&buf, path);
 +      strbuf_addch(&buf, '/');
 +      baselen = buf.len;
 +
        for (i = 0; i < 256; i++) {
 -              static char dir[4096];
 -              sprintf(dir, "%s/%02x", path, i);
 -              prune_dir(i, dir);
 +              strbuf_addf(&buf, "%02x", i);
 +              prune_dir(i, &buf);
 +              strbuf_setlen(&buf, baselen);
        }
  }
  
@@@ -129,8 -119,8 +129,8 @@@ static void remove_temporary_files(cons
                return;
        }
        while ((de = readdir(dir)) != NULL)
 -              if (!prefixcmp(de->d_name, "tmp_"))
 -                      prune_tmp_object(path, de->d_name);
 +              if (starts_with(de->d_name, "tmp_"))
 +                      prune_tmp_file(mkpath("%s/%s", path, de->d_name));
        closedir(dir);
  }
  
@@@ -180,5 -170,9 +180,9 @@@ int cmd_prune(int argc, const char **ar
        s = mkpathdup("%s/pack", get_object_directory());
        remove_temporary_files(s);
        free(s);
+       if (is_repository_shallow())
+               prune_shallow(show_only);
        return 0;
  }
diff --combined builtin/receive-pack.c
index e09994f4dfa3a50ea285ad20d61dd845f4bea357,bc4f5dc4632350c39972a0c1c589f10f90b120df..85bba356fab7743506f00bb0c5ca955eb9112bd5
@@@ -13,6 -13,7 +13,7 @@@
  #include "string-list.h"
  #include "sha1-array.h"
  #include "connected.h"
+ #include "argv-array.h"
  #include "version.h"
  
  static const char receive_pack_usage[] = "git receive-pack <git-dir>";
@@@ -43,6 -44,8 +44,8 @@@ static int fix_thin = 1
  static const char *head_name;
  static void *head_name_to_free;
  static int sent_capabilities;
+ static int shallow_update;
+ static const char *alt_shallow_file;
  
  static enum deny_action parse_deny_action(const char *var, const char *value)
  {
@@@ -121,6 -124,11 +124,11 @@@ static int receive_pack_config(const ch
                return 0;
        }
  
+       if (strcmp(var, "receive.shallowupdate") == 0) {
+               shallow_update = git_config_bool(var, value);
+               return 0;
+       }
        return git_default_config(var, value, cb);
  }
  
@@@ -178,6 -186,8 +186,8 @@@ static void write_head_info(void
        if (!sent_capabilities)
                show_ref("capabilities^{}", null_sha1);
  
+       advertise_shallow_grafts(1);
        /* EOF */
        packet_flush(1);
  }
@@@ -187,6 -197,7 +197,7 @@@ struct command 
        const char *error_string;
        unsigned int skip_update:1,
                     did_not_exist:1;
+       int index;
        unsigned char old_sha1[20];
        unsigned char new_sha1[20];
        char ref_name[FLEX_ARRAY]; /* more */
@@@ -418,7 -429,46 +429,46 @@@ static void refuse_unconfigured_deny_de
                rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
  }
  
- static const char *update(struct command *cmd)
+ static int command_singleton_iterator(void *cb_data, unsigned char sha1[20]);
+ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
+ {
+       static struct lock_file shallow_lock;
+       struct sha1_array extra = SHA1_ARRAY_INIT;
+       const char *alt_file;
+       uint32_t mask = 1 << (cmd->index % 32);
+       int i;
+       trace_printf_key("GIT_TRACE_SHALLOW",
+                        "shallow: update_shallow_ref %s\n", cmd->ref_name);
+       for (i = 0; i < si->shallow->nr; i++)
+               if (si->used_shallow[i] &&
+                   (si->used_shallow[i][cmd->index / 32] & mask) &&
+                   !delayed_reachability_test(si, i))
+                       sha1_array_append(&extra, si->shallow->sha1[i]);
+       setup_alternate_shallow(&shallow_lock, &alt_file, &extra);
+       if (check_shallow_connected(command_singleton_iterator,
+                                   0, cmd, alt_file)) {
+               rollback_lock_file(&shallow_lock);
+               sha1_array_clear(&extra);
+               return -1;
+       }
+       commit_lock_file(&shallow_lock);
+       /*
+        * Make sure setup_alternate_shallow() for the next ref does
+        * not lose these new roots..
+        */
+       for (i = 0; i < extra.nr; i++)
+               register_shallow(extra.sha1[i]);
+       si->shallow_ref[cmd->index] = 0;
+       sha1_array_clear(&extra);
+       return 0;
+ }
+ static const char *update(struct command *cmd, struct shallow_info *si)
  {
        const char *name = cmd->ref_name;
        struct strbuf namespaced_name_buf = STRBUF_INIT;
        struct ref_lock *lock;
  
        /* only refs/... are allowed */
 -      if (prefixcmp(name, "refs/") || check_refname_format(name + 5, 0)) {
 +      if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) {
                rp_error("refusing to create funny ref '%s' remotely", name);
                return "funny refname";
        }
        }
  
        if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
 -              if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
 +              if (deny_deletes && starts_with(name, "refs/heads/")) {
                        rp_error("denying ref deletion for %s", name);
                        return "deletion prohibited";
                }
  
        if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
            !is_null_sha1(old_sha1) &&
 -          !prefixcmp(name, "refs/heads/")) {
 +          starts_with(name, "refs/heads/")) {
                struct object *old_object, *new_object;
                struct commit *old_commit, *new_commit;
  
                return NULL; /* good */
        }
        else {
+               if (shallow_update && si->shallow_ref[cmd->index] &&
+                   update_shallow_ref(cmd, si))
+                       return "shallow error";
                lock = lock_any_ref_for_update(namespaced_name, old_sha1,
                                               0, NULL);
                if (!lock) {
@@@ -666,12 -720,16 +720,16 @@@ static int command_singleton_iterator(v
        return 0;
  }
  
- static void set_connectivity_errors(struct command *commands)
+ static void set_connectivity_errors(struct command *commands,
+                                   struct shallow_info *si)
  {
        struct command *cmd;
  
        for (cmd = commands; cmd; cmd = cmd->next) {
                struct command *singleton = cmd;
+               if (shallow_update && si->shallow_ref[cmd->index])
+                       /* to be checked in update_shallow_ref() */
+                       continue;
                if (!check_everything_connected(command_singleton_iterator,
                                                0, &singleton))
                        continue;
        }
  }
  
+ struct iterate_data {
+       struct command *cmds;
+       struct shallow_info *si;
+ };
  static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])
  {
-       struct command **cmd_list = cb_data;
+       struct iterate_data *data = cb_data;
+       struct command **cmd_list = &data->cmds;
        struct command *cmd = *cmd_list;
  
-       while (cmd) {
-               if (!is_null_sha1(cmd->new_sha1)) {
+       for (; cmd; cmd = cmd->next) {
+               if (shallow_update && data->si->shallow_ref[cmd->index])
+                       /* to be checked in update_shallow_ref() */
+                       continue;
+               if (!is_null_sha1(cmd->new_sha1) && !cmd->skip_update) {
                        hashcpy(sha1, cmd->new_sha1);
                        *cmd_list = cmd->next;
                        return 0;
                }
-               cmd = cmd->next;
        }
        *cmd_list = NULL;
        return -1; /* end of list */
@@@ -710,10 -776,14 +776,14 @@@ static void reject_updates_to_hidden(st
        }
  }
  
- static void execute_commands(struct command *commands, const char *unpacker_error)
+ 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;
  
        if (unpacker_error) {
                for (cmd = commands; cmd; cmd = cmd->next)
                return;
        }
  
-       cmd = commands;
-       if (check_everything_connected(iterate_receive_command_list,
-                                      0, &cmd))
-               set_connectivity_errors(commands);
+       data.cmds = commands;
+       data.si = si;
+       if (check_everything_connected(iterate_receive_command_list, 0, &data))
+               set_connectivity_errors(commands, si);
  
        reject_updates_to_hidden(commands);
  
        free(head_name_to_free);
        head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, 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);
+               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 (shallow_update) {
+               if (!checked_connectivity)
+                       error("BUG: run 'git fsck' for safety.\n"
+                             "If there are errors, try to remove "
+                             "the reported refs above");
+               if (alt_shallow_file && *alt_shallow_file)
+                       unlink(alt_shallow_file);
        }
  }
  
- static struct command *read_head_info(void)
+ static struct command *read_head_info(struct sha1_array *shallow)
  {
        struct command *commands = NULL;
        struct command **p = &commands;
                line = packet_read_line(0, &len);
                if (!line)
                        break;
 -              if (len == 48 && !prefixcmp(line, "shallow ")) {
++              if (len == 48 && starts_with(line, "shallow ")) {
+                       if (get_sha1_hex(line + 8, old_sha1))
+                               die("protocol error: expected shallow sha, got '%s'", line + 8);
+                       sha1_array_append(shallow, old_sha1);
+                       continue;
+               }
                if (len < 83 ||
                    line[40] != ' ' ||
                    line[81] != ' ' ||
@@@ -817,11 -911,14 +911,14 @@@ static const char *parse_pack_header(st
  
  static const char *pack_lockfile;
  
- static const char *unpack(int err_fd)
+ static const char *unpack(int err_fd, struct shallow_info *si)
  {
        struct pack_header hdr;
+       struct argv_array av = ARGV_ARRAY_INIT;
        const char *hdr_err;
+       int status;
        char hdr_arg[38];
+       struct child_process child;
        int fsck_objects = (receive_fsck_objects >= 0
                            ? receive_fsck_objects
                            : transfer_fsck_objects >= 0
                        "--pack_header=%"PRIu32",%"PRIu32,
                        ntohl(hdr.hdr_version), ntohl(hdr.hdr_entries));
  
+       if (si->nr_ours || si->nr_theirs) {
+               alt_shallow_file = setup_temporary_shallow(si->shallow);
+               argv_array_pushl(&av, "--shallow-file", alt_shallow_file, NULL);
+       }
+       memset(&child, 0, sizeof(child));
        if (ntohl(hdr.hdr_entries) < unpack_limit) {
-               int code, i = 0;
-               struct child_process child;
-               const char *unpacker[5];
-               unpacker[i++] = "unpack-objects";
+               argv_array_pushl(&av, "unpack-objects", hdr_arg, NULL);
                if (quiet)
-                       unpacker[i++] = "-q";
+                       argv_array_push(&av, "-q");
                if (fsck_objects)
-                       unpacker[i++] = "--strict";
-               unpacker[i++] = hdr_arg;
-               unpacker[i++] = NULL;
-               memset(&child, 0, sizeof(child));
-               child.argv = unpacker;
+                       argv_array_push(&av, "--strict");
+               child.argv = av.argv;
                child.no_stdout = 1;
                child.err = err_fd;
                child.git_cmd = 1;
-               code = run_command(&child);
-               if (!code)
-                       return NULL;
-               return "unpack-objects abnormal exit";
+               status = run_command(&child);
+               if (status)
+                       return "unpack-objects abnormal exit";
        } else {
-               const char *keeper[7];
-               int s, status, i = 0;
+               int s;
                char keep_arg[256];
-               struct child_process ip;
  
                s = sprintf(keep_arg, "--keep=receive-pack %"PRIuMAX" on ", (uintmax_t) getpid());
                if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
                        strcpy(keep_arg + s, "localhost");
  
-               keeper[i++] = "index-pack";
-               keeper[i++] = "--stdin";
+               argv_array_pushl(&av, "index-pack",
+                                "--stdin", hdr_arg, keep_arg, NULL);
                if (fsck_objects)
-                       keeper[i++] = "--strict";
+                       argv_array_push(&av, "--strict");
                if (fix_thin)
-                       keeper[i++] = "--fix-thin";
-               keeper[i++] = hdr_arg;
-               keeper[i++] = keep_arg;
-               keeper[i++] = NULL;
-               memset(&ip, 0, sizeof(ip));
-               ip.argv = keeper;
-               ip.out = -1;
-               ip.err = err_fd;
-               ip.git_cmd = 1;
-               status = start_command(&ip);
-               if (status) {
+                       argv_array_push(&av, "--fix-thin");
+               child.argv = av.argv;
+               child.out = -1;
+               child.err = err_fd;
+               child.git_cmd = 1;
+               status = start_command(&child);
+               if (status)
                        return "index-pack fork failed";
-               }
-               pack_lockfile = index_pack_lockfile(ip.out);
-               close(ip.out);
-               status = finish_command(&ip);
-               if (!status) {
-                       reprepare_packed_git();
-                       return NULL;
-               }
-               return "index-pack abnormal exit";
+               pack_lockfile = index_pack_lockfile(child.out);
+               close(child.out);
+               status = finish_command(&child);
+               if (status)
+                       return "index-pack abnormal exit";
+               reprepare_packed_git();
        }
+       return NULL;
  }
  
- static const char *unpack_with_sideband(void)
+ static const char *unpack_with_sideband(struct shallow_info *si)
  {
        struct async muxer;
        const char *ret;
  
        if (!use_sideband)
-               return unpack(0);
+               return unpack(0, si);
  
        memset(&muxer, 0, sizeof(muxer));
        muxer.proc = copy_to_sideband;
        if (start_async(&muxer))
                return NULL;
  
-       ret = unpack(muxer.in);
+       ret = unpack(muxer.in, si);
  
        finish_async(&muxer);
        return ret;
  }
  
+ static void prepare_shallow_update(struct command *commands,
+                                  struct shallow_info *si)
+ {
+       int i, j, k, bitmap_size = (si->ref->nr + 31) / 32;
+       si->used_shallow = xmalloc(sizeof(*si->used_shallow) *
+                                  si->shallow->nr);
+       assign_shallow_commits_to_refs(si, si->used_shallow, NULL);
+       si->need_reachability_test =
+               xcalloc(si->shallow->nr, sizeof(*si->need_reachability_test));
+       si->reachable =
+               xcalloc(si->shallow->nr, sizeof(*si->reachable));
+       si->shallow_ref = xcalloc(si->ref->nr, sizeof(*si->shallow_ref));
+       for (i = 0; i < si->nr_ours; i++)
+               si->need_reachability_test[si->ours[i]] = 1;
+       for (i = 0; i < si->shallow->nr; i++) {
+               if (!si->used_shallow[i])
+                       continue;
+               for (j = 0; j < bitmap_size; j++) {
+                       if (!si->used_shallow[i][j])
+                               continue;
+                       si->need_reachability_test[i]++;
+                       for (k = 0; k < 32; k++)
+                               if (si->used_shallow[i][j] & (1 << k))
+                                       si->shallow_ref[j * 32 + k]++;
+               }
+               /*
+                * true for those associated with some refs and belong
+                * in "ours" list aka "step 7 not done yet"
+                */
+               si->need_reachability_test[i] =
+                       si->need_reachability_test[i] > 1;
+       }
+       /*
+        * keep hooks happy by forcing a temporary shallow file via
+        * env variable because we can't add --shallow-file to every
+        * command. check_everything_connected() will be done with
+        * true .git/shallow though.
+        */
+       setenv(GIT_SHALLOW_FILE_ENVIRONMENT, alt_shallow_file, 1);
+ }
+ static void update_shallow_info(struct command *commands,
+                               struct shallow_info *si,
+                               struct sha1_array *ref)
+ {
+       struct command *cmd;
+       int *ref_status;
+       remove_nonexistent_theirs_shallow(si);
+       if (!si->nr_ours && !si->nr_theirs) {
+               shallow_update = 0;
+               return;
+       }
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (is_null_sha1(cmd->new_sha1))
+                       continue;
+               sha1_array_append(ref, cmd->new_sha1);
+               cmd->index = ref->nr - 1;
+       }
+       si->ref = ref;
+       if (shallow_update) {
+               prepare_shallow_update(commands, si);
+               return;
+       }
+       ref_status = xmalloc(sizeof(*ref_status) * ref->nr);
+       assign_shallow_commits_to_refs(si, NULL, ref_status);
+       for (cmd = commands; cmd; cmd = cmd->next) {
+               if (is_null_sha1(cmd->new_sha1))
+                       continue;
+               if (ref_status[cmd->index]) {
+                       cmd->error_string = "shallow update not allowed";
+                       cmd->skip_update = 1;
+               }
+       }
+       if (alt_shallow_file && *alt_shallow_file) {
+               unlink(alt_shallow_file);
+               alt_shallow_file = NULL;
+       }
+       free(ref_status);
+ }
  static void report(struct command *commands, const char *unpack_status)
  {
        struct command *cmd;
@@@ -958,6 -1135,9 +1135,9 @@@ int cmd_receive_pack(int argc, const ch
        int i;
        char *dir = NULL;
        struct command *commands;
+       struct sha1_array shallow = SHA1_ARRAY_INIT;
+       struct sha1_array ref = SHA1_ARRAY_INIT;
+       struct shallow_info si;
  
        packet_trace_identity("receive-pack");
  
        if (!enter_repo(dir, 0))
                die("'%s' does not appear to be a git repository", dir);
  
-       if (is_repository_shallow())
-               die("attempt to push into a shallow repository");
        git_config(receive_pack_config, NULL);
  
        if (0 <= transfer_unpack_limit)
        if (advertise_refs)
                return 0;
  
-       if ((commands = read_head_info()) != NULL) {
+       if ((commands = read_head_info(&shallow)) != NULL) {
                const char *unpack_status = NULL;
  
-               if (!delete_only(commands))
-                       unpack_status = unpack_with_sideband();
-               execute_commands(commands, unpack_status);
+               prepare_shallow_info(&si, &shallow);
+               if (!si.nr_ours && !si.nr_theirs)
+                       shallow_update = 0;
+               if (!delete_only(commands)) {
+                       unpack_status = unpack_with_sideband(&si);
+                       update_shallow_info(commands, &si, &ref);
+               }
+               execute_commands(commands, unpack_status, &si);
                if (pack_lockfile)
                        unlink_or_warn(pack_lockfile);
                if (report_status)
                }
                if (auto_update_server_info)
                        update_server_info(0);
+               clear_shallow_info(&si);
        }
        if (use_sideband)
                packet_flush(1);
+       sha1_array_clear(&shallow);
+       sha1_array_clear(&ref);
        return 0;
  }
diff --combined builtin/send-pack.c
index e7f0b97d315e0866a2bb3a95a0d1feebceddd545,cc257448177993dfec888e9b4a18320c116a469b..f420b74665bcf1746e94b4cc5eb66ded0a2235ff
@@@ -10,6 -10,7 +10,7 @@@
  #include "quote.h"
  #include "transport.h"
  #include "version.h"
+ #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"
@@@ -99,7 -100,8 +100,8 @@@ int cmd_send_pack(int argc, const char 
        const char *dest = NULL;
        int fd[2];
        struct child_process *conn;
-       struct extra_have_objects extra_have;
+       struct sha1_array extra_have = SHA1_ARRAY_INIT;
+       struct sha1_array shallow = SHA1_ARRAY_INIT;
        struct ref *remote_refs, *local_refs;
        int ret;
        int helper_status = 0;
                const char *arg = *argv;
  
                if (*arg == '-') {
 -                      if (!prefixcmp(arg, "--receive-pack=")) {
 +                      if (starts_with(arg, "--receive-pack=")) {
                                receivepack = arg + 15;
                                continue;
                        }
 -                      if (!prefixcmp(arg, "--exec=")) {
 +                      if (starts_with(arg, "--exec=")) {
                                receivepack = arg + 7;
                                continue;
                        }
 -                      if (!prefixcmp(arg, "--remote=")) {
 +                      if (starts_with(arg, "--remote=")) {
                                remote_name = arg + 9;
                                continue;
                        }
                                        exit(1);
                                continue;
                        }
 -                      if (!prefixcmp(arg, "--" CAS_OPT_NAME "=")) {
 +                      if (starts_with(arg, "--" CAS_OPT_NAME "=")) {
                                if (parse_push_cas_option(&cas,
                                                          strchr(arg, '=') + 1, 0) < 0)
                                        exit(1);
                        args.verbose ? CONNECT_VERBOSE : 0);
        }
  
-       memset(&extra_have, 0, sizeof(extra_have));
-       get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL, &extra_have);
+       get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL,
+                        &extra_have, &shallow);
  
        transport_verify_remote_names(nr_refspecs, refspecs);
  
diff --combined cache.h
index 83a27269b89871b166286550ae9cb22c85dc7d10,8b132878ce7d2ebcdfe046b59db53d3bb68530ca..c9efe88e9ec40f94401d7a71498396edc523a7cf
+++ b/cache.h
@@@ -354,6 -354,7 +354,7 @@@ static inline enum object_type object_t
  #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
  #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
  #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
+ #define GIT_SHALLOW_FILE_ENVIRONMENT "GIT_SHALLOW_FILE"
  #define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
  #define CONFIG_ENVIRONMENT "GIT_CONFIG"
  #define CONFIG_DATA_ENVIRONMENT "GIT_CONFIG_PARAMETERS"
@@@ -760,11 -761,11 +761,11 @@@ int daemon_avoid_alias(const char *path
  int offset_1st_component(const char *path);
  
  /* object replacement */
 -#define READ_SHA1_FILE_REPLACE 1
 +#define LOOKUP_REPLACE_OBJECT 1
  extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
  static inline void *read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
  {
 -      return read_sha1_file_extended(sha1, type, size, READ_SHA1_FILE_REPLACE);
 +      return read_sha1_file_extended(sha1, type, size, LOOKUP_REPLACE_OBJECT);
  }
  extern const unsigned char *do_lookup_replace_object(const unsigned char *sha1);
  static inline const unsigned char *lookup_replace_object(const unsigned char *sha1)
                return sha1;
        return do_lookup_replace_object(sha1);
  }
 +static inline const unsigned char *lookup_replace_object_extended(const unsigned char *sha1, unsigned flag)
 +{
 +      if (!(flag & LOOKUP_REPLACE_OBJECT))
 +              return sha1;
 +      return lookup_replace_object(sha1);
 +}
  
  /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
  extern int sha1_object_info(const unsigned char *, unsigned long *);
@@@ -1080,7 -1075,6 +1081,7 @@@ struct object_info 
        enum object_type *typep;
        unsigned long *sizep;
        unsigned long *disk_sizep;
 +      unsigned char *delta_base_sha1;
  
        /* Response */
        enum {
                } packed;
        } u;
  };
 -extern int sha1_object_info_extended(const unsigned char *, struct object_info *);
 +extern int sha1_object_info_extended(const unsigned char *, struct object_info *, unsigned flags);
  
  /* Dumb servers support */
  extern int update_server_info(int);
@@@ -1243,6 -1237,8 +1244,8 @@@ __attribute__((format (printf, 2, 3))
  extern void trace_argv_printf(const char **argv, const char *format, ...);
  extern void trace_repo_setup(const char *prefix);
  extern int trace_want(const char *key);
+ __attribute__((format (printf, 2, 3)))
+ extern void trace_printf_key(const char *key, const char *fmt, ...);
  extern void trace_strbuf(const char *key, const struct strbuf *buf);
  
  void packet_trace_identity(const char *prog);
diff --combined commit.h
index f8a451d86498cc1f6a9c60256f68a885262ecdd9,2a20b10d3957d3bdbcf09d3d69f1d39f7d6f5421..16d9c4351395ac55d6856d8d92d24ce7064df974
+++ b/commit.h
@@@ -49,7 -49,6 +49,7 @@@ struct commit *lookup_commit_or_die(con
  
  int parse_commit_buffer(struct commit *item, const void *buffer, unsigned long size);
  int parse_commit(struct commit *item);
 +void parse_commit_or_die(struct commit *item);
  
  /* Find beginning and length of commit subject. */
  int find_commit_subject(const char *commit_buffer, const char **subject);
@@@ -194,6 -193,8 +194,8 @@@ extern struct commit_list *get_octopus_
  /* largest positive number a signed 32-bit integer can contain */
  #define INFINITE_DEPTH 0x7fffffff
  
+ struct sha1_array;
+ struct ref;
  extern int register_shallow(const unsigned char *sha1);
  extern int unregister_shallow(const unsigned char *sha1);
  extern int for_each_commit_graft(each_commit_graft_fn, void *);
@@@ -201,11 -202,38 +203,38 @@@ extern int is_repository_shallow(void)
  extern struct commit_list *get_shallow_commits(struct object_array *heads,
                int depth, int shallow_flag, int not_shallow_flag);
  extern void check_shallow_file_for_update(void);
- extern void set_alternate_shallow_file(const char *path);
- extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol);
+ extern void set_alternate_shallow_file(const char *path, int override);
+ extern int write_shallow_commits(struct strbuf *out, int use_pack_protocol,
+                                const struct sha1_array *extra);
  extern void setup_alternate_shallow(struct lock_file *shallow_lock,
-                                   const char **alternate_shallow_file);
- extern char *setup_temporary_shallow(void);
+                                   const char **alternate_shallow_file,
+                                   const struct sha1_array *extra);
+ extern char *setup_temporary_shallow(const struct sha1_array *extra);
+ extern void advertise_shallow_grafts(int);
+ struct shallow_info {
+       struct sha1_array *shallow;
+       int *ours, nr_ours;
+       int *theirs, nr_theirs;
+       struct sha1_array *ref;
+       /* for receive-pack */
+       uint32_t **used_shallow;
+       int *need_reachability_test;
+       int *reachable;
+       int *shallow_ref;
+       struct commit **commits;
+       int nr_commits;
+ };
+ extern void prepare_shallow_info(struct shallow_info *, struct sha1_array *);
+ extern void clear_shallow_info(struct shallow_info *);
+ extern void remove_nonexistent_theirs_shallow(struct shallow_info *);
+ extern void assign_shallow_commits_to_refs(struct shallow_info *info,
+                                          uint32_t **used,
+                                          int *ref_status);
+ extern int delayed_reachability_test(struct shallow_info *si, int c);
+ extern void prune_shallow(int show_only);
  
  int is_descendant_of(struct commit *, struct commit_list *);
  int in_merge_bases(struct commit *, struct commit *);
@@@ -232,11 -260,11 +261,11 @@@ struct commit_extra_header 
  extern void append_merge_tag_headers(struct commit_list *parents,
                                     struct commit_extra_header ***tail);
  
 -extern int commit_tree(const struct strbuf *msg, unsigned char *tree,
 +extern int commit_tree(const struct strbuf *msg, const unsigned char *tree,
                       struct commit_list *parents, unsigned char *ret,
                       const char *author, const char *sign_commit);
  
 -extern int commit_tree_extended(const struct strbuf *msg, unsigned char *tree,
 +extern int commit_tree_extended(const struct strbuf *msg, const unsigned char *tree,
                                struct commit_list *parents, unsigned char *ret,
                                const char *author, const char *sign_commit,
                                struct commit_extra_header *);
diff --combined connect.c
index c763eeda863d62093a9f37d281e8b1afc1a1e254,efadd3cbeb0c95f00c7ffd3d1d8d01eae0d9e483..4150412e2c0ebf6129137baf41c9d268d50c60bd
+++ b/connect.c
@@@ -8,6 -8,7 +8,7 @@@
  #include "connect.h"
  #include "url.h"
  #include "string-list.h"
+ #include "sha1-array.h"
  
  static char *server_capabilities;
  static const char *parse_feature_value(const char *, const char *, int *);
@@@ -45,13 -46,6 +46,6 @@@ int check_ref_type(const struct ref *re
        return check_ref(ref->name, strlen(ref->name), flags);
  }
  
- static void add_extra_have(struct extra_have_objects *extra, unsigned char *sha1)
- {
-       ALLOC_GROW(extra->array, extra->nr + 1, extra->alloc);
-       hashcpy(&(extra->array[extra->nr][0]), sha1);
-       extra->nr++;
- }
  static void die_initial_contact(int got_at_least_one_head)
  {
        if (got_at_least_one_head)
@@@ -122,7 -116,8 +116,8 @@@ static void annotate_refs_with_symref_i
   */
  struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
                              struct ref **list, unsigned int flags,
-                             struct extra_have_objects *extra_have)
+                             struct sha1_array *extra_have,
+                             struct sha1_array *shallow_points)
  {
        struct ref **orig_list = list;
        int got_at_least_one_head = 0;
                if (!len)
                        break;
  
 -              if (len > 4 && !prefixcmp(buffer, "ERR "))
 +              if (len > 4 && starts_with(buffer, "ERR "))
                        die("remote error: %s", buffer + 4);
  
 -              if (len == 48 && !prefixcmp(buffer, "shallow ")) {
++              if (len == 48 && starts_with(buffer, "shallow ")) {
+                       if (get_sha1_hex(buffer + 8, old_sha1))
+                               die("protocol error: expected shallow sha-1, got '%s'", buffer + 8);
+                       if (!shallow_points)
+                               die("repository on the other end cannot be shallow");
+                       sha1_array_append(shallow_points, old_sha1);
+                       continue;
+               }
                if (len < 42 || get_sha1_hex(buffer, old_sha1) || buffer[40] != ' ')
                        die("protocol error: expected sha/ref, got '%s'", buffer);
                name = buffer + 41;
  
                if (extra_have &&
                    name_len == 5 && !memcmp(".have", name, 5)) {
-                       add_extra_have(extra_have, old_sha1);
+                       sha1_array_append(extra_have, old_sha1);
                        continue;
                }
  
@@@ -232,34 -236,10 +236,34 @@@ int server_supports(const char *feature
  
  enum protocol {
        PROTO_LOCAL = 1,
 +      PROTO_FILE,
        PROTO_SSH,
        PROTO_GIT
  };
  
 +int url_is_local_not_ssh(const char *url)
 +{
 +      const char *colon = strchr(url, ':');
 +      const char *slash = strchr(url, '/');
 +      return !colon || (slash && slash < colon) ||
 +              has_dos_drive_prefix(url);
 +}
 +
 +static const char *prot_name(enum protocol protocol)
 +{
 +      switch (protocol) {
 +              case PROTO_LOCAL:
 +              case PROTO_FILE:
 +                      return "file";
 +              case PROTO_SSH:
 +                      return "ssh";
 +              case PROTO_GIT:
 +                      return "git";
 +              default:
 +                      return "unkown protocol";
 +      }
 +}
 +
  static enum protocol get_protocol(const char *name)
  {
        if (!strcmp(name, "ssh"))
        if (!strcmp(name, "ssh+git"))
                return PROTO_SSH;
        if (!strcmp(name, "file"))
 -              return PROTO_LOCAL;
 +              return PROTO_FILE;
        die("I don't handle protocol '%s'", name);
  }
  
@@@ -551,31 -531,55 +555,31 @@@ static struct child_process *git_proxy_
        return proxy;
  }
  
 -#define MAX_CMD_LEN 1024
 -
 -static char *get_port(char *host)
 +static const char *get_port_numeric(const char *p)
  {
        char *end;
 -      char *p = strchr(host, ':');
 -
        if (p) {
                long port = strtol(p + 1, &end, 10);
                if (end != p + 1 && *end == '\0' && 0 <= port && port < 65536) {
 -                      *p = '\0';
 -                      return p+1;
 +                      return p;
                }
        }
  
        return NULL;
  }
  
 -static struct child_process no_fork;
 -
  /*
 - * This returns a dummy child_process if the transport protocol does not
 - * need fork(2), or a struct child_process object if it does.  Once done,
 - * finish the connection with finish_connect() with the value returned from
 - * this function (it is safe to call finish_connect() with NULL to support
 - * the former case).
 - *
 - * If it returns, the connect is successful; it just dies on errors (this
 - * will hopefully be changed in a libification effort, to return NULL when
 - * the connection failed).
 + * Extract protocol and relevant parts from the specified connection URL.
 + * The caller must free() the returned strings.
   */
 -struct child_process *git_connect(int fd[2], const char *url_orig,
 -                                const char *prog, int flags)
 +static enum protocol parse_connect_url(const char *url_orig, char **ret_host,
 +                                     char **ret_path)
  {
        char *url;
        char *host, *path;
        char *end;
 -      int c;
 -      struct child_process *conn = &no_fork;
 +      int separator = '/';
        enum protocol protocol = PROTO_LOCAL;
 -      int free_path = 0;
 -      char *port = NULL;
 -      const char **arg;
 -      struct strbuf cmd;
 -
 -      /* Without this we cannot rely on waitpid() to tell
 -       * what happened to our children.
 -       */
 -      signal(SIGCHLD, SIG_DFL);
  
        if (is_url(url_orig))
                url = url_decode(url_orig);
                *host = '\0';
                protocol = get_protocol(url);
                host += 3;
 -              c = '/';
        } else {
                host = url;
 -              c = ':';
 +              if (!url_is_local_not_ssh(url)) {
 +                      protocol = PROTO_SSH;
 +                      separator = ':';
 +              }
        }
  
        /*
 -       * Don't do destructive transforms with git:// as that
 -       * protocol code does '[]' unwrapping of its own.
 +       * Don't do destructive transforms as protocol code does
 +       * '[]' unwrapping in get_host_and_port()
         */
        if (host[0] == '[') {
                end = strchr(host + 1, ']');
                if (end) {
 -                      if (protocol != PROTO_GIT) {
 -                              *end = 0;
 -                              host++;
 -                      }
                        end++;
                } else
                        end = host;
        } else
                end = host;
  
 -      path = strchr(end, c);
 -      if (path && !has_dos_drive_prefix(end)) {
 -              if (c == ':') {
 -                      if (host != url || path < strchrnul(host, '/')) {
 -                              protocol = PROTO_SSH;
 -                              *path++ = '\0';
 -                      } else /* '/' in the host part, assume local path */
 -                              path = end;
 -              }
 -      } else
 +      if (protocol == PROTO_LOCAL)
                path = end;
 +      else if (protocol == PROTO_FILE && has_dos_drive_prefix(end))
 +              path = end; /* "file://$(pwd)" may be "file://C:/projects/repo" */
 +      else
 +              path = strchr(end, separator);
  
        if (!path || !*path)
                die("No path specified. See 'man git-pull' for valid url syntax");
         * null-terminate hostname and point path to ~ for URL's like this:
         *    ssh://host.xz/~user/repo
         */
 -      if (protocol != PROTO_LOCAL && host != url) {
 -              char *ptr = path;
 +
 +      end = path; /* Need to \0 terminate host here */
 +      if (separator == ':')
 +              path++; /* path starts after ':' */
 +      if (protocol == PROTO_GIT || protocol == PROTO_SSH) {
                if (path[1] == '~')
                        path++;
 -              else {
 -                      path = xstrdup(ptr);
 -                      free_path = 1;
 -              }
 -
 -              *ptr = '\0';
        }
  
 -      /*
 -       * Add support for ssh port: ssh://host.xy:<port>/...
 +      path = xstrdup(path);
 +      *end = '\0';
 +
 +      *ret_host = xstrdup(host);
 +      *ret_path = path;
 +      free(url);
 +      return protocol;
 +}
 +
 +static struct child_process no_fork;
 +
 +/*
 + * This returns a dummy child_process if the transport protocol does not
 + * need fork(2), or a struct child_process object if it does.  Once done,
 + * finish the connection with finish_connect() with the value returned from
 + * this function (it is safe to call finish_connect() with NULL to support
 + * the former case).
 + *
 + * If it returns, the connect is successful; it just dies on errors (this
 + * will hopefully be changed in a libification effort, to return NULL when
 + * the connection failed).
 + */
 +struct child_process *git_connect(int fd[2], const char *url,
 +                                const char *prog, int flags)
 +{
 +      char *hostandport, *path;
 +      struct child_process *conn = &no_fork;
 +      enum protocol protocol;
 +      const char **arg;
 +      struct strbuf cmd = STRBUF_INIT;
 +
 +      /* Without this we cannot rely on waitpid() to tell
 +       * what happened to our children.
         */
 -      if (protocol == PROTO_SSH && host != url)
 -              port = get_port(end);
 +      signal(SIGCHLD, SIG_DFL);
  
 -      if (protocol == PROTO_GIT) {
 +      protocol = parse_connect_url(url, &hostandport, &path);
 +      if (flags & CONNECT_DIAG_URL) {
 +              printf("Diag: url=%s\n", url ? url : "NULL");
 +              printf("Diag: protocol=%s\n", prot_name(protocol));
 +              printf("Diag: hostandport=%s\n", hostandport ? hostandport : "NULL");
 +              printf("Diag: path=%s\n", path ? path : "NULL");
 +              conn = NULL;
 +      } else if (protocol == PROTO_GIT) {
                /* These underlying connection commands die() if they
                 * cannot connect.
                 */
 -              char *target_host = xstrdup(host);
 -              if (git_use_proxy(host))
 -                      conn = git_proxy_connect(fd, host);
 +              char *target_host = xstrdup(hostandport);
 +              if (git_use_proxy(hostandport))
 +                      conn = git_proxy_connect(fd, hostandport);
                else
 -                      git_tcp_connect(fd, host, flags);
 +                      git_tcp_connect(fd, hostandport, flags);
                /*
                 * Separate original protocol components prog and path
                 * from extended host header with a NUL byte.
                             prog, path, 0,
                             target_host, 0);
                free(target_host);
 -              free(url);
 -              if (free_path)
 -                      free(path);
 -              return conn;
 -      }
 -
 -      conn = xcalloc(1, sizeof(*conn));
 -
 -      strbuf_init(&cmd, MAX_CMD_LEN);
 -      strbuf_addstr(&cmd, prog);
 -      strbuf_addch(&cmd, ' ');
 -      sq_quote_buf(&cmd, path);
 -      if (cmd.len >= MAX_CMD_LEN)
 -              die("command line too long");
 -
 -      conn->in = conn->out = -1;
 -      conn->argv = arg = xcalloc(7, sizeof(*arg));
 -      if (protocol == PROTO_SSH) {
 -              const char *ssh = getenv("GIT_SSH");
 -              int putty = ssh && strcasestr(ssh, "plink");
 -              if (!ssh) ssh = "ssh";
 -
 -              *arg++ = ssh;
 -              if (putty && !strcasestr(ssh, "tortoiseplink"))
 -                      *arg++ = "-batch";
 -              if (port) {
 -                      /* P is for PuTTY, p is for OpenSSH */
 -                      *arg++ = putty ? "-P" : "-p";
 -                      *arg++ = port;
 +      } else {
 +              conn = xcalloc(1, sizeof(*conn));
 +
 +              strbuf_addstr(&cmd, prog);
 +              strbuf_addch(&cmd, ' ');
 +              sq_quote_buf(&cmd, path);
 +
 +              conn->in = conn->out = -1;
 +              conn->argv = arg = xcalloc(7, sizeof(*arg));
 +              if (protocol == PROTO_SSH) {
 +                      const char *ssh = getenv("GIT_SSH");
 +                      int putty = ssh && strcasestr(ssh, "plink");
 +                      char *ssh_host = hostandport;
 +                      const char *port = NULL;
 +                      get_host_and_port(&ssh_host, &port);
 +                      port = get_port_numeric(port);
 +
 +                      if (!ssh) ssh = "ssh";
 +
 +                      *arg++ = ssh;
 +                      if (putty && !strcasestr(ssh, "tortoiseplink"))
 +                              *arg++ = "-batch";
 +                      if (port) {
 +                              /* P is for PuTTY, p is for OpenSSH */
 +                              *arg++ = putty ? "-P" : "-p";
 +                              *arg++ = port;
 +                      }
 +                      *arg++ = ssh_host;
 +              }       else {
 +                      /* remove repo-local variables from the environment */
 +                      conn->env = local_repo_env;
 +                      conn->use_shell = 1;
                }
 -              *arg++ = host;
 -      }
 -      else {
 -              /* remove repo-local variables from the environment */
 -              conn->env = local_repo_env;
 -              conn->use_shell = 1;
 -      }
 -      *arg++ = cmd.buf;
 -      *arg = NULL;
 +              *arg++ = cmd.buf;
 +              *arg = NULL;
  
 -      if (start_command(conn))
 -              die("unable to fork");
 +              if (start_command(conn))
 +                      die("unable to fork");
  
 -      fd[0] = conn->out; /* read from child's stdout */
 -      fd[1] = conn->in;  /* write to child's stdin */
 -      strbuf_release(&cmd);
 -      free(url);
 -      if (free_path)
 -              free(path);
 +              fd[0] = conn->out; /* read from child's stdout */
 +              fd[1] = conn->in;  /* write to child's stdin */
 +              strbuf_release(&cmd);
 +      }
 +      free(hostandport);
 +      free(path);
        return conn;
  }
  
diff --combined connected.c
index 51d8ba4bb7a5d04b7922a283e0ad0d904657c6b5,427389dc47caa207c6da351e736d37fe56868df3..be0253e21bec3617ad3a7f1b0afc7d2049efc4fd
@@@ -19,17 -19,17 +19,17 @@@ int check_everything_connected(sha1_ite
   *
   * Returns 0 if everything is connected, non-zero otherwise.
   */
- int check_everything_connected_with_transport(sha1_iterate_fn fn,
-                                             int quiet,
-                                             void *cb_data,
-                                             struct transport *transport)
+ static int check_everything_connected_real(sha1_iterate_fn fn,
+                                          int quiet,
+                                          void *cb_data,
+                                          struct transport *transport,
+                                          const char *shallow_file)
  {
        struct child_process rev_list;
-       const char *argv[] = {"rev-list", "--objects",
-                             "--stdin", "--not", "--all", NULL, NULL};
+       const char *argv[9];
        char commit[41];
        unsigned char sha1[20];
-       int err = 0;
+       int err = 0, ac = 0;
        struct packed_git *new_pack = NULL;
  
        if (fn(cb_data, sha1))
@@@ -38,7 -38,7 +38,7 @@@
        if (transport && transport->smart_options &&
            transport->smart_options->self_contained_and_connected &&
            transport->pack_lockfile &&
 -          !suffixcmp(transport->pack_lockfile, ".keep")) {
 +          ends_with(transport->pack_lockfile, ".keep")) {
                struct strbuf idx_file = STRBUF_INIT;
                strbuf_addstr(&idx_file, transport->pack_lockfile);
                strbuf_setlen(&idx_file, idx_file.len - 5); /* ".keep" */
                strbuf_release(&idx_file);
        }
  
+       if (shallow_file) {
+               argv[ac++] = "--shallow-file";
+               argv[ac++] = shallow_file;
+       }
+       argv[ac++] = "rev-list";
+       argv[ac++] = "--objects";
+       argv[ac++] = "--stdin";
+       argv[ac++] = "--not";
+       argv[ac++] = "--all";
        if (quiet)
-               argv[5] = "--quiet";
+               argv[ac++] = "--quiet";
+       argv[ac] = NULL;
  
        memset(&rev_list, 0, sizeof(rev_list));
        rev_list.argv = argv;
        sigchain_pop(SIGPIPE);
        return finish_command(&rev_list) || err;
  }
+ int check_everything_connected_with_transport(sha1_iterate_fn fn,
+                                             int quiet,
+                                             void *cb_data,
+                                             struct transport *transport)
+ {
+       return check_everything_connected_real(fn, quiet, cb_data,
+                                              transport, NULL);
+ }
+ int check_shallow_connected(sha1_iterate_fn fn, int quiet, void *cb_data,
+                           const char *shallow_file)
+ {
+       return check_everything_connected_real(fn, quiet, cb_data,
+                                              NULL, shallow_file);
+ }
diff --combined environment.c
index 3c76905b9ff1eac8f627c1e7a5c034ab421ac2e4,b73b39d72f71dd5e2b846147ce64e0a1ae96d18b..4a3437d8a61256029655f25c76256f27a6189248
@@@ -10,6 -10,7 +10,7 @@@
  #include "cache.h"
  #include "refs.h"
  #include "fmt-merge-msg.h"
+ #include "commit.h"
  
  int trust_executable_bit = 1;
  int trust_ctime = 1;
@@@ -97,6 -98,7 +98,7 @@@ const char * const local_repo_env[] = 
        INDEX_ENVIRONMENT,
        NO_REPLACE_OBJECTS_ENVIRONMENT,
        GIT_PREFIX_ENVIRONMENT,
+       GIT_SHALLOW_FILE_ENVIRONMENT,
        NULL
  };
  
@@@ -124,6 -126,7 +126,7 @@@ static char *expand_namespace(const cha
  static void setup_git_env(void)
  {
        const char *gitfile;
+       const char *shallow_file;
  
        git_dir = getenv(GIT_DIR_ENVIRONMENT);
        if (!git_dir)
                read_replace_refs = 0;
        namespace = expand_namespace(getenv(GIT_NAMESPACE_ENVIRONMENT));
        namespace_len = strlen(namespace);
+       shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT);
+       if (shallow_file)
+               set_alternate_shallow_file(shallow_file, 0);
  }
  
  int is_bare_repository(void)
@@@ -171,7 -177,7 +177,7 @@@ const char *get_git_namespace(void
  
  const char *strip_namespace(const char *namespaced_ref)
  {
 -      if (prefixcmp(namespaced_ref, get_git_namespace()) != 0)
 +      if (!starts_with(namespaced_ref, get_git_namespace()))
                return NULL;
        return namespaced_ref + namespace_len;
  }
diff --combined fetch-pack.c
index 760ed16e79a7a46eecfb4c9efecaee7feba0f121,9bc29cfce78719fbd773e5fed8a678485f8e8e0a..d52de74c4b1f1a4cfcda756918d310e475c4c12c
@@@ -13,6 -13,7 +13,7 @@@
  #include "transport.h"
  #include "version.h"
  #include "prio-queue.h"
+ #include "sha1-array.h"
  
  static int transfer_unpack_limit = -1;
  static int fetch_unpack_limit = -1;
@@@ -47,8 -48,9 +48,8 @@@ static void rev_list_push(struct commi
        if (!(commit->object.flags & mark)) {
                commit->object.flags |= mark;
  
 -              if (!(commit->object.parsed))
 -                      if (parse_commit(commit))
 -                              return;
 +              if (parse_commit(commit))
 +                      return;
  
                prio_queue_put(&rev_list, commit);
  
@@@ -127,7 -129,8 +128,7 @@@ static const unsigned char *get_rev(voi
                        return NULL;
  
                commit = prio_queue_get(&rev_list);
 -              if (!commit->object.parsed)
 -                      parse_commit(commit);
 +              parse_commit(commit);
                parents = commit->parents;
  
                commit->object.flags |= POPPED;
@@@ -174,9 -177,9 +175,9 @@@ static void consume_shallow_list(struc
                 */
                char *line;
                while ((line = packet_read_line(fd, NULL))) {
 -                      if (!prefixcmp(line, "shallow "))
 +                      if (starts_with(line, "shallow "))
                                continue;
 -                      if (!prefixcmp(line, "unshallow "))
 +                      if (starts_with(line, "unshallow "))
                                continue;
                        die("git fetch-pack: expected shallow list");
                }
@@@ -192,7 -195,7 +193,7 @@@ static enum ack_type get_ack(int fd, un
                die("git fetch-pack: expected ACK/NAK, got EOF");
        if (!strcmp(line, "NAK"))
                return NAK;
 -      if (!prefixcmp(line, "ACK ")) {
 +      if (starts_with(line, "ACK ")) {
                if (!get_sha1_hex(line+4, result_sha1)) {
                        if (len < 45)
                                return ACK;
@@@ -309,7 -312,7 +310,7 @@@ static int find_common(struct fetch_pac
        }
  
        if (is_repository_shallow())
-               write_shallow_commits(&req_buf, 1);
+               write_shallow_commits(&req_buf, 1, NULL);
        if (args->depth > 0)
                packet_buf_write(&req_buf, "deepen %d", args->depth);
        packet_buf_flush(&req_buf);
  
                send_request(args, fd[1], &req_buf);
                while ((line = packet_read_line(fd[0], NULL))) {
 -                      if (!prefixcmp(line, "shallow ")) {
 +                      if (starts_with(line, "shallow ")) {
                                if (get_sha1_hex(line + 8, sha1))
                                        die("invalid shallow line: %s", line);
                                register_shallow(sha1);
                                continue;
                        }
 -                      if (!prefixcmp(line, "unshallow ")) {
 +                      if (starts_with(line, "unshallow ")) {
                                if (get_sha1_hex(line + 10, sha1))
                                        die("invalid unshallow line: %s", line);
                                if (!lookup_object(sha1))
@@@ -521,7 -524,7 +522,7 @@@ static void filter_refs(struct fetch_pa
                }
  
                if (!keep && args->fetch_all &&
 -                  (!args->depth || prefixcmp(ref->name, "refs/tags/")))
 +                  (!args->depth || !starts_with(ref->name, "refs/tags/")))
                        keep = 1;
  
                if (keep) {
@@@ -772,6 -775,7 +773,7 @@@ static struct ref *do_fetch_pack(struc
                                 int fd[2],
                                 const struct ref *orig_ref,
                                 struct ref **sought, int nr_sought,
+                                struct shallow_info *si,
                                 char **pack_lockfile)
  {
        struct ref *ref = copy_ref_list(orig_ref);
        if (args->stateless_rpc)
                packet_flush(fd[1]);
        if (args->depth > 0)
-               setup_alternate_shallow(&shallow_lock, &alternate_shallow_file);
+               setup_alternate_shallow(&shallow_lock, &alternate_shallow_file,
+                                       NULL);
+       else if (si->nr_ours || si->nr_theirs)
+               alternate_shallow_file = setup_temporary_shallow(si->shallow);
        else
                alternate_shallow_file = NULL;
        if (get_pack(args, fd, pack_lockfile))
@@@ -922,14 -929,121 +927,121 @@@ static int remove_duplicates_in_refs(st
        return dst;
  }
  
+ static void update_shallow(struct fetch_pack_args *args,
+                          struct ref **sought, int nr_sought,
+                          struct shallow_info *si)
+ {
+       struct sha1_array ref = SHA1_ARRAY_INIT;
+       int *status;
+       int i;
+       if (args->depth > 0 && alternate_shallow_file) {
+               if (*alternate_shallow_file == '\0') { /* --unshallow */
+                       unlink_or_warn(git_path("shallow"));
+                       rollback_lock_file(&shallow_lock);
+               } else
+                       commit_lock_file(&shallow_lock);
+               return;
+       }
+       if (!si->shallow || !si->shallow->nr)
+               return;
+       if (alternate_shallow_file) {
+               /*
+                * The temporary shallow file is only useful for
+                * index-pack and unpack-objects because it may
+                * contain more roots than we want. Delete it.
+                */
+               if (*alternate_shallow_file)
+                       unlink(alternate_shallow_file);
+               free((char *)alternate_shallow_file);
+       }
+       if (args->cloning) {
+               /*
+                * remote is shallow, but this is a clone, there are
+                * no objects in repo to worry about. Accept any
+                * shallow points that exist in the pack (iow in repo
+                * after get_pack() and reprepare_packed_git())
+                */
+               struct sha1_array extra = SHA1_ARRAY_INIT;
+               unsigned char (*sha1)[20] = si->shallow->sha1;
+               for (i = 0; i < si->shallow->nr; i++)
+                       if (has_sha1_file(sha1[i]))
+                               sha1_array_append(&extra, sha1[i]);
+               if (extra.nr) {
+                       setup_alternate_shallow(&shallow_lock,
+                                               &alternate_shallow_file,
+                                               &extra);
+                       commit_lock_file(&shallow_lock);
+               }
+               sha1_array_clear(&extra);
+               return;
+       }
+       if (!si->nr_ours && !si->nr_theirs)
+               return;
+       remove_nonexistent_theirs_shallow(si);
+       if (!si->nr_ours && !si->nr_theirs)
+               return;
+       for (i = 0; i < nr_sought; i++)
+               sha1_array_append(&ref, sought[i]->old_sha1);
+       si->ref = &ref;
+       if (args->update_shallow) {
+               /*
+                * remote is also shallow, .git/shallow may be updated
+                * so all refs can be accepted. Make sure we only add
+                * shallow roots that are actually reachable from new
+                * refs.
+                */
+               struct sha1_array extra = SHA1_ARRAY_INIT;
+               unsigned char (*sha1)[20] = si->shallow->sha1;
+               assign_shallow_commits_to_refs(si, NULL, NULL);
+               if (!si->nr_ours && !si->nr_theirs) {
+                       sha1_array_clear(&ref);
+                       return;
+               }
+               for (i = 0; i < si->nr_ours; i++)
+                       sha1_array_append(&extra, sha1[si->ours[i]]);
+               for (i = 0; i < si->nr_theirs; i++)
+                       sha1_array_append(&extra, sha1[si->theirs[i]]);
+               setup_alternate_shallow(&shallow_lock,
+                                       &alternate_shallow_file,
+                                       &extra);
+               commit_lock_file(&shallow_lock);
+               sha1_array_clear(&extra);
+               sha1_array_clear(&ref);
+               return;
+       }
+       /*
+        * remote is also shallow, check what ref is safe to update
+        * without updating .git/shallow
+        */
+       status = xcalloc(nr_sought, sizeof(*status));
+       assign_shallow_commits_to_refs(si, NULL, status);
+       if (si->nr_ours || si->nr_theirs) {
+               for (i = 0; i < nr_sought; i++)
+                       if (status[i])
+                               sought[i]->status = REF_STATUS_REJECT_SHALLOW;
+       }
+       free(status);
+       sha1_array_clear(&ref);
+ }
  struct ref *fetch_pack(struct fetch_pack_args *args,
                       int fd[], struct child_process *conn,
                       const struct ref *ref,
                       const char *dest,
                       struct ref **sought, int nr_sought,
+                      struct sha1_array *shallow,
                       char **pack_lockfile)
  {
        struct ref *ref_cpy;
+       struct shallow_info si;
  
        fetch_pack_setup();
        if (nr_sought)
                packet_flush(fd[1]);
                die("no matching remote head");
        }
-       ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought, pack_lockfile);
-       if (args->depth > 0 && alternate_shallow_file) {
-               if (*alternate_shallow_file == '\0') { /* --unshallow */
-                       unlink_or_warn(git_path("shallow"));
-                       rollback_lock_file(&shallow_lock);
-               } else
-                       commit_lock_file(&shallow_lock);
-       }
+       prepare_shallow_info(&si, shallow);
+       ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
+                               &si, pack_lockfile);
        reprepare_packed_git();
+       update_shallow(args, sought, nr_sought, &si);
+       clear_shallow_info(&si);
        return ref_cpy;
  }
diff --combined fetch-pack.h
index 20ccc12e57b9499b0716c8295b4adea38d8d2a86,ada02f51c162a72d58782f315e648f2de8340d61..bb7fd76e5939d812f1f89b787324ae6ec519ca15
@@@ -4,23 -4,26 +4,27 @@@
  #include "string-list.h"
  #include "run-command.h"
  
+ struct sha1_array;
  struct fetch_pack_args {
        const char *uploadpack;
        int unpacklimit;
        int depth;
-       unsigned quiet:1,
-               keep_pack:1,
-               lock_pack:1,
-               use_thin_pack:1,
-               fetch_all:1,
-               stdin_refs:1,
-               diag_url:1,
-               verbose:1,
-               no_progress:1,
-               include_tag:1,
-               stateless_rpc:1,
-               check_self_contained_and_connected:1,
-               self_contained_and_connected:1;
+       unsigned quiet:1;
+       unsigned keep_pack:1;
+       unsigned lock_pack:1;
+       unsigned use_thin_pack:1;
+       unsigned fetch_all:1;
+       unsigned stdin_refs:1;
++      unsigned diag_url:1;
+       unsigned verbose:1;
+       unsigned no_progress:1;
+       unsigned include_tag:1;
+       unsigned stateless_rpc:1;
+       unsigned check_self_contained_and_connected:1;
+       unsigned self_contained_and_connected:1;
+       unsigned cloning:1;
+       unsigned update_shallow:1;
  };
  
  /*
@@@ -34,6 -37,7 +38,7 @@@ struct ref *fetch_pack(struct fetch_pac
                       const char *dest,
                       struct ref **sought,
                       int nr_sought,
+                      struct sha1_array *shallow,
                       char **pack_lockfile);
  
  #endif
diff --combined git.c
index bba4378458e715a70f5e58a32a33b9baba7ca3bd,179c4f6ff81d1f482e62248e33eacb74327df2b8..7cf2953eff47ca91b9a134e2e810fea1fc53e386
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -54,7 -54,7 +54,7 @@@ static int handle_options(const char **
                /*
                 * Check remaining flags.
                 */
 -              if (!prefixcmp(cmd, "--exec-path")) {
 +              if (starts_with(cmd, "--exec-path")) {
                        cmd += 11;
                        if (*cmd == '=')
                                git_set_argv_exec_path(cmd + 1);
@@@ -92,7 -92,7 +92,7 @@@
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
 -              } else if (!prefixcmp(cmd, "--git-dir=")) {
 +              } else if (starts_with(cmd, "--git-dir=")) {
                        setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
                        if (envchanged)
                                *envchanged = 1;
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
 -              } else if (!prefixcmp(cmd, "--namespace=")) {
 +              } else if (starts_with(cmd, "--namespace=")) {
                        setenv(GIT_NAMESPACE_ENVIRONMENT, cmd + 12, 1);
                        if (envchanged)
                                *envchanged = 1;
                                *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
 -              } else if (!prefixcmp(cmd, "--work-tree=")) {
 +              } else if (starts_with(cmd, "--work-tree=")) {
                        setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "--shallow-file")) {
                        (*argv)++;
                        (*argc)--;
-                       set_alternate_shallow_file((*argv)[0]);
+                       set_alternate_shallow_file((*argv)[0], 1);
                        if (envchanged)
                                *envchanged = 1;
                } else if (!strcmp(cmd, "-C")) {
@@@ -332,136 -332,127 +332,136 @@@ static int run_builtin(struct cmd_struc
        return 0;
  }
  
 -static void handle_internal_command(int argc, const char **argv)
 +static struct cmd_struct commands[] = {
 +      { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
 +      { "annotate", cmd_annotate, RUN_SETUP },
 +      { "apply", cmd_apply, RUN_SETUP_GENTLY },
 +      { "archive", cmd_archive },
 +      { "bisect--helper", cmd_bisect__helper, RUN_SETUP },
 +      { "blame", cmd_blame, RUN_SETUP },
 +      { "branch", cmd_branch, RUN_SETUP },
 +      { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
 +      { "cat-file", cmd_cat_file, RUN_SETUP },
 +      { "check-attr", cmd_check_attr, RUN_SETUP },
 +      { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
 +      { "check-mailmap", cmd_check_mailmap, RUN_SETUP },
 +      { "check-ref-format", cmd_check_ref_format },
 +      { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
 +      { "checkout-index", cmd_checkout_index,
 +              RUN_SETUP | NEED_WORK_TREE},
 +      { "cherry", cmd_cherry, RUN_SETUP },
 +      { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
 +      { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
 +      { "clone", cmd_clone },
 +      { "column", cmd_column, RUN_SETUP_GENTLY },
 +      { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
 +      { "commit-tree", cmd_commit_tree, RUN_SETUP },
 +      { "config", cmd_config, RUN_SETUP_GENTLY },
 +      { "count-objects", cmd_count_objects, RUN_SETUP },
 +      { "credential", cmd_credential, RUN_SETUP_GENTLY },
 +      { "describe", cmd_describe, RUN_SETUP },
 +      { "diff", cmd_diff },
 +      { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 +      { "diff-index", cmd_diff_index, RUN_SETUP },
 +      { "diff-tree", cmd_diff_tree, RUN_SETUP },
 +      { "fast-export", cmd_fast_export, RUN_SETUP },
 +      { "fetch", cmd_fetch, RUN_SETUP },
 +      { "fetch-pack", cmd_fetch_pack, RUN_SETUP },
 +      { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
 +      { "for-each-ref", cmd_for_each_ref, RUN_SETUP },
 +      { "format-patch", cmd_format_patch, RUN_SETUP },
 +      { "fsck", cmd_fsck, RUN_SETUP },
 +      { "fsck-objects", cmd_fsck, RUN_SETUP },
 +      { "gc", cmd_gc, RUN_SETUP },
 +      { "get-tar-commit-id", cmd_get_tar_commit_id },
 +      { "grep", cmd_grep, RUN_SETUP_GENTLY },
 +      { "hash-object", cmd_hash_object },
 +      { "help", cmd_help },
 +      { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
 +      { "init", cmd_init_db },
 +      { "init-db", cmd_init_db },
 +      { "log", cmd_log, RUN_SETUP },
 +      { "ls-files", cmd_ls_files, RUN_SETUP },
 +      { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
 +      { "ls-tree", cmd_ls_tree, RUN_SETUP },
 +      { "mailinfo", cmd_mailinfo },
 +      { "mailsplit", cmd_mailsplit },
 +      { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 +      { "merge-base", cmd_merge_base, RUN_SETUP },
 +      { "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
 +      { "merge-index", cmd_merge_index, RUN_SETUP },
 +      { "merge-ours", cmd_merge_ours, RUN_SETUP },
 +      { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 +      { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 +      { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 +      { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 +      { "merge-tree", cmd_merge_tree, RUN_SETUP },
 +      { "mktag", cmd_mktag, RUN_SETUP },
 +      { "mktree", cmd_mktree, RUN_SETUP },
 +      { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
 +      { "name-rev", cmd_name_rev, RUN_SETUP },
 +      { "notes", cmd_notes, RUN_SETUP },
 +      { "pack-objects", cmd_pack_objects, RUN_SETUP },
 +      { "pack-redundant", cmd_pack_redundant, RUN_SETUP },
 +      { "pack-refs", cmd_pack_refs, RUN_SETUP },
 +      { "patch-id", cmd_patch_id },
 +      { "pickaxe", cmd_blame, RUN_SETUP },
 +      { "prune", cmd_prune, RUN_SETUP },
 +      { "prune-packed", cmd_prune_packed, RUN_SETUP },
 +      { "push", cmd_push, RUN_SETUP },
 +      { "read-tree", cmd_read_tree, RUN_SETUP },
 +      { "receive-pack", cmd_receive_pack },
 +      { "reflog", cmd_reflog, RUN_SETUP },
 +      { "remote", cmd_remote, RUN_SETUP },
 +      { "remote-ext", cmd_remote_ext },
 +      { "remote-fd", cmd_remote_fd },
 +      { "repack", cmd_repack, RUN_SETUP },
 +      { "replace", cmd_replace, RUN_SETUP },
 +      { "rerere", cmd_rerere, RUN_SETUP },
 +      { "reset", cmd_reset, RUN_SETUP },
 +      { "rev-list", cmd_rev_list, RUN_SETUP },
 +      { "rev-parse", cmd_rev_parse },
 +      { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
 +      { "rm", cmd_rm, RUN_SETUP },
 +      { "send-pack", cmd_send_pack, RUN_SETUP },
 +      { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
 +      { "show", cmd_show, RUN_SETUP },
 +      { "show-branch", cmd_show_branch, RUN_SETUP },
 +      { "show-ref", cmd_show_ref, RUN_SETUP },
 +      { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
 +      { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 +      { "stripspace", cmd_stripspace },
 +      { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 +      { "tag", cmd_tag, RUN_SETUP },
 +      { "unpack-file", cmd_unpack_file, RUN_SETUP },
 +      { "unpack-objects", cmd_unpack_objects, RUN_SETUP },
 +      { "update-index", cmd_update_index, RUN_SETUP },
 +      { "update-ref", cmd_update_ref, RUN_SETUP },
 +      { "update-server-info", cmd_update_server_info, RUN_SETUP },
 +      { "upload-archive", cmd_upload_archive },
 +      { "upload-archive--writer", cmd_upload_archive_writer },
 +      { "var", cmd_var, RUN_SETUP_GENTLY },
 +      { "verify-pack", cmd_verify_pack },
 +      { "verify-tag", cmd_verify_tag, RUN_SETUP },
 +      { "version", cmd_version },
 +      { "whatchanged", cmd_whatchanged, RUN_SETUP },
 +      { "write-tree", cmd_write_tree, RUN_SETUP },
 +};
 +
 +int is_builtin(const char *s)
 +{
 +      int i;
 +      for (i = 0; i < ARRAY_SIZE(commands); i++) {
 +              struct cmd_struct *p = commands+i;
 +              if (!strcmp(s, p->cmd))
 +                      return 1;
 +      }
 +      return 0;
 +}
 +
 +static void handle_builtin(int argc, const char **argv)
  {
        const char *cmd = argv[0];
 -      static struct cmd_struct commands[] = {
 -              { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
 -              { "annotate", cmd_annotate, RUN_SETUP },
 -              { "apply", cmd_apply, RUN_SETUP_GENTLY },
 -              { "archive", cmd_archive },
 -              { "bisect--helper", cmd_bisect__helper, RUN_SETUP },
 -              { "blame", cmd_blame, RUN_SETUP },
 -              { "branch", cmd_branch, RUN_SETUP },
 -              { "bundle", cmd_bundle, RUN_SETUP_GENTLY },
 -              { "cat-file", cmd_cat_file, RUN_SETUP },
 -              { "check-attr", cmd_check_attr, RUN_SETUP },
 -              { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
 -              { "check-mailmap", cmd_check_mailmap, RUN_SETUP },
 -              { "check-ref-format", cmd_check_ref_format },
 -              { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
 -              { "checkout-index", cmd_checkout_index,
 -                      RUN_SETUP | NEED_WORK_TREE},
 -              { "cherry", cmd_cherry, RUN_SETUP },
 -              { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
 -              { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
 -              { "clone", cmd_clone },
 -              { "column", cmd_column, RUN_SETUP_GENTLY },
 -              { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
 -              { "commit-tree", cmd_commit_tree, RUN_SETUP },
 -              { "config", cmd_config, RUN_SETUP_GENTLY },
 -              { "count-objects", cmd_count_objects, RUN_SETUP },
 -              { "credential", cmd_credential, RUN_SETUP_GENTLY },
 -              { "describe", cmd_describe, RUN_SETUP },
 -              { "diff", cmd_diff },
 -              { "diff-files", cmd_diff_files, RUN_SETUP | NEED_WORK_TREE },
 -              { "diff-index", cmd_diff_index, RUN_SETUP },
 -              { "diff-tree", cmd_diff_tree, RUN_SETUP },
 -              { "fast-export", cmd_fast_export, RUN_SETUP },
 -              { "fetch", cmd_fetch, RUN_SETUP },
 -              { "fetch-pack", cmd_fetch_pack, RUN_SETUP },
 -              { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
 -              { "for-each-ref", cmd_for_each_ref, RUN_SETUP },
 -              { "format-patch", cmd_format_patch, RUN_SETUP },
 -              { "fsck", cmd_fsck, RUN_SETUP },
 -              { "fsck-objects", cmd_fsck, RUN_SETUP },
 -              { "gc", cmd_gc, RUN_SETUP },
 -              { "get-tar-commit-id", cmd_get_tar_commit_id },
 -              { "grep", cmd_grep, RUN_SETUP_GENTLY },
 -              { "hash-object", cmd_hash_object },
 -              { "help", cmd_help },
 -              { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY },
 -              { "init", cmd_init_db },
 -              { "init-db", cmd_init_db },
 -              { "log", cmd_log, RUN_SETUP },
 -              { "ls-files", cmd_ls_files, RUN_SETUP },
 -              { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
 -              { "ls-tree", cmd_ls_tree, RUN_SETUP },
 -              { "mailinfo", cmd_mailinfo },
 -              { "mailsplit", cmd_mailsplit },
 -              { "merge", cmd_merge, RUN_SETUP | NEED_WORK_TREE },
 -              { "merge-base", cmd_merge_base, RUN_SETUP },
 -              { "merge-file", cmd_merge_file, RUN_SETUP_GENTLY },
 -              { "merge-index", cmd_merge_index, RUN_SETUP },
 -              { "merge-ours", cmd_merge_ours, RUN_SETUP },
 -              { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 -              { "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 -              { "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 -              { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
 -              { "merge-tree", cmd_merge_tree, RUN_SETUP },
 -              { "mktag", cmd_mktag, RUN_SETUP },
 -              { "mktree", cmd_mktree, RUN_SETUP },
 -              { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
 -              { "name-rev", cmd_name_rev, RUN_SETUP },
 -              { "notes", cmd_notes, RUN_SETUP },
 -              { "pack-objects", cmd_pack_objects, RUN_SETUP },
 -              { "pack-redundant", cmd_pack_redundant, RUN_SETUP },
 -              { "pack-refs", cmd_pack_refs, RUN_SETUP },
 -              { "patch-id", cmd_patch_id },
 -              { "peek-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
 -              { "pickaxe", cmd_blame, RUN_SETUP },
 -              { "prune", cmd_prune, RUN_SETUP },
 -              { "prune-packed", cmd_prune_packed, RUN_SETUP },
 -              { "push", cmd_push, RUN_SETUP },
 -              { "read-tree", cmd_read_tree, RUN_SETUP },
 -              { "receive-pack", cmd_receive_pack },
 -              { "reflog", cmd_reflog, RUN_SETUP },
 -              { "remote", cmd_remote, RUN_SETUP },
 -              { "remote-ext", cmd_remote_ext },
 -              { "remote-fd", cmd_remote_fd },
 -              { "repack", cmd_repack, RUN_SETUP },
 -              { "replace", cmd_replace, RUN_SETUP },
 -              { "repo-config", cmd_repo_config, RUN_SETUP_GENTLY },
 -              { "rerere", cmd_rerere, RUN_SETUP },
 -              { "reset", cmd_reset, RUN_SETUP },
 -              { "rev-list", cmd_rev_list, RUN_SETUP },
 -              { "rev-parse", cmd_rev_parse },
 -              { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
 -              { "rm", cmd_rm, RUN_SETUP },
 -              { "send-pack", cmd_send_pack, RUN_SETUP },
 -              { "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
 -              { "show", cmd_show, RUN_SETUP },
 -              { "show-branch", cmd_show_branch, RUN_SETUP },
 -              { "show-ref", cmd_show_ref, RUN_SETUP },
 -              { "stage", cmd_add, RUN_SETUP | NEED_WORK_TREE },
 -              { "status", cmd_status, RUN_SETUP | NEED_WORK_TREE },
 -              { "stripspace", cmd_stripspace },
 -              { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
 -              { "tag", cmd_tag, RUN_SETUP },
 -              { "tar-tree", cmd_tar_tree },
 -              { "unpack-file", cmd_unpack_file, RUN_SETUP },
 -              { "unpack-objects", cmd_unpack_objects, RUN_SETUP },
 -              { "update-index", cmd_update_index, RUN_SETUP },
 -              { "update-ref", cmd_update_ref, RUN_SETUP },
 -              { "update-server-info", cmd_update_server_info, RUN_SETUP },
 -              { "upload-archive", cmd_upload_archive },
 -              { "upload-archive--writer", cmd_upload_archive_writer },
 -              { "var", cmd_var, RUN_SETUP_GENTLY },
 -              { "verify-pack", cmd_verify_pack },
 -              { "verify-tag", cmd_verify_tag, RUN_SETUP },
 -              { "version", cmd_version },
 -              { "whatchanged", cmd_whatchanged, RUN_SETUP },
 -              { "write-tree", cmd_write_tree, RUN_SETUP },
 -      };
        int i;
        static const char ext[] = STRIP_EXTENSION;
  
@@@ -529,8 -520,8 +529,8 @@@ static int run_argv(int *argcp, const c
        int done_alias = 0;
  
        while (1) {
 -              /* See if it's an internal command */
 -              handle_internal_command(*argcp, *argv);
 +              /* See if it's a builtin */
 +              handle_builtin(*argcp, *argv);
  
                /* .. then try the external ones */
                execv_dashed_external(*argv);
@@@ -575,14 -566,14 +575,14 @@@ int main(int argc, char **av
         *  - cannot execute it externally (since it would just do
         *    the same thing over again)
         *
 -       * So we just directly call the internal command handler, and
 -       * die if that one cannot handle it.
 +       * So we just directly call the builtin handler, and die if
 +       * that one cannot handle it.
         */
 -      if (!prefixcmp(cmd, "git-")) {
 +      if (starts_with(cmd, "git-")) {
                cmd += 4;
                argv[0] = cmd;
 -              handle_internal_command(argc, argv);
 -              die("cannot handle %s internally", cmd);
 +              handle_builtin(argc, argv);
 +              die("cannot handle %s as a builtin", cmd);
        }
  
        /* Look for flags.. */
        argc--;
        handle_options(&argv, &argc, NULL);
        if (argc > 0) {
 -              if (!prefixcmp(argv[0], "--"))
 +              if (starts_with(argv[0], "--"))
                        argv[0] += 2;
        } else {
                /* The user didn't specify a command; give them help */
diff --combined remote-curl.c
index e38c4b026f2ad63181c765cd38ceb72ef30c4ee8,d1fc163e5e178a9f109fe8dc901b617da866357a..10cb0114eafdfd9760b3fdb3c0217d801195ba13
@@@ -10,6 -10,7 +10,7 @@@
  #include "sideband.h"
  #include "argv-array.h"
  #include "credential.h"
+ #include "sha1-array.h"
  
  static struct remote *remote;
  /* always ends with a trailing slash */
@@@ -20,6 -21,8 +21,8 @@@ struct options 
        unsigned long depth;
        unsigned progress : 1,
                check_self_contained_and_connected : 1,
+               cloning : 1,
+               update_shallow : 1,
                followtags : 1,
                dry_run : 1,
                thin : 1;
@@@ -87,8 -90,23 +90,23 @@@ static int set_option(const char *name
                string_list_append(&cas_options, val.buf);
                strbuf_release(&val);
                return 0;
-       }
-       else {
+       } else if (!strcmp(name, "cloning")) {
+               if (!strcmp(value, "true"))
+                       options.cloning = 1;
+               else if (!strcmp(value, "false"))
+                       options.cloning = 0;
+               else
+                       return -1;
+               return 0;
+       } else if (!strcmp(name, "update-shallow")) {
+               if (!strcmp(value, "true"))
+                       options.update_shallow = 1;
+               else if (!strcmp(value, "false"))
+                       options.update_shallow = 0;
+               else
+                       return -1;
+               return 0;
+       } else {
                return 1 /* unsupported */;
        }
  }
@@@ -99,6 -117,7 +117,7 @@@ struct discovery 
        char *buf;
        size_t len;
        struct ref *refs;
+       struct sha1_array shallow;
        unsigned proto_git : 1;
  };
  static struct discovery *last_discovery;
@@@ -107,7 -126,7 +126,7 @@@ static struct ref *parse_git_refs(struc
  {
        struct ref *list = NULL;
        get_remote_heads(-1, heads->buf, heads->len, &list,
-                        for_push ? REF_NORMAL : 0, NULL);
+                        for_push ? REF_NORMAL : 0, NULL, &heads->shallow);
        return list;
  }
  
@@@ -168,6 -187,7 +187,7 @@@ static void free_discovery(struct disco
        if (d) {
                if (d == last_discovery)
                        last_discovery = NULL;
+               free(d->shallow.sha1);
                free(d->buf_alloc);
                free_refs(d->refs);
                free(d);
@@@ -217,7 -237,7 +237,7 @@@ static struct discovery* discover_refs(
        free_discovery(last);
  
        strbuf_addf(&refs_url, "%sinfo/refs", url.buf);
 -      if ((!prefixcmp(url.buf, "http://") || !prefixcmp(url.buf, "https://")) &&
 +      if ((starts_with(url.buf, "http://") || starts_with(url.buf, "https://")) &&
             git_env_bool("GIT_SMART_HTTP", 1)) {
                maybe_smart = 1;
                if (!strchr(url.buf, '?'))
@@@ -394,29 -414,25 +414,29 @@@ static size_t rpc_in(char *ptr, size_t 
        return size;
  }
  
 -static int run_slot(struct active_request_slot *slot)
 +static int run_slot(struct active_request_slot *slot,
 +                  struct slot_results *results)
  {
        int err;
 -      struct slot_results results;
 +      struct slot_results results_buf;
  
 -      slot->results = &results;
 +      if (!results)
 +              results = &results_buf;
 +
 +      slot->results = results;
        slot->curl_result = curl_easy_perform(slot->curl);
        finish_active_slot(slot);
  
 -      err = handle_curl_result(&results);
 +      err = handle_curl_result(results);
        if (err != HTTP_OK && err != HTTP_REAUTH) {
                error("RPC failed; result=%d, HTTP code = %ld",
 -                    results.curl_result, results.http_code);
 +                    results->curl_result, results->http_code);
        }
  
        return err;
  }
  
 -static int probe_rpc(struct rpc_state *rpc)
 +static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
  {
        struct active_request_slot *slot;
        struct curl_slist *headers = NULL;
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buf);
  
 -      err = run_slot(slot);
 +      err = run_slot(slot, results);
  
        curl_slist_free_all(headers);
        strbuf_release(&buf);
@@@ -453,7 -469,6 +473,7 @@@ static int post_rpc(struct rpc_state *r
        char *gzip_body = NULL;
        size_t gzip_size = 0;
        int err, large_request = 0;
 +      int needs_100_continue = 0;
  
        /* Try to load the entire request, if we can fit it into the
         * allocated buffer space we can use HTTP/1.0 and avoid the
        }
  
        if (large_request) {
 +              struct slot_results results;
 +
                do {
 -                      err = probe_rpc(rpc);
 +                      err = probe_rpc(rpc, &results);
                        if (err == HTTP_REAUTH)
                                credential_fill(&http_auth);
                } while (err == HTTP_REAUTH);
                if (err != HTTP_OK)
                        return -1;
 +
 +              if (results.auth_avail & CURLAUTH_GSSNEGOTIATE)
 +                      needs_100_continue = 1;
        }
  
        headers = curl_slist_append(headers, rpc->hdr_content_type);
        headers = curl_slist_append(headers, rpc->hdr_accept);
 -      headers = curl_slist_append(headers, "Expect:");
 +      headers = curl_slist_append(headers, needs_100_continue ?
 +              "Expect: 100-continue" : "Expect:");
  
  retry:
        slot = get_active_slot();
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
        curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
  
 -      err = run_slot(slot);
 +      err = run_slot(slot, NULL);
        if (err == HTTP_REAUTH && !large_request) {
                credential_fill(&http_auth);
                goto retry;
@@@ -699,7 -708,7 +719,7 @@@ static int fetch_git(struct discovery *
        struct strbuf preamble = STRBUF_INIT;
        char *depth_arg = NULL;
        int argc = 0, i, err;
-       const char *argv[16];
+       const char *argv[17];
  
        argv[argc++] = "fetch-pack";
        argv[argc++] = "--stateless-rpc";
        }
        if (options.check_self_contained_and_connected)
                argv[argc++] = "--check-self-contained-and-connected";
+       if (options.cloning)
+               argv[argc++] = "--cloning";
+       if (options.update_shallow)
+               argv[argc++] = "--update-shallow";
        if (!options.progress)
                argv[argc++] = "--no-progress";
        if (options.depth) {
                struct ref *ref = to_fetch[i];
                if (!ref->name || !*ref->name)
                        die("cannot fetch by sha1 over smart http");
-               packet_buf_write(&preamble, "%s\n", ref->name);
+               packet_buf_write(&preamble, "%s %s\n",
+                                sha1_to_hex(ref->old_sha1), ref->name);
        }
        packet_buf_flush(&preamble);
  
@@@ -766,7 -780,7 +791,7 @@@ static void parse_fetch(struct strbuf *
        int alloc_heads = 0, nr_heads = 0;
  
        do {
 -              if (!prefixcmp(buf->buf, "fetch ")) {
 +              if (starts_with(buf->buf, "fetch ")) {
                        char *p = buf->buf + strlen("fetch ");
                        char *name;
                        struct ref *ref;
@@@ -889,7 -903,7 +914,7 @@@ static void parse_push(struct strbuf *b
        int alloc_spec = 0, nr_spec = 0, i, ret;
  
        do {
 -              if (!prefixcmp(buf->buf, "push ")) {
 +              if (starts_with(buf->buf, "push ")) {
                        ALLOC_GROW(specs, nr_spec + 1, alloc_spec);
                        specs[nr_spec++] = xstrdup(buf->buf + 5);
                }
@@@ -952,19 -966,19 +977,19 @@@ int main(int argc, const char **argv
                }
                if (buf.len == 0)
                        break;
 -              if (!prefixcmp(buf.buf, "fetch ")) {
 +              if (starts_with(buf.buf, "fetch ")) {
                        if (nongit)
                                die("Fetch attempted without a local repo");
                        parse_fetch(&buf);
  
 -              } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) {
 +              } else if (!strcmp(buf.buf, "list") || starts_with(buf.buf, "list ")) {
                        int for_push = !!strstr(buf.buf + 4, "for-push");
                        output_refs(get_refs(for_push));
  
 -              } else if (!prefixcmp(buf.buf, "push ")) {
 +              } else if (starts_with(buf.buf, "push ")) {
                        parse_push(&buf);
  
 -              } else if (!prefixcmp(buf.buf, "option ")) {
 +              } else if (starts_with(buf.buf, "option ")) {
                        char *name = buf.buf + strlen("option ");
                        char *value = strchr(name, ' ');
                        int result;
diff --combined remote.h
index 00c6a76ef803005698e14d7f31eb4acd22b35649,3498091e9a91feec1ac1ef6f02afc8e48f7d34de..fb7647fab92aef651fbf2eb4118232a550450a55
+++ b/remote.h
@@@ -109,6 -109,7 +109,7 @@@ struct ref 
                REF_STATUS_REJECT_FETCH_FIRST,
                REF_STATUS_REJECT_NEEDS_FORCE,
                REF_STATUS_REJECT_STALE,
+               REF_STATUS_REJECT_SHALLOW,
                REF_STATUS_UPTODATE,
                REF_STATUS_REMOTE_REJECT,
                REF_STATUS_EXPECTING_REPORT
@@@ -128,7 -129,6 +129,7 @@@ struct ref *alloc_ref(const char *name)
  struct ref *copy_ref(const struct ref *ref);
  struct ref *copy_ref_list(const struct ref *ref);
  void sort_ref_list(struct ref **, int (*cmp)(const void *, const void *));
 +extern int count_refspec_match(const char *, struct ref *refs, struct ref **matched_ref);
  int ref_compare_name(const void *, const void *);
  
  int check_ref_type(const struct ref *ref, int flags);
   */
  void free_refs(struct ref *ref);
  
- struct extra_have_objects {
-       int nr, alloc;
-       unsigned char (*array)[20];
- };
+ struct sha1_array;
  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 *);
+                                    struct sha1_array *extra_have,
+                                    struct sha1_array *shallow);
  
  int resolve_remote_symref(struct ref *ref, struct ref *list);
  int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1);
  
  /*
 - * Removes and frees any duplicate refs in the map.
 + * Remove and free all but the first of any entries in the input list
 + * that map the same remote reference to the same local reference.  If
 + * there are two entries that map different remote references to the
 + * same local reference, emit an error message and die.  Return a
 + * pointer to the head of the resulting list.
   */
 -void ref_remove_duplicates(struct ref *ref_map);
 +struct ref *ref_remove_duplicates(struct ref *ref_map);
  
  int valid_fetch_refspec(const char *refspec);
  struct refspec *parse_fetch_refspec(int nr_refspec, const char **refspec);
  
  void free_refspec(int nr_refspec, struct refspec *refspec);
  
 +extern int query_refspecs(struct refspec *specs, int nr, struct refspec *query);
  char *apply_refspecs(struct refspec *refspecs, int nr_refspec,
                     const char *name);
  
diff --combined send-pack.c
index ac14a4d090cce0fa282447641097085a8428c05c,cdcdea7a75b7c4728d638edc112dc5d88ea20e16..6129b0fd8e8e1961ab4c74de4f421c41c7786b42
@@@ -10,6 -10,7 +10,7 @@@
  #include "quote.h"
  #include "transport.h"
  #include "version.h"
+ #include "sha1-array.h"
  
  static int feed_object(const unsigned char *sha1, int fd, int negative)
  {
@@@ -28,7 -29,7 +29,7 @@@
  /*
   * Make a pack stream and spit it out into file descriptor fd
   */
- static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
+ static int pack_objects(int fd, struct ref *refs, struct sha1_array *extra, struct send_pack_args *args)
  {
        /*
         * The child becomes pack-objects --revs; we feed
@@@ -71,7 -72,7 +72,7 @@@
         * parameters by writing to the pipe.
         */
        for (i = 0; i < extra->nr; i++)
-               if (!feed_object(extra->array[i], po.in, 1))
+               if (!feed_object(extra->sha1[i], po.in, 1))
                        break;
  
        while (refs) {
@@@ -109,7 -110,7 +110,7 @@@ static int receive_status(int in, struc
        struct ref *hint;
        int ret = 0;
        char *line = packet_read_line(in, NULL);
 -      if (prefixcmp(line, "unpack "))
 +      if (!starts_with(line, "unpack "))
                return error("did not receive remote status");
        if (strcmp(line, "unpack ok")) {
                error("unpack failed: %s", line + 7);
                line = packet_read_line(in, NULL);
                if (!line)
                        break;
 -              if (prefixcmp(line, "ok ") && prefixcmp(line, "ng ")) {
 +              if (!starts_with(line, "ok ") && !starts_with(line, "ng ")) {
                        error("invalid ref status from remote: %s", line);
                        ret = -1;
                        break;
@@@ -174,10 -175,25 +175,25 @@@ static int sideband_demux(int in, int o
        return ret;
  }
  
+ static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *cb)
+ {
+       struct strbuf *sb = cb;
+       if (graft->nr_parent == -1)
+               packet_buf_write(sb, "shallow %s\n", sha1_to_hex(graft->sha1));
+       return 0;
+ }
+ static void advertise_shallow_grafts_buf(struct strbuf *sb)
+ {
+       if (!is_repository_shallow())
+               return;
+       for_each_commit_graft(advertise_shallow_grafts_cb, sb);
+ }
  int send_pack(struct send_pack_args *args,
              int fd[], struct child_process *conn,
              struct ref *remote_refs,
-             struct extra_have_objects *extra_have)
+             struct sha1_array *extra_have)
  {
        int in = fd[0];
        int out = fd[1];
                quiet_supported = 1;
        if (server_supports("agent"))
                agent_supported = 1;
 +      if (server_supports("no-thin"))
 +              args->use_thin_pack = 0;
  
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
                return 0;
        }
  
+       if (!args->dry_run)
+               advertise_shallow_grafts_buf(&req_buf);
        /*
         * Finally, tell the other end!
         */
        }
  
        if (args->stateless_rpc) {
-               if (!args->dry_run && cmds_sent) {
+               if (!args->dry_run && (cmds_sent || is_repository_shallow())) {
                        packet_buf_flush(&req_buf);
                        send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
                }
diff --combined shallow.c
index 961cf6f02480e04d1baebb8f508aa17c5fea6355,e483780d4916a817e5256ec325e6cbcf31eb9328..bbc98b55c07969474b0afb01d9c4da70455f1903
+++ b/shallow.c
@@@ -2,15 -2,23 +2,23 @@@
  #include "commit.h"
  #include "tag.h"
  #include "pkt-line.h"
+ #include "remote.h"
+ #include "refs.h"
+ #include "sha1-array.h"
+ #include "diff.h"
+ #include "revision.h"
+ #include "commit-slab.h"
  
  static int is_shallow = -1;
  static struct stat shallow_stat;
  static char *alternate_shallow_file;
  
- void set_alternate_shallow_file(const char *path)
+ void set_alternate_shallow_file(const char *path, int override)
  {
        if (is_shallow != -1)
                die("BUG: is_repository_shallow must not be called before set_alternate_shallow_file");
+       if (alternate_shallow_file && !override)
+               return;
        free(alternate_shallow_file);
        alternate_shallow_file = path ? xstrdup(path) : NULL;
  }
@@@ -69,6 -77,7 +77,7 @@@ struct commit_list *get_shallow_commits
        struct commit_list *result = NULL;
        struct object_array stack = OBJECT_ARRAY_INIT;
        struct commit *commit = NULL;
+       struct commit_graft *graft;
  
        while (commit || i < heads->nr || stack.nr) {
                struct commit_list *p;
                                cur_depth = *(int *)commit->util;
                        }
                }
 -              if (parse_commit(commit))
 -                      die("invalid commit");
 +              parse_commit_or_die(commit);
                cur_depth++;
-               if (cur_depth >= depth) {
+               if ((depth != INFINITE_DEPTH && cur_depth >= depth) ||
+                   (is_repository_shallow() && !commit->parents &&
+                    (graft = lookup_commit_graft(commit->object.sha1)) != NULL &&
+                    graft->nr_parent < 0)) {
                        commit_list_insert(commit, &result);
                        commit->object.flags |= shallow_flag;
                        commit = NULL;
@@@ -142,10 -155,14 +154,14 @@@ void check_shallow_file_for_update(void
                die("shallow file was changed during fetch");
  }
  
+ #define SEEN_ONLY 1
+ #define VERBOSE   2
  struct write_shallow_data {
        struct strbuf *out;
        int use_pack_protocol;
        int count;
+       unsigned flags;
  };
  
  static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
        const char *hex = sha1_to_hex(graft->sha1);
        if (graft->nr_parent != -1)
                return 0;
+       if (data->flags & SEEN_ONLY) {
+               struct commit *c = lookup_commit(graft->sha1);
+               if (!c || !(c->object.flags & SEEN)) {
+                       if (data->flags & VERBOSE)
+                               printf("Removing %s from .git/shallow\n",
+                                      sha1_to_hex(c->object.sha1));
+                       return 0;
+               }
+       }
        data->count++;
        if (data->use_pack_protocol)
                packet_buf_write(data->out, "shallow %s", hex);
        return 0;
  }
  
- int write_shallow_commits(struct strbuf *out, int use_pack_protocol)
+ static int write_shallow_commits_1(struct strbuf *out, int use_pack_protocol,
+                                  const struct sha1_array *extra,
+                                  unsigned flags)
  {
        struct write_shallow_data data;
+       int i;
        data.out = out;
        data.use_pack_protocol = use_pack_protocol;
        data.count = 0;
+       data.flags = flags;
        for_each_commit_graft(write_one_shallow, &data);
+       if (!extra)
+               return data.count;
+       for (i = 0; i < extra->nr; i++) {
+               strbuf_addstr(out, sha1_to_hex(extra->sha1[i]));
+               strbuf_addch(out, '\n');
+               data.count++;
+       }
        return data.count;
  }
  
- char *setup_temporary_shallow(void)
+ int write_shallow_commits(struct strbuf *out, int use_pack_protocol,
+                         const struct sha1_array *extra)
+ {
+       return write_shallow_commits_1(out, use_pack_protocol, extra, 0);
+ }
+ char *setup_temporary_shallow(const struct sha1_array *extra)
  {
        struct strbuf sb = STRBUF_INIT;
        int fd;
  
-       if (write_shallow_commits(&sb, 0)) {
+       if (write_shallow_commits(&sb, 0, extra)) {
                struct strbuf path = STRBUF_INIT;
                strbuf_addstr(&path, git_path("shallow_XXXXXX"));
                fd = xmkstemp(path.buf);
  }
  
  void setup_alternate_shallow(struct lock_file *shallow_lock,
-                            const char **alternate_shallow_file)
+                            const char **alternate_shallow_file,
+                            const struct sha1_array *extra)
  {
        struct strbuf sb = STRBUF_INIT;
        int fd;
        check_shallow_file_for_update();
        fd = hold_lock_file_for_update(shallow_lock, git_path("shallow"),
                                       LOCK_DIE_ON_ERROR);
-       if (write_shallow_commits(&sb, 0)) {
+       if (write_shallow_commits(&sb, 0, extra)) {
                if (write_in_full(fd, sb.buf, sb.len) != sb.len)
                        die_errno("failed to write to %s",
                                  shallow_lock->filename);
                *alternate_shallow_file = "";
        strbuf_release(&sb);
  }
+ static int advertise_shallow_grafts_cb(const struct commit_graft *graft, void *cb)
+ {
+       int fd = *(int *)cb;
+       if (graft->nr_parent == -1)
+               packet_write(fd, "shallow %s\n", sha1_to_hex(graft->sha1));
+       return 0;
+ }
+ void advertise_shallow_grafts(int fd)
+ {
+       if (!is_repository_shallow())
+               return;
+       for_each_commit_graft(advertise_shallow_grafts_cb, &fd);
+ }
+ /*
+  * mark_reachable_objects() should have been run prior to this and all
+  * reachable commits marked as "SEEN".
+  */
+ void prune_shallow(int show_only)
+ {
+       static struct lock_file shallow_lock;
+       struct strbuf sb = STRBUF_INIT;
+       int fd;
+       if (show_only) {
+               write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY | VERBOSE);
+               strbuf_release(&sb);
+               return;
+       }
+       check_shallow_file_for_update();
+       fd = hold_lock_file_for_update(&shallow_lock, git_path("shallow"),
+                                      LOCK_DIE_ON_ERROR);
+       if (write_shallow_commits_1(&sb, 0, NULL, SEEN_ONLY)) {
+               if (write_in_full(fd, sb.buf, sb.len) != sb.len)
+                       die_errno("failed to write to %s",
+                                 shallow_lock.filename);
+               commit_lock_file(&shallow_lock);
+       } else {
+               unlink(git_path("shallow"));
+               rollback_lock_file(&shallow_lock);
+       }
+       strbuf_release(&sb);
+ }
+ #define TRACE_KEY "GIT_TRACE_SHALLOW"
+ /*
+  * Step 1, split sender shallow commits into "ours" and "theirs"
+  * Step 2, clean "ours" based on .git/shallow
+  */
+ void prepare_shallow_info(struct shallow_info *info, struct sha1_array *sa)
+ {
+       int i;
+       trace_printf_key(TRACE_KEY, "shallow: prepare_shallow_info\n");
+       memset(info, 0, sizeof(*info));
+       info->shallow = sa;
+       if (!sa)
+               return;
+       info->ours = xmalloc(sizeof(*info->ours) * sa->nr);
+       info->theirs = xmalloc(sizeof(*info->theirs) * sa->nr);
+       for (i = 0; i < sa->nr; i++) {
+               if (has_sha1_file(sa->sha1[i])) {
+                       struct commit_graft *graft;
+                       graft = lookup_commit_graft(sa->sha1[i]);
+                       if (graft && graft->nr_parent < 0)
+                               continue;
+                       info->ours[info->nr_ours++] = i;
+               } else
+                       info->theirs[info->nr_theirs++] = i;
+       }
+ }
+ void clear_shallow_info(struct shallow_info *info)
+ {
+       free(info->ours);
+       free(info->theirs);
+ }
+ /* Step 4, remove non-existent ones in "theirs" after getting the pack */
+ void remove_nonexistent_theirs_shallow(struct shallow_info *info)
+ {
+       unsigned char (*sha1)[20] = info->shallow->sha1;
+       int i, dst;
+       trace_printf_key(TRACE_KEY, "shallow: remove_nonexistent_theirs_shallow\n");
+       for (i = dst = 0; i < info->nr_theirs; i++) {
+               if (i != dst)
+                       info->theirs[dst] = info->theirs[i];
+               if (has_sha1_file(sha1[info->theirs[i]]))
+                       dst++;
+       }
+       info->nr_theirs = dst;
+ }
+ define_commit_slab(ref_bitmap, uint32_t *);
+ struct paint_info {
+       struct ref_bitmap ref_bitmap;
+       unsigned nr_bits;
+       char **slab;
+       char *free, *end;
+       unsigned slab_count;
+ };
+ static uint32_t *paint_alloc(struct paint_info *info)
+ {
+       unsigned nr = (info->nr_bits + 31) / 32;
+       unsigned size = nr * sizeof(uint32_t);
+       void *p;
+       if (!info->slab_count || info->free + size > info->end) {
+               info->slab_count++;
+               info->slab = xrealloc(info->slab,
+                                     info->slab_count * sizeof(*info->slab));
+               info->free = xmalloc(COMMIT_SLAB_SIZE);
+               info->slab[info->slab_count - 1] = info->free;
+               info->end = info->free + COMMIT_SLAB_SIZE;
+       }
+       p = info->free;
+       info->free += size;
+       return p;
+ }
+ /*
+  * Given a commit SHA-1, walk down to parents until either SEEN,
+  * UNINTERESTING or BOTTOM is hit. Set the id-th bit in ref_bitmap for
+  * all walked commits.
+  */
+ static void paint_down(struct paint_info *info, const unsigned char *sha1,
+                      int id)
+ {
+       unsigned int i, nr;
+       struct commit_list *head = NULL;
+       int bitmap_nr = (info->nr_bits + 31) / 32;
+       int bitmap_size = bitmap_nr * sizeof(uint32_t);
+       uint32_t *tmp = xmalloc(bitmap_size); /* to be freed before return */
+       uint32_t *bitmap = paint_alloc(info);
+       struct commit *c = lookup_commit_reference_gently(sha1, 1);
+       if (!c)
+               return;
+       memset(bitmap, 0, bitmap_size);
+       bitmap[id / 32] |= (1 << (id % 32));
+       commit_list_insert(c, &head);
+       while (head) {
+               struct commit_list *p;
+               struct commit *c = head->item;
+               uint32_t **refs = ref_bitmap_at(&info->ref_bitmap, c);
+               p = head;
+               head = head->next;
+               free(p);
+               /* XXX check "UNINTERESTING" from pack bitmaps if available */
+               if (c->object.flags & (SEEN | UNINTERESTING))
+                       continue;
+               else
+                       c->object.flags |= SEEN;
+               if (*refs == NULL)
+                       *refs = bitmap;
+               else {
+                       memcpy(tmp, *refs, bitmap_size);
+                       for (i = 0; i < bitmap_nr; i++)
+                               tmp[i] |= bitmap[i];
+                       if (memcmp(tmp, *refs, bitmap_size)) {
+                               *refs = paint_alloc(info);
+                               memcpy(*refs, tmp, bitmap_size);
+                       }
+               }
+               if (c->object.flags & BOTTOM)
+                       continue;
+               if (parse_commit(c))
+                       die("unable to parse commit %s",
+                           sha1_to_hex(c->object.sha1));
+               for (p = c->parents; p; p = p->next) {
+                       uint32_t **p_refs = ref_bitmap_at(&info->ref_bitmap,
+                                                         p->item);
+                       if (p->item->object.flags & SEEN)
+                               continue;
+                       if (*p_refs == NULL || *p_refs == *refs)
+                               *p_refs = *refs;
+                       commit_list_insert(p->item, &head);
+               }
+       }
+       nr = get_max_object_index();
+       for (i = 0; i < nr; i++) {
+               struct object *o = get_indexed_object(i);
+               if (o && o->type == OBJ_COMMIT)
+                       o->flags &= ~SEEN;
+       }
+       free(tmp);
+ }
+ static int mark_uninteresting(const char *refname,
+                             const unsigned char *sha1,
+                             int flags, void *cb_data)
+ {
+       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+       if (!commit)
+               return 0;
+       commit->object.flags |= UNINTERESTING;
+       mark_parents_uninteresting(commit);
+       return 0;
+ }
+ static void post_assign_shallow(struct shallow_info *info,
+                               struct ref_bitmap *ref_bitmap,
+                               int *ref_status);
+ /*
+  * Step 6(+7), associate shallow commits with new refs
+  *
+  * info->ref must be initialized before calling this function.
+  *
+  * If used is not NULL, it's an array of info->shallow->nr
+  * bitmaps. The n-th bit set in the m-th bitmap if ref[n] needs the
+  * m-th shallow commit from info->shallow.
+  *
+  * If used is NULL, "ours" and "theirs" are updated. And if ref_status
+  * is not NULL it's an array of ref->nr ints. ref_status[i] is true if
+  * the ref needs some shallow commits from either info->ours or
+  * info->theirs.
+  */
+ void assign_shallow_commits_to_refs(struct shallow_info *info,
+                                   uint32_t **used, int *ref_status)
+ {
+       unsigned char (*sha1)[20] = info->shallow->sha1;
+       struct sha1_array *ref = info->ref;
+       unsigned int i, nr;
+       int *shallow, nr_shallow = 0;
+       struct paint_info pi;
+       trace_printf_key(TRACE_KEY, "shallow: assign_shallow_commits_to_refs\n");
+       shallow = xmalloc(sizeof(*shallow) * (info->nr_ours + info->nr_theirs));
+       for (i = 0; i < info->nr_ours; i++)
+               shallow[nr_shallow++] = info->ours[i];
+       for (i = 0; i < info->nr_theirs; i++)
+               shallow[nr_shallow++] = info->theirs[i];
+       /*
+        * Prepare the commit graph to track what refs can reach what
+        * (new) shallow commits.
+        */
+       nr = get_max_object_index();
+       for (i = 0; i < nr; i++) {
+               struct object *o = get_indexed_object(i);
+               if (!o || o->type != OBJ_COMMIT)
+                       continue;
+               o->flags &= ~(UNINTERESTING | BOTTOM | SEEN);
+       }
+       memset(&pi, 0, sizeof(pi));
+       init_ref_bitmap(&pi.ref_bitmap);
+       pi.nr_bits = ref->nr;
+       /*
+        * "--not --all" to cut short the traversal if new refs
+        * connect to old refs. If not (e.g. force ref updates) it'll
+        * have to go down to the current shallow commits.
+        */
+       head_ref(mark_uninteresting, NULL);
+       for_each_ref(mark_uninteresting, NULL);
+       /* Mark potential bottoms so we won't go out of bound */
+       for (i = 0; i < nr_shallow; i++) {
+               struct commit *c = lookup_commit(sha1[shallow[i]]);
+               c->object.flags |= BOTTOM;
+       }
+       for (i = 0; i < ref->nr; i++)
+               paint_down(&pi, ref->sha1[i], i);
+       if (used) {
+               int bitmap_size = ((pi.nr_bits + 31) / 32) * sizeof(uint32_t);
+               memset(used, 0, sizeof(*used) * info->shallow->nr);
+               for (i = 0; i < nr_shallow; i++) {
+                       const struct commit *c = lookup_commit(sha1[shallow[i]]);
+                       uint32_t **map = ref_bitmap_at(&pi.ref_bitmap, c);
+                       if (*map)
+                               used[shallow[i]] = xmemdupz(*map, bitmap_size);
+               }
+               /*
+                * unreachable shallow commits are not removed from
+                * "ours" and "theirs". The user is supposed to run
+                * step 7 on every ref separately and not trust "ours"
+                * and "theirs" any more.
+                */
+       } else
+               post_assign_shallow(info, &pi.ref_bitmap, ref_status);
+       clear_ref_bitmap(&pi.ref_bitmap);
+       for (i = 0; i < pi.slab_count; i++)
+               free(pi.slab[i]);
+       free(pi.slab);
+       free(shallow);
+ }
+ struct commit_array {
+       struct commit **commits;
+       int nr, alloc;
+ };
+ static int add_ref(const char *refname,
+                  const unsigned char *sha1, int flags, void *cb_data)
+ {
+       struct commit_array *ca = cb_data;
+       ALLOC_GROW(ca->commits, ca->nr + 1, ca->alloc);
+       ca->commits[ca->nr] = lookup_commit_reference_gently(sha1, 1);
+       if (ca->commits[ca->nr])
+               ca->nr++;
+       return 0;
+ }
+ static void update_refstatus(int *ref_status, int nr, uint32_t *bitmap)
+ {
+       int i;
+       if (!ref_status)
+               return;
+       for (i = 0; i < nr; i++)
+               if (bitmap[i / 32] & (1 << (i % 32)))
+                       ref_status[i]++;
+ }
+ /*
+  * Step 7, reachability test on "ours" at commit level
+  */
+ static void post_assign_shallow(struct shallow_info *info,
+                               struct ref_bitmap *ref_bitmap,
+                               int *ref_status)
+ {
+       unsigned char (*sha1)[20] = info->shallow->sha1;
+       struct commit *c;
+       uint32_t **bitmap;
+       int dst, i, j;
+       int bitmap_nr = (info->ref->nr + 31) / 32;
+       struct commit_array ca;
+       trace_printf_key(TRACE_KEY, "shallow: post_assign_shallow\n");
+       if (ref_status)
+               memset(ref_status, 0, sizeof(*ref_status) * info->ref->nr);
+       /* Remove unreachable shallow commits from "theirs" */
+       for (i = dst = 0; i < info->nr_theirs; i++) {
+               if (i != dst)
+                       info->theirs[dst] = info->theirs[i];
+               c = lookup_commit(sha1[info->theirs[i]]);
+               bitmap = ref_bitmap_at(ref_bitmap, c);
+               if (!*bitmap)
+                       continue;
+               for (j = 0; j < bitmap_nr; j++)
+                       if (bitmap[0][j]) {
+                               update_refstatus(ref_status, info->ref->nr, *bitmap);
+                               dst++;
+                               break;
+                       }
+       }
+       info->nr_theirs = dst;
+       memset(&ca, 0, sizeof(ca));
+       head_ref(add_ref, &ca);
+       for_each_ref(add_ref, &ca);
+       /* Remove unreachable shallow commits from "ours" */
+       for (i = dst = 0; i < info->nr_ours; i++) {
+               if (i != dst)
+                       info->ours[dst] = info->ours[i];
+               c = lookup_commit(sha1[info->ours[i]]);
+               bitmap = ref_bitmap_at(ref_bitmap, c);
+               if (!*bitmap)
+                       continue;
+               for (j = 0; j < bitmap_nr; j++)
+                       if (bitmap[0][j] &&
+                           /* Step 7, reachability test at commit level */
+                           !in_merge_bases_many(c, ca.nr, ca.commits)) {
+                               update_refstatus(ref_status, info->ref->nr, *bitmap);
+                               dst++;
+                               break;
+                       }
+       }
+       info->nr_ours = dst;
+       free(ca.commits);
+ }
+ /* (Delayed) step 7, reachability test at commit level */
+ int delayed_reachability_test(struct shallow_info *si, int c)
+ {
+       if (si->need_reachability_test[c]) {
+               struct commit *commit = lookup_commit(si->shallow->sha1[c]);
+               if (!si->commits) {
+                       struct commit_array ca;
+                       memset(&ca, 0, sizeof(ca));
+                       head_ref(add_ref, &ca);
+                       for_each_ref(add_ref, &ca);
+                       si->commits = ca.commits;
+                       si->nr_commits = ca.nr;
+               }
+               si->reachable[c] = in_merge_bases_many(commit,
+                                                      si->nr_commits,
+                                                      si->commits);
+               si->need_reachability_test[c] = 0;
+       }
+       return si->reachable[c];
+ }
diff --combined t/t5601-clone.sh
index 62fbd7e6649d9215528a83249f1b46735de2237d,c226cff52c8b613105b6183cab35a0ba00fb3e75..5e67035be800b5cbec5a99dd9e3e458a343440df
@@@ -280,26 -280,25 +280,26 @@@ test_expect_success 'clone checking ou
        test_cmp fetch.expected fetch.actual
  '
  
 -test_expect_success 'setup ssh wrapper' '
 -      write_script "$TRASH_DIRECTORY/ssh-wrapper" <<-\EOF &&
 -      echo >>"$TRASH_DIRECTORY/ssh-output" "ssh: $*" &&
 -      # throw away all but the last argument, which should be the
 -      # command
 -      while test $# -gt 1; do shift; done
 -      eval "$1"
 -      EOF
 -
 -      GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper" &&
 -      export GIT_SSH &&
 -      export TRASH_DIRECTORY
 -'
 -
 -clear_ssh () {
 -      >"$TRASH_DIRECTORY/ssh-output"
 +setup_ssh_wrapper () {
 +      test_expect_success 'setup ssh wrapper' '
 +              write_script "$TRASH_DIRECTORY/ssh-wrapper" <<-\EOF &&
 +              echo >>"$TRASH_DIRECTORY/ssh-output" "ssh: $*" &&
 +              # throw away all but the last argument, which should be the
 +              # command
 +              while test $# -gt 1; do shift; done
 +              eval "$1"
 +              EOF
 +              GIT_SSH="$TRASH_DIRECTORY/ssh-wrapper" &&
 +              export GIT_SSH &&
 +              export TRASH_DIRECTORY &&
 +              >"$TRASH_DIRECTORY"/ssh-output
 +      '
  }
  
  expect_ssh () {
 +      test_when_finished '
 +              (cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)
 +      ' &&
        {
                case "$1" in
                none)
        (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output)
  }
  
 -test_expect_success 'cloning myhost:src uses ssh' '
 -      clear_ssh &&
 +setup_ssh_wrapper
 +
 +test_expect_success 'clone myhost:src uses ssh' '
        git clone myhost:src ssh-clone &&
        expect_ssh myhost src
  '
  
  test_expect_success NOT_MINGW,NOT_CYGWIN 'clone local path foo:bar' '
 -      clear_ssh &&
        cp -R src "foo:bar" &&
 -      git clone "./foo:bar" foobar &&
 +      git clone "foo:bar" foobar &&
        expect_ssh none
  '
  
  test_expect_success 'bracketed hostnames are still ssh' '
 -      clear_ssh &&
        git clone "[myhost:123]:src" ssh-bracket-clone &&
        expect_ssh myhost:123 src
  '
  
 +counter=0
 +# $1 url
 +# $2 none|host
 +# $3 path
 +test_clone_url () {
 +      counter=$(($counter + 1))
 +      test_might_fail git clone "$1" tmp$counter &&
 +      expect_ssh "$2" "$3"
 +}
 +
 +test_expect_success NOT_MINGW 'clone c:temp is ssl' '
 +      test_clone_url c:temp c temp
 +'
 +
 +test_expect_success MINGW 'clone c:temp is dos drive' '
 +      test_clone_url c:temp none
 +'
 +
 +#ip v4
 +for repo in rep rep/home/project 123
 +do
 +      test_expect_success "clone host:$repo" '
 +              test_clone_url host:$repo host $repo
 +      '
 +done
 +
 +#ipv6
 +for repo in rep rep/home/project 123
 +do
 +      test_expect_success "clone [::1]:$repo" '
 +              test_clone_url [::1]:$repo ::1 $repo
 +      '
 +done
 +#home directory
 +test_expect_success "clone host:/~repo" '
 +      test_clone_url host:/~repo host "~repo"
 +'
 +
 +test_expect_success "clone [::1]:/~repo" '
 +      test_clone_url [::1]:/~repo ::1 "~repo"
 +'
 +
 +# Corner cases
 +for url in foo/bar:baz [foo]bar/baz:qux [foo/bar]:baz
 +do
 +      test_expect_success "clone $url is not ssh" '
 +              test_clone_url $url none
 +      '
 +done
 +
 +#with ssh:// scheme
 +test_expect_success 'clone ssh://host.xz/home/user/repo' '
 +      test_clone_url "ssh://host.xz/home/user/repo" host.xz "/home/user/repo"
 +'
 +
 +# from home directory
 +test_expect_success 'clone ssh://host.xz/~repo' '
 +      test_clone_url "ssh://host.xz/~repo" host.xz "~repo"
 +'
 +
 +# with port number
 +test_expect_success 'clone ssh://host.xz:22/home/user/repo' '
 +      test_clone_url "ssh://host.xz:22/home/user/repo" "-p 22 host.xz" "/home/user/repo"
 +'
 +
 +# from home directory with port number
 +test_expect_success 'clone ssh://host.xz:22/~repo' '
 +      test_clone_url "ssh://host.xz:22/~repo" "-p 22 host.xz" "~repo"
 +'
 +
 +#IPv6
 +test_expect_success 'clone ssh://[::1]/home/user/repo' '
 +      test_clone_url "ssh://[::1]/home/user/repo" "::1" "/home/user/repo"
 +'
 +
 +#IPv6 from home directory
 +test_expect_success 'clone ssh://[::1]/~repo' '
 +      test_clone_url "ssh://[::1]/~repo" "::1" "~repo"
 +'
 +
 +#IPv6 with port number
 +test_expect_success 'clone ssh://[::1]:22/home/user/repo' '
 +      test_clone_url "ssh://[::1]:22/home/user/repo" "-p 22 ::1" "/home/user/repo"
 +'
 +
 +#IPv6 from home directory with port number
 +test_expect_success 'clone ssh://[::1]:22/~repo' '
 +      test_clone_url "ssh://[::1]:22/~repo" "-p 22 ::1" "~repo"
 +'
 +
  test_expect_success 'clone from a repository with two identical branches' '
  
        (
  
  '
  
+ test_expect_success 'shallow clone locally' '
+       git clone --depth=1 --no-local src ssrrcc &&
+       git clone ssrrcc ddsstt &&
+       test_cmp ssrrcc/.git/shallow ddsstt/.git/shallow &&
+       ( cd ddsstt && git fsck )
+ '
  test_done
diff --combined transport-helper.c
index 2010674bb4add9c09d02d703b5337a69b14a58a6,e2b42031599e564d3c4168859e287ea3069348bf..087f617d39d79be946bdd60c05890eda28c9675c
@@@ -190,7 -190,7 +190,7 @@@ static struct child_process *get_helper
                        data->export = 1;
                else if (!strcmp(capname, "check-connectivity"))
                        data->check_connectivity = 1;
 -              else if (!data->refspecs && !prefixcmp(capname, "refspec ")) {
 +              else if (!data->refspecs && starts_with(capname, "refspec ")) {
                        ALLOC_GROW(refspecs,
                                   refspec_nr + 1,
                                   refspec_alloc);
                        data->connect = 1;
                } else if (!strcmp(capname, "signed-tags")) {
                        data->signed_tags = 1;
 -              } else if (!prefixcmp(capname, "export-marks ")) {
 +              } else if (starts_with(capname, "export-marks ")) {
                        struct strbuf arg = STRBUF_INIT;
                        strbuf_addstr(&arg, "--export-marks=");
                        strbuf_addstr(&arg, capname + strlen("export-marks "));
                        data->export_marks = strbuf_detach(&arg, NULL);
 -              } else if (!prefixcmp(capname, "import-marks")) {
 +              } else if (starts_with(capname, "import-marks")) {
                        struct strbuf arg = STRBUF_INIT;
                        strbuf_addstr(&arg, "--import-marks=");
                        strbuf_addstr(&arg, capname + strlen("import-marks "));
                        data->import_marks = strbuf_detach(&arg, NULL);
 -              } else if (!prefixcmp(capname, "no-private-update")) {
 +              } else if (starts_with(capname, "no-private-update")) {
                        data->no_private_update = 1;
                } else if (mandatory) {
                        die("Unknown mandatory capability %s. This remote "
@@@ -311,7 -311,7 +311,7 @@@ static int set_helper_option(struct tra
  
        if (!strcmp(buf.buf, "ok"))
                ret = 0;
 -      else if (!prefixcmp(buf.buf, "error")) {
 +      else if (starts_with(buf.buf, "error")) {
                ret = -1;
        } else if (!strcmp(buf.buf, "unsupported"))
                ret = 1;
@@@ -360,6 -360,12 +360,12 @@@ static int fetch_with_fetch(struct tran
            data->transport_options.check_self_contained_and_connected)
                set_helper_option(transport, "check-connectivity", "true");
  
+       if (transport->cloning)
+               set_helper_option(transport, "cloning", "true");
+       if (data->transport_options.update_shallow)
+               set_helper_option(transport, "update-shallow", "true");
        for (i = 0; i < nr_heads; i++) {
                const struct ref *posn = to_fetch[i];
                if (posn->status & REF_STATUS_UPTODATE)
        while (1) {
                recvline(data, &buf);
  
 -              if (!prefixcmp(buf.buf, "lock ")) {
 +              if (starts_with(buf.buf, "lock ")) {
                        const char *name = buf.buf + 5;
                        if (transport->pack_lockfile)
                                warning("%s also locked %s", data->name, name);
@@@ -646,10 -652,10 +652,10 @@@ static int push_update_ref_status(struc
        char *refname, *msg;
        int status;
  
 -      if (!prefixcmp(buf->buf, "ok ")) {
 +      if (starts_with(buf->buf, "ok ")) {
                status = REF_STATUS_OK;
                refname = buf->buf + 3;
 -      } else if (!prefixcmp(buf->buf, "error ")) {
 +      } else if (starts_with(buf->buf, "error ")) {
                status = REF_STATUS_REMOTE_REJECT;
                refname = buf->buf + 6;
        } else
diff --combined transport.c
index 824c5b93f9e1c44887b17ef116b360ec377076a6,d596abb9c6dccb4105e39e862ea6524795a1dbc0..ca7bb441bf503c598d9679b338d7f7c0d8103cb0
@@@ -14,6 -14,7 +14,7 @@@
  #include "url.h"
  #include "submodule.h"
  #include "string-list.h"
+ #include "sha1-array.h"
  
  /* rsync support */
  
@@@ -169,13 -170,13 +170,13 @@@ static void set_upstreams(struct transp
                remotename = ref->name;
                tmp = resolve_ref_unsafe(localname, sha, 1, &flag);
                if (tmp && flag & REF_ISSYMREF &&
 -                      !prefixcmp(tmp, "refs/heads/"))
 +                      starts_with(tmp, "refs/heads/"))
                        localname = tmp;
  
                /* Both source and destination must be local branches. */
 -              if (!localname || prefixcmp(localname, "refs/heads/"))
 +              if (!localname || !starts_with(localname, "refs/heads/"))
                        continue;
 -              if (!remotename || prefixcmp(remotename, "refs/heads/"))
 +              if (!remotename || !starts_with(remotename, "refs/heads/"))
                        continue;
  
                if (!pretend)
  
  static const char *rsync_url(const char *url)
  {
 -      return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
 +      return !starts_with(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
  }
  
  static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
@@@ -296,8 -297,8 +297,8 @@@ static int write_one_ref(const char *na
        FILE *f;
  
        /* when called via for_each_ref(), flags is non-zero */
 -      if (flags && prefixcmp(name, "refs/heads/") &&
 -                      prefixcmp(name, "refs/tags/"))
 +      if (flags && !starts_with(name, "refs/heads/") &&
 +                      !starts_with(name, "refs/tags/"))
                return 0;
  
        strbuf_addstr(buf, name);
@@@ -454,7 -455,8 +455,8 @@@ struct git_transport_data 
        struct child_process *conn;
        int fd[2];
        unsigned got_remote_heads : 1;
-       struct extra_have_objects extra_have;
+       struct sha1_array extra_have;
+       struct sha1_array shallow;
  };
  
  static int set_git_option(struct git_transport_options *opts,
        } else if (!strcmp(name, TRANS_OPT_KEEP)) {
                opts->keep = !!value;
                return 0;
+       } else if (!strcmp(name, TRANS_OPT_UPDATE_SHALLOW)) {
+               opts->update_shallow = !!value;
+               return 0;
        } else if (!strcmp(name, TRANS_OPT_DEPTH)) {
                if (!value)
                        opts->depth = 0;
@@@ -511,7 -516,9 +516,9 @@@ static struct ref *get_refs_via_connect
  
        connect_setup(transport, for_push, 0);
        get_remote_heads(data->fd[0], NULL, 0, &refs,
-                        for_push ? REF_NORMAL : 0, &data->extra_have);
+                        for_push ? REF_NORMAL : 0,
+                        &data->extra_have,
+                        &data->shallow);
        data->got_remote_heads = 1;
  
        return refs;
@@@ -538,16 -545,19 +545,19 @@@ static int fetch_refs_via_pack(struct t
        args.depth = data->options.depth;
        args.check_self_contained_and_connected =
                data->options.check_self_contained_and_connected;
+       args.cloning = transport->cloning;
+       args.update_shallow = data->options.update_shallow;
  
        if (!data->got_remote_heads) {
                connect_setup(transport, 0, 0);
-               get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0, NULL);
+               get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0,
+                                NULL, &data->shallow);
                data->got_remote_heads = 1;
        }
  
        refs = fetch_pack(&args, data->fd, data->conn,
                          refs_tmp ? refs_tmp : transport->remote_refs,
-                         dest, to_fetch, nr_heads,
+                         dest, to_fetch, nr_heads, &data->shallow,
                          &transport->pack_lockfile);
        close(data->fd[0]);
        close(data->fd[1]);
@@@ -652,7 -662,7 +662,7 @@@ static void print_ok_ref_status(struct 
                print_ref_status('-', "[deleted]", ref, NULL, NULL, porcelain);
        else if (is_null_sha1(ref->old_sha1))
                print_ref_status('*',
 -                      (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
 +                      (starts_with(ref->name, "refs/tags/") ? "[new tag]" :
                        "[new branch]"),
                        ref, ref->peer_ref, NULL, porcelain);
        else {
@@@ -713,6 -723,10 +723,10 @@@ static int print_one_push_status(struc
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
                                                 "stale info", porcelain);
                break;
+       case REF_STATUS_REJECT_SHALLOW:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                                                "new shallow roots not allowed", porcelain);
+               break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
                                                 ref->deletion ? NULL : ref->peer_ref,
@@@ -805,7 -819,8 +819,8 @@@ static int git_transport_push(struct tr
                struct ref *tmp_refs;
                connect_setup(transport, 1, 0);
  
-               get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL, NULL);
+               get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL,
+                                NULL, &data->shallow);
                data->got_remote_heads = 1;
        }
  
@@@ -885,6 -900,14 +900,6 @@@ void transport_take_over(struct transpo
        transport->cannot_reuse = 1;
  }
  
 -static int is_local(const char *url)
 -{
 -      const char *colon = strchr(url, ':');
 -      const char *slash = strchr(url, '/');
 -      return !colon || (slash && slash < colon) ||
 -              has_dos_drive_prefix(url);
 -}
 -
  static int is_file(const char *url)
  {
        struct stat buf;
@@@ -922,18 -945,18 +937,18 @@@ struct transport *transport_get(struct 
  
                while (is_urlschemechar(p == url, *p))
                        p++;
 -              if (!prefixcmp(p, "::"))
 +              if (starts_with(p, "::"))
                        helper = xstrndup(url, p - url);
        }
  
        if (helper) {
                transport_helper_init(ret, helper);
 -      } else if (!prefixcmp(url, "rsync:")) {
 +      } else if (starts_with(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
                ret->smart_options = NULL;
 -      } else if (is_local(url) && is_file(url) && is_bundle(url, 1)) {
 +      } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
                ret->disconnect = close_bundle;
                ret->smart_options = NULL;
        } else if (!is_url(url)
 -              || !prefixcmp(url, "file://")
 -              || !prefixcmp(url, "git://")
 -              || !prefixcmp(url, "ssh://")
 -              || !prefixcmp(url, "git+ssh://")
 -              || !prefixcmp(url, "ssh+git://")) {
 +              || starts_with(url, "file://")
 +              || starts_with(url, "git://")
 +              || starts_with(url, "ssh://")
 +              || starts_with(url, "git+ssh://")
 +              || starts_with(url, "ssh+git://")) {
                /* These are builtin smart transports. */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
@@@ -1289,7 -1312,7 +1304,7 @@@ char *transport_anonymize_url(const cha
        size_t anon_len, prefix_len = 0;
  
        anon_part = strchr(url, '@');
 -      if (is_local(url) || !anon_part)
 +      if (url_is_local_not_ssh(url) || !anon_part)
                goto literal_copy;
  
        anon_len = strlen(++anon_part);
diff --combined upload-pack.c
index ec56cdbce9fe0bf87aeafb3567e09cbf546e7218,2d022978a8bfc4b9921a119ae47d90b56d0f2227..0c44f6b292563e7c87bce0b7b5d9b8a4ccd32ec3
@@@ -84,7 -84,7 +84,7 @@@ static void create_pack_file(void
        char *shallow_file = NULL;
  
        if (shallow_nr) {
-               shallow_file = setup_temporary_shallow();
+               shallow_file = setup_temporary_shallow(NULL);
                argv[arg++] = "--shallow-file";
                argv[arg++] = shallow_file;
        }
@@@ -394,7 -394,7 +394,7 @@@ static int get_common_commits(void
                        got_other = 0;
                        continue;
                }
 -              if (!prefixcmp(line, "have ")) {
 +              if (starts_with(line, "have ")) {
                        switch (got_sha1(line+5, sha1)) {
                        case -1: /* they have what we do not */
                                got_other = 1;
@@@ -540,7 -540,7 +540,7 @@@ static void receive_needs(void
                if (!line)
                        break;
  
 -              if (!prefixcmp(line, "shallow ")) {
 +              if (starts_with(line, "shallow ")) {
                        unsigned char sha1[20];
                        struct object *object;
                        if (get_sha1_hex(line + 8, sha1))
                        }
                        continue;
                }
 -              if (!prefixcmp(line, "deepen ")) {
 +              if (starts_with(line, "deepen ")) {
                        char *end;
                        depth = strtol(line + 7, &end, 0);
                        if (end == line + 7 || depth <= 0)
                                die("Invalid deepen: %s", line);
                        continue;
                }
 -              if (prefixcmp(line, "want ") ||
 +              if (!starts_with(line, "want ") ||
                    get_sha1_hex(line+5, sha1_buf))
                        die("git upload-pack: protocol error, "
                            "expected to get sha, not '%s'", line);
        if (depth > 0) {
                struct commit_list *result = NULL, *backup = NULL;
                int i;
-               if (depth == INFINITE_DEPTH)
+               if (depth == INFINITE_DEPTH && !is_repository_shallow())
                        for (i = 0; i < shallows.nr; i++) {
                                struct object *object = shallows.objects[i].item;
                                object->flags |= NOT_SHALLOW;
                                /* make sure the real parents are parsed */
                                unregister_shallow(object->sha1);
                                object->parsed = 0;
 -                              if (parse_commit((struct commit *)object))
 -                                      die("invalid commit");
 +                              parse_commit_or_die((struct commit *)object);
                                parents = ((struct commit *)object)->parents;
                                while (parents) {
                                        add_object_array(&parents->item->object,
@@@ -757,6 -758,7 +757,7 @@@ static void upload_pack(void
                reset_timeout();
                head_ref_namespaced(send_ref, &symref);
                for_each_namespaced_ref(send_ref, &symref);
+               advertise_shallow_grafts(1);
                packet_flush(1);
        } else {
                head_ref_namespaced(mark_our_ref, NULL);
@@@ -814,7 -816,7 +815,7 @@@ int main(int argc, char **argv
                        strict = 1;
                        continue;
                }
 -              if (!prefixcmp(arg, "--timeout=")) {
 +              if (starts_with(arg, "--timeout=")) {
                        timeout = atoi(arg+10);
                        daemon_mode = 1;
                        continue;
  
        if (!enter_repo(dir, strict))
                die("'%s' does not appear to be a git repository", dir);
-       if (is_repository_shallow())
-               die("attempt to fetch/clone from a shallow repository");
        git_config(upload_pack_config, NULL);
        upload_pack();
        return 0;