Merge branch 'bw/transport-protocol-policy'
authorJunio C Hamano <gitster@pobox.com>
Tue, 27 Dec 2016 08:11:41 +0000 (00:11 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 27 Dec 2016 08:11:41 +0000 (00:11 -0800)
Finer-grained control of what protocols are allowed for transports
during clone/fetch/push have been enabled via a new configuration
mechanism.

* bw/transport-protocol-policy:
http: respect protocol.*.allow=user for http-alternates
transport: add from_user parameter to is_transport_allowed
http: create function to get curl allowed protocols
transport: add protocol policy config option
http: always warn if libcurl version is too old
lib-proto-disable: variable name fix

1  2 
Documentation/config.txt
Documentation/git.txt
git-submodule.sh
http.c
t/t5550-http-fetch-dumb.sh
transport.c
transport.h
diff --combined Documentation/config.txt
index d51182a0606aa30168d640d2d821a3ec1bc2458d,50d3d06ffa0a279649b26f15a5aa5768f49d57d3..30cb9461046730eb31355e8996d882ea5cd33537
@@@ -150,34 -150,27 +150,34 @@@ integer:
         1024", "by 1024x1024", etc.
  
  color::
 -       The value for a variables that takes a color is a list of
 -       colors (at most two) and attributes (at most one), separated
 -       by spaces.  The colors accepted are `normal`, `black`,
 -       `red`, `green`, `yellow`, `blue`, `magenta`, `cyan` and
 -       `white`; the attributes are `bold`, `dim`, `ul`, `blink` and
 -       `reverse`.  The first color given is the foreground; the
 -       second is the background.  The position of the attribute, if
 -       any, doesn't matter. Attributes may be turned off specifically
 -       by prefixing them with `no` (e.g., `noreverse`, `noul`, etc).
 -+
 -Colors (foreground and background) may also be given as numbers between
 -0 and 255; these use ANSI 256-color mode (but note that not all
 -terminals may support this).  If your terminal supports it, you may also
 -specify 24-bit RGB values as hex, like `#ff0ab3`.
 -+
 -The attributes are meant to be reset at the beginning of each item
 -in the colored output, so setting color.decorate.branch to `black`
 -will paint that branch name in a plain `black`, even if the previous
 -thing on the same output line (e.g. opening parenthesis before the
 -list of branch names in `log --decorate` output) is set to be
 -painted with `bold` or some other attribute.
 +       The value for a variable that takes a color is a list of
 +       colors (at most two, one for foreground and one for background)
 +       and attributes (as many as you want), separated by spaces.
 ++
 +The basic colors accepted are `normal`, `black`, `red`, `green`, `yellow`,
 +`blue`, `magenta`, `cyan` and `white`.  The first color given is the
 +foreground; the second is the background.
 ++
 +Colors may also be given as numbers between 0 and 255; these use ANSI
 +256-color mode (but note that not all terminals may support this).  If
 +your terminal supports it, you may also specify 24-bit RGB values as
 +hex, like `#ff0ab3`.
 ++
 +The accepted attributes are `bold`, `dim`, `ul`, `blink`, `reverse`,
 +`italic`, and `strike` (for crossed-out or "strikethrough" letters).
 +The position of any attributes with respect to the colors
 +(before, after, or in between), doesn't matter. Specific attributes may
 +be turned off by prefixing them with `no` or `no-` (e.g., `noreverse`,
 +`no-ul`, etc).
 ++
 +For git's pre-defined color slots, the attributes are meant to be reset
 +at the beginning of each item in the colored output. So setting
 +`color.decorate.branch` to `black` will paint that branch name in a
 +plain `black`, even if the previous thing on the same output line (e.g.
 +opening parenthesis before the list of branch names in `log --decorate`
 +output) is set to be painted with `bold` or some other attribute.
 +However, custom log formats may do more complicated and layered
 +coloring, and the negated forms may be useful there.
  
  pathname::
        A variable that takes a pathname value can be given a
@@@ -448,13 -441,6 +448,13 @@@ specify that no proxy be used for a giv
  This is useful for excluding servers inside a firewall from
  proxy use, while defaulting to a common proxy for external domains.
  
 +core.sshCommand::
 +      If this variable is set, `git fetch` and `git push` will
 +      use the specified command instead of `ssh` when they need to
 +      connect to a remote system. The command is in the same form as
 +      the `GIT_SSH_COMMAND` environment variable and is overridden
 +      when the environment variable is set.
 +
  core.ignoreStat::
        If true, Git will avoid using lstat() calls to detect if files have
        changed by setting the "assume-unchanged" bit for those tracked files
@@@ -953,8 -939,7 +953,8 @@@ color.branch:
        A boolean to enable/disable color in the output of
        linkgit:git-branch[1]. May be set to `always`,
        `false` (or `never`) or `auto` (or `true`), in which case colors are used
 -      only when the output is to a terminal. Defaults to false.
 +      only when the output is to a terminal. If unset, then the
 +      value of `color.ui` is used (`auto` by default).
  
  color.branch.<slot>::
        Use customized color for branch coloration. `<slot>` is one of
@@@ -969,8 -954,7 +969,8 @@@ color.diff:
        linkgit:git-log[1], and linkgit:git-show[1] will use color
        for all patches.  If it is set to `true` or `auto`, those
        commands will only use color when output is to the terminal.
 -      Defaults to false.
 +      If unset, then the value of `color.ui` is used (`auto` by
 +      default).
  +
  This does not affect linkgit:git-format-patch[1] or the
  'git-diff-{asterisk}' plumbing commands.  Can be overridden on the
@@@ -993,8 -977,7 +993,8 @@@ color.decorate.<slot>:
  color.grep::
        When set to `always`, always highlight matches.  When `false` (or
        `never`), never.  When set to `true` or `auto`, use color only
 -      when the output is written to the terminal.  Defaults to `false`.
 +      when the output is written to the terminal.  If unset, then the
 +      value of `color.ui` is used (`auto` by default).
  
  color.grep.<slot>::
        Use customized color for grep colorization.  `<slot>` specifies which
@@@ -1027,8 -1010,7 +1027,8 @@@ color.interactive:
        and displays (such as those used by "git-add --interactive" and
        "git-clean --interactive"). When false (or `never`), never.
        When set to `true` or `auto`, use colors only when the output is
 -      to the terminal. Defaults to false.
 +      to the terminal. If unset, then the value of `color.ui` is
 +      used (`auto` by default).
  
  color.interactive.<slot>::
        Use customized color for 'git add --interactive' and 'git clean
@@@ -1044,15 -1026,13 +1044,15 @@@ color.showBranch:
        A boolean to enable/disable color in the output of
        linkgit:git-show-branch[1]. May be set to `always`,
        `false` (or `never`) or `auto` (or `true`), in which case colors are used
 -      only when the output is to a terminal. Defaults to false.
 +      only when the output is to a terminal. If unset, then the
 +      value of `color.ui` is used (`auto` by default).
  
  color.status::
        A boolean to enable/disable color in the output of
        linkgit:git-status[1]. May be set to `always`,
        `false` (or `never`) or `auto` (or `true`), in which case colors are used
 -      only when the output is to a terminal. Defaults to false.
 +      only when the output is to a terminal. If unset, then the
 +      value of `color.ui` is used (`auto` by default).
  
  color.status.<slot>::
        Use customized color for status colorization. `<slot>` is
@@@ -1207,15 -1187,6 +1207,15 @@@ difftool.<tool>.cmd:
  difftool.prompt::
        Prompt before each invocation of the diff tool.
  
 +fastimport.unpackLimit::
 +      If the number of objects imported by linkgit:git-fast-import[1]
 +      is below this limit, then the objects will be unpacked into
 +      loose object files.  However if the number of imported objects
 +      equals or exceeds this limit then the pack will be stored as a
 +      pack.  Storing the pack from a fast-import can make the import
 +      operation complete faster, especially on slow filesystems.  If
 +      not set, the value of `transfer.unpackLimit` is used instead.
 +
  fetch.recurseSubmodules::
        This option can be either set to a boolean value or to 'on-demand'.
        Setting it to a boolean changes the behavior of fetch and pull to
@@@ -1247,11 -1218,6 +1247,11 @@@ fetch.prune:
        If true, fetch will automatically behave as if the `--prune`
        option was given on the command line.  See also `remote.<name>.prune`.
  
 +fetch.output::
 +      Control how ref update status is printed. Valid values are
 +      `full` and `compact`. Default value is `full`. See section
 +      OUTPUT in linkgit:git-fetch[1] for detail.
 +
  format.attach::
        Enable multipart/mixed attachments as the default for
        'format-patch'.  The value can also be a double quoted string
        value as the boundary.  See the --attach option in
        linkgit:git-format-patch[1].
  
 +format.from::
 +      Provides the default value for the `--from` option to format-patch.
 +      Accepts a boolean value, or a name and email address.  If false,
 +      format-patch defaults to `--no-from`, using commit authors directly in
 +      the "From:" field of patch mails.  If true, format-patch defaults to
 +      `--from`, using your committer identity in the "From:" field of patch
 +      mails and including a "From:" field in the body of the patch mail if
 +      different.  If set to a non-boolean value, format-patch uses that
 +      value instead of your committer identity.  Defaults to false.
 +
  format.numbered::
        A boolean which can enable or disable sequence numbers in patch
        subjects.  It defaults to "auto" which enables it only if there
@@@ -1372,7 -1328,7 +1372,7 @@@ fsck.skipList:
  gc.aggressiveDepth::
        The depth parameter used in the delta compression
        algorithm used by 'git gc --aggressive'.  This defaults
 -      to 250.
 +      to 50.
  
  gc.aggressiveWindow::
        The window size parameter used in the delta compression
@@@ -1736,20 -1692,6 +1736,20 @@@ http.emptyAuth:
        a username in the URL, as libcurl normally requires a username for
        authentication.
  
 +http.delegation::
 +      Control GSSAPI credential delegation. The delegation is disabled
 +      by default in libcurl since version 7.21.7. Set parameter to tell
 +      the server what it is allowed to delegate when it comes to user
 +      credentials. Used with GSS/kerberos. Possible values are:
 ++
 +--
 +* `none` - Don't allow any delegation.
 +* `policy` - Delegates if and only if the OK-AS-DELEGATE flag is set in the
 +  Kerberos service ticket, which is a matter of realm policy.
 +* `always` - Unconditionally allow the server to delegate.
 +--
 +
 +
  http.extraHeader::
        Pass an additional HTTP header when communicating with a server.  If
        more than one such entry exists, all of them are added as extra
@@@ -2318,6 -2260,52 +2318,52 @@@ pretty.<name>:
        Note that an alias with the same name as a built-in format
        will be silently ignored.
  
+ protocol.allow::
+       If set, provide a user defined default policy for all protocols which
+       don't explicitly have a policy (`protocol.<name>.allow`).  By default,
+       if unset, known-safe protocols (http, https, git, ssh, file) have a
+       default policy of `always`, known-dangerous protocols (ext) have a
+       default policy of `never`, and all other protocols have a default
+       policy of `user`.  Supported policies:
+ +
+ --
+ * `always` - protocol is always able to be used.
+ * `never` - protocol is never able to be used.
+ * `user` - protocol is only able to be used when `GIT_PROTOCOL_FROM_USER` is
+   either unset or has a value of 1.  This policy should be used when you want a
+   protocol to be directly usable by the user but don't want it used by commands which
+   execute clone/fetch/push commands without user input, e.g. recursive
+   submodule initialization.
+ --
+ protocol.<name>.allow::
+       Set a policy to be used by protocol `<name>` with clone/fetch/push
+       commands. See `protocol.allow` above for the available policies.
+ +
+ The protocol names currently used by git are:
+ +
+ --
+   - `file`: any local file-based path (including `file://` URLs,
+     or local paths)
+   - `git`: the anonymous git protocol over a direct TCP
+     connection (or proxy, if configured)
+   - `ssh`: git over ssh (including `host:path` syntax,
+     `ssh://`, etc).
+   - `http`: git over http, both "smart http" and "dumb http".
+     Note that this does _not_ include `https`; if you want to configure
+     both, you must do so individually.
+   - any external helpers are named by their protocol (e.g., use
+     `hg` to allow the `git-remote-hg` helper)
+ --
  pull.ff::
        By default, Git does not create an extra merge commit when merging
        a commit that is a descendant of the current commit. Instead, the
@@@ -2460,20 -2448,15 +2506,20 @@@ rebase.missingCommitsCheck:
        command in the todo-list.
        Defaults to "ignore".
  
 -rebase.instructionFormat
 +rebase.instructionFormat::
        A format string, as specified in linkgit:git-log[1], to be used for
        the instruction list during an interactive rebase.  The format will automatically
        have the long commit hash prepended to the format.
  
  receive.advertiseAtomic::
        By default, git-receive-pack will advertise the atomic push
 -      capability to its clients. If you don't want to this capability
 -      to be advertised, set this variable to false.
 +      capability to its clients. If you don't want to advertise this
 +      capability, set this variable to false.
 +
 +receive.advertisePushOptions::
 +      By default, git-receive-pack will advertise the push options
 +      capability to its clients. If you don't want to advertise this
 +      capability, set this variable to false.
  
  receive.autogc::
        By default, git-receive-pack will run "git-gc --auto" after
@@@ -2528,15 -2511,6 +2574,15 @@@ receive.fsck.skipList:
        can be safely ignored such as invalid committer email addresses.
        Note: corrupt objects cannot be skipped with this setting.
  
 +receive.keepAlive::
 +      After receiving the pack from the client, `receive-pack` may
 +      produce no output (if `--quiet` was specified) while processing
 +      the pack, causing some networks to drop the TCP connection.
 +      With this option set, if `receive-pack` does not transmit
 +      any data in this phase for `receive.keepAlive` seconds, it will
 +      send a short keepalive packet.  The default is 5 seconds; set
 +      to 0 to disable keepalives entirely.
 +
  receive.unpackLimit::
        If the number of objects received in a push is below this
        limit then the objects will be unpacked into loose object
        especially on slow filesystems.  If not set, the value of
        `transfer.unpackLimit` is used instead.
  
 +receive.maxInputSize::
 +      If the size of the incoming pack stream is larger than this
 +      limit, then git-receive-pack will error out, instead of
 +      accepting the pack file. If not set or set to 0, then the size
 +      is unlimited.
 +
  receive.denyDeletes::
        If set to true, git-receive-pack will deny a ref update that deletes
        the ref. Use this to prevent such a ref deletion via a push.
@@@ -2835,13 -2803,12 +2881,13 @@@ stash.showStat:
        option will show diffstat of the stash.  Defaults to true.
        See description of 'show' command in linkgit:git-stash[1].
  
 -submodule.<name>.path::
  submodule.<name>.url::
 -      The path within this project and URL for a submodule. These
 -      variables are initially populated by 'git submodule init'. See
 -      linkgit:git-submodule[1] and linkgit:gitmodules[5] for
 -      details.
 +      The URL for a submodule. This variable is copied from the .gitmodules
 +      file to the git config via 'git submodule init'. The user can change
 +      the configured URL before obtaining the submodule via 'git submodule
 +      update'. After obtaining the submodule, the presence of this variable
 +      is used as a sign whether the submodule is of interest to git commands.
 +      See linkgit:git-submodule[1] and linkgit:gitmodules[5] for details.
  
  submodule.<name>.update::
        The default update procedure for a submodule. This variable
@@@ -2884,18 -2851,6 +2930,18 @@@ submodule.fetchJobs:
        in parallel. A value of 0 will give some reasonable default.
        If unset, it defaults to 1.
  
 +submodule.alternateLocation::
 +      Specifies how the submodules obtain alternates when submodules are
 +      cloned. Possible values are `no`, `superproject`.
 +      By default `no` is assumed, which doesn't add references. When the
 +      value is set to `superproject` the submodule to be cloned computes
 +      its alternates location relative to the superprojects alternate.
 +
 +submodule.alternateErrorStrategy
 +      Specifies how to treat errors with the alternates for a submodule
 +      as computed via `submodule.alternateLocation`. Possible values are
 +      `ignore`, `info`, `die`. Default is `die`.
 +
  tag.forceSignAnnotated::
        A boolean to specify whether annotated tags created should be GPG signed.
        If `--annotate` is specified on the command line, it takes
@@@ -2982,21 -2937,6 +3028,21 @@@ uploadpack.keepAlive:
        `uploadpack.keepAlive` seconds. Setting this option to 0
        disables keepalive packets entirely. The default is 5 seconds.
  
 +uploadpack.packObjectsHook::
 +      If this option is set, when `upload-pack` would run
 +      `git pack-objects` to create a packfile for a client, it will
 +      run this shell command instead.  The `pack-objects` command and
 +      arguments it _would_ have run (including the `git pack-objects`
 +      at the beginning) are appended to the shell command. The stdin
 +      and stdout of the hook are treated as if `pack-objects` itself
 +      was run. I.e., `upload-pack` will feed input intended for
 +      `pack-objects` to the hook, and expects a completed packfile on
 +      stdout.
 ++
 +Note that this configuration variable is ignored if it is seen in the
 +repository-level config (this is a safety measure against fetching from
 +untrusted repositories).
 +
  url.<base>.insteadOf::
        Any URL that starts with this value will be rewritten to
        start, instead, with <base>. In cases where some site serves a
diff --combined Documentation/git.txt
index 98033302bb97ef802f7050e02a1727a878bb79aa,d9fb937586c8dfe58348aa4d3cdb8b23d7b7fc12..ba222f68cc1a9642de326e816d5b7a8831afb82a
@@@ -13,7 -13,6 +13,7 @@@ SYNOPSI
      [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
      [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
      [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
 +    [--super-prefix=<path>]
      <command> [<args>]
  
  DESCRIPTION
@@@ -44,18 -43,6 +44,18 @@@ unreleased) version of Git, that is ava
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
 +* link:v2.11.0/git.html[documentation for release 2.11]
 +
 +* release notes for
 +  link:RelNotes/2.11.0.txt[2.11].
 +
 +* link:v2.10.2/git.html[documentation for release 2.10.2]
 +
 +* release notes for
 +  link:RelNotes/2.10.2.txt[2.10.2],
 +  link:RelNotes/2.10.1.txt[2.10.1],
 +  link:RelNotes/2.10.0.txt[2.10].
 +
  * link:v2.9.3/git.html[documentation for release 2.9.3]
  
  * release notes for
@@@ -609,11 -596,6 +609,11 @@@ foo.bar= ...`) sets `foo.bar` to the em
        details.  Equivalent to setting the `GIT_NAMESPACE` environment
        variable.
  
 +--super-prefix=<path>::
 +      Currently for internal use only.  Set a prefix which gives a path from
 +      above a repository down to its root.  One use is to give submodules
 +      context about the superproject that invoked it.
 +
  --bare::
        Treat the repository as a bare repository.  If GIT_DIR
        environment is not set, it is set to the current working
@@@ -871,12 -853,6 +871,12 @@@ Git so take care if using a foreign fro
        specifies a ":" separated (on Windows ";" separated) list
        of Git object directories which can be used to search for Git
        objects. New objects will not be written to these directories.
 ++
 +      Entries that begin with `"` (double-quote) will be interpreted
 +      as C-style quoted paths, removing leading and trailing
 +      double-quotes and respecting backslash escapes. E.g., the value
 +      `"path-with-\"-and-:-in-it":vanilla-path` has two paths:
 +      `path-with-"-and-:-in-it` and `vanilla-path`.
  
  `GIT_DIR`::
        If the `GIT_DIR` environment variable is set then it
@@@ -1110,14 -1086,6 +1110,14 @@@ of clones and fetches
        cloning of shallow repositories.
        See `GIT_TRACE` for available trace output options.
  
 +`GIT_TRACE_CURL`::
 +      Enables a curl full trace dump of all incoming and outgoing data,
 +      including descriptive information, of the git transport protocol.
 +      This is similar to doing curl `--trace-ascii` on the command line.
 +      This option overrides setting the `GIT_CURL_VERBOSE` environment
 +      variable.
 +      See `GIT_TRACE` for available trace output options.
 +
  `GIT_LITERAL_PATHSPECS`::
        Setting this variable to `1` will cause Git to treat all
        pathspecs literally, rather than as glob patterns. For example,
        cloning a repository to make a backup).
  
  `GIT_ALLOW_PROTOCOL`::
-       If set, provide a colon-separated list of protocols which are
-       allowed to be used with fetch/push/clone. This is useful to
-       restrict recursive submodule initialization from an untrusted
-       repository. Any protocol not mentioned will be disallowed (i.e.,
-       this is a whitelist, not a blacklist). If the variable is not
-       set at all, all protocols are enabled.  The protocol names
-       currently used by git are:
-         - `file`: any local file-based path (including `file://` URLs,
-           or local paths)
-         - `git`: the anonymous git protocol over a direct TCP
-           connection (or proxy, if configured)
-         - `ssh`: git over ssh (including `host:path` syntax,
-           `ssh://`, etc).
-         - `http`: git over http, both "smart http" and "dumb http".
-           Note that this does _not_ include `https`; if you want both,
-           you should specify both as `http:https`.
-         - any external helpers are named by their protocol (e.g., use
-           `hg` to allow the `git-remote-hg` helper)
+       If set to a colon-separated list of protocols, behave as if
+       `protocol.allow` is set to `never`, and each of the listed
+       protocols has `protocol.<name>.allow` set to `always`
+       (overriding any existing configuration). In other words, any
+       protocol not mentioned will be disallowed (i.e., this is a
+       whitelist, not a blacklist). See the description of
+       `protocol.allow` in linkgit:git-config[1] for more details.
+ `GIT_PROTOCOL_FROM_USER`::
+       Set to 0 to prevent protocols used by fetch/push/clone which are
+       configured to the `user` state.  This is useful to restrict recursive
+       submodule initialization from an untrusted repository or for programs
+       which feed potentially-untrusted URLS to git commands.  See
+       linkgit:git-config[1] for more details.
  
  Discussion[[Discussion]]
  ------------------------
diff --combined git-submodule.sh
index a024a135d6663c8a8d5ceb926e3f42df1f577411,fc440761af944bdbb8c6a768aec2fe9cdfffc3a4..0a477b4c97fbe1764f3a631de75fba022da34d5e
@@@ -9,26 -9,23 +9,22 @@@ USAGE="[--quiet] add [-b <branch>] [-f|
     or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
     or: $dashless [--quiet] init [--] [<path>...]
     or: $dashless [--quiet] deinit [-f|--force] (--all| [--] <path>...)
 -   or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--reference <repository>] [--recursive] [--] [<path>...]
 +   or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--] [<path>...]
     or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
     or: $dashless [--quiet] foreach [--recursive] <command>
     or: $dashless [--quiet] sync [--recursive] [--] [<path>...]"
  OPTIONS_SPEC=
  SUBDIRECTORY_OK=Yes
  . git-sh-setup
 -. git-sh-i18n
  . git-parse-remote
  require_work_tree
  wt_prefix=$(git rev-parse --show-prefix)
  cd_to_toplevel
  
- # Restrict ourselves to a vanilla subset of protocols; the URLs
- # we get are under control of a remote repository, and we do not
- # want them kicking off arbitrary git-remote-* programs.
- #
- # If the user has already specified a set of allowed protocols,
- # we assume they know what they're doing and use that instead.
- : ${GIT_ALLOW_PROTOCOL=file:git:http:https:ssh}
- export GIT_ALLOW_PROTOCOL
+ # Tell the rest of git that any URLs we get don't come
+ # directly from the user, so it can apply policy as appropriate.
+ GIT_PROTOCOL_FROM_USER=0
+ export GIT_PROTOCOL_FROM_USER
  
  command=
  branch=
@@@ -44,13 -41,12 +40,13 @@@ update
  prefix=
  custom_name=
  depth=
 +progress=
  
  die_if_unmatched ()
  {
        if test "$1" = "#unmatched"
        then
 -              exit 1
 +              exit ${2:-1}
        fi
  }
  
@@@ -240,15 -236,14 +236,15 @@@ Use -f if you really want to add it." >
                then
                        if test -z "$force"
                        then
 -                              echo >&2 "$(eval_gettext "A git directory for '\$sm_name' is found locally with remote(s):")"
 +                              eval_gettextln >&2 "A git directory for '\$sm_name' is found locally with remote(s):"
                                GIT_DIR=".git/modules/$sm_name" GIT_WORK_TREE=. git remote -v | grep '(fetch)' | sed -e s,^,"  ", -e s,' (fetch)',, >&2
 -                              echo >&2 "$(eval_gettext "If you want to reuse this local git directory instead of cloning again from")"
 -                              echo >&2 "  $realrepo"
 -                              echo >&2 "$(eval_gettext "use the '--force' option. If the local git directory is not the correct repo")"
 -                              die "$(eval_gettext "or you are unsure what this means choose another name with the '--name' option.")"
 +                              die "$(eval_gettextln "\
 +If you want to reuse this local git directory instead of cloning again from
 +  \$realrepo
 +use the '--force' option. If the local git directory is not the correct repo
 +or you are unsure what this means choose another name with the '--name' option.")"
                        else
 -                              echo "$(eval_gettext "Reactivating local git directory for submodule '\$sm_name'.")"
 +                              eval_gettextln "Reactivating local git directory for submodule '\$sm_name'."
                        fi
                fi
                git submodule--helper clone ${GIT_QUIET:+--quiet} --prefix "$wt_prefix" --path "$sm_path" --name "$sm_name" --url "$realrepo" ${reference:+"$reference"} ${depth:+"$depth"} || exit
@@@ -313,11 -308,11 +309,11 @@@ cmd_foreach(
  
        {
                git submodule--helper list --prefix "$wt_prefix" ||
 -              echo "#unmatched"
 +              echo "#unmatched" $?
        } |
        while read mode sha1 stage sm_path
        do
 -              die_if_unmatched "$mode"
 +              die_if_unmatched "$mode" "$sha1"
                if test -e "$sm_path"/.git
                then
                        displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
@@@ -422,11 -417,11 +418,11 @@@ cmd_deinit(
  
        {
                git submodule--helper list --prefix "$wt_prefix" "$@" ||
 -              echo "#unmatched"
 +              echo "#unmatched" $?
        } |
        while read mode sha1 stage sm_path
        do
 -              die_if_unmatched "$mode"
 +              die_if_unmatched "$mode" "$sha1"
                name=$(git submodule--helper name "$sm_path") || exit
  
                displaypath=$(git submodule--helper relative-path "$sm_path" "$wt_prefix")
                        # Protect submodules containing a .git directory
                        if test -d "$sm_path/.git"
                        then
 -                              echo >&2 "$(eval_gettext "Submodule work tree '\$displaypath' contains a .git directory")"
 -                              die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")"
 +                              die "$(eval_gettext "\
 +Submodule work tree '\$displaypath' contains a .git directory
 +(use 'rm -rf' if you really want to remove it including all of its history)")"
                        fi
  
                        if test -z "$force"
@@@ -480,8 -474,7 +476,8 @@@ fetch_in_submodule () 
        '')
                git fetch ;;
        *)
 -              git fetch $(get_default_remote) "$2" ;;
 +              shift
 +              git fetch $(get_default_remote) "$@" ;;
        esac
  )
  
@@@ -499,9 -492,6 +495,9 @@@ cmd_update(
                -q|--quiet)
                        GIT_QUIET=1
                        ;;
 +              --progress)
 +                      progress="--progress"
 +                      ;;
                -i|--init)
                        init=1
                        ;;
                --checkout)
                        update="checkout"
                        ;;
 +              --recommend-shallow)
 +                      recommend_shallow="--recommend-shallow"
 +                      ;;
 +              --no-recommend-shallow)
 +                      recommend_shallow="--no-recommend-shallow"
 +                      ;;
                --depth)
                        case "$2" in '') usage ;; esac
                        depth="--depth=$2"
  
        {
        git submodule--helper update-clone ${GIT_QUIET:+--quiet} \
 +              ${progress:+"$progress"} \
                ${wt_prefix:+--prefix "$wt_prefix"} \
                ${prefix:+--recursive-prefix "$prefix"} \
                ${update:+--update "$update"} \
 -              ${reference:+--reference "$reference"} \
 +              ${reference:+"$reference"} \
                ${depth:+--depth "$depth"} \
 +              ${recommend_shallow:+"$recommend_shallow"} \
                ${jobs:+$jobs} \
 -              "$@" || echo "#unmatched"
 +              "$@" || echo "#unmatched" $?
        } | {
        err=
        while read mode sha1 stage just_cloned sm_path
        do
 -              die_if_unmatched "$mode"
 +              die_if_unmatched "$mode" "$sha1"
  
                name=$(git submodule--helper name "$sm_path") || exit
                url=$(git config submodule."$name".url)
 -              branch=$(get_submodule_config "$name" branch master)
                if ! test -z "$update"
                then
                        update_module=$update
  
                if test -n "$remote"
                then
 +                      branch=$(git submodule--helper remote-branch "$sm_path")
                        if test -z "$nofetch"
                        then
                                # Fetch remote before determining tracking $sha1
 -                              (sanitize_submodule_env; cd "$sm_path" && git-fetch) ||
 +                              fetch_in_submodule "$sm_path" $depth ||
                                die "$(eval_gettext "Unable to fetch in submodule path '\$sm_path'")"
                        fi
                        remote_name=$(sanitize_submodule_env; cd "$sm_path" && get_default_remote)
                        sha1=$(sanitize_submodule_env; cd "$sm_path" &&
                                git rev-parse --verify "${remote_name}/${branch}") ||
 -                      die "$(eval_gettext "Unable to find current ${remote_name}/${branch} revision in submodule path '\$sm_path'")"
 +                      die "$(eval_gettext "Unable to find current \${remote_name}/\${branch} revision in submodule path '\$sm_path'")"
                fi
  
                if test "$subsha1" != "$sha1" || test -n "$force"
                                # Run fetch only if $sha1 isn't present or it
                                # is not reachable from a ref.
                                is_tip_reachable "$sm_path" "$sha1" ||
 -                              fetch_in_submodule "$sm_path" ||
 +                              fetch_in_submodule "$sm_path" $depth ||
                                die "$(eval_gettext "Unable to fetch in submodule path '\$displaypath'")"
  
                                # Now we tried the usual fetch, but $sha1 may
                                # not be reachable from any of the refs
                                is_tip_reachable "$sm_path" "$sha1" ||
 -                              fetch_in_submodule "$sm_path" "$sha1" ||
 -                              die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain $sha1. Direct fetching of that commit failed.")"
 +                              fetch_in_submodule "$sm_path" $depth "$sha1" ||
 +                              die "$(eval_gettext "Fetched in submodule path '\$displaypath', but it did not contain \$sha1. Direct fetching of that commit failed.")"
                        fi
  
                        must_die_on_failure=
                        if test $res -gt 0
                        then
                                die_msg="$(eval_gettext "Failed to recurse into submodule path '\$displaypath'")"
 -                              if test $res -eq 1
 +                              if test $res -ne 2
                                then
                                        err="${err};$die_msg"
                                        continue
@@@ -998,11 -980,11 +994,11 @@@ cmd_status(
  
        {
                git submodule--helper list --prefix "$wt_prefix" "$@" ||
 -              echo "#unmatched"
 +              echo "#unmatched" $?
        } |
        while read mode sha1 stage sm_path
        do
 -              die_if_unmatched "$mode"
 +              die_if_unmatched "$mode" "$sha1"
                name=$(git submodule--helper name "$sm_path") || exit
                url=$(git config submodule."$name".url)
                displaypath=$(git submodule--helper relative-path "$prefix$sm_path" "$wt_prefix")
@@@ -1079,11 -1061,11 +1075,11 @@@ cmd_sync(
        cd_to_toplevel
        {
                git submodule--helper list --prefix "$wt_prefix" "$@" ||
 -              echo "#unmatched"
 +              echo "#unmatched" $?
        } |
        while read mode sha1 stage sm_path
        do
 -              die_if_unmatched "$mode"
 +              die_if_unmatched "$mode" "$sha1"
                name=$(git submodule--helper name "$sm_path")
                url=$(git config -f .gitmodules --get submodule."$name".url)
  
diff --combined http.c
index 051fe6e5ab77a5dc6e53dce7011d0fc445f15ab0,2208269b33ae11c8b6371309d961b065e9fe5e05..90a1c0f1131c4a5fcbc50d6cc81c1be9cef3cd71
--- 1/http.c
--- 2/http.c
+++ b/http.c
@@@ -11,7 -11,6 +11,7 @@@
  #include "gettext.h"
  #include "transport.h"
  
 +static struct trace_key trace_curl = TRACE_KEY_INIT(CURL);
  #if LIBCURL_VERSION_NUM >= 0x070a08
  long int git_curl_ipresolve = CURL_IPRESOLVE_WHATEVER;
  #else
@@@ -90,18 -89,6 +90,18 @@@ static struct 
         * here, too
         */
  };
 +#if LIBCURL_VERSION_NUM >= 0x071600
 +static const char *curl_deleg;
 +static struct {
 +      const char *name;
 +      long curl_deleg_param;
 +} curl_deleg_levels[] = {
 +      { "none", CURLGSSAPI_DELEGATION_NONE },
 +      { "policy", CURLGSSAPI_DELEGATION_POLICY_FLAG },
 +      { "always", CURLGSSAPI_DELEGATION_FLAG },
 +};
 +#endif
 +
  static struct credential proxy_auth = CREDENTIAL_INIT;
  static const char *curl_proxyuserpwd;
  static const char *curl_cookie_file;
@@@ -215,13 -202,6 +215,13 @@@ static void finish_active_slot(struct a
                slot->callback_func(slot->callback_data);
  }
  
 +static void xmulti_remove_handle(struct active_request_slot *slot)
 +{
 +#ifdef USE_CURL_MULTI
 +      curl_multi_remove_handle(curlm, slot->curl);
 +#endif
 +}
 +
  #ifdef USE_CURL_MULTI
  static void process_curl_messages(void)
  {
                               slot->curl != curl_message->easy_handle)
                                slot = slot->next;
                        if (slot != NULL) {
 -                              curl_multi_remove_handle(curlm, slot->curl);
 +                              xmulti_remove_handle(slot);
                                slot->curl_result = curl_result;
                                finish_active_slot(slot);
                        } else {
@@@ -337,15 -317,6 +337,15 @@@ static int http_options(const char *var
                return 0;
        }
  
 +      if (!strcmp("http.delegation", var)) {
 +#if LIBCURL_VERSION_NUM >= 0x071600
 +              return git_config_string(&curl_deleg, var, value);
 +#else
 +              warning(_("Delegation control is not supported with cURL < 7.22.0"));
 +              return 0;
 +#endif
 +      }
 +
        if (!strcmp("http.pinnedpubkey", var)) {
  #if LIBCURL_VERSION_NUM >= 0x072c00
                return git_config_pathname(&ssl_pinnedkey, var, value);
  
  static void init_curl_http_auth(CURL *result)
  {
 -      if (!http_auth.username) {
 +      if (!http_auth.username || !*http_auth.username) {
                if (curl_empty_auth)
                        curl_easy_setopt(result, CURLOPT_USERPWD, ":");
                return;
@@@ -518,129 -489,25 +518,143 @@@ static void set_curl_keepalive(CURL *c
  }
  #endif
  
 +static void redact_sensitive_header(struct strbuf *header)
 +{
 +      const char *sensitive_header;
 +
 +      if (skip_prefix(header->buf, "Authorization:", &sensitive_header) ||
 +          skip_prefix(header->buf, "Proxy-Authorization:", &sensitive_header)) {
 +              /* The first token is the type, which is OK to log */
 +              while (isspace(*sensitive_header))
 +                      sensitive_header++;
 +              while (*sensitive_header && !isspace(*sensitive_header))
 +                      sensitive_header++;
 +              /* Everything else is opaque and possibly sensitive */
 +              strbuf_setlen(header,  sensitive_header - header->buf);
 +              strbuf_addstr(header, " <redacted>");
 +      }
 +}
 +
 +static void curl_dump_header(const char *text, unsigned char *ptr, size_t size, int hide_sensitive_header)
 +{
 +      struct strbuf out = STRBUF_INIT;
 +      struct strbuf **headers, **header;
 +
 +      strbuf_addf(&out, "%s, %10.10ld bytes (0x%8.8lx)\n",
 +              text, (long)size, (long)size);
 +      trace_strbuf(&trace_curl, &out);
 +      strbuf_reset(&out);
 +      strbuf_add(&out, ptr, size);
 +      headers = strbuf_split_max(&out, '\n', 0);
 +
 +      for (header = headers; *header; header++) {
 +              if (hide_sensitive_header)
 +                      redact_sensitive_header(*header);
 +              strbuf_insert((*header), 0, text, strlen(text));
 +              strbuf_insert((*header), strlen(text), ": ", 2);
 +              strbuf_rtrim((*header));
 +              strbuf_addch((*header), '\n');
 +              trace_strbuf(&trace_curl, (*header));
 +      }
 +      strbuf_list_free(headers);
 +      strbuf_release(&out);
 +}
 +
 +static void curl_dump_data(const char *text, unsigned char *ptr, size_t size)
 +{
 +      size_t i;
 +      struct strbuf out = STRBUF_INIT;
 +      unsigned int width = 60;
 +
 +      strbuf_addf(&out, "%s, %10.10ld bytes (0x%8.8lx)\n",
 +              text, (long)size, (long)size);
 +      trace_strbuf(&trace_curl, &out);
 +
 +      for (i = 0; i < size; i += width) {
 +              size_t w;
 +
 +              strbuf_reset(&out);
 +              strbuf_addf(&out, "%s: ", text);
 +              for (w = 0; (w < width) && (i + w < size); w++) {
 +                      unsigned char ch = ptr[i + w];
 +
 +                      strbuf_addch(&out,
 +                                     (ch >= 0x20) && (ch < 0x80)
 +                                     ? ch : '.');
 +              }
 +              strbuf_addch(&out, '\n');
 +              trace_strbuf(&trace_curl, &out);
 +      }
 +      strbuf_release(&out);
 +}
 +
 +static int curl_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *userp)
 +{
 +      const char *text;
 +      enum { NO_FILTER = 0, DO_FILTER = 1 };
 +
 +      switch (type) {
 +      case CURLINFO_TEXT:
 +              trace_printf_key(&trace_curl, "== Info: %s", data);
 +      default:                /* we ignore unknown types by default */
 +              return 0;
 +
 +      case CURLINFO_HEADER_OUT:
 +              text = "=> Send header";
 +              curl_dump_header(text, (unsigned char *)data, size, DO_FILTER);
 +              break;
 +      case CURLINFO_DATA_OUT:
 +              text = "=> Send data";
 +              curl_dump_data(text, (unsigned char *)data, size);
 +              break;
 +      case CURLINFO_SSL_DATA_OUT:
 +              text = "=> Send SSL data";
 +              curl_dump_data(text, (unsigned char *)data, size);
 +              break;
 +      case CURLINFO_HEADER_IN:
 +              text = "<= Recv header";
 +              curl_dump_header(text, (unsigned char *)data, size, NO_FILTER);
 +              break;
 +      case CURLINFO_DATA_IN:
 +              text = "<= Recv data";
 +              curl_dump_data(text, (unsigned char *)data, size);
 +              break;
 +      case CURLINFO_SSL_DATA_IN:
 +              text = "<= Recv SSL data";
 +              curl_dump_data(text, (unsigned char *)data, size);
 +              break;
 +      }
 +      return 0;
 +}
 +
 +void setup_curl_trace(CURL *handle)
 +{
 +      if (!trace_want(&trace_curl))
 +              return;
 +      curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L);
 +      curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, curl_trace);
 +      curl_easy_setopt(handle, CURLOPT_DEBUGDATA, NULL);
 +}
 +
+ static long get_curl_allowed_protocols(int from_user)
+ {
+       long allowed_protocols = 0;
+       if (is_transport_allowed("http", from_user))
+               allowed_protocols |= CURLPROTO_HTTP;
+       if (is_transport_allowed("https", from_user))
+               allowed_protocols |= CURLPROTO_HTTPS;
+       if (is_transport_allowed("ftp", from_user))
+               allowed_protocols |= CURLPROTO_FTP;
+       if (is_transport_allowed("ftps", from_user))
+               allowed_protocols |= CURLPROTO_FTPS;
+       return allowed_protocols;
+ }
  
  static CURL *get_curl_handle(void)
  {
        CURL *result = curl_easy_init();
-       long allowed_protocols = 0;
  
        if (!result)
                die("curl_easy_init failed");
        curl_easy_setopt(result, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
  #endif
  
 +#if LIBCURL_VERSION_NUM >= 0x071600
 +      if (curl_deleg) {
 +              int i;
 +              for (i = 0; i < ARRAY_SIZE(curl_deleg_levels); i++) {
 +                      if (!strcmp(curl_deleg, curl_deleg_levels[i].name)) {
 +                              curl_easy_setopt(result, CURLOPT_GSSAPI_DELEGATION,
 +                                              curl_deleg_levels[i].curl_deleg_param);
 +                              break;
 +                      }
 +              }
 +              if (i == ARRAY_SIZE(curl_deleg_levels))
 +                      warning("Unknown delegation method '%s': using default",
 +                              curl_deleg);
 +      }
 +#endif
 +
        if (http_proactive_auth)
                init_curl_http_auth(result);
  
        curl_easy_setopt(result, CURLOPT_POST301, 1);
  #endif
  #if LIBCURL_VERSION_NUM >= 0x071304
-       if (is_transport_allowed("http"))
-               allowed_protocols |= CURLPROTO_HTTP;
-       if (is_transport_allowed("https"))
-               allowed_protocols |= CURLPROTO_HTTPS;
-       if (is_transport_allowed("ftp"))
-               allowed_protocols |= CURLPROTO_FTP;
-       if (is_transport_allowed("ftps"))
-               allowed_protocols |= CURLPROTO_FTPS;
-       curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols);
-       curl_easy_setopt(result, CURLOPT_PROTOCOLS, allowed_protocols);
+       curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS,
+                        get_curl_allowed_protocols(0));
+       curl_easy_setopt(result, CURLOPT_PROTOCOLS,
+                        get_curl_allowed_protocols(-1));
  #else
-       if (transport_restrict_protocols())
-               warning("protocol restrictions not applied to curl redirects because\n"
-                       "your curl version is too old (>= 7.19.4)");
+       warning("protocol restrictions not applied to curl redirects because\n"
+               "your curl version is too old (>= 7.19.4)");
  #endif
 -
        if (getenv("GIT_CURL_VERBOSE"))
 -              curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
 +              curl_easy_setopt(result, CURLOPT_VERBOSE, 1L);
 +      setup_curl_trace(result);
  
        curl_easy_setopt(result, CURLOPT_USERAGENT,
                user_agent ? user_agent : git_user_agent());
         * precedence here, as in CURL.
         */
        if (!curl_http_proxy) {
 -              if (!strcmp(http_auth.protocol, "https")) {
 +              if (http_auth.protocol && !strcmp(http_auth.protocol, "https")) {
                        var_override(&curl_http_proxy, getenv("HTTPS_PROXY"));
                        var_override(&curl_http_proxy, getenv("https_proxy"));
                } else {
@@@ -937,7 -781,9 +944,7 @@@ void http_cleanup(void
        while (slot != NULL) {
                struct active_request_slot *next = slot->next;
                if (slot->curl != NULL) {
 -#ifdef USE_CURL_MULTI
 -                      curl_multi_remove_handle(curlm, slot->curl);
 -#endif
 +                      xmulti_remove_handle(slot);
                        curl_easy_cleanup(slot->curl);
                }
                free(slot);
@@@ -1086,8 -932,6 +1093,8 @@@ int start_active_slot(struct active_req
  
        if (curlm_result != CURLM_OK &&
            curlm_result != CURLM_CALL_MULTI_PERFORM) {
 +              warning("curl_multi_add_handle failed: %s",
 +                      curl_multi_strerror(curlm_result));
                active_requests--;
                slot->in_use = 0;
                return 0;
@@@ -1227,13 -1071,13 +1234,13 @@@ void run_active_slot(struct active_requ
  static void release_active_slot(struct active_request_slot *slot)
  {
        closedown_active_slot(slot);
 -      if (slot->curl && curl_session_count > min_curl_sessions) {
 -#ifdef USE_CURL_MULTI
 -              curl_multi_remove_handle(curlm, slot->curl);
 -#endif
 -              curl_easy_cleanup(slot->curl);
 -              slot->curl = NULL;
 -              curl_session_count--;
 +      if (slot->curl) {
 +              xmulti_remove_handle(slot);
 +              if (curl_session_count > min_curl_sessions) {
 +                      curl_easy_cleanup(slot->curl);
 +                      slot->curl = NULL;
 +                      curl_session_count--;
 +              }
        }
  #ifdef USE_CURL_MULTI
        fill_active_slots();
index 264a1ab8b0ea794ce398d9d5c3a40adb31bd0ff9,c0ee29c65df25604be9501ef07228d317f1d670f..aeb3a63f7c07caa3f53ff4da5096dc51493dd4e3
@@@ -263,15 -263,15 +263,15 @@@ check_language () 
                >expect
                ;;
        ?*)
 -              echo "Accept-Language: $1" >expect
 +              echo "=> Send header: Accept-Language: $1" >expect
                ;;
        esac &&
 -      GIT_CURL_VERBOSE=1 \
 +      GIT_TRACE_CURL=true \
        LANGUAGE=$2 \
        git ls-remote "$HTTPD_URL/dumb/repo.git" >output 2>&1 &&
        tr -d '\015' <output |
        sort -u |
 -      sed -ne '/^Accept-Language:/ p' >actual &&
 +      sed -ne '/^=> Send header: Accept-Language:/ p' >actual &&
        test_cmp expect actual
  }
  
@@@ -295,16 -295,8 +295,16 @@@ ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0
  '
  
  test_expect_success 'git client does not send an empty Accept-Language' '
 -      GIT_CURL_VERBOSE=1 LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr &&
 -      ! grep "^Accept-Language:" stderr
 +      GIT_TRACE_CURL=true LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr &&
 +      ! grep "^=> Send header: Accept-Language:" stderr
 +'
 +
 +test_expect_success 'remote-http complains cleanly about malformed urls' '
 +      # do not actually issue "list" or other commands, as we do not
 +      # want to rely on what curl would actually do with such a broken
 +      # URL. This is just about making sure we do not segfault during
 +      # initialization.
 +      test_must_fail git remote-http http::/example.com/repo.git
  '
  
  test_expect_success 'redirects can be forbidden/allowed' '
@@@ -368,5 -360,15 +368,15 @@@ test_expect_success 'http-alternates ca
                clone "$HTTPD_URL/dumb/evil.git" evil-file
  '
  
+ test_expect_success 'http-alternates triggers not-from-user protocol check' '
+       echo "$HTTPD_URL/dumb/victim.git/objects" \
+               >"$evil/objects/info/http-alternates" &&
+       test_config_global http.followRedirects true &&
+       test_must_fail git -c protocol.http.allow=user \
+               clone $HTTPD_URL/dumb/evil.git evil-user &&
+       git -c protocol.http.allow=always \
+               clone $HTTPD_URL/dumb/evil.git evil-user
+ '
  stop_httpd
  test_done
diff --combined transport.c
index 04e5d6623e39014622e0a185d37f8a456fd352e6,f50c31a572f35964cb12c2d88ee5748858908cd1..3e8799a6111b3c8672e4068450c9d225f3fcccc5
@@@ -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,18 -148,9 +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;
  }
@@@ -220,9 -211,6 +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;
@@@ -307,9 -295,7 +307,9 @@@ void transport_update_tracking_ref(stru
        }
  }
  
 -static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg, int porcelain)
 +static void print_ref_status(char flag, const char *summary,
 +                           struct ref *to, struct ref *from, const char *msg,
 +                           int porcelain, int summary_width)
  {
        if (porcelain) {
                if (from)
                else
                        fprintf(stdout, "%s\n", summary);
        } else {
 -              fprintf(stderr, " %c %-*s ", flag, TRANSPORT_SUMMARY_WIDTH, summary);
 +              fprintf(stderr, " %c %-*s ", flag, summary_width, summary);
                if (from)
                        fprintf(stderr, "%s -> %s", prettify_refname(from->name), prettify_refname(to->name));
                else
        }
  }
  
 -static void print_ok_ref_status(struct ref *ref, int porcelain)
 +static void print_ok_ref_status(struct ref *ref, int porcelain, int summary_width)
  {
        if (ref->deletion)
 -              print_ref_status('-', "[deleted]", ref, NULL, NULL, porcelain);
 +              print_ref_status('-', "[deleted]", ref, NULL, NULL,
 +                               porcelain, summary_width);
        else if (is_null_oid(&ref->old_oid))
                print_ref_status('*',
                        (starts_with(ref->name, "refs/tags/") ? "[new tag]" :
                        "[new branch]"),
 -                      ref, ref->peer_ref, NULL, porcelain);
 +                      ref, ref->peer_ref, NULL, porcelain, summary_width);
        else {
                struct strbuf quickref = STRBUF_INIT;
                char type;
                strbuf_add_unique_abbrev(&quickref, ref->new_oid.hash,
                                         DEFAULT_ABBREV);
  
 -              print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg, porcelain);
 +              print_ref_status(type, quickref.buf, ref, ref->peer_ref, msg,
 +                               porcelain, summary_width);
                strbuf_release(&quickref);
        }
  }
  
 -static int print_one_push_status(struct ref *ref, const char *dest, int count, int porcelain)
 +static int print_one_push_status(struct ref *ref, const char *dest, int count,
 +                               int porcelain, int summary_width)
  {
        if (!count) {
                char *url = transport_anonymize_url(dest);
  
        switch(ref->status) {
        case REF_STATUS_NONE:
 -              print_ref_status('X', "[no match]", ref, NULL, NULL, porcelain);
 +              print_ref_status('X', "[no match]", ref, NULL, NULL,
 +                               porcelain, summary_width);
                break;
        case REF_STATUS_REJECT_NODELETE:
                print_ref_status('!', "[rejected]", ref, NULL,
 -                                               "remote does not support deleting refs", porcelain);
 +                               "remote does not support deleting refs",
 +                               porcelain, summary_width);
                break;
        case REF_STATUS_UPTODATE:
                print_ref_status('=', "[up to date]", ref,
 -                                               ref->peer_ref, NULL, porcelain);
 +                               ref->peer_ref, NULL, porcelain, summary_width);
                break;
        case REF_STATUS_REJECT_NONFASTFORWARD:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 -                                               "non-fast-forward", porcelain);
 +                               "non-fast-forward", porcelain, summary_width);
                break;
        case REF_STATUS_REJECT_ALREADY_EXISTS:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 -                                               "already exists", porcelain);
 +                               "already exists", porcelain, summary_width);
                break;
        case REF_STATUS_REJECT_FETCH_FIRST:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 -                                               "fetch first", porcelain);
 +                               "fetch first", porcelain, summary_width);
                break;
        case REF_STATUS_REJECT_NEEDS_FORCE:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 -                                               "needs force", porcelain);
 +                               "needs force", porcelain, summary_width);
                break;
        case REF_STATUS_REJECT_STALE:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 -                                               "stale info", porcelain);
 +                               "stale info", porcelain, summary_width);
                break;
        case REF_STATUS_REJECT_SHALLOW:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 -                                               "new shallow roots not allowed", porcelain);
 +                               "new shallow roots not allowed",
 +                               porcelain, summary_width);
                break;
        case REF_STATUS_REMOTE_REJECT:
                print_ref_status('!', "[remote rejected]", ref,
 -                                               ref->deletion ? NULL : ref->peer_ref,
 -                                               ref->remote_status, porcelain);
 +                               ref->deletion ? NULL : ref->peer_ref,
 +                               ref->remote_status, porcelain, summary_width);
                break;
        case REF_STATUS_EXPECTING_REPORT:
                print_ref_status('!', "[remote failure]", ref,
 -                                               ref->deletion ? NULL : ref->peer_ref,
 -                                               "remote failed to report status", porcelain);
 +                               ref->deletion ? NULL : ref->peer_ref,
 +                               "remote failed to report status",
 +                               porcelain, summary_width);
                break;
        case REF_STATUS_ATOMIC_PUSH_FAILED:
                print_ref_status('!', "[rejected]", ref, ref->peer_ref,
 -                                               "atomic push failed", porcelain);
 +                               "atomic push failed", porcelain, summary_width);
                break;
        case REF_STATUS_OK:
 -              print_ok_ref_status(ref, porcelain);
 +              print_ok_ref_status(ref, porcelain, summary_width);
                break;
        }
  
        return 1;
  }
  
 +static int measure_abbrev(const struct object_id *oid, int sofar)
 +{
 +      char hex[GIT_SHA1_HEXSZ + 1];
 +      int w = find_unique_abbrev_r(hex, oid->hash, DEFAULT_ABBREV);
 +
 +      return (w < sofar) ? sofar : w;
 +}
 +
 +int transport_summary_width(const struct ref *refs)
 +{
 +      int maxw = -1;
 +
 +      for (; refs; refs = refs->next) {
 +              maxw = measure_abbrev(&refs->old_oid, maxw);
 +              maxw = measure_abbrev(&refs->new_oid, maxw);
 +      }
 +      if (maxw < 0)
 +              maxw = FALLBACK_DEFAULT_ABBREV;
 +      return (2 * maxw + 3);
 +}
 +
  void transport_print_push_status(const char *dest, struct ref *refs,
                                  int verbose, int porcelain, unsigned int *reject_reasons)
  {
        int n = 0;
        unsigned char head_sha1[20];
        char *head;
 +      int summary_width = transport_summary_width(refs);
  
        head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL);
  
        if (verbose) {
                for (ref = refs; ref; ref = ref->next)
                        if (ref->status == REF_STATUS_UPTODATE)
 -                              n += print_one_push_status(ref, dest, n, porcelain);
 +                              n += print_one_push_status(ref, dest, n,
 +                                                         porcelain, summary_width);
        }
  
        for (ref = refs; ref; ref = ref->next)
                if (ref->status == REF_STATUS_OK)
 -                      n += print_one_push_status(ref, dest, n, porcelain);
 +                      n += print_one_push_status(ref, dest, n,
 +                                                 porcelain, summary_width);
  
        *reject_reasons = 0;
        for (ref = refs; ref; ref = ref->next) {
                if (ref->status != REF_STATUS_NONE &&
                    ref->status != REF_STATUS_UPTODATE &&
                    ref->status != REF_STATUS_OK)
 -                      n += print_one_push_status(ref, dest, n, porcelain);
 +                      n += print_one_push_status(ref, dest, n,
 +                                                 porcelain, summary_width);
                if (ref->status == REF_STATUS_REJECT_NONFASTFORWARD) {
                        if (head != NULL && !strcmp(head, ref->name))
                                *reject_reasons |= REJECT_NON_FF_HEAD;
@@@ -556,7 -510,6 +556,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)
@@@ -610,7 -563,7 +610,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));
@@@ -664,21 -617,89 +664,89 @@@ static const struct string_list *protoc
        return enabled ? &allowed : NULL;
  }
  
- int is_transport_allowed(const char *type)
+ enum protocol_allow_config {
+       PROTOCOL_ALLOW_NEVER = 0,
+       PROTOCOL_ALLOW_USER_ONLY,
+       PROTOCOL_ALLOW_ALWAYS
+ };
+ static enum protocol_allow_config parse_protocol_config(const char *key,
+                                                       const char *value)
  {
-       const struct string_list *allowed = protocol_whitelist();
-       return !allowed || string_list_has_string(allowed, type);
+       if (!strcasecmp(value, "always"))
+               return PROTOCOL_ALLOW_ALWAYS;
+       else if (!strcasecmp(value, "never"))
+               return PROTOCOL_ALLOW_NEVER;
+       else if (!strcasecmp(value, "user"))
+               return PROTOCOL_ALLOW_USER_ONLY;
+       die("unknown value for config '%s': %s", key, value);
  }
  
void transport_check_allowed(const char *type)
static enum protocol_allow_config get_protocol_config(const char *type)
  {
-       if (!is_transport_allowed(type))
-               die("transport '%s' not allowed", type);
+       char *key = xstrfmt("protocol.%s.allow", type);
+       char *value;
+       /* first check the per-protocol config */
+       if (!git_config_get_string(key, &value)) {
+               enum protocol_allow_config ret =
+                       parse_protocol_config(key, value);
+               free(key);
+               free(value);
+               return ret;
+       }
+       free(key);
+       /* if defined, fallback to user-defined default for unknown protocols */
+       if (!git_config_get_string("protocol.allow", &value)) {
+               enum protocol_allow_config ret =
+                       parse_protocol_config("protocol.allow", value);
+               free(value);
+               return ret;
+       }
+       /* fallback to built-in defaults */
+       /* known safe */
+       if (!strcmp(type, "http") ||
+           !strcmp(type, "https") ||
+           !strcmp(type, "git") ||
+           !strcmp(type, "ssh") ||
+           !strcmp(type, "file"))
+               return PROTOCOL_ALLOW_ALWAYS;
+       /* known scary; err on the side of caution */
+       if (!strcmp(type, "ext"))
+               return PROTOCOL_ALLOW_NEVER;
+       /* unknown; by default let them be used only directly by the user */
+       return PROTOCOL_ALLOW_USER_ONLY;
  }
  
- int transport_restrict_protocols(void)
+ int is_transport_allowed(const char *type, int from_user)
  {
-       return !!protocol_whitelist();
+       const struct string_list *whitelist = protocol_whitelist();
+       if (whitelist)
+               return string_list_has_string(whitelist, type);
+       switch (get_protocol_config(type)) {
+       case PROTOCOL_ALLOW_ALWAYS:
+               return 1;
+       case PROTOCOL_ALLOW_NEVER:
+               return 0;
+       case PROTOCOL_ALLOW_USER_ONLY:
+               if (from_user < 0)
+                       from_user = git_env_bool("GIT_PROTOCOL_FROM_USER", 1);
+               return from_user;
+       }
+       die("BUG: invalid protocol_allow_config type");
+ }
+ void transport_check_allowed(const char *type)
+ {
+       if (!is_transport_allowed(type, -1))
+               die("transport '%s' not allowed", type);
  }
  
  struct transport *transport_get(struct remote *remote, const char *url)
@@@ -814,19 -835,19 +882,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,
@@@ -949,39 -970,23 +1017,39 @@@ int transport_push(struct transport *tr
  
                if ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) && !is_bare_repository()) {
                        struct ref *ref = remote_refs;
 +                      struct sha1_array commits = SHA1_ARRAY_INIT;
 +
                        for (; ref; ref = ref->next)
 -                              if (!is_null_oid(&ref->new_oid) &&
 -                                  !push_unpushed_submodules(ref->new_oid.hash,
 -                                          transport->remote->name))
 -                                  die ("Failed to push all needed submodules!");
 +                              if (!is_null_oid(&ref->new_oid))
 +                                      sha1_array_append(&commits, ref->new_oid.hash);
 +
 +                      if (!push_unpushed_submodules(&commits,
 +                                                    transport->remote->name,
 +                                                    pretend)) {
 +                              sha1_array_clear(&commits);
 +                              die("Failed to push all needed submodules!");
 +                      }
 +                      sha1_array_clear(&commits);
                }
  
 -              if ((flags & (TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND |
 -                            TRANSPORT_RECURSE_SUBMODULES_CHECK)) && !is_bare_repository()) {
 +              if (((flags & TRANSPORT_RECURSE_SUBMODULES_CHECK) ||
 +                   ((flags & TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND) &&
 +                    !pretend)) && !is_bare_repository()) {
                        struct ref *ref = remote_refs;
                        struct string_list needs_pushing = STRING_LIST_INIT_DUP;
 +                      struct sha1_array commits = SHA1_ARRAY_INIT;
  
                        for (; ref; ref = ref->next)
 -                              if (!is_null_oid(&ref->new_oid) &&
 -                                  find_unpushed_submodules(ref->new_oid.hash,
 -                                          transport->remote->name, &needs_pushing))
 -                                      die_with_unpushed_submodules(&needs_pushing);
 +                              if (!is_null_oid(&ref->new_oid))
 +                                      sha1_array_append(&commits, ref->new_oid.hash);
 +
 +                      if (find_unpushed_submodules(&commits, transport->remote->name,
 +                                              &needs_pushing)) {
 +                              sha1_array_clear(&commits);
 +                              die_with_unpushed_submodules(&needs_pushing);
 +                      }
 +                      string_list_clear(&needs_pushing, 0);
 +                      sha1_array_clear(&commits);
                }
  
                push_ret = transport->push_refs(transport, remote_refs, flags);
@@@ -1146,7 -1151,9 +1214,7 @@@ static int refs_from_alternate_cb(struc
        const struct ref *extra;
        struct alternate_refs_data *cb = data;
  
 -      e->name[-1] = '\0';
 -      other = xstrdup(real_path(e->base));
 -      e->name[-1] = '/';
 +      other = xstrdup(real_path(e->path));
        len = strlen(other);
  
        while (other[len-1] == '/')
diff --combined transport.h
index b8e4ee8099260a74e5207048cf2837c9fd686568,4f1c801994fc01c5eca7861d7cea3b1bfcfbcd1e..9820f10b8e41c6aa6ff6131e5660866d30bf541a
@@@ -5,8 -5,6 +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;
@@@ -53,12 -48,6 +53,12 @@@ struct transport 
         */
        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
  #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)
 +extern int transport_summary_width(const struct ref *refs);
  
  /* Returns a transport suitable for the url */
  struct transport *transport_get(struct remote *, const char *);
  
  /*
-  * Check whether a transport is allowed by the environment. Type should
-  * generally be the URL scheme, as described in Documentation/git.txt
+  * Check whether a transport is allowed by the environment.
+  *
+  * Type should generally be the URL scheme, as described in
+  * Documentation/git.txt
+  *
+  * from_user specifies if the transport was given by the user.  If unknown pass
+  * a -1 to read from the environment to determine if the transport was given by
+  * the user.
+  *
   */
- int is_transport_allowed(const char *type);
+ int is_transport_allowed(const char *type, int from_user);
  
  /*
   * Check whether a transport is allowed by the environment,
   */
  void transport_check_allowed(const char *type);
  
- /*
-  * Returns true if the user has attempted to turn on protocol
-  * restrictions at all.
-  */
- int transport_restrict_protocols(void);
  /* Transport options which apply to git:// and scp-style URLs */
  
  /* The program to use on the remote side to send a pack */
  /* 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"