Merge branch 'nd/conditional-config-include'
authorJunio C Hamano <gitster@pobox.com>
Mon, 24 Apr 2017 05:07:46 +0000 (22:07 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 24 Apr 2017 05:07:46 +0000 (22:07 -0700)
$GIT_DIR may in some cases be normalized with all symlinks resolved
while "gitdir" path expansion in the pattern does not receive the
same treatment, leading to incorrect mismatch.  This has been fixed.

* nd/conditional-config-include:
  config: resolve symlinks in conditional include's patterns
  path.c: and an option to call real_path() in expand_user_path()

1  2 
builtin/commit.c
builtin/config.c
cache.h
config.c
credential-cache.c
path.c

diff --combined builtin/commit.c
@@@ -496,7 -496,7 +496,7 @@@ static const char *prepare_index(int ar
  static int run_status(FILE *fp, const char *index_file, const char *prefix, int nowarn,
                      struct wt_status *s)
  {
 -      unsigned char sha1[20];
 +      struct object_id oid;
  
        if (s->relative_paths)
                s->prefix = prefix;
        s->index_file = index_file;
        s->fp = fp;
        s->nowarn = nowarn;
 -      s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
 +      s->is_initial = get_sha1(s->reference, oid.hash) ? 1 : 0;
        if (!s->is_initial)
 -              hashcpy(s->sha1_commit, sha1);
 +              hashcpy(s->sha1_commit, oid.hash);
        s->status_format = status_format;
        s->ignore_submodule_arg = ignore_submodule_arg;
  
@@@ -885,7 -885,7 +885,7 @@@ static int prepare_to_commit(const cha
                commitable = run_status(s->fp, index_file, prefix, 1, s);
                s->use_color = saved_color_setting;
        } else {
 -              unsigned char sha1[20];
 +              struct object_id oid;
                const char *parent = "HEAD";
  
                if (!active_nr && read_cache() < 0)
                if (amend)
                        parent = "HEAD^1";
  
 -              if (get_sha1(parent, sha1)) {
 +              if (get_sha1(parent, oid.hash)) {
                        int i, ita_nr = 0;
  
                        for (i = 0; i < active_nr; i++)
@@@ -1332,7 -1332,7 +1332,7 @@@ int cmd_status(int argc, const char **a
  {
        static struct wt_status s;
        int fd;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        static struct option builtin_status_options[] = {
                OPT__VERBOSE(&verbose, N_("be verbose")),
                OPT_SET_INT('s', "short", &status_format,
  
        fd = hold_locked_index(&index_lock, 0);
  
 -      s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
 +      s.is_initial = get_sha1(s.reference, oid.hash) ? 1 : 0;
        if (!s.is_initial)
 -              hashcpy(s.sha1_commit, sha1);
 +              hashcpy(s.sha1_commit, oid.hash);
  
        s.ignore_submodule_arg = ignore_submodule_arg;
        s.status_format = status_format;
  
  static const char *implicit_ident_advice(void)
  {
-       char *user_config = expand_user_path("~/.gitconfig");
+       char *user_config = expand_user_path("~/.gitconfig", 0);
        char *xdg_config = xdg_config_home("config");
        int config_exists = file_exists(user_config) || file_exists(xdg_config);
  
  
  }
  
 -static void print_summary(const char *prefix, const unsigned char *sha1,
 +static void print_summary(const char *prefix, const struct object_id *oid,
                          int initial_commit)
  {
        struct rev_info rev;
        struct commit *commit;
        struct strbuf format = STRBUF_INIT;
 -      unsigned char junk_sha1[20];
 +      struct object_id junk_oid;
        const char *head;
        struct pretty_print_context pctx = {0};
        struct strbuf author_ident = STRBUF_INIT;
        struct strbuf committer_ident = STRBUF_INIT;
  
 -      commit = lookup_commit(sha1);
 +      commit = lookup_commit(oid->hash);
        if (!commit)
                die(_("couldn't look up newly created commit"));
        if (parse_commit(commit))
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
  
 -      head = resolve_ref_unsafe("HEAD", 0, junk_sha1, NULL);
 +      head = resolve_ref_unsafe("HEAD", 0, junk_oid.hash, NULL);
        if (!strcmp(head, "HEAD"))
                head = _("detached HEAD");
        else
@@@ -1522,8 -1522,8 +1522,8 @@@ static int git_commit_config(const cha
        return git_status_config(k, v, s);
  }
  
 -static int run_rewrite_hook(const unsigned char *oldsha1,
 -                          const unsigned char *newsha1)
 +static int run_rewrite_hook(const struct object_id *oldoid,
 +                          const struct object_id *newoid)
  {
        struct child_process proc = CHILD_PROCESS_INIT;
        const char *argv[3];
        code = start_command(&proc);
        if (code)
                return code;
 -      strbuf_addf(&sb, "%s %s\n", sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
 +      strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid));
        sigchain_push(SIGPIPE, SIG_IGN);
        write_in_full(proc.in, sb.buf, sb.len);
        close(proc.in);
@@@ -1636,7 -1636,7 +1636,7 @@@ int cmd_commit(int argc, const char **a
        struct strbuf author_ident = STRBUF_INIT;
        const char *index_file, *reflog_msg;
        char *nl;
 -      unsigned char sha1[20];
 +      struct object_id oid;
        struct commit_list *parents = NULL;
        struct stat statbuf;
        struct commit *current_head = NULL;
        status_format = STATUS_FORMAT_NONE; /* Ignore status.short */
        s.colopts = 0;
  
 -      if (get_sha1("HEAD", sha1))
 +      if (get_sha1("HEAD", oid.hash))
                current_head = NULL;
        else {
 -              current_head = lookup_commit_or_die(sha1, "HEAD");
 +              current_head = lookup_commit_or_die(oid.hash, "HEAD");
                if (parse_commit(current_head))
                        die(_("could not parse HEAD commit"));
        }
        }
  
        if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->sha1,
 -                       parents, sha1, author_ident.buf, sign_commit, extra)) {
 +                       parents, oid.hash, author_ident.buf, sign_commit, extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
  
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
 -          ref_transaction_update(transaction, "HEAD", sha1,
 +          ref_transaction_update(transaction, "HEAD", oid.hash,
                                   current_head
                                   ? current_head->object.oid.hash : null_sha1,
                                   0, sb.buf, &err) ||
                cfg = init_copy_notes_for_rewrite("amend");
                if (cfg) {
                        /* we are amending, so current_head is not NULL */
 -                      copy_note_for_rewrite(cfg, current_head->object.oid.hash, sha1);
 +                      copy_note_for_rewrite(cfg, current_head->object.oid.hash, oid.hash);
                        finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
                }
 -              run_rewrite_hook(current_head->object.oid.hash, sha1);
 +              run_rewrite_hook(&current_head->object.oid, &oid);
        }
        if (!quiet)
 -              print_summary(prefix, sha1, !current_head);
 +              print_summary(prefix, &oid, !current_head);
  
        strbuf_release(&err);
        return 0;
diff --combined builtin/config.c
@@@ -502,7 -502,7 +502,7 @@@ int cmd_config(int argc, const char **a
        }
  
        if (use_global_config) {
-               char *user_config = expand_user_path("~/.gitconfig");
+               char *user_config = expand_user_path("~/.gitconfig", 0);
                char *xdg_config = xdg_config_home("config");
  
                if (!user_config)
        else if (given_config_source.file) {
                if (!is_absolute_path(given_config_source.file) && prefix)
                        given_config_source.file =
 -                              xstrdup(prefix_filename(prefix,
 -                                                      strlen(prefix),
 -                                                      given_config_source.file));
 +                              prefix_filename(prefix, given_config_source.file);
        }
  
        if (respect_includes == -1)
diff --combined cache.h
+++ b/cache.h
@@@ -10,8 -10,8 +10,8 @@@
  #include "trace.h"
  #include "string-list.h"
  #include "pack-revindex.h"
 +#include "hash.h"
  
 -#include SHA1_HEADER
  #ifndef platform_SHA_CTX
  /*
   * platform's underlying implementation of SHA-1; could be OpenSSL,
@@@ -66,12 -66,8 +66,12 @@@ unsigned long git_deflate_bound(git_zst
  #define GIT_SHA1_RAWSZ 20
  #define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
  
 +/* The length in byte and in hex digits of the largest possible hash value. */
 +#define GIT_MAX_RAWSZ GIT_SHA1_RAWSZ
 +#define GIT_MAX_HEXSZ GIT_SHA1_HEXSZ
 +
  struct object_id {
 -      unsigned char hash[GIT_SHA1_RAWSZ];
 +      unsigned char hash[GIT_MAX_RAWSZ];
  };
  
  #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
@@@ -347,7 -343,6 +347,7 @@@ struct index_state 
  extern struct index_state the_index;
  
  /* Name hashing */
 +extern int test_lazy_init_name_hash(struct index_state *istate, int try_threaded);
  extern void add_name_hash(struct index_state *istate, struct cache_entry *ce);
  extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
  extern void free_name_hash(struct index_state *istate);
@@@ -415,7 -410,6 +415,7 @@@ static inline enum object_type object_t
  #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
  #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
  #define GIT_SUPER_PREFIX_ENVIRONMENT "GIT_INTERNAL_SUPER_PREFIX"
 +#define GIT_TOPLEVEL_PREFIX_ENVIRONMENT "GIT_INTERNAL_TOPLEVEL_PREFIX"
  #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
  #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
  #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
@@@ -524,30 -518,11 +524,30 @@@ extern void set_git_work_tree(const cha
  #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
  
  extern void setup_work_tree(void);
 +/*
 + * Find GIT_DIR of the repository that contains the current working directory,
 + * without changing the working directory or other global state. The result is
 + * appended to gitdir. The return value is either NULL if no repository was
 + * found, or pointing to the path inside gitdir's buffer.
 + */
 +extern const char *discover_git_directory(struct strbuf *gitdir);
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
  extern char *prefix_path(const char *prefix, int len, const char *path);
  extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path);
 -extern const char *prefix_filename(const char *prefix, int len, const char *path);
 +
 +/*
 + * Concatenate "prefix" (if len is non-zero) and "path", with no
 + * connecting characters (so "prefix" should end with a "/").
 + * Unlike prefix_path, this should be used if the named file does
 + * not have to interact with index entry; i.e. name of a random file
 + * on the filesystem.
 + *
 + * The return value is always a newly allocated string (even if the
 + * prefix was empty).
 + */
 +extern char *prefix_filename(const char *prefix, const char *path);
 +
  extern int check_filename(const char *prefix, const char *name);
  extern void verify_filename(const char *prefix,
                            const char *name,
@@@ -982,7 -957,7 +982,7 @@@ extern char *sha1_pack_index_name(cons
  extern const char *find_unique_abbrev(const unsigned char *sha1, int len);
  extern int find_unique_abbrev_r(char *hex, const unsigned char *sha1, int len);
  
 -extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
 +extern const unsigned char null_sha1[GIT_MAX_RAWSZ];
  extern const struct object_id null_oid;
  
  static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
@@@ -1070,6 -1045,9 +1070,6 @@@ static inline int is_empty_tree_oid(con
        return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN);
  }
  
 -
 -int git_mkstemp(char *path, size_t n, const char *template);
 -
  /* set default permissions by passing mode arguments to open(2) */
  int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
  int git_mkstemp_mode(char *pattern, int mode);
@@@ -1094,9 -1072,8 +1094,9 @@@ int adjust_shared_perm(const char *path
  
  /*
   * Create the directory containing the named path, using care to be
 - * somewhat safe against races.  Return one of the scld_error values
 - * to indicate success/failure.
 + * somewhat safe against races. Return one of the scld_error values to
 + * indicate success/failure. On error, set errno to describe the
 + * problem.
   *
   * SCLD_VANISHED indicates that one of the ancestor directories of the
   * path existed at one point during the function call and then
@@@ -1120,51 -1097,8 +1120,51 @@@ enum scld_error 
  enum scld_error safe_create_leading_directories(char *path);
  enum scld_error safe_create_leading_directories_const(const char *path);
  
 +/*
 + * Callback function for raceproof_create_file(). This function is
 + * expected to do something that makes dirname(path) permanent despite
 + * the fact that other processes might be cleaning up empty
 + * directories at the same time. Usually it will create a file named
 + * path, but alternatively it could create another file in that
 + * directory, or even chdir() into that directory. The function should
 + * return 0 if the action was completed successfully. On error, it
 + * should return a nonzero result and set errno.
 + * raceproof_create_file() treats two errno values specially:
 + *
 + * - ENOENT -- dirname(path) does not exist. In this case,
 + *             raceproof_create_file() tries creating dirname(path)
 + *             (and any parent directories, if necessary) and calls
 + *             the function again.
 + *
 + * - EISDIR -- the file already exists and is a directory. In this
 + *             case, raceproof_create_file() removes the directory if
 + *             it is empty (and recursively any empty directories that
 + *             it contains) and calls the function again.
 + *
 + * Any other errno causes raceproof_create_file() to fail with the
 + * callback's return value and errno.
 + *
 + * Obviously, this function should be OK with being called again if it
 + * fails with ENOENT or EISDIR. In other scenarios it will not be
 + * called again.
 + */
 +typedef int create_file_fn(const char *path, void *cb);
 +
 +/*
 + * Create a file in dirname(path) by calling fn, creating leading
 + * directories if necessary. Retry a few times in case we are racing
 + * with another process that is trying to clean up the directory that
 + * contains path. See the documentation for create_file_fn for more
 + * details.
 + *
 + * Return the value and set the errno that resulted from the most
 + * recent call of fn. fn is always called at least once, and will be
 + * called more than once if it returns ENOENT or EISDIR.
 + */
 +int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
 +
  int mkdir_in_gitdir(const char *path);
- extern char *expand_user_path(const char *path);
+ extern char *expand_user_path(const char *path, int real_home);
  const char *enter_repo(const char *path, int strict);
  static inline int is_absolute_path(const char *path)
  {
@@@ -1175,7 -1109,7 +1175,7 @@@ char *strbuf_realpath(struct strbuf *re
                      int die_on_error);
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
 -char *real_pathdup(const char *path);
 +char *real_pathdup(const char *path, int die_on_error);
  const char *absolute_path(const char *path);
  char *absolute_pathdup(const char *path);
  const char *remove_leading_path(const char *in, const char *prefix);
@@@ -1194,13 -1128,6 +1194,13 @@@ extern int is_ntfs_dotgit(const char *n
   */
  extern char *xdg_config_home(const char *filename);
  
 +/**
 + * Return a newly allocated string with the evaluation of
 + * "$XDG_CACHE_HOME/git/$filename" if $XDG_CACHE_HOME is non-empty, otherwise
 + * "$HOME/.cache/git/$filename". Return NULL upon error.
 + */
 +extern char *xdg_cache_home(const char *filename);
 +
  /* object replacement */
  #define LOOKUP_REPLACE_OBJECT 1
  #define LOOKUP_UNKNOWN_OBJECT 2
@@@ -1302,9 -1229,6 +1302,9 @@@ extern int has_pack_index(const unsigne
  
  extern void assert_sha1_type(const unsigned char *sha1, enum object_type expect);
  
 +/* Helper to check and "touch" a file */
 +extern int check_and_freshen_file(const char *fn, int freshen);
 +
  extern const signed char hexval_table[256];
  static inline unsigned int hexval(unsigned char c)
  {
@@@ -1364,7 -1288,7 +1364,7 @@@ extern int get_sha1_with_context(const 
  
  extern int get_oid(const char *str, struct object_id *oid);
  
 -typedef int each_abbrev_fn(const unsigned char *sha1, void *);
 +typedef int each_abbrev_fn(const struct object_id *oid, void *);
  extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
  
  extern int set_disambiguate_hint_config(const char *var, const char *value);
@@@ -1395,46 -1319,7 +1395,46 @@@ extern char *oid_to_hex_r(char *out, co
  extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
  extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
  
 -extern int interpret_branch_name(const char *str, int len, struct strbuf *);
 +/*
 + * Parse a 40-character hexadecimal object ID starting from hex, updating the
 + * pointer specified by end when parsing stops.  The resulting object ID is
 + * stored in oid.  Returns 0 on success.  Parsing will stop on the first NUL or
 + * other invalid character.  end is only updated on success; otherwise, it is
 + * unmodified.
 + */
 +extern int parse_oid_hex(const char *hex, struct object_id *oid, const char **end);
 +
 +/*
 + * This reads short-hand syntax that not only evaluates to a commit
 + * object name, but also can act as if the end user spelled the name
 + * of the branch from the command line.
 + *
 + * - "@{-N}" finds the name of the Nth previous branch we were on, and
 + *   places the name of the branch in the given buf and returns the
 + *   number of characters parsed if successful.
 + *
 + * - "<branch>@{upstream}" finds the name of the other ref that
 + *   <branch> is configured to merge with (missing <branch> defaults
 + *   to the current branch), and places the name of the branch in the
 + *   given buf and returns the number of characters parsed if
 + *   successful.
 + *
 + * If the input is not of the accepted format, it returns a negative
 + * number to signal an error.
 + *
 + * If the input was ok but there are not N branch switches in the
 + * reflog, it returns 0.
 + *
 + * If "allowed" is non-zero, it is a treated as a bitfield of allowable
 + * expansions: local branches ("refs/heads/"), remote branches
 + * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is
 + * allowed, even ones to refs outside of those namespaces.
 + */
 +#define INTERPRET_BRANCH_LOCAL (1<<0)
 +#define INTERPRET_BRANCH_REMOTE (1<<1)
 +#define INTERPRET_BRANCH_HEAD (1<<2)
 +extern int interpret_branch_name(const char *str, int len, struct strbuf *,
 +                               unsigned allowed);
  extern int get_oid_mb(const char *str, struct object_id *oid);
  
  extern int validate_headref(const char *ref);
@@@ -1678,30 -1563,6 +1678,30 @@@ extern struct packed_git *find_sha1_pac
  
  extern void pack_report(void);
  
 +/*
 + * Create a temporary file rooted in the object database directory, or
 + * die on failure. The filename is taken from "pattern", which should have the
 + * usual "XXXXXX" trailer, and the resulting filename is written into the
 + * "template" buffer. Returns the open descriptor.
 + */
 +extern int odb_mkstemp(struct strbuf *template, const char *pattern);
 +
 +/*
 + * Generate the filename to be used for a pack file with checksum "sha1" and
 + * extension "ext". The result is written into the strbuf "buf", overwriting
 + * any existing contents. A pointer to buf->buf is returned as a convenience.
 + *
 + * Example: odb_pack_name(out, sha1, "idx") => ".git/objects/pack/pack-1234..idx"
 + */
 +extern char *odb_pack_name(struct strbuf *buf, const unsigned char *sha1, const char *ext);
 +
 +/*
 + * Create a pack .keep file named "name" (which should generally be the output
 + * of odb_pack_name). Returns a file descriptor opened for writing, or -1 on
 + * error.
 + */
 +extern int odb_pack_keep(const char *name);
 +
  /*
   * mmap the index file for the specified packfile (if it is not
   * already mmapped).  Return 0 on success.
@@@ -1738,12 -1599,6 +1738,12 @@@ extern void check_pack_index_ptr(const 
   * error.
   */
  extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t n);
 +/*
 + * Like nth_packed_object_sha1, but write the data into the object specified by
 + * the the first argument.  Returns the first argument on success, and NULL on
 + * error.
 + */
 +extern const struct object_id *nth_packed_object_oid(struct object_id *, struct packed_git *, uint32_t n);
  
  /*
   * Return the offset of the nth object within the specified packfile.
@@@ -1785,7 -1640,7 +1785,7 @@@ extern int unpack_object_header(struct 
   * scratch buffer, but restored to its original contents before
   * the function returns.
   */
 -typedef int each_loose_object_fn(const unsigned char *sha1,
 +typedef int each_loose_object_fn(const struct object_id *oid,
                                 const char *path,
                                 void *data);
  typedef int each_loose_cruft_fn(const char *basename,
@@@ -1811,7 -1666,7 +1811,7 @@@ int for_each_loose_file_in_objdir_buf(s
   * LOCAL_ONLY flag is set).
   */
  #define FOR_EACH_OBJECT_LOCAL_ONLY 0x1
 -typedef int each_packed_object_fn(const unsigned char *sha1,
 +typedef int each_packed_object_fn(const struct object_id *oid,
                                  struct packed_git *pack,
                                  uint32_t pos,
                                  void *data);
@@@ -1898,7 -1753,6 +1898,7 @@@ extern int git_config_from_blob_sha1(co
                                     const unsigned char *sha1, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
 +extern void read_early_config(config_fn_t cb, void *data);
  extern void git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
                                   struct git_config_source *config_source,
@@@ -1908,7 -1762,6 +1908,7 @@@ extern int git_parse_maybe_bool(const c
  extern int git_config_int(const char *, const char *);
  extern int64_t git_config_int64(const char *, const char *);
  extern unsigned long git_config_ulong(const char *, const char *);
 +extern ssize_t git_config_ssize_t(const char *, const char *);
  extern int git_config_bool_or_int(const char *, const char *, int *);
  extern int git_config_bool(const char *, const char *);
  extern int git_config_maybe_bool(const char *, const char *);
@@@ -1966,11 -1819,8 +1966,11 @@@ extern int git_config_include(const cha
   *
   * (i.e., what gets handed to a config_fn_t). The caller provides the section;
   * we return -1 if it does not match, 0 otherwise. The subsection and key
 - * out-parameters are filled by the function (and subsection is NULL if it is
 + * out-parameters are filled by the function (and *subsection is NULL if it is
   * missing).
 + *
 + * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if
 + * there is no subsection at all.
   */
  extern int parse_config_key(const char *var,
                            const char *section,
@@@ -2032,11 -1882,6 +2032,11 @@@ extern int git_config_get_bool_or_int(c
  extern int git_config_get_maybe_bool(const char *key, int *dest);
  extern int git_config_get_pathname(const char *key, const char **dest);
  extern int git_config_get_untracked_cache(void);
 +extern int git_config_get_split_index(void);
 +extern int git_config_get_max_percent_split_change(void);
 +
 +/* This dies if the configured or default date is in the future */
 +extern int git_config_get_expiry(const char *key, const char **output);
  
  /*
   * This is a hack for test programs like test-dump-untracked-cache to
diff --combined config.c
+++ b/config.c
@@@ -135,7 -135,7 +135,7 @@@ static int handle_path_include(const ch
        if (!path)
                return config_error_nonbool("include.path");
  
-       expanded = expand_user_path(path);
+       expanded = expand_user_path(path, 0);
        if (!expanded)
                return error("could not expand include path '%s'", path);
        path = expanded;
@@@ -177,7 -177,7 +177,7 @@@ static int prepare_include_condition_pa
        char *expanded;
        int prefix = 0;
  
-       expanded = expand_user_path(pat->buf);
+       expanded = expand_user_path(pat->buf, 1);
        if (expanded) {
                strbuf_reset(pat);
                strbuf_addstr(pat, expanded);
                        return error(_("relative config include "
                                       "conditionals must come from files"));
  
-               strbuf_add_absolute_path(&path, cf->path);
+               strbuf_realpath(&path, cf->path, 1);
                slash = find_last_dir_sep(path.buf);
                if (!slash)
                        die("BUG: how is this possible?");
@@@ -213,7 -213,7 +213,7 @@@ static int include_by_gitdir(const cha
        struct strbuf pattern = STRBUF_INIT;
        int ret = 0, prefix;
  
-       strbuf_add_absolute_path(&text, get_git_dir());
+       strbuf_realpath(&text, get_git_dir(), 1);
        strbuf_add(&pattern, cond, cond_len);
        prefix = prepare_include_condition_pattern(&pattern);
  
@@@ -293,105 -293,11 +293,105 @@@ void git_config_push_parameter(const ch
        strbuf_release(&env);
  }
  
 +static inline int iskeychar(int c)
 +{
 +      return isalnum(c) || c == '-';
 +}
 +
 +/*
 + * Auxiliary function to sanity-check and split the key into the section
 + * identifier and variable name.
 + *
 + * Returns 0 on success, -1 when there is an invalid character in the key and
 + * -2 if there is no section name in the key.
 + *
 + * store_key - pointer to char* which will hold a copy of the key with
 + *             lowercase section and variable name
 + * baselen - pointer to int which will hold the length of the
 + *           section + subsection part, can be NULL
 + */
 +static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
 +{
 +      int i, dot, baselen;
 +      const char *last_dot = strrchr(key, '.');
 +
 +      /*
 +       * Since "key" actually contains the section name and the real
 +       * key name separated by a dot, we have to know where the dot is.
 +       */
 +
 +      if (last_dot == NULL || last_dot == key) {
 +              if (!quiet)
 +                      error("key does not contain a section: %s", key);
 +              return -CONFIG_NO_SECTION_OR_NAME;
 +      }
 +
 +      if (!last_dot[1]) {
 +              if (!quiet)
 +                      error("key does not contain variable name: %s", key);
 +              return -CONFIG_NO_SECTION_OR_NAME;
 +      }
 +
 +      baselen = last_dot - key;
 +      if (baselen_)
 +              *baselen_ = baselen;
 +
 +      /*
 +       * Validate the key and while at it, lower case it for matching.
 +       */
 +      if (store_key)
 +              *store_key = xmallocz(strlen(key));
 +
 +      dot = 0;
 +      for (i = 0; key[i]; i++) {
 +              unsigned char c = key[i];
 +              if (c == '.')
 +                      dot = 1;
 +              /* Leave the extended basename untouched.. */
 +              if (!dot || i > baselen) {
 +                      if (!iskeychar(c) ||
 +                          (i == baselen + 1 && !isalpha(c))) {
 +                              if (!quiet)
 +                                      error("invalid key: %s", key);
 +                              goto out_free_ret_1;
 +                      }
 +                      c = tolower(c);
 +              } else if (c == '\n') {
 +                      if (!quiet)
 +                              error("invalid key (newline): %s", key);
 +                      goto out_free_ret_1;
 +              }
 +              if (store_key)
 +                      (*store_key)[i] = c;
 +      }
 +
 +      return 0;
 +
 +out_free_ret_1:
 +      if (store_key) {
 +              free(*store_key);
 +              *store_key = NULL;
 +      }
 +      return -CONFIG_INVALID_KEY;
 +}
 +
 +int git_config_parse_key(const char *key, char **store_key, int *baselen)
 +{
 +      return git_config_parse_key_1(key, store_key, baselen, 0);
 +}
 +
 +int git_config_key_is_valid(const char *key)
 +{
 +      return !git_config_parse_key_1(key, NULL, NULL, 1);
 +}
 +
  int git_config_parse_parameter(const char *text,
                               config_fn_t fn, void *data)
  {
        const char *value;
 +      char *canonical_name;
        struct strbuf **pair;
 +      int ret;
  
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
                strbuf_list_free(pair);
                return error("bogus config parameter: %s", text);
        }
 -      strbuf_tolower(pair[0]);
 -      if (fn(pair[0]->buf, value, data) < 0) {
 -              strbuf_list_free(pair);
 -              return -1;
 +
 +      if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {
 +              ret = -1;
 +      } else {
 +              ret = (fn(canonical_name, value, data) < 0) ? -1 : 0;
 +              free(canonical_name);
        }
        strbuf_list_free(pair);
 -      return 0;
 +      return ret;
  }
  
  int git_config_from_parameters(config_fn_t fn, void *data)
@@@ -544,6 -448,11 +544,6 @@@ static char *parse_value(void
        }
  }
  
 -static inline int iskeychar(int c)
 -{
 -      return isalnum(c) || c == '-';
 -}
 -
  static int get_value(config_fn_t fn, void *data, struct strbuf *name)
  {
        int c;
@@@ -834,15 -743,6 +834,15 @@@ int git_parse_ulong(const char *value, 
        return 1;
  }
  
 +static int git_parse_ssize_t(const char *value, ssize_t *ret)
 +{
 +      intmax_t tmp;
 +      if (!git_parse_signed(value, &tmp, maximum_signed_value_of_type(ssize_t)))
 +              return 0;
 +      *ret = tmp;
 +      return 1;
 +}
 +
  NORETURN
  static void die_bad_number(const char *name, const char *value)
  {
@@@ -901,14 -801,6 +901,14 @@@ unsigned long git_config_ulong(const ch
        return ret;
  }
  
 +ssize_t git_config_ssize_t(const char *name, const char *value)
 +{
 +      ssize_t ret;
 +      if (!git_parse_ssize_t(value, &ret))
 +              die_bad_number(name, value);
 +      return ret;
 +}
 +
  int git_parse_maybe_bool(const char *value)
  {
        if (!value)
@@@ -965,7 -857,7 +965,7 @@@ int git_config_pathname(const char **de
  {
        if (!value)
                return config_error_nonbool(var);
-       *dest = expand_user_path(value);
+       *dest = expand_user_path(value, 0);
        if (!*dest)
                die(_("failed to expand user dir in: '%s'"), value);
        return 0;
@@@ -1515,7 -1407,7 +1515,7 @@@ static int do_git_config_sequence(confi
  {
        int ret = 0;
        char *xdg_config = xdg_config_home("config");
-       char *user_config = expand_user_path("~/.gitconfig");
+       char *user_config = expand_user_path("~/.gitconfig", 0);
        char *repo_config = have_git_dir() ? git_pathdup("config") : NULL;
  
        current_parsing_scope = CONFIG_SCOPE_SYSTEM;
@@@ -1612,31 -1504,6 +1612,31 @@@ static void configset_iter(struct confi
        }
  }
  
 +void read_early_config(config_fn_t cb, void *data)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +
 +      git_config_with_options(cb, data, NULL, 1);
 +
 +      /*
 +       * When setup_git_directory() was not yet asked to discover the
 +       * GIT_DIR, we ask discover_git_directory() to figure out whether there
 +       * is any repository config we should use (but unlike
 +       * setup_git_directory_gently(), no global state is changed, most
 +       * notably, the current working directory is still the same after the
 +       * call).
 +       */
 +      if (!have_git_dir() && discover_git_directory(&buf)) {
 +              struct git_config_source repo_config;
 +
 +              memset(&repo_config, 0, sizeof(repo_config));
 +              strbuf_addstr(&buf, "/config");
 +              repo_config.file = buf.buf;
 +              git_config_with_options(cb, data, &repo_config, 1);
 +      }
 +      strbuf_release(&buf);
 +}
 +
  static void git_config_check_init(void);
  
  void git_config(config_fn_t fn, void *data)
@@@ -1937,19 -1804,6 +1937,19 @@@ int git_config_get_pathname(const char 
        return ret;
  }
  
 +int git_config_get_expiry(const char *key, const char **output)
 +{
 +      int ret = git_config_get_string_const(key, output);
 +      if (ret)
 +              return ret;
 +      if (strcmp(*output, "now")) {
 +              unsigned long now = approxidate("now");
 +              if (approxidate(*output) >= now)
 +                      git_die_config(key, _("Invalid %s: '%s'"), key, *output);
 +      }
 +      return ret;
 +}
 +
  int git_config_get_untracked_cache(void)
  {
        int val = -1;
                if (!strcasecmp(v, "keep"))
                        return -1;
  
 -              error("unknown core.untrackedCache value '%s'; "
 -                    "using 'keep' default value", v);
 +              error(_("unknown core.untrackedCache value '%s'; "
 +                      "using 'keep' default value"), v);
                return -1;
        }
  
        return -1; /* default value */
  }
  
 +int git_config_get_split_index(void)
 +{
 +      int val;
 +
 +      if (!git_config_get_maybe_bool("core.splitindex", &val))
 +              return val;
 +
 +      return -1; /* default value */
 +}
 +
 +int git_config_get_max_percent_split_change(void)
 +{
 +      int val = -1;
 +
 +      if (!git_config_get_int("splitindex.maxpercentchange", &val)) {
 +              if (0 <= val && val <= 100)
 +                      return val;
 +
 +              return error(_("splitIndex.maxPercentChange value '%d' "
 +                             "should be between 0 and 100"), val);
 +      }
 +
 +      return -1; /* default value */
 +}
 +
  NORETURN
  void git_die_config_linenr(const char *key, const char *filename, int linenr)
  {
@@@ -2252,6 -2081,93 +2252,6 @@@ void git_config_set(const char *key, co
        git_config_set_multivar(key, value, NULL, 0);
  }
  
 -/*
 - * Auxiliary function to sanity-check and split the key into the section
 - * identifier and variable name.
 - *
 - * Returns 0 on success, -1 when there is an invalid character in the key and
 - * -2 if there is no section name in the key.
 - *
 - * store_key - pointer to char* which will hold a copy of the key with
 - *             lowercase section and variable name
 - * baselen - pointer to int which will hold the length of the
 - *           section + subsection part, can be NULL
 - */
 -static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
 -{
 -      int i, dot, baselen;
 -      const char *last_dot = strrchr(key, '.');
 -
 -      /*
 -       * Since "key" actually contains the section name and the real
 -       * key name separated by a dot, we have to know where the dot is.
 -       */
 -
 -      if (last_dot == NULL || last_dot == key) {
 -              if (!quiet)
 -                      error("key does not contain a section: %s", key);
 -              return -CONFIG_NO_SECTION_OR_NAME;
 -      }
 -
 -      if (!last_dot[1]) {
 -              if (!quiet)
 -                      error("key does not contain variable name: %s", key);
 -              return -CONFIG_NO_SECTION_OR_NAME;
 -      }
 -
 -      baselen = last_dot - key;
 -      if (baselen_)
 -              *baselen_ = baselen;
 -
 -      /*
 -       * Validate the key and while at it, lower case it for matching.
 -       */
 -      if (store_key)
 -              *store_key = xmallocz(strlen(key));
 -
 -      dot = 0;
 -      for (i = 0; key[i]; i++) {
 -              unsigned char c = key[i];
 -              if (c == '.')
 -                      dot = 1;
 -              /* Leave the extended basename untouched.. */
 -              if (!dot || i > baselen) {
 -                      if (!iskeychar(c) ||
 -                          (i == baselen + 1 && !isalpha(c))) {
 -                              if (!quiet)
 -                                      error("invalid key: %s", key);
 -                              goto out_free_ret_1;
 -                      }
 -                      c = tolower(c);
 -              } else if (c == '\n') {
 -                      if (!quiet)
 -                              error("invalid key (newline): %s", key);
 -                      goto out_free_ret_1;
 -              }
 -              if (store_key)
 -                      (*store_key)[i] = c;
 -      }
 -
 -      return 0;
 -
 -out_free_ret_1:
 -      if (store_key) {
 -              free(*store_key);
 -              *store_key = NULL;
 -      }
 -      return -CONFIG_INVALID_KEY;
 -}
 -
 -int git_config_parse_key(const char *key, char **store_key, int *baselen)
 -{
 -      return git_config_parse_key_1(key, store_key, baselen, 0);
 -}
 -
 -int git_config_key_is_valid(const char *key)
 -{
 -      return !git_config_parse_key_1(key, NULL, NULL, 1);
 -}
 -
  /*
   * If value==NULL, unset in (remove from) config,
   * if value_regex!=NULL, disregard key/value pairs where value does not match.
@@@ -2712,10 -2628,11 +2712,10 @@@ int parse_config_key(const char *var
                     const char **subsection, int *subsection_len,
                     const char **key)
  {
 -      int section_len = strlen(section);
        const char *dot;
  
        /* Does it start with "section." ? */
 -      if (!starts_with(var, section) || var[section_len] != '.')
 +      if (!skip_prefix(var, section, &var) || *var != '.')
                return -1;
  
        /*
        *key = dot + 1;
  
        /* Did we have a subsection at all? */
 -      if (dot == var + section_len) {
 -              *subsection = NULL;
 -              *subsection_len = 0;
 +      if (dot == var) {
 +              if (subsection) {
 +                      *subsection = NULL;
 +                      *subsection_len = 0;
 +              }
        }
        else {
 -              *subsection = var + section_len + 1;
 +              if (!subsection)
 +                      return -1;
 +              *subsection = var + 1;
                *subsection_len = dot - *subsection;
        }
  
diff --combined credential-cache.c
@@@ -83,19 -83,6 +83,19 @@@ static void do_cache(const char *socket
        strbuf_release(&buf);
  }
  
-       old_dir = expand_user_path("~/.git-credential-cache");
 +static char *get_socket_path(void)
 +{
 +      struct stat sb;
 +      char *old_dir, *socket;
++      old_dir = expand_user_path("~/.git-credential-cache", 0);
 +      if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode))
 +              socket = xstrfmt("%s/socket", old_dir);
 +      else
 +              socket = xdg_cache_home("credential/socket");
 +      free(old_dir);
 +      return socket;
 +}
 +
  int cmd_main(int argc, const char **argv)
  {
        char *socket_path = NULL;
        op = argv[0];
  
        if (!socket_path)
 -              socket_path = expand_user_path("~/.git-credential-cache/socket", 0);
 +              socket_path = get_socket_path();
        if (!socket_path)
                die("unable to find a suitable socket path; use --socket");
  
diff --combined path.c
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -471,19 -471,39 +471,19 @@@ const char *worktree_git_path(const str
  }
  
  /* Returns 0 on success, negative on failure. */
 -#define SUBMODULE_PATH_ERR_NOT_CONFIGURED -1
  static int do_submodule_path(struct strbuf *buf, const char *path,
                             const char *fmt, va_list args)
  {
 -      const char *git_dir;
        struct strbuf git_submodule_common_dir = STRBUF_INIT;
        struct strbuf git_submodule_dir = STRBUF_INIT;
 -      const struct submodule *sub;
 -      int err = 0;
 +      int ret;
  
 -      strbuf_addstr(buf, path);
 -      strbuf_complete(buf, '/');
 -      strbuf_addstr(buf, ".git");
 -
 -      git_dir = read_gitfile(buf->buf);
 -      if (git_dir) {
 -              strbuf_reset(buf);
 -              strbuf_addstr(buf, git_dir);
 -      }
 -      if (!is_git_directory(buf->buf)) {
 -              gitmodules_config();
 -              sub = submodule_from_path(null_sha1, path);
 -              if (!sub) {
 -                      err = SUBMODULE_PATH_ERR_NOT_CONFIGURED;
 -                      goto cleanup;
 -              }
 -              strbuf_reset(buf);
 -              strbuf_git_path(buf, "%s/%s", "modules", sub->name);
 -      }
 -
 -      strbuf_addch(buf, '/');
 -      strbuf_addbuf(&git_submodule_dir, buf);
 +      ret = submodule_to_gitdir(&git_submodule_dir, path);
 +      if (ret)
 +              goto cleanup;
  
 +      strbuf_complete(&git_submodule_dir, '/');
 +      strbuf_addbuf(buf, &git_submodule_dir);
        strbuf_vaddf(buf, fmt, args);
  
        if (get_common_dir_noenv(&git_submodule_common_dir, git_submodule_dir.buf))
  cleanup:
        strbuf_release(&git_submodule_dir);
        strbuf_release(&git_submodule_common_dir);
 -
 -      return err;
 +      return ret;
  }
  
  char *git_pathdup_submodule(const char *path, const char *fmt, ...)
@@@ -617,8 -638,10 +617,10 @@@ static struct passwd *getpw_str(const c
   * Return a string with ~ and ~user expanded via getpw*.  If buf != NULL,
   * then it is a newly allocated string. Returns NULL on getpw failure or
   * if path is NULL.
+  *
+  * If real_home is true, real_path($HOME) is used in the expansion.
   */
- char *expand_user_path(const char *path)
+ char *expand_user_path(const char *path, int real_home)
  {
        struct strbuf user_path = STRBUF_INIT;
        const char *to_copy = path;
                        const char *home = getenv("HOME");
                        if (!home)
                                goto return_null;
-                       strbuf_addstr(&user_path, home);
+                       if (real_home)
+                               strbuf_addstr(&user_path, real_path(home));
+                       else
+                               strbuf_addstr(&user_path, home);
  #ifdef GIT_WINDOWS_NATIVE
                        convert_slashes(user_path.buf);
  #endif
@@@ -702,7 -728,7 +707,7 @@@ const char *enter_repo(const char *path
                strbuf_add(&validated_path, path, len);
  
                if (used_path.buf[0] == '~') {
-                       char *newpath = expand_user_path(used_path.buf);
+                       char *newpath = expand_user_path(used_path.buf, 0);
                        if (!newpath)
                                return NULL;
                        strbuf_attach(&used_path, newpath, strlen(newpath),
@@@ -1251,21 -1277,6 +1256,21 @@@ char *xdg_config_home(const char *filen
        return NULL;
  }
  
 +char *xdg_cache_home(const char *filename)
 +{
 +      const char *home, *cache_home;
 +
 +      assert(filename);
 +      cache_home = getenv("XDG_CACHE_HOME");
 +      if (cache_home && *cache_home)
 +              return mkpathdup("%s/git/%s", cache_home, filename);
 +
 +      home = getenv("HOME");
 +      if (home)
 +              return mkpathdup("%s/.cache/git/%s", home, filename);
 +      return NULL;
 +}
 +
  GIT_PATH_FUNC(git_path_cherry_pick_head, "CHERRY_PICK_HEAD")
  GIT_PATH_FUNC(git_path_revert_head, "REVERT_HEAD")
  GIT_PATH_FUNC(git_path_squash_msg, "SQUASH_MSG")