Merge branch 'nd/shallow-deepen'
authorJunio C Hamano <gitster@pobox.com>
Mon, 10 Oct 2016 21:03:50 +0000 (14:03 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 10 Oct 2016 21:03:50 +0000 (14:03 -0700)
The existing "git fetch --depth=<n>" option was hard to use
correctly when making the history of an existing shallow clone
deeper. A new option, "--deepen=<n>", has been added to make this
easier to use. "git clone" also learned "--shallow-since=<date>"
and "--shallow-exclude=<tag>" options to make it easier to specify
"I am interested only in the recent N months worth of history" and
"Give me only the history since that version".

* nd/shallow-deepen: (27 commits)
fetch, upload-pack: --deepen=N extends shallow boundary by N commits
upload-pack: add get_reachable_list()
upload-pack: split check_unreachable() in two, prep for get_reachable_list()
t5500, t5539: tests for shallow depth excluding a ref
clone: define shallow clone boundary with --shallow-exclude
fetch: define shallow boundary with --shallow-exclude
upload-pack: support define shallow boundary by excluding revisions
refs: add expand_ref()
t5500, t5539: tests for shallow depth since a specific date
clone: define shallow clone boundary based on time with --shallow-since
fetch: define shallow boundary with --shallow-since
upload-pack: add deepen-since to cut shallow repos based on time
shallow.c: implement a generic shallow boundary finder based on rev-list
fetch-pack: use a separate flag for fetch in deepening mode
fetch-pack.c: mark strings for translating
fetch-pack: use a common function for verbose printing
fetch-pack: use skip_prefix() instead of starts_with()
upload-pack: move rev-list code out of check_non_tip()
upload-pack: make check_non_tip() clean things up on error
upload-pack: tighten number parsing at "deepen" lines
...

20 files changed:
1  2 
Documentation/fetch-options.txt
Documentation/git-clone.txt
Documentation/git-fetch-pack.txt
Documentation/gitremote-helpers.txt
Documentation/technical/pack-protocol.txt
Documentation/technical/protocol-capabilities.txt
builtin/clone.c
builtin/fetch-pack.c
builtin/fetch.c
commit.h
fetch-pack.c
refs.c
refs.h
remote-curl.c
shallow.c
t/t5500-fetch-pack.sh
transport-helper.c
transport.c
transport.h
upload-pack.c
index 9eab1f5fa42859651c6a0928ae0434799d5ce30e,3b91f1528e01c91f2b278dcae87db152418ccc75..fb6bebbc618c3f71ab400ff4267264a0167a887a
        linkgit:git-clone[1]), deepen or shorten the history to the specified
        number of commits. Tags for the deepened commits are not fetched.
  
+ --deepen=<depth>::
+       Similar to --depth, except it specifies the number of commits
+       from the current shallow boundary instead of from the tip of
+       each remote branch history.
+ --shallow-since=<date>::
+       Deepen or shorten the history of a shallow repository to
+       include all reachable commits after <date>.
+ --shallow-exclude=<revision>::
+       Deepen or shorten the history of a shallow repository to
+       exclude commits reachable from a specified remote branch or tag.
+       This option can be specified multiple times.
  --unshallow::
        If the source repository is complete, convert a shallow
        repository to a complete one, removing all the limitations
@@@ -52,7 -66,7 +66,7 @@@ ifndef::git-pull[
  
  -p::
  --prune::
 -      After fetching, remove any remote-tracking references that no
 +      Before 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
@@@ -88,7 -102,7 +102,7 @@@ ifndef::git-pull[
        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').
 +      destination of an explicit refspec; see `--prune`).
  
  --recurse-submodules[=yes|on-demand|no]::
        This option controls if and under what conditions new commits of
  
  --no-recurse-submodules::
        Disable recursive fetching of submodules (this has the same effect as
 -      using the '--recurse-submodules=no' option).
 +      using the `--recurse-submodules=no` option).
  
  --submodule-prefix=<path>::
        Prepend <path> to paths printed in informative messages
@@@ -137,7 -151,7 +151,7 @@@ endif::git-pull[
  
  --upload-pack <upload-pack>::
        When given, and the repository to fetch from is handled
 -      by 'git fetch-pack', '--exec=<upload-pack>' is passed to
 +      by 'git fetch-pack', `--exec=<upload-pack>` is passed to
        the command to specify non-default path for the command
        run on the other end.
  
@@@ -158,11 -172,3 +172,11 @@@ endif::git-pull[
        by default when it is attached to a terminal, unless -q
        is specified. This flag forces progress status even if the
        standard error stream is not directed to a terminal.
 +
 +-4::
 +--ipv4::
 +      Use IPv4 addresses only, ignoring IPv6 addresses.
 +
 +-6::
 +--ipv6::
 +      Use IPv6 addresses only, ignoring IPv4 addresses.
index e316c4bd51a64fb9df87ce003f2edffad38b63ad,5049663f3b4d80bf90e9ba4ed696c493981e9a94..35cc34b2fb9a0e696eb86e208416fa93aae3c0c5
@@@ -14,8 -14,8 +14,8 @@@ SYNOPSI
          [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]
          [--dissociate] [--separate-git-dir <git dir>]
          [--depth <depth>] [--[no-]single-branch]
 -        [--recursive | --recurse-submodules] [--] <repository>
 -        [<directory>]
 +        [--recursive | --recurse-submodules] [--[no-]shallow-submodules]
 +        [--jobs <n>] [--] <repository> [<directory>]
  
  DESCRIPTION
  -----------
@@@ -90,16 -90,13 +90,16 @@@ If you want to break the dependency of 
  its source repository, you can simply run `git repack -a` to copy all
  objects from the source repository into a pack in the cloned repository.
  
 ---reference <repository>::
 +--reference[-if-able] <repository>::
        If the reference repository is on the local machine,
        automatically setup `.git/objects/info/alternates` to
        obtain objects from the reference repository.  Using
        an already existing repository as an alternate will
        require fewer objects to be copied from the repository
        being cloned, reducing network and local storage costs.
 +      When using the `--reference-if-able`, a non existing
 +      directory is skipped with a warning instead of aborting
 +      the clone.
  +
  *NOTE*: see the NOTE for the `--shared` option, and also the
  `--dissociate` option.
        Create a 'shallow' clone with a history truncated to the
        specified number of commits. Implies `--single-branch` unless
        `--no-single-branch` is given to fetch the histories near the
 -      tips of all branches.
 +      tips of all branches. If you want to clone submodules shallowly,
 +      also pass `--shallow-submodules`.
  
+ --shallow-since=<date>::
+       Create a shallow clone with a history after the specified time.
+ --shallow-exclude=<revision>::
+       Create a shallow clone with a history, excluding commits
+       reachable from a specified remote branch or tag.  This option
+       can be specified multiple times.
  --[no-]single-branch::
        Clone only the history leading to the tip of a single branch,
        either specified by the `--branch` option or the primary
        repository does not have a worktree/checkout (i.e. if any of
        `--no-checkout`/`-n`, `--bare`, or `--mirror` is given)
  
 +--[no-]shallow-submodules::
 +      All submodules which are cloned will be shallow with a depth of 1.
 +
  --separate-git-dir=<git dir>::
        Instead of placing the cloned repository where it is supposed
        to be, place the cloned repository at the specified directory,
        The result is Git repository can be separated from working
        tree.
  
 +-j <n>::
 +--jobs <n>::
 +      The number of submodules fetched at the same time.
 +      Defaults to the `submodule.fetchJobs` option.
  
  <repository>::
        The (possibly remote) repository to clone from.  See the
index 24417ee3a66218c9033f450ca07c7b26a3bcc018,c20958f58fac0da0b9b0be5317941d66ab59505d..d45f6adc69fef8d99d9297c04bba532cd79a1c71
@@@ -41,13 -41,13 +41,13 @@@ OPTION
        option, then the refs from stdin are processed after those
        on the command line.
  +
 -If '--stateless-rpc' is specified together with this option then
 +If `--stateless-rpc` is specified together with this option then
  the list of refs must be in packet format (pkt-line). Each ref must
  be in a separate packet, and the list must end with a flush packet.
  
  -q::
  --quiet::
 -      Pass '-q' flag to 'git unpack-objects'; this makes the
 +      Pass `-q` flag to 'git unpack-objects'; this makes the
        cloning process less verbose.
  
  -k::
        'git-upload-pack' treats the special depth 2147483647 as
        infinite even if there is an ancestor-chain that long.
  
+ --shallow-since=<date>::
+       Deepen or shorten the history of a shallow'repository to
+       include all reachable commits after <date>.
+ --shallow-exclude=<revision>::
+       Deepen or shorten the history of a shallow repository to
+       exclude commits reachable from a specified remote branch or tag.
+       This option can be specified multiple times.
+ --deepen-relative::
+       Argument --depth specifies the number of commits from the
+       current shallow boundary instead of from the tip of each
+       remote branch history.
  --no-progress::
        Do not show the progress.
  
        The remote heads to update from. This is relative to
        $GIT_DIR (e.g. "HEAD", "refs/heads/master").  When
        unspecified, update from all heads the remote side has.
 ++
 +If the remote has enabled the options `uploadpack.allowTipSHA1InWant` or
 +`uploadpack.allowReachableSHA1InWant`, they may alternatively be 40-hex
 +sha1s present on the remote.
  
  SEE ALSO
  --------
index a4de50ad227bb1653977ee9502a3df20afefaf92,6fca2687422be78106c6bc3ec534a1c046c02a14..9e8681f9e1b554113e7d1881e214bc617af14109
@@@ -43,7 -43,7 +43,7 @@@ arguments. The first argument specifie
  it is either the name of a configured remote or a URL. The second
  argument specifies a URL; it is usually of the form
  '<transport>://<address>', but any arbitrary string is possible.
 -The 'GIT_DIR' environment variable is set up for the remote helper
 +The `GIT_DIR` environment variable is set up for the remote helper
  and can be used to determine where to store additional data or from
  which directory to invoke auxiliary Git commands.
  
@@@ -61,10 -61,10 +61,10 @@@ argument. If such a URL is encountered 
  the first argument is '<address>', and if it is encountered in a
  configured remote, the first argument is the name of that remote.
  
 -Additionally, when a configured remote has 'remote.<name>.vcs' set to
 +Additionally, when a configured remote has `remote.<name>.vcs` set to
  '<transport>', Git explicitly invokes 'git remote-<transport>' with
  '<name>' as the first argument. If set, the second argument is
 -'remote.<name>.url'; otherwise, the second argument is omitted.
 +`remote.<name>.url`; otherwise, the second argument is omitted.
  
  INPUT FORMAT
  ------------
@@@ -210,17 -210,17 +210,17 @@@ the remote repository
  'export-marks' <file>::
        This modifies the 'export' capability, instructing Git to dump the
        internal marks table to <file> when complete. For details,
 -      read up on '--export-marks=<file>' in linkgit:git-fast-export[1].
 +      read up on `--export-marks=<file>` in linkgit:git-fast-export[1].
  
  'import-marks' <file>::
        This modifies the 'export' capability, instructing Git to load the
        marks specified in <file> before processing any input. For details,
 -      read up on '--import-marks=<file>' in linkgit:git-fast-export[1].
 +      read up on `--import-marks=<file>` in linkgit:git-fast-export[1].
  
  'signed-tags'::
        This modifies the 'export' capability, instructing Git to pass
 -      '--signed-tags=verbatim' to linkgit:git-fast-export[1].  In the
 -      absence of this capability, Git will use '--signed-tags=warn-strip'.
 +      `--signed-tags=verbatim` to linkgit:git-fast-export[1].  In the
 +      absence of this capability, Git will use `--signed-tags=warn-strip`.
  
  
  
@@@ -298,7 -298,7 +298,7 @@@ Supported if the helper has the "fetch
        is followed by a blank line). For example, the following would
        be two batches of 'push', the first asking the remote-helper
        to push the local ref 'master' to the remote ref 'master' and
 -      the local 'HEAD' to the remote 'branch', and the second
 +      the local `HEAD` to the remote 'branch', and the second
        asking to push ref 'foo' to ref 'bar' (forced update requested
        by the '+').
  +
@@@ -415,6 -415,17 +415,17 @@@ set by Git if the remote helper has th
  'option depth' <depth>::
        Deepens the history of a shallow repository.
  
+ 'option deepen-since <timestamp>::
+       Deepens the history of a shallow repository based on time.
+ 'option deepen-not <ref>::
+       Deepens the history of a shallow repository excluding ref.
+       Multiple options add up.
+ 'option deepen-relative {'true'|'false'}::
+       Deepens the history of a shallow repository relative to
+       current boundary. Only valid when used with "option depth".
  'option followtags' {'true'|'false'}::
        If enabled the helper should automatically fetch annotated
        tag objects if the object the tag points at was transferred
index 736f3894a85602c10e0bf13fb74eb7f556207672,dee33a66b8e729265d1110c74fb652a3458ac4f4..c59ac9936a89e5e195b9a75bd4fd5d64a2385ffa
@@@ -219,7 -219,9 +219,9 @@@ out of what the server said it could d
  
    shallow-line      =  PKT-LINE("shallow" SP obj-id)
  
-   depth-request     =  PKT-LINE("deepen" SP depth)
+   depth-request     =  PKT-LINE("deepen" SP depth) /
+                      PKT-LINE("deepen-since" SP timestamp) /
+                      PKT-LINE("deepen-not" SP ref)
  
    first-want        =  PKT-LINE("want" SP obj-id SP capability-list)
    additional-want   =  PKT-LINE("want" SP obj-id)
@@@ -307,7 -309,7 +309,7 @@@ In multi_ack mode
      ready to make a packfile, it will blindly ACK all 'have' obj-ids
      back to the client.
  
 -  * the server will then send a 'NACK' and then wait for another response
 +  * the server will then send a 'NAK' and then wait for another response
      from the client - either a 'done' or another list of 'have' lines.
  
  In multi_ack_detailed mode:
@@@ -454,8 -456,7 +456,8 @@@ The reference discovery phase is done n
  fetching protocol. Each reference obj-id and name on the server is sent
  in packet-line format to the client, followed by a flush-pkt.  The only
  real difference is that the capability listing is different - the only
 -possible values are 'report-status', 'delete-refs' and 'ofs-delta'.
 +possible values are 'report-status', 'delete-refs', 'ofs-delta' and
 +'push-options'.
  
  Reference Update Request and Packfile Transfer
  ----------------------------------------------
@@@ -466,10 -467,9 +468,10 @@@ that it wants to update, it sends a lin
  the server, the obj-id the client would like to update it to and the name
  of the reference.
  
 -This list is followed by a flush-pkt and then the packfile that should
 -contain all the objects that the server will need to complete the new
 -references.
 +This list is followed by a flush-pkt. Then the push options are transmitted
 +one per packet followed by another flush-pkt. After that the packfile that
 +should contain all the objects that the server will need to complete the new
 +references will be sent.
  
  ----
    update-request    =  *shallow ( command-list | push-cert ) [packfile]
@@@ -528,7 -528,7 +530,7 @@@ Push Certificat
  
  A push certificate begins with a set of header lines.  After the
  header and an empty line, the protocol commands follow, one per
 -line. Note that the the trailing LF in push-cert PKT-LINEs is _not_
 +line. Note that the trailing LF in push-cert PKT-LINEs is _not_
  optional; it must be present.
  
  Currently, the following header fields are defined:
index 4c28d3a8aea3312190d859e4accb70f9d28973bf,4fd6dccdb52770c1ec0e57229ae87faa33e37eae..26dcc6f502020da5214f1e161f1678813dd24059
@@@ -179,6 -179,31 +179,31 @@@ This capability adds "deepen", "shallow
  the  fetch-pack/upload-pack protocol so clients can request shallow
  clones.
  
+ deepen-since
+ ------------
+ This capability adds "deepen-since" command to fetch-pack/upload-pack
+ protocol so the client can request shallow clones that are cut at a
+ specific time, instead of depth. Internally it's equivalent of doing
+ "rev-list --max-age=<timestamp>" on the server side. "deepen-since"
+ cannot be used with "deepen".
+ deepen-not
+ ----------
+ This capability adds "deepen-not" command to fetch-pack/upload-pack
+ protocol so the client can request shallow clones that are cut at a
+ specific revision, instead of depth. Internally it's equivalent of
+ doing "rev-list --not <rev>" on the server side. "deepen-not"
+ cannot be used with "deepen", but can be used with "deepen-since".
+ deepen-relative
+ ---------------
+ If this capability is requested by the client, the semantics of
+ "deepen" command is changed. The "depth" argument is the depth from
+ the current shallow boundary, instead of the depth from remote refs.
  no-progress
  -----------
  
@@@ -253,15 -278,6 +278,15 @@@ atomic pushes. If the pushing client re
  will update the refs in one atomic transaction. Either all refs are
  updated or none.
  
 +push-options
 +------------
 +
 +If the server sends the 'push-options' capability it is able to accept
 +push options after the update commands have been sent, but before the
 +packfile is streamed. If the pushing client requests this capability,
 +the server will pass the options to the pre- and post- receive hooks
 +that process this push request.
 +
  allow-tip-sha1-in-want
  ----------------------
  
diff --combined builtin/clone.c
index fb75f7ee64a29e4bb79f163e82c73bb8a7bd5280,38492315a6d30087ff7a86a1a2dc67e5d6747357..6c80690adf32dfe55a3a587a85f36e279ccd150e
@@@ -40,20 -40,18 +40,22 @@@ static const char * const builtin_clone
  
  static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
  static int option_local = -1, option_no_hardlinks, option_shared, option_recursive;
- static char *option_template, *option_depth;
 +static int option_shallow_submodules;
+ static int deepen;
+ static char *option_template, *option_depth, *option_since;
  static char *option_origin = NULL;
  static char *option_branch = NULL;
+ static struct string_list option_not = STRING_LIST_INIT_NODUP;
  static const char *real_git_dir;
  static char *option_upload_pack = "git-upload-pack";
  static int option_verbosity;
  static int option_progress = -1;
 -static struct string_list option_config;
 -static struct string_list option_reference;
 +static enum transport_family family;
 +static struct string_list option_config = STRING_LIST_INIT_NODUP;
 +static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
 +static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
  static int option_dissociate;
 +static int max_jobs = -1;
  
  static struct option builtin_clone_options[] = {
        OPT__VERBOSITY(&option_verbosity),
                    N_("initialize submodules in the clone")),
        OPT_BOOL(0, "recurse-submodules", &option_recursive,
                    N_("initialize submodules in the clone")),
 +      OPT_INTEGER('j', "jobs", &max_jobs,
 +                  N_("number of submodules cloned in parallel")),
        OPT_STRING(0, "template", &option_template, N_("template-directory"),
                   N_("directory from which templates will be used")),
 -      OPT_STRING_LIST(0, "reference", &option_reference, N_("repo"),
 +      OPT_STRING_LIST(0, "reference", &option_required_reference, N_("repo"),
                        N_("reference repository")),
 +      OPT_STRING_LIST(0, "reference-if-able", &option_optional_reference,
 +                      N_("repo"), N_("reference repository")),
        OPT_BOOL(0, "dissociate", &option_dissociate,
                 N_("use --reference only while cloning")),
        OPT_STRING('o', "origin", &option_origin, N_("name"),
                   N_("path to git-upload-pack on the remote")),
        OPT_STRING(0, "depth", &option_depth, N_("depth"),
                    N_("create a shallow clone of that depth")),
+       OPT_STRING(0, "shallow-since", &option_since, N_("time"),
+                   N_("create a shallow clone since a specific time")),
+       OPT_STRING_LIST(0, "shallow-exclude", &option_not, N_("revision"),
+                       N_("deepen history of shallow clone by excluding rev")),
        OPT_BOOL(0, "single-branch", &option_single_branch,
                    N_("clone only one branch, HEAD or --branch")),
 +      OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules,
 +                  N_("any cloned submodules will be shallow")),
        OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
                   N_("separate git dir from working tree")),
        OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
                        N_("set config inside the new repository")),
 +      OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
 +                      TRANSPORT_FAMILY_IPV4),
 +      OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
 +                      TRANSPORT_FAMILY_IPV6),
        OPT_END()
  };
  
 -static const char *argv_submodule[] = {
 -      "submodule", "update", "--init", "--recursive", NULL
 -};
 -
  static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
  {
        static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
@@@ -241,8 -237,8 +247,8 @@@ static char *guess_dir_name(const char 
        strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git");
  
        if (!len || (len == 1 && *start == '/'))
 -          die("No directory name could be guessed.\n"
 -              "Please specify a directory on the command line");
 +              die(_("No directory name could be guessed.\n"
 +                    "Please specify a directory on the command line"));
  
        if (is_bare)
                dir = xstrfmt("%.*s.git", (int)len, start);
@@@ -285,37 -281,50 +291,37 @@@ static void strip_trailing_slashes(cha
  
  static int add_one_reference(struct string_list_item *item, void *cb_data)
  {
 -      char *ref_git;
 -      const char *repo;
 -      struct strbuf alternate = STRBUF_INIT;
 -
 -      /* Beware: read_gitfile(), real_path() and mkpath() return static buffer */
 -      ref_git = xstrdup(real_path(item->string));
 -
 -      repo = read_gitfile(ref_git);
 -      if (!repo)
 -              repo = read_gitfile(mkpath("%s/.git", ref_git));
 -      if (repo) {
 -              free(ref_git);
 -              ref_git = xstrdup(repo);
 -      }
 +      struct strbuf err = STRBUF_INIT;
 +      int *required = cb_data;
 +      char *ref_git = compute_alternate_path(item->string, &err);
  
 -      if (!repo && is_directory(mkpath("%s/.git/objects", ref_git))) {
 -              char *ref_git_git = mkpathdup("%s/.git", ref_git);
 -              free(ref_git);
 -              ref_git = ref_git_git;
 -      } else if (!is_directory(mkpath("%s/objects", ref_git))) {
 +      if (!ref_git) {
 +              if (*required)
 +                      die("%s", err.buf);
 +              else
 +                      fprintf(stderr,
 +                              _("info: Could not add alternate for '%s': %s\n"),
 +                              item->string, err.buf);
 +      } else {
                struct strbuf sb = STRBUF_INIT;
 -              if (get_common_dir(&sb, ref_git))
 -                      die(_("reference repository '%s' as a linked checkout is not supported yet."),
 -                          item->string);
 -              die(_("reference repository '%s' is not a local repository."),
 -                  item->string);
 +              strbuf_addf(&sb, "%s/objects", ref_git);
 +              add_to_alternates_file(sb.buf);
 +              strbuf_release(&sb);
        }
  
 -      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);
 +      strbuf_release(&err);
        free(ref_git);
        return 0;
  }
  
  static void setup_reference(void)
  {
 -      for_each_string_list(&option_reference, add_one_reference, NULL);
 +      int required = 1;
 +      for_each_string_list(&option_required_reference,
 +                           add_one_reference, &required);
 +      required = 0;
 +      for_each_string_list(&option_optional_reference,
 +                           add_one_reference, &required);
  }
  
  static void copy_alternates(struct strbuf *src, struct strbuf *dst,
@@@ -614,13 -623,13 +620,13 @@@ static void update_remote_refs(const st
        const struct ref *rm = mapped_refs;
  
        if (check_connectivity) {
 -              if (transport->progress)
 -                      fprintf(stderr, _("Checking connectivity... "));
 -              if (check_everything_connected_with_transport(iterate_ref_map,
 -                                                            0, &rm, transport))
 +              struct check_connected_options opt = CHECK_CONNECTED_INIT;
 +
 +              opt.transport = transport;
 +              opt.progress = transport->progress;
 +
 +              if (check_connected(iterate_ref_map, &rm, &opt))
                        die(_("remote did not send all necessary objects"));
 -              if (transport->progress)
 -                      fprintf(stderr, _("done.\n"));
        }
  
        if (refs) {
                if (create_symref(head_ref.buf,
                                  remote_head_points_at->peer_ref->name,
                                  msg) < 0)
 -                      die("unable to update %s", head_ref.buf);
 +                      die(_("unable to update %s"), head_ref.buf);
                strbuf_release(&head_ref);
        }
  }
@@@ -648,7 -657,7 +654,7 @@@ static void update_head(const struct re
        if (our && skip_prefix(our->name, "refs/heads/", &head)) {
                /* Local default branch link */
                if (create_symref("HEAD", our->name, NULL) < 0)
 -                      die("unable to update HEAD");
 +                      die(_("unable to update HEAD"));
                if (!option_bare) {
                        update_ref(msg, "HEAD", our->old_oid.hash, NULL, 0,
                                   UPDATE_REFS_DIE_ON_ERR);
        }
  }
  
 -static int checkout(void)
 +static int checkout(int submodule_progress)
  {
        unsigned char sha1[20];
        char *head;
        err |= run_hook_le(NULL, "post-checkout", sha1_to_hex(null_sha1),
                           sha1_to_hex(sha1), "1", NULL);
  
 -      if (!err && option_recursive)
 -              err = run_command_v_opt(argv_submodule, RUN_GIT_CMD);
 +      if (!err && option_recursive) {
 +              struct argv_array args = ARGV_ARRAY_INIT;
 +              argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL);
 +
 +              if (option_shallow_submodules == 1)
 +                      argv_array_push(&args, "--depth=1");
 +
 +              if (max_jobs != -1)
 +                      argv_array_pushf(&args, "--jobs=%d", max_jobs);
 +
 +              if (submodule_progress)
 +                      argv_array_push(&args, "--progress");
 +
 +              err = run_command_v_opt(args.argv, RUN_GIT_CMD);
 +              argv_array_clear(&args);
 +      }
  
        return err;
  }
  
  static int write_one_config(const char *key, const char *value, void *data)
  {
 -      return git_config_set_multivar(key, value ? value : "true", "^$", 0);
 +      return git_config_set_multivar_gently(key, value ? value : "true", "^$", 0);
  }
  
  static void write_config(struct string_list *config)
        for (i = 0; i < config->nr; i++) {
                if (git_config_parse_parameter(config->items[i].string,
                                               write_one_config, NULL) < 0)
 -                      die("unable to write parameters to config file");
 +                      die(_("unable to write parameters to config file"));
        }
  }
  
@@@ -844,7 -839,6 +850,7 @@@ int cmd_clone(int argc, const char **ar
        const char *src_ref_prefix = "refs/heads/";
        struct remote *remote;
        int err = 0, complete_refs_before_fetch = 1;
 +      int submodule_progress;
  
        struct refspec *refspec;
        const char *fetch_pattern;
                usage_msg_opt(_("You must specify a repository to clone."),
                        builtin_clone_usage, builtin_clone_options);
  
+       if (option_depth || option_since || option_not.nr)
+               deepen = 1;
        if (option_single_branch == -1)
-               option_single_branch = option_depth ? 1 : 0;
+               option_single_branch = deepen ? 1 : 0;
  
        if (option_mirror)
                option_bare = 1;
                set_git_work_tree(work_tree);
        }
  
 -      junk_git_dir = git_dir;
 +      junk_git_dir = real_git_dir ? real_git_dir : git_dir;
        if (safe_create_leading_directories_const(git_dir) < 0)
                die(_("could not create leading directories of '%s'"), git_dir);
  
 -      set_git_dir_init(git_dir, real_git_dir, 0);
 -      if (real_git_dir) {
 -              git_dir = real_git_dir;
 -              junk_git_dir = real_git_dir;
 -      }
 -
        if (0 <= option_verbosity) {
                if (option_bare)
                        fprintf(stderr, _("Cloning into bare repository '%s'...\n"), dir);
                else
                        fprintf(stderr, _("Cloning into '%s'...\n"), dir);
        }
 -      init_db(option_template, INIT_DB_QUIET);
 +
 +      if (option_recursive) {
 +              if (option_required_reference.nr &&
 +                  option_optional_reference.nr)
 +                      die(_("clone --recursive is not compatible with "
 +                            "both --reference and --reference-if-able"));
 +              else if (option_required_reference.nr) {
 +                      string_list_append(&option_config,
 +                              "submodule.alternateLocation=superproject");
 +                      string_list_append(&option_config,
 +                              "submodule.alternateErrorStrategy=die");
 +              } else if (option_optional_reference.nr) {
 +                      string_list_append(&option_config,
 +                              "submodule.alternateLocation=superproject");
 +                      string_list_append(&option_config,
 +                              "submodule.alternateErrorStrategy=info");
 +              }
 +      }
 +
 +      init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET);
 +
 +      if (real_git_dir)
 +              git_dir = real_git_dir;
 +
        write_config(&option_config);
  
        git_config(git_default_config, NULL);
        git_config_set(key.buf, repo);
        strbuf_reset(&key);
  
 -      if (option_reference.nr)
 +      if (option_required_reference.nr || option_optional_reference.nr)
                setup_reference();
  
        fetch_pattern = value.buf;
        remote = remote_get(option_origin);
        transport = transport_get(remote, remote->url[0]);
        transport_set_verbosity(transport, option_verbosity, option_progress);
 +      transport->family = family;
  
        path = get_repo_path(remote->url[0], &is_bundle);
        is_local = option_local != 0 && path && !is_bundle;
        if (is_local) {
                if (option_depth)
                        warning(_("--depth is ignored in local clones; use file:// instead."));
+               if (option_since)
+                       warning(_("--shallow-since is ignored in local clones; use file:// instead."));
+               if (option_not.nr)
+                       warning(_("--shallow-exclude 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"));
        if (option_depth)
                transport_set_option(transport, TRANS_OPT_DEPTH,
                                     option_depth);
+       if (option_since)
+               transport_set_option(transport, TRANS_OPT_DEEPEN_SINCE,
+                                    option_since);
+       if (option_not.nr)
+               transport_set_option(transport, TRANS_OPT_DEEPEN_NOT,
+                                    (const char *)&option_not);
        if (option_single_branch)
                transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1");
  
                transport_set_option(transport, TRANS_OPT_UPLOADPACK,
                                     option_upload_pack);
  
-       if (transport->smart_options && !option_depth)
+       if (transport->smart_options && !deepen)
                transport->smart_options->check_self_contained_and_connected = 1;
  
        refs = transport_get_remote_refs(transport);
  
        update_head(our_head_points_at, remote_head, reflog_msg.buf);
  
 +      /*
 +       * We want to show progress for recursive submodule clones iff
 +       * we did so for the main clone. But only the transport knows
 +       * the final decision for this flag, so we need to rescue the value
 +       * before we free the transport.
 +       */
 +      submodule_progress = transport->progress;
 +
        transport_unlock_pack(transport);
        transport_disconnect(transport);
  
        }
  
        junk_mode = JUNK_LEAVE_REPO;
 -      err = checkout();
 +      err = checkout(submodule_progress);
  
        strbuf_release(&reflog_msg);
        strbuf_release(&branch_top);
diff --combined builtin/fetch-pack.c
index bfd0be44a91f66034c98361dc713cc0883693a69,82653487f8ac5ff1e4f44532f2c0bb278f65b554..cfe9e447c27469407ab439bacb540a4d0b68d4b4
@@@ -10,34 -10,33 +10,34 @@@ static const char fetch_pack_usage[] 
  "[--include-tag] [--upload-pack=<git-upload-pack>] [--depth=<n>] "
  "[--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)
 +static void add_sought_entry(struct ref ***sought, int *nr, int *alloc,
 +                           const char *name)
  {
 -      struct ref *ref = xcalloc(1, sizeof(*ref) + namelen + 1);
 +      struct ref *ref;
        struct object_id oid;
 -      const int chunksz = GIT_SHA1_HEXSZ + 1;
  
 -      if (namelen > chunksz && name[chunksz - 1] == ' ' &&
 -              !get_oid_hex(name, &oid)) {
 -              oidcpy(&ref->old_oid, &oid);
 -              name += chunksz;
 -              namelen -= chunksz;
 +      if (!get_oid_hex(name, &oid)) {
 +              if (name[GIT_SHA1_HEXSZ] == ' ') {
 +                      /* <sha1> <ref>, find refname */
 +                      name += GIT_SHA1_HEXSZ + 1;
 +              } else if (name[GIT_SHA1_HEXSZ] == '\0') {
 +                      ; /* <sha1>, leave sha1 as name */
 +              } else {
 +                      /* <ref>, clear cruft from oid */
 +                      oidclr(&oid);
 +              }
 +      } else {
 +              /* <ref>, clear cruft from get_oid_hex */
 +              oidclr(&oid);
        }
  
 -      memcpy(ref->name, name, namelen);
 -      ref->name[namelen] = '\0';
 +      ref = alloc_ref(name);
 +      oidcpy(&ref->old_oid, &oid);
        (*nr)++;
        ALLOC_GROW(*sought, *nr, *alloc);
        (*sought)[*nr - 1] = ref;
  }
  
 -static void add_sought_entry(struct ref ***sought, int *nr, int *alloc,
 -                           const char *string)
 -{
 -      add_sought_entry_mem(sought, nr, alloc, string, strlen(string));
 -}
 -
  int cmd_fetch_pack(int argc, const char **argv, const char *prefix)
  {
        int i, ret;
@@@ -51,6 -50,7 +51,7 @@@
        struct child_process *conn;
        struct fetch_pack_args args;
        struct sha1_array shallow = SHA1_ARRAY_INIT;
+       struct string_list deepen_not = STRING_LIST_INIT_DUP;
  
        packet_trace_identity("fetch-pack");
  
        for (i = 1; i < argc && *argv[i] == '-'; i++) {
                const char *arg = argv[i];
  
-               if (starts_with(arg, "--upload-pack=")) {
-                       args.uploadpack = arg + 14;
+               if (skip_prefix(arg, "--upload-pack=", &arg)) {
+                       args.uploadpack = arg;
                        continue;
                }
-               if (starts_with(arg, "--exec=")) {
-                       args.uploadpack = arg + 7;
+               if (skip_prefix(arg, "--exec=", &arg)) {
+                       args.uploadpack = arg;
                        continue;
                }
                if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
                        args.verbose = 1;
                        continue;
                }
-               if (starts_with(arg, "--depth=")) {
-                       args.depth = strtol(arg + 8, NULL, 0);
+               if (skip_prefix(arg, "--depth=", &arg)) {
+                       args.depth = strtol(arg, NULL, 0);
+                       continue;
+               }
+               if (skip_prefix(arg, "--shallow-since=", &arg)) {
+                       args.deepen_since = xstrdup(arg);
+                       continue;
+               }
+               if (skip_prefix(arg, "--shallow-exclude=", &arg)) {
+                       string_list_append(&deepen_not, arg);
+                       continue;
+               }
+               if (!strcmp(arg, "--deepen-relative")) {
+                       args.deepen_relative = 1;
                        continue;
                }
                if (!strcmp("--no-progress", arg)) {
                }
                usage(fetch_pack_usage);
        }
+       if (deepen_not.nr)
+               args.deepen_not = &deepen_not;
  
        if (i < argc)
                dest = argv[i++];
diff --combined builtin/fetch.c
index 164623bb6f2eb3ffad3eac59b8ec440b9cf60d9c,7b0ea1c86ea8c3528d109faa4b9210c0d9cab875..d5329f915e57225e5046f33fae89f6348263f191
@@@ -15,7 -15,6 +15,7 @@@
  #include "submodule.h"
  #include "connected.h"
  #include "argv-array.h"
 +#include "utf8.h"
  
  static const char * const builtin_fetch_usage[] = {
        N_("git fetch [<options>] [<repository> [<refspec>...]]"),
@@@ -35,13 -34,14 +35,15 @@@ static int fetch_prune_config = -1; /* 
  static int prune = -1; /* unspecified */
  #define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
  
- static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity;
+ static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
  static int progress = -1, recurse_submodules = RECURSE_SUBMODULES_DEFAULT;
- static int tags = TAGS_DEFAULT, unshallow, update_shallow;
+ static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
 -static int max_children = 1;
 +static int max_children = -1;
 +static enum transport_family family;
  static const char *depth;
+ static const char *deepen_since;
  static const char *upload_pack;
+ static struct string_list deepen_not = STRING_LIST_INIT_NODUP;
  static struct strbuf default_rla = STRBUF_INIT;
  static struct transport *gtransport;
  static struct transport *gsecondary;
@@@ -117,6 -117,12 +119,12 @@@ static struct option builtin_fetch_opti
        OPT_BOOL(0, "progress", &progress, N_("force progress reporting")),
        OPT_STRING(0, "depth", &depth, N_("depth"),
                   N_("deepen history of shallow clone")),
+       OPT_STRING(0, "shallow-since", &deepen_since, N_("time"),
+                  N_("deepen history of shallow repository based on time")),
+       OPT_STRING_LIST(0, "shallow-exclude", &deepen_not, N_("revision"),
+                       N_("deepen history of shallow clone by excluding rev")),
+       OPT_INTEGER(0, "deepen", &deepen_relative,
+                   N_("deepen history of shallow clone")),
        { OPTION_SET_INT, 0, "unshallow", &unshallow, NULL,
                   N_("convert to a complete repository"),
                   PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1 },
                 N_("accept refs that update .git/shallow")),
        { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"),
          N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg },
 +      OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
 +                      TRANSPORT_FAMILY_IPV4),
 +      OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
 +                      TRANSPORT_FAMILY_IPV6),
        OPT_END()
  };
  
@@@ -450,132 -452,7 +458,132 @@@ fail
                           : STORE_REF_ERROR_OTHER;
  }
  
 -#define REFCOL_WIDTH  10
 +static int refcol_width = 10;
 +static int compact_format;
 +
 +static void adjust_refcol_width(const struct ref *ref)
 +{
 +      int max, rlen, llen, len;
 +
 +      /* uptodate lines are only shown on high verbosity level */
 +      if (!verbosity && !oidcmp(&ref->peer_ref->old_oid, &ref->old_oid))
 +              return;
 +
 +      max    = term_columns();
 +      rlen   = utf8_strwidth(prettify_refname(ref->name));
 +
 +      llen   = utf8_strwidth(prettify_refname(ref->peer_ref->name));
 +
 +      /*
 +       * rough estimation to see if the output line is too long and
 +       * should not be counted (we can't do precise calculation
 +       * anyway because we don't know if the error explanation part
 +       * will be printed in update_local_ref)
 +       */
 +      if (compact_format) {
 +              llen = 0;
 +              max = max * 2 / 3;
 +      }
 +      len = 21 /* flag and summary */ + rlen + 4 /* -> */ + llen;
 +      if (len >= max)
 +              return;
 +
 +      /*
 +       * Not precise calculation for compact mode because '*' can
 +       * appear on the left hand side of '->' and shrink the column
 +       * back.
 +       */
 +      if (refcol_width < rlen)
 +              refcol_width = rlen;
 +}
 +
 +static void prepare_format_display(struct ref *ref_map)
 +{
 +      struct ref *rm;
 +      const char *format = "full";
 +
 +      git_config_get_string_const("fetch.output", &format);
 +      if (!strcasecmp(format, "full"))
 +              compact_format = 0;
 +      else if (!strcasecmp(format, "compact"))
 +              compact_format = 1;
 +      else
 +              die(_("configuration fetch.output contains invalid value %s"),
 +                  format);
 +
 +      for (rm = ref_map; rm; rm = rm->next) {
 +              if (rm->status == REF_STATUS_REJECT_SHALLOW ||
 +                  !rm->peer_ref ||
 +                  !strcmp(rm->name, "HEAD"))
 +                      continue;
 +
 +              adjust_refcol_width(rm);
 +      }
 +}
 +
 +static void print_remote_to_local(struct strbuf *display,
 +                                const char *remote, const char *local)
 +{
 +      strbuf_addf(display, "%-*s -> %s", refcol_width, remote, local);
 +}
 +
 +static int find_and_replace(struct strbuf *haystack,
 +                          const char *needle,
 +                          const char *placeholder)
 +{
 +      const char *p = strstr(haystack->buf, needle);
 +      int plen, nlen;
 +
 +      if (!p)
 +              return 0;
 +
 +      if (p > haystack->buf && p[-1] != '/')
 +              return 0;
 +
 +      plen = strlen(p);
 +      nlen = strlen(needle);
 +      if (plen > nlen && p[nlen] != '/')
 +              return 0;
 +
 +      strbuf_splice(haystack, p - haystack->buf, nlen,
 +                    placeholder, strlen(placeholder));
 +      return 1;
 +}
 +
 +static void print_compact(struct strbuf *display,
 +                        const char *remote, const char *local)
 +{
 +      struct strbuf r = STRBUF_INIT;
 +      struct strbuf l = STRBUF_INIT;
 +
 +      if (!strcmp(remote, local)) {
 +              strbuf_addf(display, "%-*s -> *", refcol_width, remote);
 +              return;
 +      }
 +
 +      strbuf_addstr(&r, remote);
 +      strbuf_addstr(&l, local);
 +
 +      if (!find_and_replace(&r, local, "*"))
 +              find_and_replace(&l, remote, "*");
 +      print_remote_to_local(display, r.buf, l.buf);
 +
 +      strbuf_release(&r);
 +      strbuf_release(&l);
 +}
 +
 +static void format_display(struct strbuf *display, char code,
 +                         const char *summary, const char *error,
 +                         const char *remote, const char *local)
 +{
 +      strbuf_addf(display, "%c %-*s ", code, TRANSPORT_SUMMARY(summary));
 +      if (!compact_format)
 +              print_remote_to_local(display, remote, local);
 +      else
 +              print_compact(display, remote, local);
 +      if (error)
 +              strbuf_addf(display, "  (%s)", error);
 +}
  
  static int update_local_ref(struct ref *ref,
                            const char *remote,
  
        if (!oidcmp(&ref->old_oid, &ref->new_oid)) {
                if (verbosity > 0)
 -                      strbuf_addf(display, "= %-*s %-*s -> %s",
 -                                  TRANSPORT_SUMMARY(_("[up to date]")),
 -                                  REFCOL_WIDTH, remote, pretty_ref);
 +                      format_display(display, '=', _("[up to date]"), NULL,
 +                                     remote, pretty_ref);
                return 0;
        }
  
                 * If this is the head, and it's not okay to update
                 * the head, and the old value of the head isn't empty...
                 */
 -              strbuf_addf(display,
 -                          _("! %-*s %-*s -> %s  (can't fetch in current branch)"),
 -                          TRANSPORT_SUMMARY(_("[rejected]")),
 -                          REFCOL_WIDTH, remote, pretty_ref);
 +              format_display(display, '!', _("[rejected]"),
 +                             _("can't fetch in current branch"),
 +                             remote, pretty_ref);
                return 1;
        }
  
            starts_with(ref->name, "refs/tags/")) {
                int r;
                r = s_update_ref("updating tag", ref, 0);
 -              strbuf_addf(display, "%c %-*s %-*s -> %s%s",
 -                          r ? '!' : '-',
 -                          TRANSPORT_SUMMARY(_("[tag update]")),
 -                          REFCOL_WIDTH, remote, pretty_ref,
 -                          r ? _("  (unable to update local ref)") : "");
 +              format_display(display, r ? '!' : 't', _("[tag update]"),
 +                             r ? _("unable to update local ref") : NULL,
 +                             remote, pretty_ref);
                return r;
        }
  
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_oid.hash);
                r = s_update_ref(msg, ref, 0);
 -              strbuf_addf(display, "%c %-*s %-*s -> %s%s",
 -                          r ? '!' : '*',
 -                          TRANSPORT_SUMMARY(what),
 -                          REFCOL_WIDTH, remote, pretty_ref,
 -                          r ? _("  (unable to update local ref)") : "");
 +              format_display(display, r ? '!' : '*', what,
 +                             r ? _("unable to update local ref") : NULL,
 +                             remote, pretty_ref);
                return r;
        }
  
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_oid.hash);
                r = s_update_ref("fast-forward", ref, 1);
 -              strbuf_addf(display, "%c %-*s %-*s -> %s%s",
 -                          r ? '!' : ' ',
 -                          TRANSPORT_SUMMARY_WIDTH, quickref.buf,
 -                          REFCOL_WIDTH, remote, pretty_ref,
 -                          r ? _("  (unable to update local ref)") : "");
 +              format_display(display, r ? '!' : ' ', quickref.buf,
 +                             r ? _("unable to update local ref") : NULL,
 +                             remote, pretty_ref);
                strbuf_release(&quickref);
                return r;
        } else if (force || ref->force) {
                    (recurse_submodules != RECURSE_SUBMODULES_ON))
                        check_for_new_submodule_commits(ref->new_oid.hash);
                r = s_update_ref("forced-update", ref, 1);
 -              strbuf_addf(display, "%c %-*s %-*s -> %s  (%s)",
 -                          r ? '!' : '+',
 -                          TRANSPORT_SUMMARY_WIDTH, quickref.buf,
 -                          REFCOL_WIDTH, remote, pretty_ref,
 -                          r ? _("unable to update local ref") : _("forced update"));
 +              format_display(display, r ? '!' : '+', quickref.buf,
 +                             r ? _("unable to update local ref") : _("forced update"),
 +                             remote, pretty_ref);
                strbuf_release(&quickref);
                return r;
        } else {
 -              strbuf_addf(display, "! %-*s %-*s -> %s  %s",
 -                          TRANSPORT_SUMMARY(_("[rejected]")),
 -                          REFCOL_WIDTH, remote, pretty_ref,
 -                          _("(non-fast-forward)"));
 +              format_display(display, '!', _("[rejected]"), _("non-fast-forward"),
 +                             remote, pretty_ref);
                return 1;
        }
  }
@@@ -721,7 -610,7 +729,7 @@@ static int store_updated_refs(const cha
  
        fp = fopen(filename, "a");
        if (!fp)
 -              return error(_("cannot open %s: %s\n"), filename, strerror(errno));
 +              return error_errno(_("cannot open %s"), filename);
  
        if (raw_url)
                url = transport_anonymize_url(raw_url);
                url = xstrdup("foreign");
  
        rm = ref_map;
 -      if (check_everything_connected(iterate_ref_map, 0, &rm)) {
 +      if (check_connected(iterate_ref_map, &rm, NULL)) {
                rc = error(_("%s did not send all necessary objects\n"), url);
                goto abort;
        }
  
 +      prepare_format_display(ref_map);
 +
        /*
         * We do a pass for each fetch_head_status type in their enum order, so
         * merged entries are written before not-for-merge. That lets readers
                                rc |= update_local_ref(ref, what, rm, &note);
                                free(ref);
                        } else
 -                              strbuf_addf(&note, "* %-*s %-*s -> FETCH_HEAD",
 -                                          TRANSPORT_SUMMARY_WIDTH,
 -                                          *kind ? kind : "branch",
 -                                          REFCOL_WIDTH,
 -                                          *what ? what : "HEAD");
 +                              format_display(&note, '*',
 +                                             *kind ? kind : "branch", NULL,
 +                                             *what ? what : "HEAD",
 +                                             "FETCH_HEAD");
                        if (note.len) {
                                if (verbosity >= 0 && !shown_url) {
                                        fprintf(stderr, _("From %.*s\n"),
  static int quickfetch(struct ref *ref_map)
  {
        struct ref *rm = ref_map;
 +      struct check_connected_options opt = CHECK_CONNECTED_INIT;
  
        /*
         * If we are deepening a shallow clone we already have these
         * really need to perform.  Claiming failure now will ensure
         * we perform the network exchange to deepen our history.
         */
-       if (depth)
+       if (deepen)
                return -1;
 -      return check_everything_connected(iterate_ref_map, 1, &rm);
 +      opt.quiet = 1;
 +      return check_connected(iterate_ref_map, &rm, &opt);
  }
  
  static int fetch_refs(struct transport *transport, struct ref *ref_map)
@@@ -923,21 -809,19 +931,21 @@@ static int prune_refs(struct refspec *r
                for (ref = stale_refs; ref; ref = ref->next)
                        string_list_append(&refnames, ref->name);
  
 -              result = delete_refs(&refnames);
 +              result = delete_refs(&refnames, 0);
                string_list_clear(&refnames, 0);
        }
  
        if (verbosity >= 0) {
                for (ref = stale_refs; ref; ref = ref->next) {
 +                      struct strbuf sb = STRBUF_INIT;
                        if (!shown_url) {
                                fprintf(stderr, _("From %.*s\n"), url_len, url);
                                shown_url = 1;
                        }
 -                      fprintf(stderr, " x %-*s %-*s -> %s\n",
 -                              TRANSPORT_SUMMARY(_("[deleted]")),
 -                              REFCOL_WIDTH, _("(none)"), prettify_refname(ref->name));
 +                      format_display(&sb, '-', _("[deleted]"), NULL,
 +                                     _("(none)"), prettify_refname(ref->name));
 +                      fprintf(stderr, " %s\n",sb.buf);
 +                      strbuf_release(&sb);
                        warn_dangling_symref(stderr, dangling_msg, ref->name);
                }
        }
@@@ -967,7 -851,7 +975,7 @@@ static int truncate_fetch_head(void
        FILE *fp = fopen_for_writing(filename);
  
        if (!fp)
 -              return error(_("cannot open %s: %s\n"), filename, strerror(errno));
 +              return error_errno(_("cannot open %s"), filename);
        fclose(fp);
        return 0;
  }
@@@ -983,18 -867,24 +991,25 @@@ static void set_option(struct transpor
                        name, transport->url);
  }
  
- static struct transport *prepare_transport(struct remote *remote)
+ static struct transport *prepare_transport(struct remote *remote, int deepen)
  {
        struct transport *transport;
        transport = transport_get(remote, NULL);
        transport_set_verbosity(transport, verbosity, progress);
 +      transport->family = family;
        if (upload_pack)
                set_option(transport, TRANS_OPT_UPLOADPACK, upload_pack);
        if (keep)
                set_option(transport, TRANS_OPT_KEEP, "yes");
        if (depth)
                set_option(transport, TRANS_OPT_DEPTH, depth);
+       if (deepen && deepen_since)
+               set_option(transport, TRANS_OPT_DEEPEN_SINCE, deepen_since);
+       if (deepen && deepen_not.nr)
+               set_option(transport, TRANS_OPT_DEEPEN_NOT,
+                          (const char *)&deepen_not);
+       if (deepen_relative)
+               set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, "yes");
        if (update_shallow)
                set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes");
        return transport;
  
  static void backfill_tags(struct transport *transport, struct ref *ref_map)
  {
-       if (transport->cannot_reuse) {
-               gsecondary = prepare_transport(transport->remote);
+       int cannot_reuse;
+       /*
+        * Once we have set TRANS_OPT_DEEPEN_SINCE, we can't unset it
+        * when remote helper is used (setting it to an empty string
+        * is not unsetting). We could extend the remote helper
+        * protocol for that, but for now, just force a new connection
+        * without deepen-since. Similar story for deepen-not.
+        */
+       cannot_reuse = transport->cannot_reuse ||
+               deepen_since || deepen_not.nr;
+       if (cannot_reuse) {
+               gsecondary = prepare_transport(transport->remote, 0);
                transport = gsecondary;
        }
  
        transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL);
        transport_set_option(transport, TRANS_OPT_DEPTH, "0");
+       transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL);
        fetch_refs(transport, ref_map);
  
        if (gsecondary) {
@@@ -1124,7 -1026,7 +1151,7 @@@ static int get_remote_group(const char 
                        size_t wordlen = strcspn(value, " \t\n");
  
                        if (wordlen >= 1)
 -                              string_list_append(g->list,
 +                              string_list_append_nodup(g->list,
                                                   xstrndup(value, wordlen));
                        value += wordlen + (value[wordlen] != '\0');
                }
@@@ -1141,9 -1043,10 +1168,9 @@@ static int add_remote_or_group(const ch
  
        git_config(get_remote_group, &g);
        if (list->nr == prev_nr) {
 -              struct remote *remote;
 -              if (!remote_is_configured(name))
 +              struct remote *remote = remote_get(name);
 +              if (!remote_is_configured(remote))
                        return 0;
 -              remote = remote_get(name);
                string_list_append(list, remote->name);
        }
        return 1;
@@@ -1219,7 -1122,7 +1246,7 @@@ static int fetch_one(struct remote *rem
                die(_("No remote repository specified.  Please, specify either a URL or a\n"
                    "remote name from which new revisions should be fetched."));
  
-       gtransport = prepare_transport(remote);
+       gtransport = prepare_transport(remote, 1);
  
        if (prune < 0) {
                /* no command line request */
        if (argc > 0) {
                int j = 0;
                int i;
 -              refs = xcalloc(argc + 1, sizeof(const char *));
 +              refs = xcalloc(st_add(argc, 1), sizeof(const char *));
                for (i = 0; i < argc; i++) {
                        if (!strcmp(argv[i], "tag")) {
                                i++;
  int cmd_fetch(int argc, const char **argv, const char *prefix)
  {
        int i;
 -      struct string_list list = STRING_LIST_INIT_NODUP;
 +      struct string_list list = STRING_LIST_INIT_DUP;
        struct remote *remote;
        int result = 0;
        struct argv_array argv_gc_auto = ARGV_ARRAY_INIT;
        argc = parse_options(argc, argv, prefix,
                             builtin_fetch_options, builtin_fetch_usage, 0);
  
+       if (deepen_relative) {
+               if (deepen_relative < 0)
+                       die(_("Negative depth in --deepen is not supported"));
+               if (depth)
+                       die(_("--deepen and --depth are mutually exclusive"));
+               depth = xstrfmt("%d", deepen_relative);
+       }
        if (unshallow) {
                if (depth)
                        die(_("--depth and --unshallow cannot be used together"));
        /* 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 (depth || deepen_since || deepen_not.nr)
+               deepen = 1;
  
        if (recurse_submodules != RECURSE_SUBMODULES_OFF) {
                if (recurse_submodules_default) {
                argv_array_clear(&options);
        }
  
 -      /* All names were strdup()ed or strndup()ed */
 -      list.strdup_strings = 1;
        string_list_clear(&list, 0);
  
        close_all_packs();
diff --combined commit.h
index 32e1a113e589f10ab3080dc173295083cd0c84ca,b717be1eb1f06a12619feb5baee4f1d58766d5e3..afd14f318c0c4d18f3a03781d3229f2fdc0f64c6
+++ b/commit.h
@@@ -131,17 -131,11 +131,17 @@@ enum cmit_fmt 
        CMIT_FMT_FULLER,
        CMIT_FMT_ONELINE,
        CMIT_FMT_EMAIL,
 +      CMIT_FMT_MBOXRD,
        CMIT_FMT_USERFORMAT,
  
        CMIT_FMT_UNSPECIFIED
  };
  
 +static inline int cmit_fmt_is_mail(enum cmit_fmt fmt)
 +{
 +      return (fmt == CMIT_FMT_EMAIL || fmt == CMIT_FMT_MBOXRD);
 +}
 +
  struct pretty_print_context {
        /*
         * Callers should tweak these to change the behavior of pp_* functions.
        int preserve_subject;
        struct date_mode date_mode;
        unsigned date_mode_explicit:1;
 +      int expand_tabs_in_log;
        int need_8bit_cte;
        char *notes_message;
        struct reflog_walk_info *reflog_info;
         * should not be counted on by callers.
         */
        struct string_list in_body_headers;
 +      int graph_width;
  };
  
  struct userformat_want {
@@@ -184,7 -176,6 +184,7 @@@ extern const char *format_subject(struc
                                  const char *line_separator);
  extern void userformat_find_requirements(const char *fmt, struct userformat_want *w);
  extern int commit_format_is_empty(enum cmit_fmt);
 +extern const char *skip_blank_lines(const char *msg);
  extern void format_commit_message(const struct commit *commit,
                                  const char *format, struct strbuf *sb,
                                  const struct pretty_print_context *context);
@@@ -267,6 -258,8 +267,8 @@@ extern int for_each_commit_graft(each_c
  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 struct commit_list *get_shallow_commits_by_rev_list(
+               int ac, const char **av, int shallow_flag, int not_shallow_flag);
  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);
@@@ -362,11 -355,9 +364,11 @@@ extern void for_each_mergetag(each_merg
  
  struct merge_remote_desc {
        struct object *obj; /* the named object, could be a tag */
 -      const char *name;
 +      char name[FLEX_ARRAY];
  };
  #define merge_remote_util(commit) ((struct merge_remote_desc *)((commit)->util))
 +extern void set_merge_remote_desc(struct commit *commit,
 +                                const char *name, struct object *obj);
  
  /*
   * Given "name" from the command line to merge, find the commit object
@@@ -379,6 -370,10 +381,6 @@@ extern int parse_signed_commit(const st
                               struct strbuf *message, struct strbuf *signature);
  extern int remove_signature(struct strbuf *buf);
  
 -extern void print_commit_list(struct commit_list *list,
 -                            const char *format_cur,
 -                            const char *format_last);
 -
  /*
   * Check the signature of the given commit. The result of the check is stored
   * in sig->check_result, 'G' for a good signature, 'U' for a good signature
diff --combined fetch-pack.c
index 300763fae1e5b602108ad15020d4f50152778b2b,e2a235f536a2448feb9a396019ec6da5e3b9d609..cb45c346ea97059b66642b458c7ca372ece67aaa
@@@ -21,6 -21,8 +21,8 @@@ static int fetch_unpack_limit = -1
  static int unpack_limit = 100;
  static int prefer_ofs_delta = 1;
  static int no_done;
+ static int deepen_since_ok;
+ static int deepen_not_ok;
  static int fetch_fsck_objects = -1;
  static int transfer_fsck_objects = -1;
  static int agent_supported;
@@@ -50,6 -52,21 +52,21 @@@ static int non_common_revs, multi_ack, 
  #define ALLOW_REACHABLE_SHA1  02
  static unsigned int allow_unadvertised_object_request;
  
+ __attribute__((format (printf, 2, 3)))
+ static inline void print_verbose(const struct fetch_pack_args *args,
+                                const char *fmt, ...)
+ {
+       va_list params;
+       if (!args->verbose)
+               return;
+       va_start(params, fmt);
+       vfprintf(stderr, fmt, params);
+       va_end(params);
+       fputc('\n', stderr);
+ }
  static void rev_list_push(struct commit *commit, int mark)
  {
        if (!(commit->object.flags & mark)) {
@@@ -182,7 -199,7 +199,7 @@@ enum ack_type 
  
  static void consume_shallow_list(struct fetch_pack_args *args, int fd)
  {
-       if (args->stateless_rpc && args->depth > 0) {
+       if (args->stateless_rpc && args->deepen) {
                /* If we sent a depth we will get back "duplicate"
                 * shallow and unshallow commands every time there
                 * is a block of have lines exchanged.
                                continue;
                        if (starts_with(line, "unshallow "))
                                continue;
-                       die("git fetch-pack: expected shallow list");
+                       die(_("git fetch-pack: expected shallow list"));
                }
        }
  }
@@@ -205,7 -222,7 +222,7 @@@ static enum ack_type get_ack(int fd, un
        const char *arg;
  
        if (!len)
-               die("git fetch-pack: expected ACK/NAK, got EOF");
+               die(_("git fetch-pack: expected ACK/NAK, got EOF"));
        if (!strcmp(line, "NAK"))
                return NAK;
        if (skip_prefix(line, "ACK ", &arg)) {
                        return ACK;
                }
        }
-       die("git fetch_pack: expected ACK/NAK, got '%s'", line);
+       die(_("git fetch_pack: expected ACK/NAK, got '%s'"), line);
  }
  
  static void send_request(struct fetch_pack_args *args,
@@@ -243,21 -260,16 +260,21 @@@ static void insert_one_alternate_ref(co
  
  #define INITIAL_FLUSH 16
  #define PIPESAFE_FLUSH 32
 -#define LARGE_FLUSH 1024
 +#define LARGE_FLUSH 16384
  
  static int next_flush(struct fetch_pack_args *args, int count)
  {
 -      int flush_limit = args->stateless_rpc ? LARGE_FLUSH : PIPESAFE_FLUSH;
 -
 -      if (count < flush_limit)
 -              count <<= 1;
 -      else
 -              count += flush_limit;
 +      if (args->stateless_rpc) {
 +              if (count < LARGE_FLUSH)
 +                      count <<= 1;
 +              else
 +                      count = count * 11 / 10;
 +      } else {
 +              if (count < PIPESAFE_FLUSH)
 +                      count <<= 1;
 +              else
 +                      count += PIPESAFE_FLUSH;
 +      }
        return count;
  }
  
@@@ -275,7 -287,7 +292,7 @@@ static int find_common(struct fetch_pac
        size_t state_len = 0;
  
        if (args->stateless_rpc && multi_ack == 1)
-               die("--stateless-rpc requires multi_ack_detailed");
+               die(_("--stateless-rpc requires multi_ack_detailed"));
        if (marked)
                for_each_ref(clear_marks, NULL);
        marked = 1;
                        if (no_done)            strbuf_addstr(&c, " no-done");
                        if (use_sideband == 2)  strbuf_addstr(&c, " side-band-64k");
                        if (use_sideband == 1)  strbuf_addstr(&c, " side-band");
+                       if (args->deepen_relative) strbuf_addstr(&c, " deepen-relative");
                        if (args->use_thin_pack) strbuf_addstr(&c, " thin-pack");
                        if (args->no_progress)   strbuf_addstr(&c, " no-progress");
                        if (args->include_tag)   strbuf_addstr(&c, " include-tag");
                        if (prefer_ofs_delta)   strbuf_addstr(&c, " ofs-delta");
+                       if (deepen_since_ok)    strbuf_addstr(&c, " deepen-since");
+                       if (deepen_not_ok)      strbuf_addstr(&c, " deepen-not");
                        if (agent_supported)    strbuf_addf(&c, " agent=%s",
                                                            git_user_agent_sanitized());
                        packet_buf_write(&req_buf, "want %s%s\n", remote_hex, c.buf);
                write_shallow_commits(&req_buf, 1, NULL);
        if (args->depth > 0)
                packet_buf_write(&req_buf, "deepen %d", args->depth);
+       if (args->deepen_since) {
+               unsigned long max_age = approxidate(args->deepen_since);
+               packet_buf_write(&req_buf, "deepen-since %lu", max_age);
+       }
+       if (args->deepen_not) {
+               int i;
+               for (i = 0; i < args->deepen_not->nr; i++) {
+                       struct string_list_item *s = args->deepen_not->items + i;
+                       packet_buf_write(&req_buf, "deepen-not %s", s->string);
+               }
+       }
        packet_buf_flush(&req_buf);
        state_len = req_buf.len;
  
-       if (args->depth > 0) {
+       if (args->deepen) {
                char *line;
                const char *arg;
                unsigned char sha1[20];
                while ((line = packet_read_line(fd[0], NULL))) {
                        if (skip_prefix(line, "shallow ", &arg)) {
                                if (get_sha1_hex(arg, sha1))
-                                       die("invalid shallow line: %s", line);
+                                       die(_("invalid shallow line: %s"), line);
                                register_shallow(sha1);
                                continue;
                        }
                        if (skip_prefix(line, "unshallow ", &arg)) {
                                if (get_sha1_hex(arg, sha1))
-                                       die("invalid unshallow line: %s", line);
+                                       die(_("invalid unshallow line: %s"), line);
                                if (!lookup_object(sha1))
-                                       die("object not found: %s", line);
+                                       die(_("object not found: %s"), line);
                                /* make sure that it is parsed as shallow */
                                if (!parse_object(sha1))
-                                       die("error in object: %s", line);
+                                       die(_("error in object: %s"), line);
                                if (unregister_shallow(sha1))
-                                       die("no shallow found: %s", line);
+                                       die(_("no shallow found: %s"), line);
                                continue;
                        }
-                       die("expected shallow/unshallow, got %s", line);
+                       die(_("expected shallow/unshallow, got %s"), line);
                }
        } else if (!args->stateless_rpc)
                send_request(args, fd[1], &req_buf);
        retval = -1;
        while ((sha1 = get_rev())) {
                packet_buf_write(&req_buf, "have %s\n", sha1_to_hex(sha1));
-               if (args->verbose)
-                       fprintf(stderr, "have %s\n", sha1_to_hex(sha1));
+               print_verbose(args, "have %s", sha1_to_hex(sha1));
                in_vain++;
                if (flush_at <= ++count) {
                        int ack;
                        consume_shallow_list(args, fd[0]);
                        do {
                                ack = get_ack(fd[0], result_sha1);
-                               if (args->verbose && ack)
-                                       fprintf(stderr, "got ack %d %s\n", ack,
-                                                       sha1_to_hex(result_sha1));
+                               if (ack)
+                                       print_verbose(args, _("got %s %d %s"), "ack",
+                                                     ack, sha1_to_hex(result_sha1));
                                switch (ack) {
                                case ACK:
                                        flushes = 0;
                                        struct commit *commit =
                                                lookup_commit(result_sha1);
                                        if (!commit)
-                                               die("invalid commit %s", sha1_to_hex(result_sha1));
+                                               die(_("invalid commit %s"), sha1_to_hex(result_sha1));
                                        if (args->stateless_rpc
                                         && ack == ACK_common
                                         && !(commit->object.flags & COMMON)) {
                                                const char *hex = sha1_to_hex(result_sha1);
                                                packet_buf_write(&req_buf, "have %s\n", hex);
                                                state_len = req_buf.len;
 -                                      }
 +                                              /*
 +                                               * Reset in_vain because an ack
 +                                               * for this commit has not been
 +                                               * seen.
 +                                               */
 +                                              in_vain = 0;
 +                                      } else if (!args->stateless_rpc
 +                                                 || ack != ACK_common)
 +                                              in_vain = 0;
                                        mark_common(commit, 0, 1);
                                        retval = 0;
 -                                      in_vain = 0;
                                        got_continue = 1;
                                        if (ack == ACK_ready) {
                                                clear_prio_queue(&rev_list);
                        } while (ack);
                        flushes--;
                        if (got_continue && MAX_IN_VAIN < in_vain) {
-                               if (args->verbose)
-                                       fprintf(stderr, "giving up\n");
+                               print_verbose(args, _("giving up"));
                                break; /* give up */
                        }
                }
@@@ -461,8 -478,7 +490,7 @@@ done
                packet_buf_write(&req_buf, "done\n");
                send_request(args, fd[1], &req_buf);
        }
-       if (args->verbose)
-               fprintf(stderr, "done\n");
+       print_verbose(args, _("done"));
        if (retval != 0) {
                multi_ack = 0;
                flushes++;
        while (flushes || multi_ack) {
                int ack = get_ack(fd[0], result_sha1);
                if (ack) {
-                       if (args->verbose)
-                               fprintf(stderr, "got ack (%d) %s\n", ack,
-                                       sha1_to_hex(result_sha1));
+                       print_verbose(args, _("got %s (%d) %s"), "ack",
+                                     ack, sha1_to_hex(result_sha1));
                        if (ack == ACK)
                                return 0;
                        multi_ack = 1;
@@@ -521,9 -536,8 +548,8 @@@ static void mark_recent_complete_commit
                                         unsigned long cutoff)
  {
        while (complete && cutoff <= complete->item->date) {
-               if (args->verbose)
-                       fprintf(stderr, "Marking %s as complete\n",
-                               oid_to_hex(&complete->item->object.oid));
+               print_verbose(args, _("Marking %s as complete"),
+                             oid_to_hex(&complete->item->object.oid));
                pop_most_recent_commit(&complete, COMPLETE);
        }
  }
@@@ -559,7 -573,7 +585,7 @@@ static void filter_refs(struct fetch_pa
                }
  
                if (!keep && args->fetch_all &&
-                   (!args->depth || !starts_with(ref->name, "refs/tags/")))
+                   (!args->deepen || !starts_with(ref->name, "refs/tags/")))
                        keep = 1;
  
                if (keep) {
@@@ -629,7 -643,7 +655,7 @@@ static int everything_local(struct fetc
                }
        }
  
-       if (!args->depth) {
+       if (!args->deepen) {
                for_each_ref(mark_complete_oid, NULL);
                for_each_alternate_ref(mark_alternate_complete, NULL);
                commit_list_sort_by_date(&complete);
                o = lookup_object(remote);
                if (!o || !(o->flags & COMPLETE)) {
                        retval = 0;
-                       if (!args->verbose)
-                               continue;
-                       fprintf(stderr,
-                               "want %s (%s)\n", sha1_to_hex(remote),
-                               ref->name);
+                       print_verbose(args, "want %s (%s)", sha1_to_hex(remote),
+                                     ref->name);
                        continue;
                }
-               if (!args->verbose)
-                       continue;
-               fprintf(stderr,
-                       "already have %s (%s)\n", sha1_to_hex(remote),
-                       ref->name);
+               print_verbose(args, _("already have %s (%s)"), sha1_to_hex(remote),
+                             ref->name);
        }
        return retval;
  }
  static int sideband_demux(int in, int out, void *data)
  {
        int *xd = data;
 +      int ret;
  
 -      int ret = recv_sideband("fetch-pack", xd[0], out);
 +      ret = recv_sideband("fetch-pack", xd[0], out);
        close(out);
        return ret;
  }
@@@ -710,10 -717,8 +730,9 @@@ static int get_pack(struct fetch_pack_a
                demux.proc = sideband_demux;
                demux.data = xd;
                demux.out = -1;
 +              demux.isolate_sigpipe = 1;
                if (start_async(&demux))
-                       die("fetch-pack: unable to fork off sideband"
-                           " demultiplexer");
+                       die(_("fetch-pack: unable to fork off sideband demultiplexer"));
        }
        else
                demux.out = xd[0];
        if (!args->keep_pack && unpack_limit) {
  
                if (read_pack_header(demux.out, &header))
-                       die("protocol error: bad pack header");
+                       die(_("protocol error: bad pack header"));
                pass_header = 1;
                if (ntohl(header.hdr_entries) < unpack_limit)
                        do_keep = 0;
        cmd.in = demux.out;
        cmd.git_cmd = 1;
        if (start_command(&cmd))
-               die("fetch-pack: unable to fork off %s", cmd_name);
+               die(_("fetch-pack: unable to fork off %s"), cmd_name);
        if (do_keep && pack_lockfile) {
                *pack_lockfile = index_pack_lockfile(cmd.out);
                close(cmd.out);
                        args->check_self_contained_and_connected &&
                        ret == 0;
        else
-               die("%s failed", cmd_name);
+               die(_("%s failed"), cmd_name);
        if (use_sideband && finish_async(&demux))
-               die("error in sideband demultiplexer");
+               die(_("error in sideband demultiplexer"));
        return 0;
  }
  
@@@ -819,44 -824,39 +838,39 @@@ static struct ref *do_fetch_pack(struc
        int agent_len;
  
        sort_ref_list(&ref, ref_compare_name);
 -      qsort(sought, nr_sought, sizeof(*sought), cmp_ref_by_name);
 +      QSORT(sought, nr_sought, cmp_ref_by_name);
  
        if ((args->depth > 0 || is_repository_shallow()) && !server_supports("shallow"))
-               die("Server does not support shallow clients");
+               die(_("Server does not support shallow clients"));
+       if (args->depth > 0 || args->deepen_since || args->deepen_not)
+               args->deepen = 1;
        if (server_supports("multi_ack_detailed")) {
-               if (args->verbose)
-                       fprintf(stderr, "Server supports multi_ack_detailed\n");
+               print_verbose(args, _("Server supports multi_ack_detailed"));
                multi_ack = 2;
                if (server_supports("no-done")) {
-                       if (args->verbose)
-                               fprintf(stderr, "Server supports no-done\n");
+                       print_verbose(args, _("Server supports no-done"));
                        if (args->stateless_rpc)
                                no_done = 1;
                }
        }
        else if (server_supports("multi_ack")) {
-               if (args->verbose)
-                       fprintf(stderr, "Server supports multi_ack\n");
+               print_verbose(args, _("Server supports multi_ack"));
                multi_ack = 1;
        }
        if (server_supports("side-band-64k")) {
-               if (args->verbose)
-                       fprintf(stderr, "Server supports side-band-64k\n");
+               print_verbose(args, _("Server supports side-band-64k"));
                use_sideband = 2;
        }
        else if (server_supports("side-band")) {
-               if (args->verbose)
-                       fprintf(stderr, "Server supports side-band\n");
+               print_verbose(args, _("Server supports side-band"));
                use_sideband = 1;
        }
        if (server_supports("allow-tip-sha1-in-want")) {
-               if (args->verbose)
-                       fprintf(stderr, "Server supports allow-tip-sha1-in-want\n");
+               print_verbose(args, _("Server supports allow-tip-sha1-in-want"));
                allow_unadvertised_object_request |= ALLOW_TIP_SHA1;
        }
        if (server_supports("allow-reachable-sha1-in-want")) {
-               if (args->verbose)
-                       fprintf(stderr, "Server supports allow-reachable-sha1-in-want\n");
+               print_verbose(args, _("Server supports allow-reachable-sha1-in-want"));
                allow_unadvertised_object_request |= ALLOW_REACHABLE_SHA1;
        }
        if (!server_supports("thin-pack"))
                args->no_progress = 0;
        if (!server_supports("include-tag"))
                args->include_tag = 0;
-       if (server_supports("ofs-delta")) {
-               if (args->verbose)
-                       fprintf(stderr, "Server supports ofs-delta\n");
-       } else
+       if (server_supports("ofs-delta"))
+               print_verbose(args, _("Server supports ofs-delta"));
+       else
                prefer_ofs_delta = 0;
  
        if ((agent_feature = server_feature_value("agent", &agent_len))) {
                agent_supported = 1;
-               if (args->verbose && agent_len)
-                       fprintf(stderr, "Server version is %.*s\n",
-                               agent_len, agent_feature);
+               if (agent_len)
+                       print_verbose(args, _("Server version is %.*s"),
+                                     agent_len, agent_feature);
        }
+       if (server_supports("deepen-since"))
+               deepen_since_ok = 1;
+       else if (args->deepen_since)
+               die(_("Server does not support --shallow-since"));
+       if (server_supports("deepen-not"))
+               deepen_not_ok = 1;
+       else if (args->deepen_not)
+               die(_("Server does not support --shallow-exclude"));
+       if (!server_supports("deepen-relative") && args->deepen_relative)
+               die(_("Server does not support --deepen"));
  
        if (everything_local(args, &ref, sought, nr_sought)) {
                packet_flush(fd[1]);
                        /* When cloning, it is not unusual to have
                         * no common commit.
                         */
-                       warning("no common commits");
+                       warning(_("no common commits"));
  
        if (args->stateless_rpc)
                packet_flush(fd[1]);
-       if (args->depth > 0)
+       if (args->deepen)
                setup_alternate_shallow(&shallow_lock, &alternate_shallow_file,
                                        NULL);
        else if (si->nr_ours || si->nr_theirs)
        else
                alternate_shallow_file = NULL;
        if (get_pack(args, fd, pack_lockfile))
-               die("git fetch-pack: fetch failed.");
+               die(_("git fetch-pack: fetch failed."));
  
   all_done:
        return ref;
@@@ -958,7 -967,7 +981,7 @@@ static void update_shallow(struct fetch
        int *status;
        int i;
  
-       if (args->depth > 0 && alternate_shallow_file) {
+       if (args->deepen && alternate_shallow_file) {
                if (*alternate_shallow_file == '\0') { /* --unshallow */
                        unlink_or_warn(git_path_shallow());
                        rollback_lock_file(&shallow_lock);
@@@ -1061,7 -1070,7 +1084,7 @@@ struct ref *fetch_pack(struct fetch_pac
  
        if (!ref) {
                packet_flush(fd[1]);
-               die("no matching remote head");
+               die(_("no matching remote head"));
        }
        prepare_shallow_info(&si, shallow);
        ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
diff --combined refs.c
index 5ffdd778d9766cad21e251e9020e2be2ce677acf,842e4d89dfff8a598030e7122fa7f987023c4ddf..abc721b607cd8ff4916cce7f8381521fbb58fb5d
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -9,25 -9,6 +9,25 @@@
  #include "object.h"
  #include "tag.h"
  
 +/*
 + * List of all available backends
 + */
 +static struct ref_storage_be *refs_backends = &refs_be_files;
 +
 +static struct ref_storage_be *find_ref_storage_backend(const char *name)
 +{
 +      struct ref_storage_be *be;
 +      for (be = refs_backends; be; be = be->next)
 +              if (!strcmp(be->name, name))
 +                      return be;
 +      return NULL;
 +}
 +
 +int ref_storage_backend_exists(const char *name)
 +{
 +      return find_ref_storage_backend(name) != NULL;
 +}
 +
  /*
   * How to handle various characters in refnames:
   * 0: An acceptable character for refs
@@@ -139,33 -120,25 +139,33 @@@ int check_refname_format(const char *re
  
  int refname_is_safe(const char *refname)
  {
 -      if (starts_with(refname, "refs/")) {
 +      const char *rest;
 +
 +      if (skip_prefix(refname, "refs/", &rest)) {
                char *buf;
                int result;
 +              size_t restlen = strlen(rest);
 +
 +              /* rest must not be empty, or start or end with "/" */
 +              if (!restlen || *rest == '/' || rest[restlen - 1] == '/')
 +                      return 0;
  
 -              buf = xmalloc(strlen(refname) + 1);
                /*
                 * Does the refname try to escape refs/?
                 * For example: refs/foo/../bar is safe but refs/foo/../../bar
                 * is not.
                 */
 -              result = !normalize_path_copy(buf, refname + strlen("refs/"));
 +              buf = xmallocz(restlen);
 +              result = !normalize_path_copy(buf, rest) && !strcmp(buf, rest);
                free(buf);
                return result;
        }
 -      while (*refname) {
 +
 +      do {
                if (!isupper(*refname) && *refname != '_')
                        return 0;
                refname++;
 -      }
 +      } while (*refname);
        return 1;
  }
  
@@@ -419,6 -392,13 +419,13 @@@ static char *substitute_branch_name(con
  int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
  {
        char *last_branch = substitute_branch_name(&str, &len);
+       int   refs_found  = expand_ref(str, len, sha1, ref);
+       free(last_branch);
+       return refs_found;
+ }
+ int expand_ref(const char *str, int len, unsigned char *sha1, char **ref)
+ {
        const char **p, *r;
        int refs_found = 0;
  
                        warning("ignoring broken ref %s.", fullref);
                }
        }
-       free(last_branch);
        return refs_found;
  }
  
@@@ -523,7 -502,7 +529,7 @@@ static int write_pseudoref(const char *
        filename = git_path("%s", pseudoref);
        fd = hold_lock_file_for_update(&lock, filename, LOCK_DIE_ON_ERROR);
        if (fd < 0) {
 -              strbuf_addf(err, "Could not open '%s' for writing: %s",
 +              strbuf_addf(err, "could not open '%s' for writing: %s",
                            filename, strerror(errno));
                return -1;
        }
                if (read_ref(pseudoref, actual_old_sha1))
                        die("could not read ref '%s'", pseudoref);
                if (hashcmp(actual_old_sha1, old_sha1)) {
 -                      strbuf_addf(err, "Unexpected sha1 when writing %s", pseudoref);
 +                      strbuf_addf(err, "unexpected sha1 when writing '%s'", pseudoref);
                        rollback_lock_file(&lock);
                        goto done;
                }
        }
  
        if (write_in_full(fd, buf.buf, buf.len) != buf.len) {
 -              strbuf_addf(err, "Could not write to '%s'", filename);
 +              strbuf_addf(err, "could not write to '%s'", filename);
                rollback_lock_file(&lock);
                goto done;
        }
@@@ -785,33 -764,15 +791,33 @@@ void ref_transaction_free(struct ref_tr
        free(transaction);
  }
  
 -static struct ref_update *add_update(struct ref_transaction *transaction,
 -                                   const char *refname)
 +struct ref_update *ref_transaction_add_update(
 +              struct ref_transaction *transaction,
 +              const char *refname, unsigned int flags,
 +              const unsigned char *new_sha1,
 +              const unsigned char *old_sha1,
 +              const char *msg)
  {
 -      size_t len = strlen(refname) + 1;
 -      struct ref_update *update = xcalloc(1, sizeof(*update) + len);
 +      struct ref_update *update;
 +
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: update called for transaction that is not open");
  
 -      memcpy((char *)update->refname, refname, len); /* includes NUL */
 +      if ((flags & REF_ISPRUNING) && !(flags & REF_NODEREF))
 +              die("BUG: REF_ISPRUNING set without REF_NODEREF");
 +
 +      FLEX_ALLOC_STR(update, refname, refname);
        ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
        transaction->updates[transaction->nr++] = update;
 +
 +      update->flags = flags;
 +
 +      if (flags & REF_HAVE_NEW)
 +              hashcpy(update->new_sha1, new_sha1);
 +      if (flags & REF_HAVE_OLD)
 +              hashcpy(update->old_sha1, old_sha1);
 +      if (msg)
 +              update->msg = xstrdup(msg);
        return update;
  }
  
@@@ -822,20 -783,32 +828,20 @@@ int ref_transaction_update(struct ref_t
                           unsigned int flags, const char *msg,
                           struct strbuf *err)
  {
 -      struct ref_update *update;
 -
        assert(err);
  
 -      if (transaction->state != REF_TRANSACTION_OPEN)
 -              die("BUG: update called for transaction that is not open");
 -
 -      if (new_sha1 && !is_null_sha1(new_sha1) &&
 -          check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
 -              strbuf_addf(err, "refusing to update ref with bad name %s",
 +      if ((new_sha1 && !is_null_sha1(new_sha1)) ?
 +          check_refname_format(refname, REFNAME_ALLOW_ONELEVEL) :
 +          !refname_is_safe(refname)) {
 +              strbuf_addf(err, "refusing to update ref with bad name '%s'",
                            refname);
                return -1;
        }
  
 -      update = add_update(transaction, refname);
 -      if (new_sha1) {
 -              hashcpy(update->new_sha1, new_sha1);
 -              flags |= REF_HAVE_NEW;
 -      }
 -      if (old_sha1) {
 -              hashcpy(update->old_sha1, old_sha1);
 -              flags |= REF_HAVE_OLD;
 -      }
 -      update->flags = flags;
 -      if (msg)
 -              update->msg = xstrdup(msg);
 +      flags |= (new_sha1 ? REF_HAVE_NEW : 0) | (old_sha1 ? REF_HAVE_OLD : 0);
 +
 +      ref_transaction_add_update(transaction, refname, flags,
 +                                 new_sha1, old_sha1, msg);
        return 0;
  }
  
@@@ -877,14 -850,6 +883,14 @@@ int ref_transaction_verify(struct ref_t
                                      flags, NULL, err);
  }
  
 +int update_ref_oid(const char *msg, const char *refname,
 +             const struct object_id *new_oid, const struct object_id *old_oid,
 +             unsigned int flags, enum action_on_err onerr)
 +{
 +      return update_ref(msg, refname, new_oid ? new_oid->hash : NULL,
 +              old_oid ? old_oid->hash : NULL, flags, onerr);
 +}
 +
  int update_ref(const char *msg, const char *refname,
               const unsigned char *new_sha1, const unsigned char *old_sha1,
               unsigned int flags, enum action_on_err onerr)
@@@ -949,7 -914,7 +955,7 @@@ char *shorten_unambiguous_ref(const cha
                        /* -2 for strlen("%.*s") - strlen("%s"); +1 for NUL */
                        total_len += strlen(ref_rev_parse_rules[nr_rules]) - 2 + 1;
  
 -              scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
 +              scanf_fmts = xmalloc(st_add(st_mult(sizeof(char *), nr_rules), total_len));
  
                offset = 0;
                for (i = 0; i < nr_rules; i++) {
@@@ -1108,457 -1073,18 +1114,457 @@@ const char *find_descendant_ref(const c
        return NULL;
  }
  
 -int rename_ref_available(const char *oldname, const char *newname)
 +int rename_ref_available(const char *old_refname, const char *new_refname)
  {
        struct string_list skip = STRING_LIST_INIT_NODUP;
        struct strbuf err = STRBUF_INIT;
 -      int ret;
 +      int ok;
  
 -      string_list_insert(&skip, oldname);
 -      ret = !verify_refname_available(newname, NULL, &skip, &err);
 -      if (!ret)
 +      string_list_insert(&skip, old_refname);
 +      ok = !verify_refname_available(new_refname, NULL, &skip, &err);
 +      if (!ok)
                error("%s", err.buf);
  
        string_list_clear(&skip, 0);
        strbuf_release(&err);
 +      return ok;
 +}
 +
 +int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
 +{
 +      struct object_id oid;
 +      int flag;
 +
 +      if (submodule) {
 +              if (resolve_gitlink_ref(submodule, "HEAD", oid.hash) == 0)
 +                      return fn("HEAD", &oid, 0, cb_data);
 +
 +              return 0;
 +      }
 +
 +      if (!read_ref_full("HEAD", RESOLVE_REF_READING, oid.hash, &flag))
 +              return fn("HEAD", &oid, flag, cb_data);
 +
 +      return 0;
 +}
 +
 +int head_ref(each_ref_fn fn, void *cb_data)
 +{
 +      return head_ref_submodule(NULL, fn, cb_data);
 +}
 +
 +/*
 + * Call fn for each reference in the specified submodule for which the
 + * refname begins with prefix. If trim is non-zero, then trim that
 + * many characters off the beginning of each refname before passing
 + * the refname to fn. flags can be DO_FOR_EACH_INCLUDE_BROKEN to
 + * include broken references in the iteration. If fn ever returns a
 + * non-zero value, stop the iteration and return that value;
 + * otherwise, return 0.
 + */
 +static int do_for_each_ref(const char *submodule, const char *prefix,
 +                         each_ref_fn fn, int trim, int flags, void *cb_data)
 +{
 +      struct ref_store *refs = get_ref_store(submodule);
 +      struct ref_iterator *iter;
 +
 +      if (!refs)
 +              return 0;
 +
 +      iter = refs->be->iterator_begin(refs, prefix, flags);
 +      iter = prefix_ref_iterator_begin(iter, prefix, trim);
 +
 +      return do_for_each_ref_iterator(iter, fn, cb_data);
 +}
 +
 +int for_each_ref(each_ref_fn fn, void *cb_data)
 +{
 +      return do_for_each_ref(NULL, "", fn, 0, 0, cb_data);
 +}
 +
 +int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data)
 +{
 +      return do_for_each_ref(submodule, "", fn, 0, 0, cb_data);
 +}
 +
 +int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
 +{
 +      return do_for_each_ref(NULL, prefix, fn, strlen(prefix), 0, cb_data);
 +}
 +
 +int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken)
 +{
 +      unsigned int flag = 0;
 +
 +      if (broken)
 +              flag = DO_FOR_EACH_INCLUDE_BROKEN;
 +      return do_for_each_ref(NULL, prefix, fn, 0, flag, cb_data);
 +}
 +
 +int for_each_ref_in_submodule(const char *submodule, const char *prefix,
 +              each_ref_fn fn, void *cb_data)
 +{
 +      return do_for_each_ref(submodule, prefix, fn, strlen(prefix), 0, cb_data);
 +}
 +
 +int for_each_replace_ref(each_ref_fn fn, void *cb_data)
 +{
 +      return do_for_each_ref(NULL, git_replace_ref_base, fn,
 +                             strlen(git_replace_ref_base), 0, cb_data);
 +}
 +
 +int for_each_namespaced_ref(each_ref_fn fn, void *cb_data)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      int ret;
 +      strbuf_addf(&buf, "%srefs/", get_git_namespace());
 +      ret = do_for_each_ref(NULL, buf.buf, fn, 0, 0, cb_data);
 +      strbuf_release(&buf);
        return ret;
  }
 +
 +int for_each_rawref(each_ref_fn fn, void *cb_data)
 +{
 +      return do_for_each_ref(NULL, "", fn, 0,
 +                             DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
 +}
 +
 +/* This function needs to return a meaningful errno on failure */
 +static const char *resolve_ref_recursively(struct ref_store *refs,
 +                                         const char *refname,
 +                                         int resolve_flags,
 +                                         unsigned char *sha1, int *flags)
 +{
 +      static struct strbuf sb_refname = STRBUF_INIT;
 +      int unused_flags;
 +      int symref_count;
 +
 +      if (!flags)
 +              flags = &unused_flags;
 +
 +      *flags = 0;
 +
 +      if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
 +              if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
 +                  !refname_is_safe(refname)) {
 +                      errno = EINVAL;
 +                      return NULL;
 +              }
 +
 +              /*
 +               * dwim_ref() uses REF_ISBROKEN to distinguish between
 +               * missing refs and refs that were present but invalid,
 +               * to complain about the latter to stderr.
 +               *
 +               * We don't know whether the ref exists, so don't set
 +               * REF_ISBROKEN yet.
 +               */
 +              *flags |= REF_BAD_NAME;
 +      }
 +
 +      for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) {
 +              unsigned int read_flags = 0;
 +
 +              if (refs->be->read_raw_ref(refs, refname,
 +                                         sha1, &sb_refname, &read_flags)) {
 +                      *flags |= read_flags;
 +                      if (errno != ENOENT || (resolve_flags & RESOLVE_REF_READING))
 +                              return NULL;
 +                      hashclr(sha1);
 +                      if (*flags & REF_BAD_NAME)
 +                              *flags |= REF_ISBROKEN;
 +                      return refname;
 +              }
 +
 +              *flags |= read_flags;
 +
 +              if (!(read_flags & REF_ISSYMREF)) {
 +                      if (*flags & REF_BAD_NAME) {
 +                              hashclr(sha1);
 +                              *flags |= REF_ISBROKEN;
 +                      }
 +                      return refname;
 +              }
 +
 +              refname = sb_refname.buf;
 +              if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
 +                      hashclr(sha1);
 +                      return refname;
 +              }
 +              if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
 +                      if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
 +                          !refname_is_safe(refname)) {
 +                              errno = EINVAL;
 +                              return NULL;
 +                      }
 +
 +                      *flags |= REF_ISBROKEN | REF_BAD_NAME;
 +              }
 +      }
 +
 +      errno = ELOOP;
 +      return NULL;
 +}
 +
 +/* backend functions */
 +int refs_init_db(struct strbuf *err)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->init_db(refs, err);
 +}
 +
 +const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
 +                             unsigned char *sha1, int *flags)
 +{
 +      return resolve_ref_recursively(get_ref_store(NULL), refname,
 +                                     resolve_flags, sha1, flags);
 +}
 +
 +int resolve_gitlink_ref(const char *submodule, const char *refname,
 +                      unsigned char *sha1)
 +{
 +      size_t len = strlen(submodule);
 +      struct ref_store *refs;
 +      int flags;
 +
 +      while (len && submodule[len - 1] == '/')
 +              len--;
 +
 +      if (!len)
 +              return -1;
 +
 +      if (submodule[len]) {
 +              /* We need to strip off one or more trailing slashes */
 +              char *stripped = xmemdupz(submodule, len);
 +
 +              refs = get_ref_store(stripped);
 +              free(stripped);
 +      } else {
 +              refs = get_ref_store(submodule);
 +      }
 +
 +      if (!refs)
 +              return -1;
 +
 +      if (!resolve_ref_recursively(refs, refname, 0, sha1, &flags) ||
 +          is_null_sha1(sha1))
 +              return -1;
 +      return 0;
 +}
 +
 +/* A pointer to the ref_store for the main repository: */
 +static struct ref_store *main_ref_store;
 +
 +/* A linked list of ref_stores for submodules: */
 +static struct ref_store *submodule_ref_stores;
 +
 +void base_ref_store_init(struct ref_store *refs,
 +                       const struct ref_storage_be *be,
 +                       const char *submodule)
 +{
 +      refs->be = be;
 +      if (!submodule) {
 +              if (main_ref_store)
 +                      die("BUG: main_ref_store initialized twice");
 +
 +              refs->submodule = "";
 +              refs->next = NULL;
 +              main_ref_store = refs;
 +      } else {
 +              if (lookup_ref_store(submodule))
 +                      die("BUG: ref_store for submodule '%s' initialized twice",
 +                          submodule);
 +
 +              refs->submodule = xstrdup(submodule);
 +              refs->next = submodule_ref_stores;
 +              submodule_ref_stores = refs;
 +      }
 +}
 +
 +struct ref_store *ref_store_init(const char *submodule)
 +{
 +      const char *be_name = "files";
 +      struct ref_storage_be *be = find_ref_storage_backend(be_name);
 +
 +      if (!be)
 +              die("BUG: reference backend %s is unknown", be_name);
 +
 +      if (!submodule || !*submodule)
 +              return be->init(NULL);
 +      else
 +              return be->init(submodule);
 +}
 +
 +struct ref_store *lookup_ref_store(const char *submodule)
 +{
 +      struct ref_store *refs;
 +
 +      if (!submodule || !*submodule)
 +              return main_ref_store;
 +
 +      for (refs = submodule_ref_stores; refs; refs = refs->next) {
 +              if (!strcmp(submodule, refs->submodule))
 +                      return refs;
 +      }
 +
 +      return NULL;
 +}
 +
 +struct ref_store *get_ref_store(const char *submodule)
 +{
 +      struct ref_store *refs;
 +
 +      if (!submodule || !*submodule) {
 +              refs = lookup_ref_store(NULL);
 +
 +              if (!refs)
 +                      refs = ref_store_init(NULL);
 +      } else {
 +              refs = lookup_ref_store(submodule);
 +
 +              if (!refs) {
 +                      struct strbuf submodule_sb = STRBUF_INIT;
 +
 +                      strbuf_addstr(&submodule_sb, submodule);
 +                      if (is_nonbare_repository_dir(&submodule_sb))
 +                              refs = ref_store_init(submodule);
 +                      strbuf_release(&submodule_sb);
 +              }
 +      }
 +
 +      return refs;
 +}
 +
 +void assert_main_repository(struct ref_store *refs, const char *caller)
 +{
 +      if (*refs->submodule)
 +              die("BUG: %s called for a submodule", caller);
 +}
 +
 +/* backend functions */
 +int pack_refs(unsigned int flags)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->pack_refs(refs, flags);
 +}
 +
 +int peel_ref(const char *refname, unsigned char *sha1)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->peel_ref(refs, refname, sha1);
 +}
 +
 +int create_symref(const char *ref_target, const char *refs_heads_master,
 +                const char *logmsg)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->create_symref(refs, ref_target, refs_heads_master,
 +                                     logmsg);
 +}
 +
 +int ref_transaction_commit(struct ref_transaction *transaction,
 +                         struct strbuf *err)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->transaction_commit(refs, transaction, err);
 +}
 +
 +int verify_refname_available(const char *refname,
 +                           const struct string_list *extra,
 +                           const struct string_list *skip,
 +                           struct strbuf *err)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->verify_refname_available(refs, refname, extra, skip, err);
 +}
 +
 +int for_each_reflog(each_ref_fn fn, void *cb_data)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +      struct ref_iterator *iter;
 +
 +      iter = refs->be->reflog_iterator_begin(refs);
 +
 +      return do_for_each_ref_iterator(iter, fn, cb_data);
 +}
 +
 +int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,
 +                              void *cb_data)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->for_each_reflog_ent_reverse(refs, refname,
 +                                                   fn, cb_data);
 +}
 +
 +int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn,
 +                      void *cb_data)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->for_each_reflog_ent(refs, refname, fn, cb_data);
 +}
 +
 +int reflog_exists(const char *refname)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->reflog_exists(refs, refname);
 +}
 +
 +int safe_create_reflog(const char *refname, int force_create,
 +                     struct strbuf *err)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->create_reflog(refs, refname, force_create, err);
 +}
 +
 +int delete_reflog(const char *refname)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->delete_reflog(refs, refname);
 +}
 +
 +int reflog_expire(const char *refname, const unsigned char *sha1,
 +                unsigned int flags,
 +                reflog_expiry_prepare_fn prepare_fn,
 +                reflog_expiry_should_prune_fn should_prune_fn,
 +                reflog_expiry_cleanup_fn cleanup_fn,
 +                void *policy_cb_data)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->reflog_expire(refs, refname, sha1, flags,
 +                                     prepare_fn, should_prune_fn,
 +                                     cleanup_fn, policy_cb_data);
 +}
 +
 +int initial_ref_transaction_commit(struct ref_transaction *transaction,
 +                                 struct strbuf *err)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->initial_transaction_commit(refs, transaction, err);
 +}
 +
 +int delete_refs(struct string_list *refnames, unsigned int flags)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->delete_refs(refs, refnames, flags);
 +}
 +
 +int rename_ref(const char *oldref, const char *newref, const char *logmsg)
 +{
 +      struct ref_store *refs = get_ref_store(NULL);
 +
 +      return refs->be->rename_ref(refs, oldref, newref, logmsg);
 +}
diff --combined refs.h
index fe51280fc61d24eccca0e38f8fc75ecee7fcb751,31a2fa68a37306f9d707d89c7dea8a022a9a44a8..69478439137d64f6dab7f20d4f157f6c7a982ac2
--- 1/refs.h
--- 2/refs.h
+++ b/refs.h
  #define RESOLVE_REF_NO_RECURSE 0x02
  #define RESOLVE_REF_ALLOW_BAD_NAME 0x04
  
 -extern const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
 -                                    unsigned char *sha1, int *flags);
 +const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
 +                             unsigned char *sha1, int *flags);
  
 -extern char *resolve_refdup(const char *refname, int resolve_flags,
 -                          unsigned char *sha1, int *flags);
 +char *resolve_refdup(const char *refname, int resolve_flags,
 +                   unsigned char *sha1, int *flags);
  
 -extern int read_ref_full(const char *refname, int resolve_flags,
 -                       unsigned char *sha1, int *flags);
 -extern int read_ref(const char *refname, unsigned char *sha1);
 +int read_ref_full(const char *refname, int resolve_flags,
 +                unsigned char *sha1, int *flags);
 +int read_ref(const char *refname, unsigned char *sha1);
  
 -extern int ref_exists(const char *refname);
 +int ref_exists(const char *refname);
  
 -extern int is_branch(const char *refname);
 +int is_branch(const char *refname);
 +
 +extern int refs_init_db(struct strbuf *err);
  
  /*
   * If refname is a non-symbolic reference that refers to a tag object,
   * Symbolic references are considered unpeelable, even if they
   * ultimately resolve to a peelable tag.
   */
 -extern int peel_ref(const char *refname, unsigned char *sha1);
 +int peel_ref(const char *refname, unsigned char *sha1);
  
  /**
 - * Resolve refname in the nested "gitlink" repository that is located
 - * at path.  If the resolution is successful, return 0 and set sha1 to
 - * the name of the object; otherwise, return a non-zero value.
 + * Resolve refname in the nested "gitlink" repository in the specified
 + * submodule (which must be non-NULL). If the resolution is
 + * successful, return 0 and set sha1 to the name of the object;
 + * otherwise, return a non-zero value.
   */
 -extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
 +int resolve_gitlink_ref(const char *submodule, const char *refname,
 +                      unsigned char *sha1);
  
  /*
   * Return true iff abbrev_name is a possible abbreviation for
   * full_name according to the rules defined by ref_rev_parse_rules in
   * refs.c.
   */
 -extern int refname_match(const char *abbrev_name, const char *full_name);
 +int refname_match(const char *abbrev_name, const char *full_name);
  
 -extern int expand_ref(const char *str, int len, unsigned char *sha1, char **ref);
 -extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 -extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
++int expand_ref(const char *str, int len, unsigned char *sha1, char **ref);
 +int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
 +int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
  
  /*
   * A ref_transaction represents a collection of ref updates
   *   If this succeeds, the ref updates will have taken place and
   *   the transaction cannot be rolled back.
   *
 + * - Instead of `ref_transaction_commit`, use
 + *   `initial_ref_transaction_commit()` if the ref database is known
 + *   to be empty (e.g. during clone).  This is likely to be much
 + *   faster.
 + *
   * - At any time call `ref_transaction_free()` to discard the
   *   transaction and free associated resources.  In particular,
   *   this rolls back the transaction if it has not been
   *
   * The message is appended to err without first clearing err.
   * err will not be '\n' terminated.
 + *
 + * Caveats
 + * -------
 + *
 + * Note that no locks are taken, and no refs are read, until
 + * `ref_transaction_commit` is called.  So `ref_transaction_verify`
 + * won't report a verification failure until the commit is attempted.
   */
  struct ref_transaction;
  
  /*
 - * Bit values set in the flags argument passed to each_ref_fn():
 + * Bit values set in the flags argument passed to each_ref_fn() and
 + * stored in ref_iterator::flags. Other bits are for internal use
 + * only:
   */
  
  /* Reference is a symbolic reference. */
@@@ -188,45 -171,38 +189,45 @@@ typedef int each_ref_fn(const char *ref
   * modifies the reference also returns a nonzero value to immediately
   * stop the iteration.
   */
 -extern int head_ref(each_ref_fn fn, void *cb_data);
 -extern int for_each_ref(each_ref_fn fn, void *cb_data);
 -extern int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
 -extern int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken);
 -extern int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 -extern int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 -extern int for_each_remote_ref(each_ref_fn fn, void *cb_data);
 -extern int for_each_replace_ref(each_ref_fn fn, void *cb_data);
 -extern int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
 -extern int for_each_glob_ref_in(each_ref_fn fn, const char *pattern, const char *prefix, void *cb_data);
 -
 -extern int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 -extern int for_each_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 -extern int for_each_ref_in_submodule(const char *submodule, const char *prefix,
 +int head_ref(each_ref_fn fn, void *cb_data);
 +int for_each_ref(each_ref_fn fn, void *cb_data);
 +int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
 +int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
 +                      unsigned int broken);
 +int for_each_tag_ref(each_ref_fn fn, void *cb_data);
 +int for_each_branch_ref(each_ref_fn fn, void *cb_data);
 +int for_each_remote_ref(each_ref_fn fn, void *cb_data);
 +int for_each_replace_ref(each_ref_fn fn, void *cb_data);
 +int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
 +int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
 +                       const char *prefix, void *cb_data);
 +
 +int head_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 +int for_each_ref_submodule(const char *submodule,
 +                         each_ref_fn fn, void *cb_data);
 +int for_each_ref_in_submodule(const char *submodule, const char *prefix,
                each_ref_fn fn, void *cb_data);
 -extern int for_each_tag_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 -extern int for_each_branch_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 -extern int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *cb_data);
 +int for_each_tag_ref_submodule(const char *submodule,
 +                             each_ref_fn fn, void *cb_data);
 +int for_each_branch_ref_submodule(const char *submodule,
 +                                each_ref_fn fn, void *cb_data);
 +int for_each_remote_ref_submodule(const char *submodule,
 +                                each_ref_fn fn, void *cb_data);
  
 -extern int head_ref_namespaced(each_ref_fn fn, void *cb_data);
 -extern int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
 +int head_ref_namespaced(each_ref_fn fn, void *cb_data);
 +int for_each_namespaced_ref(each_ref_fn fn, void *cb_data);
  
  /* can be used to learn about broken ref and symref */
 -extern int for_each_rawref(each_ref_fn fn, void *cb_data);
 +int for_each_rawref(each_ref_fn fn, void *cb_data);
  
  static inline const char *has_glob_specials(const char *pattern)
  {
        return strpbrk(pattern, "?*[");
  }
  
 -extern void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
 -extern void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames);
 +void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname);
 +void warn_dangling_symrefs(FILE *fp, const char *msg_fmt,
 +                         const struct string_list *refnames);
  
  /*
   * Flags for controlling behaviour of pack_refs()
@@@ -258,13 -234,13 +259,13 @@@ int pack_refs(unsigned int flags)
  int safe_create_reflog(const char *refname, int force_create, struct strbuf *err);
  
  /** Reads log for the value of ref during at_time. **/
 -extern int read_ref_at(const char *refname, unsigned int flags,
 -                     unsigned long at_time, int cnt,
 -                     unsigned char *sha1, char **msg,
 -                     unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
 +int read_ref_at(const char *refname, unsigned int flags,
 +              unsigned long at_time, int cnt,
 +              unsigned char *sha1, char **msg,
 +              unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
  
  /** Check if a particular reflog exists */
 -extern int reflog_exists(const char *refname);
 +int reflog_exists(const char *refname);
  
  /*
   * Delete the specified reference. If old_sha1 is non-NULL, then
   * exists, regardless of its old value. It is an error for old_sha1 to
   * be NULL_SHA1. flags is passed through to ref_transaction_delete().
   */
 -extern int delete_ref(const char *refname, const unsigned char *old_sha1,
 -                    unsigned int flags);
 +int delete_ref(const char *refname, const unsigned char *old_sha1,
 +             unsigned int flags);
  
  /*
   * Delete the specified references. If there are any problems, emit
   * errors but attempt to keep going (i.e., the deletes are not done in
 - * an all-or-nothing transaction).
 + * an all-or-nothing transaction). flags is passed through to
 + * ref_transaction_delete().
   */
 -extern int delete_refs(struct string_list *refnames);
 +int delete_refs(struct string_list *refnames, unsigned int flags);
  
  /** Delete a reflog */
 -extern int delete_reflog(const char *refname);
 +int delete_reflog(const char *refname);
  
  /* iterate over reflog entries */
 -typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
 +typedef int each_reflog_ent_fn(
 +              unsigned char *old_sha1, unsigned char *new_sha1,
 +              const char *committer, unsigned long timestamp,
 +              int tz, const char *msg, void *cb_data);
 +
  int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
  int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
  
   * Calls the specified function for each reflog file until it returns nonzero,
   * and returns the value
   */
 -extern int for_each_reflog(each_ref_fn, void *);
 +int for_each_reflog(each_ref_fn fn, void *cb_data);
  
  #define REFNAME_ALLOW_ONELEVEL 1
  #define REFNAME_REFSPEC_PATTERN 2
   * allow a single "*" wildcard character in the refspec. No leading or
   * repeated slashes are accepted.
   */
 -extern int check_refname_format(const char *refname, int flags);
 +int check_refname_format(const char *refname, int flags);
  
 -extern const char *prettify_refname(const char *refname);
 +const char *prettify_refname(const char *refname);
  
 -extern char *shorten_unambiguous_ref(const char *refname, int strict);
 +char *shorten_unambiguous_ref(const char *refname, int strict);
  
  /** rename ref, return 0 on success **/
 -extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
 +int rename_ref(const char *oldref, const char *newref, const char *logmsg);
 +
 +int create_symref(const char *refname, const char *target, const char *logmsg);
  
 -extern int create_symref(const char *refname, const char *target, const char *logmsg);
 +/*
 + * Update HEAD of the specified gitdir.
 + * Similar to create_symref("relative-git-dir/HEAD", target, NULL), but
 + * this can update the main working tree's HEAD regardless of where
 + * $GIT_DIR points to.
 + * Return 0 if successful, non-zero otherwise.
 + * */
 +int set_worktree_head_symref(const char *gitdir, const char *target);
  
  enum action_on_err {
        UPDATE_REFS_MSG_ON_ERR,
@@@ -363,7 -325,7 +364,7 @@@ struct ref_transaction *ref_transaction
   *     msg -- a message describing the change (for the reflog).
   *
   *     err -- a strbuf for receiving a description of any error that
 - *         might have occured.
 + *         might have occurred.
   *
   * The functions make internal copies of refname and msg, so the
   * caller retains ownership of these parameters.
@@@ -480,11 -442,8 +481,11 @@@ void ref_transaction_free(struct ref_tr
  int update_ref(const char *msg, const char *refname,
               const unsigned char *new_sha1, const unsigned char *old_sha1,
               unsigned int flags, enum action_on_err onerr);
 +int update_ref_oid(const char *msg, const char *refname,
 +             const struct object_id *new_oid, const struct object_id *old_oid,
 +             unsigned int flags, enum action_on_err onerr);
  
 -extern int parse_hide_refs_config(const char *var, const char *value, const char *);
 +int parse_hide_refs_config(const char *var, const char *value, const char *);
  
  /*
   * Check whether a ref is hidden. If no namespace is set, both the first and
   * the ref is outside that namespace, the first parameter is NULL. The second
   * parameter always points to the full ref name.
   */
 -extern int ref_is_hidden(const char *, const char *);
 +int ref_is_hidden(const char *, const char *);
  
  enum ref_type {
        REF_TYPE_PER_WORKTREE,
@@@ -543,13 -502,11 +544,13 @@@ typedef void reflog_expiry_cleanup_fn(v
   * enum expire_reflog_flags. The three function pointers are described
   * above. On success, return zero.
   */
 -extern int reflog_expire(const char *refname, const unsigned char *sha1,
 -                       unsigned int flags,
 -                       reflog_expiry_prepare_fn prepare_fn,
 -                       reflog_expiry_should_prune_fn should_prune_fn,
 -                       reflog_expiry_cleanup_fn cleanup_fn,
 -                       void *policy_cb_data);
 +int reflog_expire(const char *refname, const unsigned char *sha1,
 +                unsigned int flags,
 +                reflog_expiry_prepare_fn prepare_fn,
 +                reflog_expiry_should_prune_fn should_prune_fn,
 +                reflog_expiry_cleanup_fn cleanup_fn,
 +                void *policy_cb_data);
 +
 +int ref_storage_backend_exists(const char *name);
  
  #endif /* REFS_H */
diff --combined remote-curl.c
index 6b83b7783e9c62fcf623acd0ba034dd1c0b9bd10,d56412deb14237fcb4c5dca1f11cda1ae8f4fcdf..f14c41f4c0d6d6c9bbfd18d6417d16f0e6068c1d
@@@ -20,6 -20,8 +20,8 @@@ static struct strbuf url = STRBUF_INIT
  struct options {
        int verbosity;
        unsigned long depth;
+       char *deepen_since;
+       struct string_list deepen_not;
        unsigned progress : 1,
                check_self_contained_and_connected : 1,
                cloning : 1,
@@@ -28,7 -30,8 +30,8 @@@
                dry_run : 1,
                thin : 1,
                /* One of the SEND_PACK_PUSH_CERT_* constants. */
-               push_cert : 2;
+               push_cert : 2,
+               deepen_relative : 1;
  };
  static struct options options;
  static struct string_list cas_options = STRING_LIST_INIT_DUP;
@@@ -60,6 -63,23 +63,23 @@@ static int set_option(const char *name
                options.depth = v;
                return 0;
        }
+       else if (!strcmp(name, "deepen-since")) {
+               options.deepen_since = xstrdup(value);
+               return 0;
+       }
+       else if (!strcmp(name, "deepen-not")) {
+               string_list_append(&options.deepen_not, value);
+               return 0;
+       }
+       else if (!strcmp(name, "deepen-relative")) {
+               if (!strcmp(value, "true"))
+                       options.deepen_relative = 1;
+               else if (!strcmp(value, "false"))
+                       options.deepen_relative = 0;
+               else
+                       return -1;
+               return 0;
+       }
        else if (!strcmp(name, "followtags")) {
                if (!strcmp(value, "true"))
                        options.followtags = 1;
                else
                        return -1;
                return 0;
 +
 +#if LIBCURL_VERSION_NUM >= 0x070a08
 +      } else if (!strcmp(name, "family")) {
 +              if (!strcmp(value, "ipv4"))
 +                      git_curl_ipresolve = CURL_IPRESOLVE_V4;
 +              else if (!strcmp(value, "ipv6"))
 +                      git_curl_ipresolve = CURL_IPRESOLVE_V6;
 +              else if (!strcmp(value, "all"))
 +                      git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER;
 +              else
 +                      return -1;
 +              return 0;
 +#endif /* LIBCURL_VERSION_NUM >= 0x070a08 */
        } else {
                return 1 /* unsupported */;
        }
@@@ -452,20 -459,8 +472,20 @@@ static int run_slot(struct active_reque
        err = run_one_slot(slot, results);
  
        if (err != HTTP_OK && err != HTTP_REAUTH) {
 -              error("RPC failed; result=%d, HTTP code = %ld",
 -                    results->curl_result, results->http_code);
 +              struct strbuf msg = STRBUF_INIT;
 +              if (results->http_code && results->http_code != 200)
 +                      strbuf_addf(&msg, "HTTP %ld", results->http_code);
 +              if (results->curl_result != CURLE_OK) {
 +                      if (msg.len)
 +                              strbuf_addch(&msg, ' ');
 +                      strbuf_addf(&msg, "curl %d", results->curl_result);
 +                      if (curl_errorstr[0]) {
 +                              strbuf_addch(&msg, ' ');
 +                              strbuf_addstr(&msg, curl_errorstr);
 +                      }
 +              }
 +              error("RPC failed; %s", msg.buf);
 +              strbuf_release(&msg);
        }
  
        return err;
  static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
  {
        struct active_request_slot *slot;
 -      struct curl_slist *headers = NULL;
 +      struct curl_slist *headers = http_copy_default_headers();
        struct strbuf buf = STRBUF_INIT;
        int err;
  
  static int post_rpc(struct rpc_state *rpc)
  {
        struct active_request_slot *slot;
 -      struct curl_slist *headers = NULL;
 +      struct curl_slist *headers = http_copy_default_headers();
        int use_gzip = rpc->gzip_request;
        char *gzip_body = NULL;
        size_t gzip_size = 0;
@@@ -721,12 -716,11 +741,12 @@@ static int rpc_service(struct rpc_stat
  static int fetch_dumb(int nr_heads, struct ref **to_fetch)
  {
        struct walker *walker;
 -      char **targets = xmalloc(nr_heads * sizeof(char*));
 +      char **targets;
        int ret, i;
  
-       if (options.depth)
-               die("dumb http transport does not support --depth");
 +      ALLOC_ARRAY(targets, nr_heads);
+       if (options.depth || options.deepen_since)
+               die("dumb http transport does not support shallow capabilities");
        for (i = 0; i < nr_heads; i++)
                targets[i] = xstrdup(oid_to_hex(&to_fetch[i]->old_oid));
  
@@@ -751,38 -745,35 +771,35 @@@ static int fetch_git(struct discovery *
  {
        struct rpc_state rpc;
        struct strbuf preamble = STRBUF_INIT;
-       char *depth_arg = NULL;
-       int argc = 0, i, err;
-       const char *argv[17];
-       argv[argc++] = "fetch-pack";
-       argv[argc++] = "--stateless-rpc";
-       argv[argc++] = "--stdin";
-       argv[argc++] = "--lock-pack";
+       int i, err;
+       struct argv_array args = ARGV_ARRAY_INIT;
+       argv_array_pushl(&args, "fetch-pack", "--stateless-rpc",
+                        "--stdin", "--lock-pack", NULL);
        if (options.followtags)
-               argv[argc++] = "--include-tag";
+               argv_array_push(&args, "--include-tag");
        if (options.thin)
-               argv[argc++] = "--thin";
-       if (options.verbosity >= 3) {
-               argv[argc++] = "-v";
-               argv[argc++] = "-v";
-       }
+               argv_array_push(&args, "--thin");
+       if (options.verbosity >= 3)
+               argv_array_pushl(&args, "-v", "-v", NULL);
        if (options.check_self_contained_and_connected)
-               argv[argc++] = "--check-self-contained-and-connected";
+               argv_array_push(&args, "--check-self-contained-and-connected");
        if (options.cloning)
-               argv[argc++] = "--cloning";
+               argv_array_push(&args, "--cloning");
        if (options.update_shallow)
-               argv[argc++] = "--update-shallow";
+               argv_array_push(&args, "--update-shallow");
        if (!options.progress)
-               argv[argc++] = "--no-progress";
-       if (options.depth) {
-               struct strbuf buf = STRBUF_INIT;
-               strbuf_addf(&buf, "--depth=%lu", options.depth);
-               depth_arg = strbuf_detach(&buf, NULL);
-               argv[argc++] = depth_arg;
-       }
-       argv[argc++] = url.buf;
-       argv[argc++] = NULL;
+               argv_array_push(&args, "--no-progress");
+       if (options.depth)
+               argv_array_pushf(&args, "--depth=%lu", options.depth);
+       if (options.deepen_since)
+               argv_array_pushf(&args, "--shallow-since=%s", options.deepen_since);
+       for (i = 0; i < options.deepen_not.nr; i++)
+               argv_array_pushf(&args, "--shallow-exclude=%s",
+                                options.deepen_not.items[i].string);
+       if (options.deepen_relative && options.depth)
+               argv_array_push(&args, "--deepen-relative");
+       argv_array_push(&args, url.buf);
  
        for (i = 0; i < nr_heads; i++) {
                struct ref *ref = to_fetch[i];
  
        memset(&rpc, 0, sizeof(rpc));
        rpc.service_name = "git-upload-pack",
-       rpc.argv = argv;
+       rpc.argv = args.argv;
        rpc.stdin_preamble = &preamble;
        rpc.gzip_request = 1;
  
                write_or_die(1, rpc.result.buf, rpc.result.len);
        strbuf_release(&rpc.result);
        strbuf_release(&preamble);
-       free(depth_arg);
+       argv_array_clear(&args);
        return err;
  }
  
@@@ -871,22 -862,23 +888,22 @@@ static void parse_fetch(struct strbuf *
  
  static int push_dav(int nr_spec, char **specs)
  {
 -      const char **argv = xmalloc((10 + nr_spec) * sizeof(char*));
 -      int argc = 0, i;
 +      struct child_process child = CHILD_PROCESS_INIT;
 +      size_t i;
  
 -      argv[argc++] = "http-push";
 -      argv[argc++] = "--helper-status";
 +      child.git_cmd = 1;
 +      argv_array_push(&child.args, "http-push");
 +      argv_array_push(&child.args, "--helper-status");
        if (options.dry_run)
 -              argv[argc++] = "--dry-run";
 +              argv_array_push(&child.args, "--dry-run");
        if (options.verbosity > 1)
 -              argv[argc++] = "--verbose";
 -      argv[argc++] = url.buf;
 +              argv_array_push(&child.args, "--verbose");
 +      argv_array_push(&child.args, url.buf);
        for (i = 0; i < nr_spec; i++)
 -              argv[argc++] = specs[i];
 -      argv[argc++] = NULL;
 +              argv_array_push(&child.args, specs[i]);
  
 -      if (run_command_v_opt(argv, RUN_GIT_CMD))
 -              die("git-%s failed", argv[0]);
 -      free(argv);
 +      if (run_command(&child))
 +              die("git-http-push failed");
        return 0;
  }
  
@@@ -984,11 -976,14 +1001,11 @@@ static void parse_push(struct strbuf *b
        free(specs);
  }
  
 -int main(int argc, const char **argv)
 +int cmd_main(int argc, const char **argv)
  {
        struct strbuf buf = STRBUF_INIT;
        int nongit;
  
 -      git_setup_gettext();
 -
 -      git_extract_argv0_path(argv[0]);
        setup_git_directory_gently(&nongit);
        if (argc < 2) {
                error("remote-curl: usage: git remote-curl <remote> [<url>]");
        options.verbosity = 1;
        options.progress = !!isatty(2);
        options.thin = 1;
+       string_list_init(&options.deepen_not, 1);
  
        remote = remote_get(argv[1]);
  
diff --combined shallow.c
index 54e2db73349ef22d644552c6b122ae5ed6ba2ce1,40c2485d0d303c7249f13a1dcda0786579a6fc8d..2531e3af3b0d87e1f78fe09208cb85917ceff207
+++ b/shallow.c
@@@ -10,6 -10,8 +10,8 @@@
  #include "diff.h"
  #include "revision.h"
  #include "commit-slab.h"
+ #include "revision.h"
+ #include "list-objects.h"
  
  static int is_shallow = -1;
  static struct stat_validity shallow_stat;
@@@ -137,6 -139,82 +139,82 @@@ struct commit_list *get_shallow_commits
        return result;
  }
  
+ static void show_commit(struct commit *commit, void *data)
+ {
+       commit_list_insert(commit, data);
+ }
+ /*
+  * Given rev-list arguments, run rev-list. All reachable commits
+  * except border ones are marked with not_shallow_flag. Border commits
+  * are marked with shallow_flag. The list of border/shallow commits
+  * are also returned.
+  */
+ struct commit_list *get_shallow_commits_by_rev_list(int ac, const char **av,
+                                                   int shallow_flag,
+                                                   int not_shallow_flag)
+ {
+       struct commit_list *result = NULL, *p;
+       struct commit_list *not_shallow_list = NULL;
+       struct rev_info revs;
+       int both_flags = shallow_flag | not_shallow_flag;
+       /*
+        * SHALLOW (excluded) and NOT_SHALLOW (included) should not be
+        * set at this point. But better be safe than sorry.
+        */
+       clear_object_flags(both_flags);
+       is_repository_shallow(); /* make sure shallows are read */
+       init_revisions(&revs, NULL);
+       save_commit_buffer = 0;
+       setup_revisions(ac, av, &revs, NULL);
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       traverse_commit_list(&revs, show_commit, NULL, &not_shallow_list);
+       /* Mark all reachable commits as NOT_SHALLOW */
+       for (p = not_shallow_list; p; p = p->next)
+               p->item->object.flags |= not_shallow_flag;
+       /*
+        * mark border commits SHALLOW + NOT_SHALLOW.
+        * We cannot clear NOT_SHALLOW right now. Imagine border
+        * commit A is processed first, then commit B, whose parent is
+        * A, later. If NOT_SHALLOW on A is cleared at step 1, B
+        * itself is considered border at step 2, which is incorrect.
+        */
+       for (p = not_shallow_list; p; p = p->next) {
+               struct commit *c = p->item;
+               struct commit_list *parent;
+               if (parse_commit(c))
+                       die("unable to parse commit %s",
+                           oid_to_hex(&c->object.oid));
+               for (parent = c->parents; parent; parent = parent->next)
+                       if (!(parent->item->object.flags & not_shallow_flag)) {
+                               c->object.flags |= shallow_flag;
+                               commit_list_insert(c, &result);
+                               break;
+                       }
+       }
+       free_commit_list(not_shallow_list);
+       /*
+        * Now we can clean up NOT_SHALLOW on border commits. Having
+        * both flags set can confuse the caller.
+        */
+       for (p = result; p; p = p->next) {
+               struct object *o = &p->item->object;
+               if ((o->flags & both_flags) == both_flags)
+                       o->flags &= ~not_shallow_flag;
+       }
+       return result;
+ }
  static void check_shallow_file_for_update(void)
  {
        if (is_shallow == -1)
@@@ -315,8 -393,8 +393,8 @@@ void prepare_shallow_info(struct shallo
        info->shallow = sa;
        if (!sa)
                return;
 -      info->ours = xmalloc(sizeof(*info->ours) * sa->nr);
 -      info->theirs = xmalloc(sizeof(*info->theirs) * sa->nr);
 +      ALLOC_ARRAY(info->ours, sa->nr);
 +      ALLOC_ARRAY(info->theirs, sa->nr);
        for (i = 0; i < sa->nr; i++) {
                if (has_sha1_file(sa->sha1[i])) {
                        struct commit_graft *graft;
@@@ -389,7 -467,7 +467,7 @@@ static void paint_down(struct paint_inf
        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);
 +      size_t bitmap_size = st_mult(sizeof(uint32_t), bitmap_nr);
        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);
@@@ -487,7 -565,7 +565,7 @@@ void assign_shallow_commits_to_refs(str
        struct paint_info pi;
  
        trace_printf_key(&trace_shallow, "shallow: assign_shallow_commits_to_refs\n");
 -      shallow = xmalloc(sizeof(*shallow) * (info->nr_ours + info->nr_theirs));
 +      ALLOC_ARRAY(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++)
diff --combined t/t5500-fetch-pack.sh
index 82d913a6a87c45dd8141e3c7384df9943f9b83cb,a908036be72808c4bc2f17f1b43dc0b6e96cfc73..505e1b4a7f421d377aa3798e94c90c14249bce91
@@@ -259,8 -259,7 +259,8 @@@ test_expect_success 'clone shallow obje
  test_expect_success 'pull in shallow repo with missing merge base' '
        (
                cd shallow &&
 -              test_must_fail git pull --depth 4 .. A
 +              git fetch --depth 4 .. A
 +              test_must_fail git merge --allow-unrelated-histories FETCH_HEAD
        )
  '
  
@@@ -280,10 -279,9 +280,10 @@@ test_expect_success 'clone shallow dept
  test_expect_success 'clone shallow object count' '
        (
                cd shallow &&
 +              git prune &&
                git count-objects -v
        ) > count.shallow &&
 -      grep "^count: 55" count.shallow
 +      grep "^count: 54" count.shallow
  '
  
  test_expect_success 'fetch --no-shallow on full repo' '
@@@ -533,20 -531,6 +533,20 @@@ test_expect_success 'shallow fetch wit
                git fsck
        )
  '
 +
 +test_expect_success 'fetch-pack can fetch a raw sha1' '
 +      git init hidden &&
 +      (
 +              cd hidden &&
 +              test_commit 1 &&
 +              test_commit 2 &&
 +              git update-ref refs/hidden/one HEAD^ &&
 +              git config transfer.hiderefs refs/hidden &&
 +              git config uploadpack.allowtipsha1inwant true
 +      ) &&
 +      git fetch-pack hidden $(git -C hidden rev-parse refs/hidden/one)
 +'
 +
  check_prot_path () {
        cat >expected <<-EOF &&
        Diag: url=$1
  }
  
  check_prot_host_port_path () {
 -      local diagport
        case "$2" in
                *ssh*)
                pp=ssh
@@@ -652,4 -637,72 +652,72 @@@ test_expect_success MINGW 'fetch-pack -
        check_prot_path c:repo file c:repo
  '
  
+ test_expect_success 'clone shallow since ...' '
+       test_create_repo shallow-since &&
+       (
+       cd shallow-since &&
+       GIT_COMMITTER_DATE="100000000 +0700" git commit --allow-empty -m one &&
+       GIT_COMMITTER_DATE="200000000 +0700" git commit --allow-empty -m two &&
+       GIT_COMMITTER_DATE="300000000 +0700" git commit --allow-empty -m three &&
+       git clone --shallow-since "300000000 +0700" "file://$(pwd)/." ../shallow11 &&
+       git -C ../shallow11 log --pretty=tformat:%s HEAD >actual &&
+       echo three >expected &&
+       test_cmp expected actual
+       )
+ '
+ test_expect_success 'fetch shallow since ...' '
+       git -C shallow11 fetch --shallow-since "200000000 +0700" origin &&
+       git -C shallow11 log --pretty=tformat:%s origin/master >actual &&
+       cat >expected <<-\EOF &&
+       three
+       two
+       EOF
+       test_cmp expected actual
+ '
+ test_expect_success 'shallow clone exclude tag two' '
+       test_create_repo shallow-exclude &&
+       (
+       cd shallow-exclude &&
+       test_commit one &&
+       test_commit two &&
+       test_commit three &&
+       git clone --shallow-exclude two "file://$(pwd)/." ../shallow12 &&
+       git -C ../shallow12 log --pretty=tformat:%s HEAD >actual &&
+       echo three >expected &&
+       test_cmp expected actual
+       )
+ '
+ test_expect_success 'fetch exclude tag one' '
+       git -C shallow12 fetch --shallow-exclude one origin &&
+       git -C shallow12 log --pretty=tformat:%s origin/master >actual &&
+       test_write_lines three two >expected &&
+       test_cmp expected actual
+ '
+ test_expect_success 'fetching deepen' '
+       test_create_repo shallow-deepen &&
+       (
+       cd shallow-deepen &&
+       test_commit one &&
+       test_commit two &&
+       test_commit three &&
+       git clone --depth 1 "file://$(pwd)/." deepen &&
+       test_commit four &&
+       git -C deepen log --pretty=tformat:%s master >actual &&
+       echo three >expected &&
+       test_cmp expected actual &&
+       git -C deepen fetch --deepen=1 &&
+       git -C deepen log --pretty=tformat:%s origin/master >actual &&
+       cat >expected <<-\EOF &&
+       four
+       three
+       two
+       EOF
+       test_cmp expected actual
+       )
+ '
  test_done
diff --combined transport-helper.c
index db2f930c740348bbe55525a2e471857b6dc463f1,a5cdd7730ceeb763bb9f08096f3b2d8e657e204a..91aed35ebbc3cbb4fb9acbad55ab3de69e62d9f5
@@@ -258,8 -258,51 +258,51 @@@ static const char *boolean_options[] = 
        TRANS_OPT_THIN,
        TRANS_OPT_KEEP,
        TRANS_OPT_FOLLOWTAGS,
+       TRANS_OPT_DEEPEN_RELATIVE
        };
  
+ static int strbuf_set_helper_option(struct helper_data *data,
+                                   struct strbuf *buf)
+ {
+       int ret;
+       sendline(data, buf);
+       if (recvline(data, buf))
+               exit(128);
+       if (!strcmp(buf->buf, "ok"))
+               ret = 0;
+       else if (starts_with(buf->buf, "error"))
+               ret = -1;
+       else if (!strcmp(buf->buf, "unsupported"))
+               ret = 1;
+       else {
+               warning("%s unexpectedly said: '%s'", data->name, buf->buf);
+               ret = 1;
+       }
+       return ret;
+ }
+ static int string_list_set_helper_option(struct helper_data *data,
+                                        const char *name,
+                                        struct string_list *list)
+ {
+       struct strbuf buf = STRBUF_INIT;
+       int i, ret = 0;
+       for (i = 0; i < list->nr; i++) {
+               strbuf_addf(&buf, "option %s ", name);
+               quote_c_style(list->items[i].string, &buf, NULL, 0);
+               strbuf_addch(&buf, '\n');
+               if ((ret = strbuf_set_helper_option(data, &buf)))
+                       break;
+               strbuf_reset(&buf);
+       }
+       strbuf_release(&buf);
+       return ret;
+ }
  static int set_helper_option(struct transport *transport,
                          const char *name, const char *value)
  {
        if (!data->option)
                return 1;
  
+       if (!strcmp(name, "deepen-not"))
+               return string_list_set_helper_option(data, name,
+                                                    (struct string_list *)value);
        for (i = 0; i < ARRAY_SIZE(unsupported_options); i++) {
                if (!strcmp(name, unsupported_options[i]))
                        return 1;
                quote_c_style(value, &buf, NULL, 0);
        strbuf_addch(&buf, '\n');
  
-       sendline(data, &buf);
-       if (recvline(data, &buf))
-               exit(128);
-       if (!strcmp(buf.buf, "ok"))
-               ret = 0;
-       else if (starts_with(buf.buf, "error")) {
-               ret = -1;
-       } else if (!strcmp(buf.buf, "unsupported"))
-               ret = 1;
-       else {
-               warning("%s unexpectedly said: '%s'", data->name, buf.buf);
-               ret = 1;
-       }
+       ret = strbuf_set_helper_option(data, &buf);
        strbuf_release(&buf);
        return ret;
  }
@@@ -321,21 -355,6 +355,21 @@@ static void standard_options(struct tra
        if (n >= sizeof(buf))
                die("impossibly large verbosity value");
        set_helper_option(t, "verbosity", buf);
 +
 +      switch (t->family) {
 +      case TRANSPORT_FAMILY_ALL:
 +              /*
 +               * this is already the default,
 +               * do not break old remote helpers by setting "all" here
 +               */
 +              break;
 +      case TRANSPORT_FAMILY_IPV4:
 +              set_helper_option(t, "family", "ipv4");
 +              break;
 +      case TRANSPORT_FAMILY_IPV6:
 +              set_helper_option(t, "family", "ipv6");
 +              break;
 +      }
  }
  
  static int release_helper(struct transport *transport)
@@@ -1038,7 -1057,7 +1072,7 @@@ static struct ref *get_refs_list(struc
                                (*tail)->status |= REF_STATUS_UPTODATE;
                                if (read_ref((*tail)->name,
                                             (*tail)->old_oid.hash) < 0)
 -                                      die(N_("Could not read ref %s"),
 +                                      die(_("Could not read ref %s"),
                                            (*tail)->name);
                        }
                }
@@@ -1103,7 -1122,7 +1137,7 @@@ static void transfer_debug(const char *
  }
  
  /* Stream state: More data may be coming in this direction. */
 -#define SSTATE_TRANSFERING 0
 +#define SSTATE_TRANSFERRING 0
  /*
   * Stream state: No more data coming in this direction, flushing rest of
   * data.
  /* Stream state: Transfer in this direction finished. */
  #define SSTATE_FINISHED 2
  
 -#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERING)
 +#define STATE_NEEDS_READING(state) ((state) <= SSTATE_TRANSFERRING)
  #define STATE_NEEDS_WRITING(state) ((state) <= SSTATE_FLUSHING)
  #define STATE_NEEDS_CLOSING(state) ((state) == SSTATE_FLUSHING)
  
@@@ -1152,7 -1171,7 +1186,7 @@@ static void udt_close_if_finished(struc
  }
  
  /*
 - * Tries to read read data from source into buffer. If buffer is full,
 + * Tries to read data from source into buffer. If buffer is full,
   * no data is read. Returns 0 on success, -1 on error.
   */
  static int udt_do_read(struct unidirectional_transfer *t)
        bytes = read(t->src, t->buf + t->bufuse, BUFFERSIZE - t->bufuse);
        if (bytes < 0 && errno != EWOULDBLOCK && errno != EAGAIN &&
                errno != EINTR) {
 -              error("read(%s) failed: %s", t->src_name, strerror(errno));
 +              error_errno("read(%s) failed", t->src_name);
                return -1;
        } else if (bytes == 0) {
                transfer_debug("%s EOF (with %i bytes in buffer)",
@@@ -1193,7 -1212,7 +1227,7 @@@ static int udt_do_write(struct unidirec
        transfer_debug("%s is writable", t->dest_name);
        bytes = xwrite(t->dest, t->buf, t->bufuse);
        if (bytes < 0 && errno != EWOULDBLOCK) {
 -              error("write(%s) failed: %s", t->dest_name, strerror(errno));
 +              error_errno("write(%s) failed", t->dest_name);
                return -1;
        } else if (bytes > 0) {
                t->bufuse -= bytes;
@@@ -1306,7 -1325,7 +1340,7 @@@ static int tloop_join(pid_t pid, const 
  {
        int tret;
        if (waitpid(pid, &tret, 0) < 0) {
 -              error("%s process failed to wait: %s", name, strerror(errno));
 +              error_errno("%s process failed to wait", name);
                return 1;
        }
        if (!WIFEXITED(tret) || WEXITSTATUS(tret)) {
@@@ -1369,7 -1388,7 +1403,7 @@@ int bidirectional_transfer_loop(int inp
        state.ptg.dest = 1;
        state.ptg.src_is_sock = (input == output);
        state.ptg.dest_is_sock = 0;
 -      state.ptg.state = SSTATE_TRANSFERING;
 +      state.ptg.state = SSTATE_TRANSFERRING;
        state.ptg.bufuse = 0;
        state.ptg.src_name = "remote input";
        state.ptg.dest_name = "stdout";
        state.gtp.dest = output;
        state.gtp.src_is_sock = 0;
        state.gtp.dest_is_sock = (input == output);
 -      state.gtp.state = SSTATE_TRANSFERING;
 +      state.gtp.state = SSTATE_TRANSFERRING;
        state.gtp.bufuse = 0;
        state.gtp.src_name = "stdin";
        state.gtp.dest_name = "remote output";
diff --combined transport.c
index 94d6dc3725a2bb091df66ce6fe266ddd5317ed02,3e76a9af4d58fba352bcb89f0ea158f0b24b270a..a85801042b012c2e2684cbb4e5e3dc1925e43582
@@@ -59,7 -59,7 +59,7 @@@ static void set_upstreams(struct transp
                                localname + 11, transport->remote->name,
                                remotename);
                else
 -                      printf("Would set upstream of '%s' to '%s' of '%s'\n",
 +                      printf(_("Would set upstream of '%s' to '%s' of '%s'\n"),
                                localname + 11, remotename + 11,
                                transport->remote->name);
        }
@@@ -148,9 -148,18 +148,18 @@@ static int set_git_option(struct git_tr
                        char *end;
                        opts->depth = strtol(value, &end, 0);
                        if (*end)
 -                              die("transport: invalid depth option '%s'", value);
 +                              die(_("transport: invalid depth option '%s'"), value);
                }
                return 0;
+       } else if (!strcmp(name, TRANS_OPT_DEEPEN_SINCE)) {
+               opts->deepen_since = value;
+               return 0;
+       } else if (!strcmp(name, TRANS_OPT_DEEPEN_NOT)) {
+               opts->deepen_not = (const struct string_list *)value;
+               return 0;
+       } else if (!strcmp(name, TRANS_OPT_DEEPEN_RELATIVE)) {
+               opts->deepen_relative = !!value;
+               return 0;
        }
        return 1;
  }
@@@ -163,12 -172,6 +172,12 @@@ static int connect_setup(struct transpo
        if (data->conn)
                return 0;
  
 +      switch (transport->family) {
 +      case TRANSPORT_FAMILY_ALL: break;
 +      case TRANSPORT_FAMILY_IPV4: flags |= CONNECT_IPV4; break;
 +      case TRANSPORT_FAMILY_IPV6: flags |= CONNECT_IPV6; break;
 +      }
 +
        data->conn = git_connect(data->fd, transport->url,
                                 for_push ? data->options.receivepack :
                                 data->options.uploadpack,
@@@ -211,6 -214,9 +220,9 @@@ static int fetch_refs_via_pack(struct t
        args.quiet = (transport->verbose < 0);
        args.no_progress = !transport->progress;
        args.depth = data->options.depth;
+       args.deepen_since = data->options.deepen_since;
+       args.deepen_not = data->options.deepen_not;
+       args.deepen_relative = data->options.deepen_relative;
        args.check_self_contained_and_connected =
                data->options.check_self_contained_and_connected;
        args.cloning = transport->cloning;
@@@ -321,6 -327,11 +333,6 @@@ static void print_ref_status(char flag
        }
  }
  
 -static const char *status_abbrev(unsigned char sha1[20])
 -{
 -      return find_unique_abbrev(sha1, DEFAULT_ABBREV);
 -}
 -
  static void print_ok_ref_status(struct ref *ref, int porcelain)
  {
        if (ref->deletion)
                char type;
                const char *msg;
  
 -              strbuf_addstr(&quickref, status_abbrev(ref->old_oid.hash));
 +              strbuf_add_unique_abbrev(&quickref, ref->old_oid.hash,
 +                                       DEFAULT_ABBREV);
                if (ref->forced_update) {
                        strbuf_addstr(&quickref, "...");
                        type = '+';
                        type = ' ';
                        msg = NULL;
                }
 -              strbuf_addstr(&quickref, status_abbrev(ref->new_oid.hash));
 +              strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash,
 +                                       DEFAULT_ABBREV);
  
                print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg, porcelain);
                strbuf_release(&quickref);
  
  static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain)
  {
 -      if (!count)
 -              fprintf(porcelain ? stdout : stderr, "To %s\n", dest);
 +      if (!count) {
 +              char *url = transport_anonymize_url(dest);
 +              fprintf(porcelain ? stdout : stderr, "To %s\n", url);
 +              free(url);
 +      }
  
        switch(ref->status) {
        case REF_STATUS_NONE:
@@@ -510,7 -516,6 +522,7 @@@ static int git_transport_push(struct tr
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
        args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
        args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
 +      args.push_options = transport->push_options;
        args.url = transport->url;
  
        if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
@@@ -564,7 -569,7 +576,7 @@@ void transport_take_over(struct transpo
        struct git_transport_data *data;
  
        if (!transport->smart_options)
 -              die("Bug detected: Taking over transport requires non-NULL "
 +              die("BUG: taking over transport requires non-NULL "
                    "smart_options field.");
  
        data = xcalloc(1, sizeof(*data));
@@@ -679,9 -684,8 +691,9 @@@ struct transport *transport_get(struct 
                || starts_with(url, "file://")
                || starts_with(url, "git://")
                || starts_with(url, "ssh://")
 -              || starts_with(url, "git+ssh://")
 -              || starts_with(url, "ssh+git://")) {
 +              || starts_with(url, "git+ssh://") /* deprecated - do not use */
 +              || starts_with(url, "ssh+git://") /* deprecated - do not use */
 +              ) {
                /*
                 * These are builtin smart transports; "allowed" transports
                 * will be checked individually in git_connect.
@@@ -768,19 -772,19 +780,19 @@@ static void die_with_unpushed_submodule
  {
        int i;
  
 -      fprintf(stderr, "The following submodule paths contain changes that can\n"
 -                      "not be found on any remote:\n");
 +      fprintf(stderr, _("The following submodule paths contain changes that can\n"
 +                      "not be found on any remote:\n"));
        for (i = 0; i < needs_pushing->nr; i++)
 -              printf("  %s\n", needs_pushing->items[i].string);
 -      fprintf(stderr, "\nPlease try\n\n"
 -                      "       git push --recurse-submodules=on-demand\n\n"
 -                      "or cd to the path and use\n\n"
 -                      "       git push\n\n"
 -                      "to push them to a remote.\n\n");
 +              fprintf(stderr, "  %s\n", needs_pushing->items[i].string);
 +      fprintf(stderr, _("\nPlease try\n\n"
 +                        "     git push --recurse-submodules=on-demand\n\n"
 +                        "or cd to the path and use\n\n"
 +                        "     git push\n\n"
 +                        "to push them to a remote.\n\n"));
  
        string_list_clear(needs_pushing, 0);
  
 -      die("Aborting.");
 +      die(_("Aborting."));
  }
  
  static int run_pre_push_hook(struct transport *transport,
@@@ -985,7 -989,7 +997,7 @@@ int transport_fetch_refs(struct transpo
                 * This condition shouldn't be met in a non-deepening fetch
                 * (see builtin/fetch.c:quickfetch()).
                 */
 -              heads = xmalloc(nr_refs * sizeof(*heads));
 +              ALLOC_ARRAY(heads, nr_refs);
                for (rm = refs; rm; rm = rm->next)
                        heads[nr_heads++] = rm;
        }
@@@ -1029,7 -1033,7 +1041,7 @@@ int transport_disconnect(struct transpo
   */
  char *transport_anonymize_url(const char *url)
  {
 -      char *anon_url, *scheme_prefix, *anon_part;
 +      char *scheme_prefix, *anon_part;
        size_t anon_len, prefix_len = 0;
  
        anon_part = strchr(url, '@');
                        goto literal_copy;
                prefix_len = scheme_prefix - url + 3;
        }
 -      anon_url = xcalloc(1, 1 + prefix_len + anon_len);
 -      memcpy(anon_url, url, prefix_len);
 -      memcpy(anon_url + prefix_len, anon_part, anon_len);
 -      return anon_url;
 +      return xstrfmt("%.*s%.*s", (int)prefix_len, url,
 +                     (int)anon_len, anon_part);
  literal_copy:
        return xstrdup(url);
  }
diff --combined transport.h
index 6fe3485325dfccfba3b018c8c25b89a6d4643a12,bdc35181c303a7d85083583a6f967e87089cd653..68669f14d08f52d15f476fe97a813044a0d07dda
@@@ -5,6 -5,8 +5,8 @@@
  #include "run-command.h"
  #include "remote.h"
  
+ struct string_list;
  struct git_transport_options {
        unsigned thin : 1;
        unsigned keep : 1;
        unsigned check_self_contained_and_connected : 1;
        unsigned self_contained_and_connected : 1;
        unsigned update_shallow : 1;
+       unsigned deepen_relative : 1;
        int depth;
+       const char *deepen_since;
+       const struct string_list *deepen_not;
        const char *uploadpack;
        const char *receivepack;
        struct push_cas_option *cas;
  };
  
 +enum transport_family {
 +      TRANSPORT_FAMILY_ALL = 0,
 +      TRANSPORT_FAMILY_IPV4,
 +      TRANSPORT_FAMILY_IPV6
 +};
 +
  struct transport {
        struct remote *remote;
        const char *url;
         */
        unsigned cloning : 1;
  
 +      /*
 +       * These strings will be passed to the {pre, post}-receive hook,
 +       * on the remote side, if both sides support the push options capability.
 +       */
 +      const struct string_list *push_options;
 +
        /**
         * Returns 0 if successful, positive if the option is not
         * recognized or is inapplicable, and negative if the option
         * actually turns out to be smart.
         */
        struct git_transport_options *smart_options;
 +
 +      enum transport_family family;
  };
  
  #define TRANSPORT_PUSH_ALL 1
  #define TRANSPORT_PUSH_CERT_ALWAYS 2048
  #define TRANSPORT_PUSH_CERT_IF_ASKED 4096
  #define TRANSPORT_PUSH_ATOMIC 8192
 +#define TRANSPORT_PUSH_OPTIONS 16384
  
  #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
  #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
@@@ -186,6 -176,15 +191,15 @@@ int transport_restrict_protocols(void)
  /* Limit the depth of the fetch if not null */
  #define TRANS_OPT_DEPTH "depth"
  
+ /* Limit the depth of the fetch based on time if not null */
+ #define TRANS_OPT_DEEPEN_SINCE "deepen-since"
+ /* Limit the depth of the fetch based on revs if not null */
+ #define TRANS_OPT_DEEPEN_NOT "deepen-not"
+ /* Limit the deepen of the fetch if not null */
+ #define TRANS_OPT_DEEPEN_RELATIVE "deepen-relative"
  /* Aggressively fetch annotated tags if possible */
  #define TRANS_OPT_FOLLOWTAGS "followtags"
  
diff --combined upload-pack.c
index ca7f9417800bfb27a7dbc0559df8bec772c267fa,e40d15adaff6d164d72156ebddb9b4f152d92d96..5ec21e61d916999f4639162b8086115ad382f877
  #include "sigchain.h"
  #include "version.h"
  #include "string-list.h"
 +#include "parse-options.h"
+ #include "argv-array.h"
  
 -static const char upload_pack_usage[] = "git upload-pack [--strict] [--timeout=<n>] <dir>";
 +static const char * const upload_pack_usage[] = {
 +      N_("git upload-pack [<options>] <dir>"),
 +      NULL
 +};
  
  /* Remember to update object flag allocation in object.h */
  #define THEY_HAVE     (1u << 11)
@@@ -35,6 -32,7 +36,7 @@@
  
  static unsigned long oldest_have;
  
+ static int deepen_relative;
  static int multi_ack;
  static int no_done;
  static int use_thin_pack, use_ofs_delta, use_include_tag;
@@@ -56,28 -54,26 +58,28 @@@ static int keepalive = 5
  static int use_sideband;
  static int advertise_refs;
  static int stateless_rpc;
 +static const char *pack_objects_hook;
  
  static void reset_timeout(void)
  {
        alarm(timeout);
  }
  
 -static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
 +static void send_client_data(int fd, const char *data, ssize_t sz)
  {
 -      if (use_sideband)
 -              return send_sideband(1, fd, data, sz, use_sideband);
 +      if (use_sideband) {
 +              send_sideband(1, fd, data, sz, use_sideband);
 +              return;
 +      }
        if (fd == 3)
                /* emergency quit */
                fd = 2;
        if (fd == 2) {
                /* XXX: are we happy to lose stuff here? */
                xwrite(fd, data, sz);
 -              return sz;
 +              return;
        }
        write_or_die(fd, data, sz);
 -      return sz;
  }
  
  static int write_one_shallow(const struct commit_graft *graft, void *cb_data)
@@@ -96,39 -92,35 +98,39 @@@ static void create_pack_file(void
                "corruption on the remote side.";
        int buffered = -1;
        ssize_t sz;
 -      const char *argv[13];
 -      int i, arg = 0;
 +      int i;
        FILE *pipe_fd;
  
 +      if (!pack_objects_hook)
 +              pack_objects.git_cmd = 1;
 +      else {
 +              argv_array_push(&pack_objects.args, pack_objects_hook);
 +              argv_array_push(&pack_objects.args, "git");
 +              pack_objects.use_shell = 1;
 +      }
 +
        if (shallow_nr) {
 -              argv[arg++] = "--shallow-file";
 -              argv[arg++] = "";
 +              argv_array_push(&pack_objects.args, "--shallow-file");
 +              argv_array_push(&pack_objects.args, "");
        }
 -      argv[arg++] = "pack-objects";
 -      argv[arg++] = "--revs";
 +      argv_array_push(&pack_objects.args, "pack-objects");
 +      argv_array_push(&pack_objects.args, "--revs");
        if (use_thin_pack)
 -              argv[arg++] = "--thin";
 +              argv_array_push(&pack_objects.args, "--thin");
  
 -      argv[arg++] = "--stdout";
 +      argv_array_push(&pack_objects.args, "--stdout");
        if (shallow_nr)
 -              argv[arg++] = "--shallow";
 +              argv_array_push(&pack_objects.args, "--shallow");
        if (!no_progress)
 -              argv[arg++] = "--progress";
 +              argv_array_push(&pack_objects.args, "--progress");
        if (use_ofs_delta)
 -              argv[arg++] = "--delta-base-offset";
 +              argv_array_push(&pack_objects.args, "--delta-base-offset");
        if (use_include_tag)
 -              argv[arg++] = "--include-tag";
 -      argv[arg++] = NULL;
 +              argv_array_push(&pack_objects.args, "--include-tag");
  
        pack_objects.in = -1;
        pack_objects.out = -1;
        pack_objects.err = -1;
 -      pack_objects.git_cmd = 1;
 -      pack_objects.argv = argv;
  
        if (start_command(&pack_objects))
                die("git upload-pack: unable to fork git-pack-objects");
  
                if (ret < 0) {
                        if (errno != EINTR) {
 -                              error("poll failed, resuming: %s",
 -                                    strerror(errno));
 +                              error_errno("poll failed, resuming");
                                sleep(1);
                        }
                        continue;
                        }
                        else
                                buffered = -1;
 -                      sz = send_client_data(1, data, sz);
 -                      if (sz < 0)
 -                              goto fail;
 +                      send_client_data(1, data, sz);
                }
  
                /*
        /* flush the data */
        if (0 <= buffered) {
                data[0] = buffered;
 -              sz = send_client_data(1, data, 1);
 -              if (sz < 0)
 -                      goto fail;
 +              send_client_data(1, data, 1);
                fprintf(stderr, "flushed.\n");
        }
        if (use_sideband)
        die("git upload-pack: %s", abort_msg);
  }
  
- static int got_sha1(char *hex, unsigned char *sha1)
+ static int got_sha1(const char *hex, unsigned char *sha1)
  {
        struct object *o;
        int we_knew_they_have = 0;
@@@ -387,6 -384,8 +389,8 @@@ static int get_common_commits(void
  
        for (;;) {
                char *line = packet_read_line(0, NULL);
+               const char *arg;
                reset_timeout();
  
                if (!line) {
                        got_other = 0;
                        continue;
                }
-               if (starts_with(line, "have ")) {
-                       switch (got_sha1(line+5, sha1)) {
+               if (skip_prefix(line, "have ", &arg)) {
+                       switch (got_sha1(arg, sha1)) {
                        case -1: /* they have what we do not */
                                got_other = 1;
                                if (multi_ack && ok_to_give_up()) {
@@@ -454,73 -453,136 +458,136 @@@ static int is_our_ref(struct object *o
        return o->flags & ((allow_hidden_ref ? HIDDEN_REF : 0) | OUR_REF);
  }
  
- static void check_non_tip(void)
+ /*
+  * on successful case, it's up to the caller to close cmd->out
+  */
+ static int do_reachable_revlist(struct child_process *cmd,
+                               struct object_array *src,
+                               struct object_array *reachable)
  {
        static const char *argv[] = {
                "rev-list", "--stdin", NULL,
        };
-       static struct child_process cmd = CHILD_PROCESS_INIT;
        struct object *o;
        char namebuf[42]; /* ^ + SHA-1 + LF */
        int i;
  
-       /*
-        * In the normal in-process case without
-        * uploadpack.allowReachableSHA1InWant,
-        * non-tip requests can never happen.
-        */
-       if (!stateless_rpc && !(allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1))
-               goto error;
-       cmd.argv = argv;
-       cmd.git_cmd = 1;
-       cmd.no_stderr = 1;
-       cmd.in = -1;
-       cmd.out = -1;
-       if (start_command(&cmd))
-               goto error;
+       cmd->argv = argv;
+       cmd->git_cmd = 1;
+       cmd->no_stderr = 1;
+       cmd->in = -1;
+       cmd->out = -1;
  
        /*
-        * If rev-list --stdin encounters an unknown commit, it
-        * terminates, which will cause SIGPIPE in the write loop
+        * If the next rev-list --stdin encounters an unknown commit,
+        * it terminates, which will cause SIGPIPE in the write loop
         * below.
         */
        sigchain_push(SIGPIPE, SIG_IGN);
  
+       if (start_command(cmd))
+               goto error;
        namebuf[0] = '^';
        namebuf[41] = '\n';
        for (i = get_max_object_index(); 0 < i; ) {
                o = get_indexed_object(--i);
                if (!o)
                        continue;
+               if (reachable && o->type == OBJ_COMMIT)
+                       o->flags &= ~TMP_MARK;
                if (!is_our_ref(o))
                        continue;
                memcpy(namebuf + 1, oid_to_hex(&o->oid), GIT_SHA1_HEXSZ);
-               if (write_in_full(cmd.in, namebuf, 42) < 0)
+               if (write_in_full(cmd->in, namebuf, 42) < 0)
                        goto error;
        }
        namebuf[40] = '\n';
-       for (i = 0; i < want_obj.nr; i++) {
-               o = want_obj.objects[i].item;
-               if (is_our_ref(o))
+       for (i = 0; i < src->nr; i++) {
+               o = src->objects[i].item;
+               if (is_our_ref(o)) {
+                       if (reachable)
+                               add_object_array(o, NULL, reachable);
                        continue;
+               }
+               if (reachable && o->type == OBJ_COMMIT)
+                       o->flags |= TMP_MARK;
                memcpy(namebuf, oid_to_hex(&o->oid), GIT_SHA1_HEXSZ);
-               if (write_in_full(cmd.in, namebuf, 41) < 0)
+               if (write_in_full(cmd->in, namebuf, 41) < 0)
                        goto error;
        }
-       close(cmd.in);
+       close(cmd->in);
+       cmd->in = -1;
+       sigchain_pop(SIGPIPE);
  
+       return 0;
+ error:
        sigchain_pop(SIGPIPE);
  
+       if (cmd->in >= 0)
+               close(cmd->in);
+       if (cmd->out >= 0)
+               close(cmd->out);
+       return -1;
+ }
+ static int get_reachable_list(struct object_array *src,
+                             struct object_array *reachable)
+ {
+       struct child_process cmd = CHILD_PROCESS_INIT;
+       int i;
+       struct object *o;
+       char namebuf[42]; /* ^ + SHA-1 + LF */
+       if (do_reachable_revlist(&cmd, src, reachable) < 0)
+               return -1;
+       while ((i = read_in_full(cmd.out, namebuf, 41)) == 41) {
+               struct object_id sha1;
+               if (namebuf[40] != '\n' || get_oid_hex(namebuf, &sha1))
+                       break;
+               o = lookup_object(sha1.hash);
+               if (o && o->type == OBJ_COMMIT) {
+                       o->flags &= ~TMP_MARK;
+               }
+       }
+       for (i = get_max_object_index(); 0 < i; i--) {
+               o = get_indexed_object(i - 1);
+               if (o && o->type == OBJ_COMMIT &&
+                   (o->flags & TMP_MARK)) {
+                       add_object_array(o, NULL, reachable);
+                               o->flags &= ~TMP_MARK;
+               }
+       }
+       close(cmd.out);
+       if (finish_command(&cmd))
+               return -1;
+       return 0;
+ }
+ static int has_unreachable(struct object_array *src)
+ {
+       struct child_process cmd = CHILD_PROCESS_INIT;
+       char buf[1];
+       int i;
+       if (do_reachable_revlist(&cmd, src, NULL) < 0)
+               return 1;
        /*
         * The commits out of the rev-list are not ancestors of
         * our ref.
         */
-       i = read_in_full(cmd.out, namebuf, 1);
+       i = read_in_full(cmd.out, buf, 1);
        if (i)
                goto error;
        close(cmd.out);
+       cmd.out = -1;
  
        /*
         * rev-list may have died by encountering a bad commit
                goto error;
  
        /* All the non-tip ones are ancestors of what we advertised */
-       return;
+       return 0;
+ error:
+       sigchain_pop(SIGPIPE);
+       if (cmd.out >= 0)
+               close(cmd.out);
+       return 1;
+ }
+ static void check_non_tip(void)
+ {
+       int i;
+       /*
+        * In the normal in-process case without
+        * uploadpack.allowReachableSHA1InWant,
+        * non-tip requests can never happen.
+        */
+       if (!stateless_rpc && !(allow_unadvertised_object_request & ALLOW_REACHABLE_SHA1))
+               goto error;
+       if (!has_unreachable(&want_obj))
+               /* All the non-tip ones are ancestors of what we advertised */
+               return;
  
  error:
        /* Pick one of them (we know there at least is one) */
        for (i = 0; i < want_obj.nr; i++) {
-               o = want_obj.objects[i].item;
+               struct object *o = want_obj.objects[i].item;
                if (!is_our_ref(o))
                        die("git upload-pack: not our ref %s",
                            oid_to_hex(&o->oid));
        }
  }
  
+ static void send_shallow(struct commit_list *result)
+ {
+       while (result) {
+               struct object *object = &result->item->object;
+               if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) {
+                       packet_write(1, "shallow %s",
+                                    oid_to_hex(&object->oid));
+                       register_shallow(object->oid.hash);
+                       shallow_nr++;
+               }
+               result = result->next;
+       }
+ }
+ static void send_unshallow(const struct object_array *shallows)
+ {
+       int i;
+       for (i = 0; i < shallows->nr; i++) {
+               struct object *object = shallows->objects[i].item;
+               if (object->flags & NOT_SHALLOW) {
+                       struct commit_list *parents;
+                       packet_write(1, "unshallow %s",
+                                    oid_to_hex(&object->oid));
+                       object->flags &= ~CLIENT_SHALLOW;
+                       /*
+                        * We want to _register_ "object" as shallow, but we
+                        * also need to traverse object's parents to deepen a
+                        * shallow clone. Unregister it for now so we can
+                        * parse and add the parents to the want list, then
+                        * re-register it.
+                        */
+                       unregister_shallow(object->oid.hash);
+                       object->parsed = 0;
+                       parse_commit_or_die((struct commit *)object);
+                       parents = ((struct commit *)object)->parents;
+                       while (parents) {
+                               add_object_array(&parents->item->object,
+                                                NULL, &want_obj);
+                               parents = parents->next;
+                       }
+                       add_object_array(object, NULL, &extra_edge_obj);
+               }
+               /* make sure commit traversal conforms to client */
+               register_shallow(object->oid.hash);
+       }
+ }
+ static void deepen(int depth, int deepen_relative,
+                  struct object_array *shallows)
+ {
+       if (depth == INFINITE_DEPTH && !is_repository_shallow()) {
+               int i;
+               for (i = 0; i < shallows->nr; i++) {
+                       struct object *object = shallows->objects[i].item;
+                       object->flags |= NOT_SHALLOW;
+               }
+       } else if (deepen_relative) {
+               struct object_array reachable_shallows = OBJECT_ARRAY_INIT;
+               struct commit_list *result;
+               get_reachable_list(shallows, &reachable_shallows);
+               result = get_shallow_commits(&reachable_shallows,
+                                            depth + 1,
+                                            SHALLOW, NOT_SHALLOW);
+               send_shallow(result);
+               free_commit_list(result);
+               object_array_clear(&reachable_shallows);
+       } else {
+               struct commit_list *result;
+               result = get_shallow_commits(&want_obj, depth,
+                                            SHALLOW, NOT_SHALLOW);
+               send_shallow(result);
+               free_commit_list(result);
+       }
+       send_unshallow(shallows);
+       packet_flush(1);
+ }
+ static void deepen_by_rev_list(int ac, const char **av,
+                              struct object_array *shallows)
+ {
+       struct commit_list *result;
+       result = get_shallow_commits_by_rev_list(ac, av, SHALLOW, NOT_SHALLOW);
+       send_shallow(result);
+       free_commit_list(result);
+       send_unshallow(shallows);
+       packet_flush(1);
+ }
  static void receive_needs(void)
  {
        struct object_array shallows = OBJECT_ARRAY_INIT;
+       struct string_list deepen_not = STRING_LIST_INIT_DUP;
        int depth = 0;
        int has_non_tip = 0;
+       unsigned long deepen_since = 0;
+       int deepen_rev_list = 0;
  
        shallow_nr = 0;
        for (;;) {
                const char *features;
                unsigned char sha1_buf[20];
                char *line = packet_read_line(0, NULL);
+               const char *arg;
                reset_timeout();
                if (!line)
                        break;
  
-               if (starts_with(line, "shallow ")) {
+               if (skip_prefix(line, "shallow ", &arg)) {
                        unsigned char sha1[20];
                        struct object *object;
-                       if (get_sha1_hex(line + 8, sha1))
+                       if (get_sha1_hex(arg, sha1))
                                die("invalid shallow line: %s", line);
                        object = parse_object(sha1);
                        if (!object)
                        }
                        continue;
                }
-               if (starts_with(line, "deepen ")) {
-                       char *end;
-                       depth = strtol(line + 7, &end, 0);
-                       if (end == line + 7 || depth <= 0)
+               if (skip_prefix(line, "deepen ", &arg)) {
+                       char *end = NULL;
+                       depth = strtol(arg, &end, 0);
+                       if (!end || *end || depth <= 0)
                                die("Invalid deepen: %s", line);
                        continue;
                }
-               if (!starts_with(line, "want ") ||
-                   get_sha1_hex(line+5, sha1_buf))
+               if (skip_prefix(line, "deepen-since ", &arg)) {
+                       char *end = NULL;
+                       deepen_since = strtoul(arg, &end, 0);
+                       if (!end || *end || !deepen_since ||
+                           /* revisions.c's max_age -1 is special */
+                           deepen_since == -1)
+                               die("Invalid deepen-since: %s", line);
+                       deepen_rev_list = 1;
+                       continue;
+               }
+               if (skip_prefix(line, "deepen-not ", &arg)) {
+                       char *ref = NULL;
+                       unsigned char sha1[20];
+                       if (expand_ref(arg, strlen(arg), sha1, &ref) != 1)
+                               die("git upload-pack: ambiguous deepen-not: %s", line);
+                       string_list_append(&deepen_not, ref);
+                       free(ref);
+                       deepen_rev_list = 1;
+                       continue;
+               }
+               if (!skip_prefix(line, "want ", &arg) ||
+                   get_sha1_hex(arg, sha1_buf))
                        die("git upload-pack: protocol error, "
                            "expected to get sha, not '%s'", line);
  
-               features = line + 45;
+               features = arg + 40;
  
+               if (parse_feature_request(features, "deepen-relative"))
+                       deepen_relative = 1;
                if (parse_feature_request(features, "multi_ack_detailed"))
                        multi_ack = 2;
                else if (parse_feature_request(features, "multi_ack"))
        if (!use_sideband && daemon_mode)
                no_progress = 1;
  
-       if (depth == 0 && shallows.nr == 0)
+       if (depth == 0 && !deepen_rev_list && shallows.nr == 0)
                return;
-       if (depth > 0) {
-               struct commit_list *result = NULL, *backup = NULL;
+       if (depth > 0 && deepen_rev_list)
+               die("git upload-pack: deepen and deepen-since (or deepen-not) cannot be used together");
+       if (depth > 0)
+               deepen(depth, deepen_relative, &shallows);
+       else if (deepen_rev_list) {
+               struct argv_array av = ARGV_ARRAY_INIT;
                int i;
-               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;
-                       }
-               else
-                       backup = result =
-                               get_shallow_commits(&want_obj, depth,
-                                                   SHALLOW, NOT_SHALLOW);
-               while (result) {
-                       struct object *object = &result->item->object;
-                       if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) {
-                               packet_write(1, "shallow %s",
-                                               oid_to_hex(&object->oid));
-                               register_shallow(object->oid.hash);
-                               shallow_nr++;
+               argv_array_push(&av, "rev-list");
+               if (deepen_since)
+                       argv_array_pushf(&av, "--max-age=%lu", deepen_since);
+               if (deepen_not.nr) {
+                       argv_array_push(&av, "--not");
+                       for (i = 0; i < deepen_not.nr; i++) {
+                               struct string_list_item *s = deepen_not.items + i;
+                               argv_array_push(&av, s->string);
                        }
-                       result = result->next;
+                       argv_array_push(&av, "--not");
                }
-               free_commit_list(backup);
-               for (i = 0; i < shallows.nr; i++) {
-                       struct object *object = shallows.objects[i].item;
-                       if (object->flags & NOT_SHALLOW) {
-                               struct commit_list *parents;
-                               packet_write(1, "unshallow %s",
-                                       oid_to_hex(&object->oid));
-                               object->flags &= ~CLIENT_SHALLOW;
-                               /* make sure the real parents are parsed */
-                               unregister_shallow(object->oid.hash);
-                               object->parsed = 0;
-                               parse_commit_or_die((struct commit *)object);
-                               parents = ((struct commit *)object)->parents;
-                               while (parents) {
-                                       add_object_array(&parents->item->object,
-                                                       NULL, &want_obj);
-                                       parents = parents->next;
-                               }
-                               add_object_array(object, NULL, &extra_edge_obj);
-                       }
-                       /* make sure commit traversal conforms to client */
-                       register_shallow(object->oid.hash);
+               for (i = 0; i < want_obj.nr; i++) {
+                       struct object *o = want_obj.objects[i].item;
+                       argv_array_push(&av, oid_to_hex(&o->oid));
                }
-               packet_flush(1);
-       } else
+               deepen_by_rev_list(av.argc, av.argv, &shallows);
+               argv_array_clear(&av);
+       }
+       else
                if (shallows.nr > 0) {
                        int i;
                        for (i = 0; i < shallows.nr; i++)
@@@ -729,8 -914,8 +919,8 @@@ static int send_ref(const char *refname
                    int flag, void *cb_data)
  {
        static const char *capabilities = "multi_ack thin-pack side-band"
-               " side-band-64k ofs-delta shallow no-progress"
-               " include-tag multi_ack_detailed";
+               " side-band-64k ofs-delta shallow deepen-since deepen-not"
+               " deepen-relative no-progress include-tag multi_ack_detailed";
        const char *refname_nons = strip_namespace(refname);
        struct object_id peeled;
  
@@@ -821,43 -1006,56 +1011,43 @@@ static int upload_pack_config(const cha
                keepalive = git_config_int(var, value);
                if (!keepalive)
                        keepalive = -1;
 +      } else if (current_config_scope() != CONFIG_SCOPE_REPO) {
 +              if (!strcmp("uploadpack.packobjectshook", var))
 +                      return git_config_string(&pack_objects_hook, var, value);
        }
        return parse_hide_refs_config(var, value, "uploadpack");
  }
  
 -int main(int argc, char **argv)
 +int cmd_main(int argc, const char **argv)
  {
 -      char *dir;
 -      int i;
 +      const char *dir;
        int strict = 0;
 -
 -      git_setup_gettext();
 +      struct option options[] = {
 +              OPT_BOOL(0, "stateless-rpc", &stateless_rpc,
 +                       N_("quit after a single request/response exchange")),
 +              OPT_BOOL(0, "advertise-refs", &advertise_refs,
 +                       N_("exit immediately after initial ref advertisement")),
 +              OPT_BOOL(0, "strict", &strict,
 +                       N_("do not try <directory>/.git/ if <directory> is no Git directory")),
 +              OPT_INTEGER(0, "timeout", &timeout,
 +                          N_("interrupt transfer after <n> seconds of inactivity")),
 +              OPT_END()
 +      };
  
        packet_trace_identity("upload-pack");
 -      git_extract_argv0_path(argv[0]);
        check_replace_refs = 0;
  
 -      for (i = 1; i < argc; i++) {
 -              const char *arg = argv[i];
 +      argc = parse_options(argc, argv, NULL, options, upload_pack_usage, 0);
  
 -              if (arg[0] != '-')
 -                      break;
 -              if (!strcmp(arg, "--advertise-refs")) {
 -                      advertise_refs = 1;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "--stateless-rpc")) {
 -                      stateless_rpc = 1;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "--strict")) {
 -                      strict = 1;
 -                      continue;
 -              }
 -              if (skip_prefix(arg, "--timeout=", &arg)) {
 -                      timeout = atoi(arg);
 -                      daemon_mode = 1;
 -                      continue;
 -              }
 -              if (!strcmp(arg, "--")) {
 -                      i++;
 -                      break;
 -              }
 -      }
 +      if (argc != 1)
 +              usage_with_options(upload_pack_usage, options);
  
 -      if (i != argc-1)
 -              usage(upload_pack_usage);
 +      if (timeout)
 +              daemon_mode = 1;
  
        setup_path();
  
 -      dir = argv[i];
 +      dir = argv[0];
  
        if (!enter_repo(dir, strict))
                die("'%s' does not appear to be a git repository", dir);