Merge branch 'jk/upload-pack-hook'
authorJunio C Hamano <gitster@pobox.com>
Wed, 6 Jul 2016 20:38:11 +0000 (13:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 6 Jul 2016 20:38:11 +0000 (13:38 -0700)
"upload-pack" allows a custom "git pack-objects" replacement when
responding to "fetch/clone" via the uploadpack.packObjectsHook.

* jk/upload-pack-hook:
upload-pack: provide a hook for running pack-objects
t1308: do not get fooled by symbolic links to the source tree
config: add a notion of "scope"
config: return configset value for current_config_ functions
config: set up config_source for command-line config
git_config_parse_parameter: refactor cleanup code
git_config_with_options: drop "found" counting

1  2 
Documentation/config.txt
cache.h
config.c
upload-pack.c
diff --combined Documentation/config.txt
index 626243f61a9597ec441446c29429129ac55986b3,a7abcbeff3672f888b6ff6f7265cdb852502c32b..e208af118d9e0db34e88b9170de2c1f2ad338578
@@@ -434,7 -434,7 +434,7 @@@ core.gitProxy:
        may be set multiple times and is matched in the given order;
        the first match wins.
  +
 -Can be overridden by the 'GIT_PROXY_COMMAND' environment variable
 +Can be overridden by the `GIT_PROXY_COMMAND` environment variable
  (which always applies universally, without the special "for"
  handling).
  +
@@@ -478,9 -478,9 +478,9 @@@ false), while all other repositories ar
  
  core.worktree::
        Set the path to the root of the working tree.
 -      If GIT_COMMON_DIR environment variable is set, core.worktree
 +      If `GIT_COMMON_DIR` environment variable is set, core.worktree
        is ignored and not used for determining the root of working tree.
 -      This can be overridden by the GIT_WORK_TREE environment
 +      This can be overridden by the `GIT_WORK_TREE` environment
        variable and the '--work-tree' command-line option.
        The value can be an absolute path or relative to the path to
        the .git directory, which is either specified by --git-dir
@@@ -545,7 -545,7 +545,7 @@@ core.compression:
        -1 is the zlib default. 0 means no compression,
        and 1..9 are various speed/size tradeoffs, 9 being slowest.
        If set, this provides a default to other compression variables,
 -      such as 'core.looseCompression' and 'pack.compression'.
 +      such as `core.looseCompression` and `pack.compression`.
  
  core.looseCompression::
        An integer -1..9, indicating the compression level for objects that
@@@ -619,9 -619,9 +619,9 @@@ core.excludesFile:
  core.askPass::
        Some commands (e.g. svn and http interfaces) that interactively
        ask for a password can be told to use an external program given
 -      via the value of this variable. Can be overridden by the 'GIT_ASKPASS'
 +      via the value of this variable. Can be overridden by the `GIT_ASKPASS`
        environment variable. If not set, fall back to the value of the
 -      'SSH_ASKPASS' environment variable or, failing that, a simple password
 +      `SSH_ASKPASS` environment variable or, failing that, a simple password
        prompt. The external program shall be given a suitable prompt as
        command-line argument and write the password on its STDOUT.
  
@@@ -764,7 -764,7 +764,7 @@@ core.notesRef:
        notes should be printed.
  +
  This setting defaults to "refs/notes/commits", and it can be overridden by
 -the 'GIT_NOTES_REF' environment variable.  See linkgit:git-notes[1].
 +the `GIT_NOTES_REF` environment variable.  See linkgit:git-notes[1].
  
  core.sparseCheckout::
        Enable "sparse checkout" feature. See section "Sparse checkout" in
@@@ -800,7 -800,7 +800,7 @@@ it will be treated as a shell command
  "gitk --all --not ORIG_HEAD".  Note that shell commands will be
  executed from the top-level directory of a repository, which may
  not necessarily be the current directory.
 -'GIT_PREFIX' is set as returned by running 'git rev-parse --show-prefix'
 +`GIT_PREFIX` is set as returned by running 'git rev-parse --show-prefix'
  from the original current directory. See linkgit:git-rev-parse[1].
  
  am.keepcr::
@@@ -1189,15 -1189,6 +1189,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
@@@ -1435,18 -1426,18 +1435,18 @@@ gitcvs.usecrlfattr:
        treat it as text. If they suppress text conversion, the file
        will be set with '-kb' mode, which suppresses any newline munging
        the client might otherwise do. If the attributes do not allow
 -      the file type to be determined, then 'gitcvs.allBinary' is
 +      the file type to be determined, then `gitcvs.allBinary` is
        used. See linkgit:gitattributes[5].
  
  gitcvs.allBinary::
 -      This is used if 'gitcvs.usecrlfattr' does not resolve
 +      This is used if `gitcvs.usecrlfattr` does not resolve
        the correct '-kb' mode to use. If true, all
        unresolved files are sent to the client in
        mode '-kb'. This causes the client to treat them
        as binary files, which suppresses any newline munging it
        otherwise might do. Alternatively, if it is set to "guess",
        then the contents of the file are examined to decide if
 -      it is binary, similar to 'core.autocrlf'.
 +      it is binary, similar to `core.autocrlf`.
  
  gitcvs.dbName::
        Database used by git-cvsserver to cache revision information
@@@ -1465,7 -1456,7 +1465,7 @@@ gitcvs.dbDriver:
        See linkgit:git-cvsserver[1].
  
  gitcvs.dbUser, gitcvs.dbPass::
 -      Database user and password. Only useful if setting 'gitcvs.dbDriver',
 +      Database user and password. Only useful if setting `gitcvs.dbDriver`,
        since SQLite has no concept of database users and/or passwords.
        'gitcvs.dbUser' supports variable substitution (see
        linkgit:git-cvsserver[1] for details).
@@@ -1477,8 -1468,8 +1477,8 @@@ gitcvs.dbTableNamePrefix:
        linkgit:git-cvsserver[1] for details).  Any non-alphabetic
        characters will be replaced with underscores.
  
 -All gitcvs variables except for 'gitcvs.usecrlfattr' and
 -'gitcvs.allBinary' can also be specified as
 +All gitcvs variables except for `gitcvs.usecrlfattr` and
 +`gitcvs.allBinary` can also be specified as
  'gitcvs.<access_method>.<varname>' (where 'access_method'
  is one of "ext" and "pserver") to make them apply only for the given
  access method.
@@@ -1511,7 -1502,7 +1511,7 @@@ grep.patternType:
  
  grep.extendedRegexp::
        If set to true, enable '--extended-regexp' option by default. This
 -      option is ignored when the 'grep.patternType' option is set to a value
 +      option is ignored when the `grep.patternType` option is set to a value
        other than 'default'.
  
  grep.threads::
@@@ -1596,7 -1587,7 +1596,7 @@@ guitool.<name>.cmd:
        of the linkgit:git-gui[1] `Tools` menu is invoked. This option is
        mandatory for every tool. The command is executed from the root of
        the working directory, and in the environment it receives the name of
 -      the tool as 'GIT_GUITOOL', the name of the currently selected file as
 +      the tool as `GIT_GUITOOL`, the name of the currently selected file as
        'FILENAME', and the name of the current branch as 'CUR_BRANCH' (if
        the head is detached, 'CUR_BRANCH' is empty).
  
@@@ -1617,7 -1608,7 +1617,7 @@@ guitool.<name>.confirm:
  
  guitool.<name>.argPrompt::
        Request a string argument from the user, and pass it to the tool
 -      through the 'ARGS' environment variable. Since requesting an
 +      through the `ARGS` environment variable. Since requesting an
        argument implies confirmation, the 'confirm' option has no effect
        if this is enabled. If the option is set to 'true', 'yes', or '1',
        the dialog uses a built-in generic prompt; otherwise the exact
  
  guitool.<name>.revPrompt::
        Request a single valid revision from the user, and set the
 -      'REVISION' environment variable. In other aspects this option
 +      `REVISION` environment variable. In other aspects this option
        is similar to 'argPrompt', and can be used together with it.
  
  guitool.<name>.revUnmerged::
@@@ -1681,7 -1672,7 +1681,7 @@@ http.proxyAuthMethod:
        only takes effect if the configured proxy string contains a user name part
        (i.e. is of the form 'user@host' or 'user@host:port'). This can be
        overridden on a per-remote basis; see `remote.<name>.proxyAuthMethod`.
 -      Both can be overridden by the 'GIT_HTTP_PROXY_AUTHMETHOD' environment
 +      Both can be overridden by the `GIT_HTTP_PROXY_AUTHMETHOD` environment
        variable.  Possible values are:
  +
  --
@@@ -1740,9 -1731,9 +1740,9 @@@ http.sslVersion:
        - tlsv1.2
  
  +
 -Can be overridden by the 'GIT_SSL_VERSION' environment variable.
 +Can be overridden by the `GIT_SSL_VERSION` environment variable.
  To force git to use libcurl's default ssl version and ignore any
 -explicit http.sslversion option, set 'GIT_SSL_VERSION' to the
 +explicit http.sslversion option, set `GIT_SSL_VERSION` to the
  empty string.
  
  http.sslCipherList::
    option; see the libcurl documentation for more details on the format
    of this list.
  +
 -Can be overridden by the 'GIT_SSL_CIPHER_LIST' environment variable.
 +Can be overridden by the `GIT_SSL_CIPHER_LIST` environment variable.
  To force git to use libcurl's default cipher list and ignore any
 -explicit http.sslCipherList option, set 'GIT_SSL_CIPHER_LIST' to the
 +explicit http.sslCipherList option, set `GIT_SSL_CIPHER_LIST` to the
  empty string.
  
  http.sslVerify::
        Whether to verify the SSL certificate when fetching or pushing
 -      over HTTPS. Can be overridden by the 'GIT_SSL_NO_VERIFY' environment
 +      over HTTPS. Can be overridden by the `GIT_SSL_NO_VERIFY` environment
        variable.
  
  http.sslCert::
        File containing the SSL certificate when fetching or pushing
 -      over HTTPS. Can be overridden by the 'GIT_SSL_CERT' environment
 +      over HTTPS. Can be overridden by the `GIT_SSL_CERT` environment
        variable.
  
  http.sslKey::
        File containing the SSL private key when fetching or pushing
 -      over HTTPS. Can be overridden by the 'GIT_SSL_KEY' environment
 +      over HTTPS. Can be overridden by the `GIT_SSL_KEY` environment
        variable.
  
  http.sslCertPasswordProtected::
        Enable Git's password prompt for the SSL certificate.  Otherwise
        OpenSSL will prompt the user, possibly many times, if the
        certificate or private key is encrypted.  Can be overridden by the
 -      'GIT_SSL_CERT_PASSWORD_PROTECTED' environment variable.
 +      `GIT_SSL_CERT_PASSWORD_PROTECTED` environment variable.
  
  http.sslCAInfo::
        File containing the certificates to verify the peer with when
        fetching or pushing over HTTPS. Can be overridden by the
 -      'GIT_SSL_CAINFO' environment variable.
 +      `GIT_SSL_CAINFO` environment variable.
  
  http.sslCAPath::
        Path containing files with the CA certificates to verify the peer
        with when fetching or pushing over HTTPS. Can be overridden
 -      by the 'GIT_SSL_CAPATH' environment variable.
 +      by the `GIT_SSL_CAPATH` environment variable.
  
  http.pinnedpubkey::
        Public key of the https service. It may either be the filename of
@@@ -1807,7 -1798,7 +1807,7 @@@ http.sslTry:
  
  http.maxRequests::
        How many HTTP requests to launch in parallel. Can be overridden
 -      by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
 +      by the `GIT_HTTP_MAX_REQUESTS` environment variable. Default is 5.
  
  http.minSessions::
        The number of curl sessions (counted across slots) to be kept across
@@@ -1826,13 -1817,13 +1826,13 @@@ http.postBuffer:
  http.lowSpeedLimit, http.lowSpeedTime::
        If the HTTP transfer speed is less than 'http.lowSpeedLimit'
        for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
 -      Can be overridden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and
 -      'GIT_HTTP_LOW_SPEED_TIME' environment variables.
 +      Can be overridden by the `GIT_HTTP_LOW_SPEED_LIMIT` and
 +      `GIT_HTTP_LOW_SPEED_TIME` environment variables.
  
  http.noEPSV::
        A boolean which disables using of EPSV ftp command by curl.
        This can helpful with some "poor" ftp servers which don't
 -      support EPSV mode. Can be overridden by the 'GIT_CURL_FTP_NO_EPSV'
 +      support EPSV mode. Can be overridden by the `GIT_CURL_FTP_NO_EPSV`
        environment variable. Default is false (curl will use EPSV).
  
  http.userAgent::
        such as Mozilla/4.0.  This may be necessary, for instance, if
        connecting through a firewall that restricts HTTP connections to a set
        of common USER_AGENT strings (but not including those like git/1.7.1).
 -      Can be overridden by the 'GIT_HTTP_USER_AGENT' environment variable.
 +      Can be overridden by the `GIT_HTTP_USER_AGENT` environment variable.
  
  http.<url>.*::
        Any of the http.* options above can be applied selectively to some URLs.
@@@ -1965,10 -1956,7 +1965,10 @@@ log.decorate:
        command. If 'short' is specified, the ref name prefixes 'refs/heads/',
        'refs/tags/' and 'refs/remotes/' will not be printed. If 'full' is
        specified, the full ref name (including prefix) will be printed.
 -      This is the same as the log commands '--decorate' option.
 +      If 'auto' is specified, then if the output is going to a terminal,
 +      the ref names are shown as if 'short' were given, otherwise no ref
 +      names are shown. This is the same as the '--decorate' option
 +      of the `git log`.
  
  log.follow::
        If `true`, `git log` will act as if the `--follow` option was used when
@@@ -2639,7 -2627,7 +2639,7 @@@ sendemail.identity:
        A configuration identity. When given, causes values in the
        'sendemail.<identity>' subsection to take precedence over
        values in the 'sendemail' section. The default identity is
 -      the value of 'sendemail.identity'.
 +      the value of `sendemail.identity`.
  
  sendemail.smtpEncryption::
        See linkgit:git-send-email[1] for description.  Note that this
@@@ -2656,7 -2644,7 +2656,7 @@@ sendemail.<identity>.*:
        Identity-specific versions of the 'sendemail.*' parameters
        found below, taking precedence over those when the this
        identity is selected, through command-line or
 -      'sendemail.identity'.
 +      `sendemail.identity`.
  
  sendemail.aliasesFile::
  sendemail.aliasFileType::
@@@ -2686,7 -2674,7 +2686,7 @@@ sendemail.xmailer:
        See linkgit:git-send-email[1] for description.
  
  sendemail.signedoffcc (deprecated)::
 -      Deprecated alias for 'sendemail.signedoffbycc'.
 +      Deprecated alias for `sendemail.signedoffbycc`.
  
  showbranch.default::
        The default set of branches for linkgit:git-show-branch[1].
@@@ -2892,6 -2880,21 +2892,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
@@@ -2918,17 -2921,17 +2933,17 @@@ url.<base>.pushInsteadOf:
  
  user.email::
        Your email address to be recorded in any newly created commits.
 -      Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
 -      'EMAIL' environment variables.  See linkgit:git-commit-tree[1].
 +      Can be overridden by the `GIT_AUTHOR_EMAIL`, `GIT_COMMITTER_EMAIL`, and
 +      `EMAIL` environment variables.  See linkgit:git-commit-tree[1].
  
  user.name::
        Your full name to be recorded in any newly created commits.
 -      Can be overridden by the 'GIT_AUTHOR_NAME' and 'GIT_COMMITTER_NAME'
 +      Can be overridden by the `GIT_AUTHOR_NAME` and `GIT_COMMITTER_NAME`
        environment variables.  See linkgit:git-commit-tree[1].
  
  user.useConfigOnly::
 -      Instruct Git to avoid trying to guess defaults for 'user.email'
 -      and 'user.name', and instead retrieve the values only from the
 +      Instruct Git to avoid trying to guess defaults for `user.email`
 +      and `user.name`, and instead retrieve the values only from the
        configuration. For example, if you have multiple email addresses
        and would like to use a different one for each repository, then
        with this configuration option set to `true` in the global config
diff --combined cache.h
index c42a7fab4c5ee75f7b9e6847da9338b8c5a6e4af,2e3b377ae8ac4b68e740de28c9e339965aa685e4..f1dc289d068f554b73434e9f8bf5d723a3e78519
+++ b/cache.h
@@@ -367,8 -367,8 +367,8 @@@ extern void free_name_hash(struct index
  #define rename_cache_entry_at(pos, new_name) rename_index_entry_at(&the_index, (pos), (new_name))
  #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
  #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
 -#define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags))
 -#define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags))
 +#define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags), 0)
 +#define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags), 0)
  #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL, NULL)
  #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
  #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
@@@ -581,8 -581,8 +581,8 @@@ extern int remove_file_from_index(struc
  #define ADD_CACHE_IGNORE_ERRORS       4
  #define ADD_CACHE_IGNORE_REMOVAL 8
  #define ADD_CACHE_INTENT 16
 -extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
 -extern int add_file_to_index(struct index_state *, const char *path, int flags);
 +extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags, int force_mode);
 +extern int add_file_to_index(struct index_state *, const char *path, int flags, int force_mode);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, unsigned int refresh_options);
  extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
@@@ -1604,6 -1604,16 +1604,16 @@@ extern const char *get_log_output_encod
  extern const char *get_commit_output_encoding(void);
  
  extern int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
+ enum config_scope {
+       CONFIG_SCOPE_UNKNOWN = 0,
+       CONFIG_SCOPE_SYSTEM,
+       CONFIG_SCOPE_GLOBAL,
+       CONFIG_SCOPE_REPO,
+       CONFIG_SCOPE_CMDLINE,
+ };
+ extern enum config_scope current_config_scope(void);
  extern const char *current_config_origin_type(void);
  extern const char *current_config_name(void);
  
@@@ -1696,6 -1706,8 +1706,8 @@@ extern int ignore_untracked_cache_confi
  struct key_value_info {
        const char *filename;
        int linenr;
+       const char *origin_type;
+       enum config_scope scope;
  };
  
  extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));
@@@ -1721,6 -1733,7 +1733,6 @@@ extern int copy_file(const char *dst, c
  extern int copy_file_with_time(const char *dst, const char *src, int mode);
  
  extern void write_or_die(int fd, const void *buf, size_t count);
 -extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
  extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
  extern void fsync_or_die(int fd, const char *);
  
@@@ -1771,7 -1784,7 +1783,7 @@@ void packet_trace_identity(const char *
   * return 0 if success, 1 - if addition of a file failed and
   * ADD_FILES_IGNORE_ERRORS was specified in flags
   */
 -int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags);
 +int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags, int force_mode);
  
  /* diff.c */
  extern int diff_auto_refresh_index;
diff --combined config.c
index d7ce34b33d67c38245e77b7082d80f350e4f7eb7,87629164b3623885fa0428790a6e7fa2eb643215..bea937e4ecf8417a910882c1aafb263e792f2e1c
+++ b/config.c
@@@ -38,7 -38,33 +38,33 @@@ struct config_source 
        long (*do_ftell)(struct config_source *c);
  };
  
+ /*
+  * These variables record the "current" config source, which
+  * can be accessed by parsing callbacks.
+  *
+  * The "cf" variable will be non-NULL only when we are actually parsing a real
+  * config source (file, blob, cmdline, etc).
+  *
+  * The "current_config_kvi" variable will be non-NULL only when we are feeding
+  * cached config from a configset into a callback.
+  *
+  * They should generally never be non-NULL at the same time. If they are both
+  * NULL, then we aren't parsing anything (and depending on the function looking
+  * at the variables, it's either a bug for it to be called in the first place,
+  * or it's a function which can be reused for non-config purposes, and should
+  * fall back to some sane behavior).
+  */
  static struct config_source *cf;
+ static struct key_value_info *current_config_kvi;
+ /*
+  * Similar to the variables above, this gives access to the "scope" of the
+  * current value (repo, global, etc). For cached values, it can be found via
+  * the current_config_kvi as above. During parsing, the current value can be
+  * found in this variable. It's not part of "cf" because it transcends a single
+  * file (i.e., a file included from .git/config is still in "repo" scope).
+  */
+ static enum config_scope current_parsing_scope;
  
  static int zlib_compression_seen;
  
@@@ -131,7 -157,9 +157,9 @@@ static int handle_path_include(const ch
        if (!access_or_die(path, R_OK, 0)) {
                if (++inc->depth > MAX_INCLUDE_DEPTH)
                        die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
-                           cf && cf->name ? cf->name : "the command line");
+                           !cf ? "<unknown>" :
+                           cf->name ? cf->name :
+                           "the command line");
                ret = git_config_from_file(git_config_include, path, inc);
                inc->depth--;
        }
@@@ -205,32 -233,40 +233,40 @@@ int git_config_parse_parameter(const ch
  int git_config_from_parameters(config_fn_t fn, void *data)
  {
        const char *env = getenv(CONFIG_DATA_ENVIRONMENT);
+       int ret = 0;
        char *envw;
        const char **argv = NULL;
        int nr = 0, alloc = 0;
        int i;
+       struct config_source source;
  
        if (!env)
                return 0;
+       memset(&source, 0, sizeof(source));
+       source.prev = cf;
+       cf = &source;
        /* sq_dequote will write over it */
        envw = xstrdup(env);
  
        if (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {
-               free(envw);
-               return error("bogus format in " CONFIG_DATA_ENVIRONMENT);
+               ret = error("bogus format in " CONFIG_DATA_ENVIRONMENT);
+               goto out;
        }
  
        for (i = 0; i < nr; i++) {
                if (git_config_parse_parameter(argv[i], fn, data) < 0) {
-                       free(argv);
-                       free(envw);
-                       return -1;
+                       ret = -1;
+                       goto out;
                }
        }
  
+ out:
        free(argv);
        free(envw);
-       return nr > 0;
+       cf = source.prev;
+       return ret;
  }
  
  static int get_next_char(void)
@@@ -1197,47 -1233,36 +1233,36 @@@ int git_config_system(void
  
  static int do_git_config_sequence(config_fn_t fn, void *data)
  {
-       int ret = 0, found = 0;
+       int ret = 0;
        char *xdg_config = xdg_config_home("config");
        char *user_config = expand_user_path("~/.gitconfig");
        char *repo_config = git_pathdup("config");
  
-       if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
+       current_parsing_scope = CONFIG_SCOPE_SYSTEM;
+       if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0))
                ret += git_config_from_file(fn, git_etc_gitconfig(),
                                            data);
-               found += 1;
-       }
  
-       if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) {
+       current_parsing_scope = CONFIG_SCOPE_GLOBAL;
+       if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
                ret += git_config_from_file(fn, xdg_config, data);
-               found += 1;
-       }
  
-       if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) {
+       if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
                ret += git_config_from_file(fn, user_config, data);
-               found += 1;
-       }
  
-       if (repo_config && !access_or_die(repo_config, R_OK, 0)) {
+       current_parsing_scope = CONFIG_SCOPE_REPO;
+       if (repo_config && !access_or_die(repo_config, R_OK, 0))
                ret += git_config_from_file(fn, repo_config, data);
-               found += 1;
-       }
  
-       switch (git_config_from_parameters(fn, data)) {
-       case -1: /* error */
+       current_parsing_scope = CONFIG_SCOPE_CMDLINE;
+       if (git_config_from_parameters(fn, data) < 0)
                die(_("unable to parse command-line config"));
-               break;
-       case 0: /* found nothing */
-               break;
-       default: /* found at least one item */
-               found++;
-               break;
-       }
  
+       current_parsing_scope = CONFIG_SCOPE_UNKNOWN;
        free(xdg_config);
        free(user_config);
        free(repo_config);
-       return ret == 0 ? found : ret;
+       return ret;
  }
  
  int git_config_with_options(config_fn_t fn, void *data,
@@@ -1272,7 -1297,7 +1297,7 @@@ static void git_config_raw(config_fn_t 
        if (git_config_with_options(fn, data, NULL, 1) < 0)
                /*
                 * git_config_with_options() normally returns only
-                * positive values, as most errors are fatal, and
+                * zero, as most errors are fatal, and
                 * non-fatal potential errors are guarded by "if"
                 * statements that are entered only when no error is
                 * possible.
                 * something went really wrong and we should stop
                 * immediately.
                 */
 -              die(_("unknown error occured while reading the configuration files"));
 +              die(_("unknown error occurred while reading the configuration files"));
  }
  
  static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)
        struct string_list *values;
        struct config_set_element *entry;
        struct configset_list *list = &cs->list;
-       struct key_value_info *kv_info;
  
        for (i = 0; i < list->nr; i++) {
                entry = list->items[i].e;
                value_index = list->items[i].value_index;
                values = &entry->value_list;
-               if (fn(entry->key, values->items[value_index].string, data) < 0) {
-                       kv_info = values->items[value_index].util;
-                       git_die_config_linenr(entry->key, kv_info->filename, kv_info->linenr);
-               }
+               current_config_kvi = values->items[value_index].util;
+               if (fn(entry->key, values->items[value_index].string, data) < 0)
+                       git_die_config_linenr(entry->key,
+                                             current_config_kvi->filename,
+                                             current_config_kvi->linenr);
+               current_config_kvi = NULL;
        }
  }
  
@@@ -1356,14 -1385,19 +1385,19 @@@ static int configset_add_value(struct c
        l_item->e = e;
        l_item->value_index = e->value_list.nr - 1;
  
-       if (cf) {
+       if (!cf)
+               die("BUG: configset_add_value has no source");
+       if (cf->name) {
                kv_info->filename = strintern(cf->name);
                kv_info->linenr = cf->linenr;
+               kv_info->origin_type = strintern(cf->origin_type);
        } else {
                /* for values read from `git_config_from_parameters()` */
                kv_info->filename = NULL;
                kv_info->linenr = -1;
+               kv_info->origin_type = NULL;
        }
+       kv_info->scope = current_parsing_scope;
        si->util = kv_info;
  
        return 0;
@@@ -2442,10 -2476,32 +2476,32 @@@ int parse_config_key(const char *var
  
  const char *current_config_origin_type(void)
  {
-       return cf && cf->origin_type ? cf->origin_type : "command line";
+       const char *type;
+       if (current_config_kvi)
+               type = current_config_kvi->origin_type;
+       else if(cf)
+               type = cf->origin_type;
+       else
+               die("BUG: current_config_origin_type called outside config callback");
+       return type ? type : "command line";
  }
  
  const char *current_config_name(void)
  {
-       return cf && cf->name ? cf->name : "";
+       const char *name;
+       if (current_config_kvi)
+               name = current_config_kvi->filename;
+       else if (cf)
+               name = cf->name;
+       else
+               die("BUG: current_config_name called outside config callback");
+       return name ? name : "";
+ }
+ enum config_scope current_config_scope(void)
+ {
+       if (current_config_kvi)
+               return current_config_kvi->scope;
+       else
+               return current_parsing_scope;
  }
diff --combined upload-pack.c
index a86d49cfb3e4a1702cd8f1ea0739de63f4b6faca,8979be6394fc5a737ec83d770ac5efb63db438b4..f93787003a21b465c33ab8302347d80e46c10428
  #include "sigchain.h"
  #include "version.h"
  #include "string-list.h"
 +#include "parse-options.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)
@@@ -56,27 -52,27 +56,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)
@@@ -98,6 -94,14 +99,14 @@@ static void create_pack_file(void
        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_array_push(&pack_objects.args, "--shallow-file");
                argv_array_push(&pack_objects.args, "");
        pack_objects.in = -1;
        pack_objects.out = -1;
        pack_objects.err = -1;
-       pack_objects.git_cmd = 1;
  
        if (start_command(&pack_objects))
                die("git upload-pack: unable to fork git-pack-objects");
                        }
                        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)
@@@ -813,25 -820,18 +821,28 @@@ 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 main(int argc, const char **argv)
  {
 -      char *dir;
 -      int i;
 +      const char *dir;
        int strict = 0;
 +      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 intial 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()
 +      };
  
        git_setup_gettext();
  
        git_extract_argv0_path(argv[0]);
        check_replace_refs = 0;
  
 -      for (i = 1; i < argc; i++) {
 -              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 (starts_with(arg, "--timeout=")) {
 -                      timeout = atoi(arg+10);
 -                      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);