Merge branch 'pt/xdg-config-path'
authorJunio C Hamano <gitster@pobox.com>
Mon, 11 May 2015 21:24:01 +0000 (14:24 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 11 May 2015 21:24:01 +0000 (14:24 -0700)
Code clean-up for xdg configuration path support.

* pt/xdg-config-path:
path.c: remove home_config_paths()
git-config: replace use of home_config_paths()
git-commit: replace use of home_config_paths()
credential-store.c: replace home_config_paths() with xdg_config_home()
dir.c: replace home_config_paths() with xdg_config_home()
attr.c: replace home_config_paths() with xdg_config_home()
path.c: implement xdg_config_home()

1  2 
attr.c
builtin/commit.c
builtin/config.c
cache.h
config.c
credential-store.c
dir.c
path.c
diff --combined attr.c
index 7f445965c1886fe8cb53966969b48f4b0bde4826,c82904b6f291b2285f72afc1fa38244a9fa7669b..8f2ac6c88c8c2f7cff514981a7c01c136f734892
--- 1/attr.c
--- 2/attr.c
+++ b/attr.c
@@@ -12,7 -12,6 +12,7 @@@
  #include "exec_cmd.h"
  #include "attr.h"
  #include "dir.h"
 +#include "utf8.h"
  
  const char git_attr__true[] = "(builtin)true";
  const char git_attr__false[] = "\0(builtin)false";
@@@ -33,12 -32,9 +33,12 @@@ struct git_attr 
        struct git_attr *next;
        unsigned h;
        int attr_nr;
 +      int maybe_macro;
 +      int maybe_real;
        char name[FLEX_ARRAY];
  };
  static int attr_nr;
 +static int cannot_trust_maybe_real;
  
  static struct git_attr_check *check_all_attr;
  static struct git_attr *(git_attr_hash[HASHSIZE]);
@@@ -99,8 -95,6 +99,8 @@@ static struct git_attr *git_attr_intern
        a->h = hval;
        a->next = git_attr_hash[pos];
        a->attr_nr = attr_nr++;
 +      a->maybe_macro = 0;
 +      a->maybe_real = 0;
        git_attr_hash[pos] = a;
  
        REALLOC_ARRAY(check_all_attr, attr_nr);
@@@ -250,10 -244,9 +250,10 @@@ static struct match_attr *parse_attr_li
                      sizeof(*res) +
                      sizeof(struct attr_state) * num_attr +
                      (is_macro ? 0 : namelen + 1));
 -      if (is_macro)
 +      if (is_macro) {
                res->u.attr = git_attr_internal(name, namelen);
 -      else {
 +              res->u.attr->maybe_macro = 1;
 +      } else {
                char *p = (char *)&(res->state[num_attr]);
                memcpy(p, name, namelen);
                res->u.pat.pattern = p;
        /* Second pass to fill the attr_states */
        for (cp = states, i = 0; *cp; i++) {
                cp = parse_attr(src, lineno, cp, &(res->state[i]));
 +              if (!is_macro)
 +                      res->state[i].attr->maybe_real = 1;
 +              if (res->state[i].attr->maybe_macro)
 +                      cannot_trust_maybe_real = 1;
        }
  
        return res;
@@@ -380,12 -369,8 +380,12 @@@ static struct attr_stack *read_attr_fro
                return NULL;
        }
        res = xcalloc(1, sizeof(*res));
 -      while (fgets(buf, sizeof(buf), fp))
 -              handle_attr_line(res, buf, path, ++lineno, macro_ok);
 +      while (fgets(buf, sizeof(buf), fp)) {
 +              char *bufp = buf;
 +              if (!lineno)
 +                      skip_utf8_bom(&bufp, strlen(bufp));
 +              handle_attr_line(res, bufp, path, ++lineno, macro_ok);
 +      }
        fclose(fp);
        return res;
  }
@@@ -493,7 -478,6 +493,6 @@@ static int git_attr_system(void
  static void bootstrap_attr_stack(void)
  {
        struct attr_stack *elem;
-       char *xdg_attributes_file;
  
        if (attr_stack)
                return;
                }
        }
  
-       if (!git_attributes_file) {
-               home_config_paths(NULL, &xdg_attributes_file, "attributes");
-               git_attributes_file = xdg_attributes_file;
-       }
+       if (!git_attributes_file)
+               git_attributes_file = xdg_config_home("attributes");
        if (git_attributes_file) {
                elem = read_attr_from_file(git_attributes_file, 1);
                if (elem) {
@@@ -696,14 -678,13 +693,14 @@@ static int fill(const char *path, int p
        return rem;
  }
  
 -static int macroexpand_one(int attr_nr, int rem)
 +static int macroexpand_one(int nr, int rem)
  {
        struct attr_stack *stk;
        struct match_attr *a = NULL;
        int i;
  
 -      if (check_all_attr[attr_nr].value != ATTR__TRUE)
 +      if (check_all_attr[nr].value != ATTR__TRUE ||
 +          !check_all_attr[nr].attr->maybe_macro)
                return rem;
  
        for (stk = attr_stack; !a && stk; stk = stk->prev)
                        struct match_attr *ma = stk->attrs[i];
                        if (!ma->is_macro)
                                continue;
 -                      if (ma->u.attr->attr_nr == attr_nr)
 +                      if (ma->u.attr->attr_nr == nr)
                                a = ma;
                }
  
  }
  
  /*
 - * Collect all attributes for path into the array pointed to by
 - * check_all_attr.
 + * Collect attributes for path into the array pointed to by
 + * check_all_attr. If num is non-zero, only attributes in check[] are
 + * collected. Otherwise all attributes are collected.
   */
 -static void collect_all_attrs(const char *path)
 +static void collect_some_attrs(const char *path, int num,
 +                             struct git_attr_check *check)
 +
  {
        struct attr_stack *stk;
        int i, pathlen, rem, dirlen;
        prepare_attr_stack(path, dirlen);
        for (i = 0; i < attr_nr; i++)
                check_all_attr[i].value = ATTR__UNKNOWN;
 +      if (num && !cannot_trust_maybe_real) {
 +              rem = 0;
 +              for (i = 0; i < num; i++) {
 +                      if (!check[i].attr->maybe_real) {
 +                              struct git_attr_check *c;
 +                              c = check_all_attr + check[i].attr->attr_nr;
 +                              c->value = ATTR__UNSET;
 +                              rem++;
 +                      }
 +              }
 +              if (rem == num)
 +                      return;
 +      }
  
        rem = attr_nr;
        for (stk = attr_stack; 0 < rem && stk; stk = stk->prev)
@@@ -774,7 -739,7 +771,7 @@@ int git_check_attr(const char *path, in
  {
        int i;
  
 -      collect_all_attrs(path);
 +      collect_some_attrs(path, num, check);
  
        for (i = 0; i < num; i++) {
                const char *value = check_all_attr[check[i].attr->attr_nr].value;
@@@ -790,7 -755,7 +787,7 @@@ int git_all_attrs(const char *path, in
  {
        int i, count, j;
  
 -      collect_all_attrs(path);
 +      collect_some_attrs(path, 0, NULL);
  
        /* Count the number of attributes that are set. */
        count = 0;
diff --combined builtin/commit.c
index 310674cfd0f25c52b9952678eeb3ee164fd97fea,b4aaaab5bc908d440ab195072ea0b7aa4fac15d4..d6515a2a50e78e9376f1b55fa6fe79b1e867f25c
  #include "mailmap.h"
  
  static const char * const builtin_commit_usage[] = {
 -      N_("git commit [options] [--] <pathspec>..."),
 +      N_("git commit [<options>] [--] <pathspec>..."),
        NULL
  };
  
  static const char * const builtin_status_usage[] = {
 -      N_("git status [options] [--] <pathspec>..."),
 +      N_("git status [<options>] [--] <pathspec>..."),
        NULL
  };
  
@@@ -170,7 -170,7 +170,7 @@@ static void determine_whence(struct wt_
                whence = FROM_MERGE;
        else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
                whence = FROM_CHERRY_PICK;
 -              if (file_exists(git_path("sequencer")))
 +              if (file_exists(git_path(SEQ_DIR)))
                        sequencer_in_use = 1;
        }
        else
@@@ -229,7 -229,7 +229,7 @@@ static int commit_index_files(void
  static int list_paths(struct string_list *list, const char *with_tree,
                      const char *prefix, const struct pathspec *pattern)
  {
 -      int i;
 +      int i, ret;
        char *m;
  
        if (!pattern->nr)
                        item->util = item; /* better a valid pointer than a fake one */
        }
  
 -      return report_path_error(m, pattern, prefix);
 +      ret = report_path_error(m, pattern, prefix);
 +      free(m);
 +      return ret;
  }
  
  static void add_remove_files(struct string_list *list)
@@@ -561,14 -559,20 +561,14 @@@ static void set_ident_var(char **buf, c
        *buf = val;
  }
  
 -static char *envdup(const char *var)
 -{
 -      const char *val = getenv(var);
 -      return val ? xstrdup(val) : NULL;
 -}
 -
  static void determine_author_info(struct strbuf *author_ident)
  {
        char *name, *email, *date;
        struct ident_split author;
  
 -      name = envdup("GIT_AUTHOR_NAME");
 -      email = envdup("GIT_AUTHOR_EMAIL");
 -      date = envdup("GIT_AUTHOR_DATE");
 +      name = xstrdup_or_null(getenv("GIT_AUTHOR_NAME"));
 +      email = xstrdup_or_null(getenv("GIT_AUTHOR_EMAIL"));
 +      date = xstrdup_or_null(getenv("GIT_AUTHOR_DATE"));
  
        if (author_message) {
                struct ident_split ident;
@@@ -1052,7 -1056,7 +1052,7 @@@ static const char *find_author_by_nickn
                clear_mailmap(&mailmap);
                return strbuf_detach(&buf, NULL);
        }
 -      die(_("No existing author found with '%s'"), name);
 +      die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name);
  }
  
  
@@@ -1398,12 -1402,10 +1398,10 @@@ int cmd_status(int argc, const char **a
  
  static const char *implicit_ident_advice(void)
  {
-       char *user_config = NULL;
-       char *xdg_config = NULL;
-       int config_exists;
+       char *user_config = expand_user_path("~/.gitconfig");
+       char *xdg_config = xdg_config_home("config");
+       int config_exists = file_exists(user_config) || file_exists(xdg_config);
  
-       home_config_paths(&user_config, &xdg_config, "config");
-       config_exists = file_exists(user_config) || file_exists(xdg_config);
        free(user_config);
        free(xdg_config);
  
@@@ -1768,8 -1770,8 +1766,8 @@@ int cmd_commit(int argc, const char **a
        if (!transaction ||
            ref_transaction_update(transaction, "HEAD", sha1,
                                   current_head
 -                                 ? current_head->object.sha1 : NULL,
 -                                 0, !!current_head, sb.buf, &err) ||
 +                                 ? current_head->object.sha1 : null_sha1,
 +                                 0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                rollback_index_files();
                die("%s", err.buf);
diff --combined builtin/config.c
index 28f57c8fb96c61151042a670ee70722eb8ead143,2f8bf7d7cfdb36029046c1d5a28af56ad708a9c4..7188405f7ef587d7b09b1cf2c6e422c8385ed794
@@@ -5,7 -5,7 +5,7 @@@
  #include "urlmatch.h"
  
  static const char *const builtin_config_usage[] = {
 -      N_("git config [options]"),
 +      N_("git config [<options>]"),
        NULL
  };
  
@@@ -193,7 -193,7 +193,7 @@@ static int get_value(const char *key_, 
  
                key_regexp = (regex_t*)xmalloc(sizeof(regex_t));
                if (regcomp(key_regexp, key, REG_EXTENDED)) {
 -                      fprintf(stderr, "Invalid key pattern: %s\n", key_);
 +                      error("invalid key pattern: %s", key_);
                        free(key_regexp);
                        key_regexp = NULL;
                        ret = CONFIG_INVALID_PATTERN;
  
                regexp = (regex_t*)xmalloc(sizeof(regex_t));
                if (regcomp(regexp, regex_, REG_EXTENDED)) {
 -                      fprintf(stderr, "Invalid pattern: %s\n", regex_);
 +                      error("invalid pattern: %s", regex_);
                        free(regexp);
                        regexp = NULL;
                        ret = CONFIG_INVALID_PATTERN;
@@@ -455,9 -455,9 +455,9 @@@ static char *default_user_config(void
        struct strbuf buf = STRBUF_INIT;
        strbuf_addf(&buf,
                    _("# This is Git's per-user configuration file.\n"
 -                    "[core]\n"
 +                    "[user]\n"
                      "# Please adapt and uncomment the following lines:\n"
 -                    "#        user = %s\n"
 +                    "#        name = %s\n"
                      "#        email = %s\n"),
                    ident_default_name(),
                    ident_default_email());
@@@ -488,10 -488,8 +488,8 @@@ int cmd_config(int argc, const char **a
        }
  
        if (use_global_config) {
-               char *user_config = NULL;
-               char *xdg_config = NULL;
-               home_config_paths(&user_config, &xdg_config, "config");
+               char *user_config = expand_user_path("~/.gitconfig");
+               char *xdg_config = xdg_config_home("config");
  
                if (!user_config)
                        /*
diff --combined cache.h
index 5970940743618fcee3490a2d082b46e934ee2e3d,aa8d3774483b17128b7f44eaaeb2b58a0bc91883..54f108a28fc0442c7c4c053f5ad2a24c40037bd0
+++ b/cache.h
@@@ -43,14 -43,6 +43,14 @@@ int git_deflate_end_gently(git_zstream 
  int git_deflate(git_zstream *, int flush);
  unsigned long git_deflate_bound(git_zstream *, unsigned long);
  
 +/* The length in bytes and in hex digits of an object name (SHA-1 value). */
 +#define GIT_SHA1_RAWSZ 20
 +#define GIT_SHA1_HEXSZ (2 * GIT_SHA1_RAWSZ)
 +
 +struct object_id {
 +      unsigned char hash[GIT_SHA1_RAWSZ];
 +};
 +
  #if defined(DT_UNKNOWN) && !defined(NO_D_TYPE_IN_DIRENT)
  #define DTYPE(de)     ((de)->d_type)
  #else
@@@ -378,7 -370,6 +378,7 @@@ static inline enum object_type object_t
  
  /* Double-check local_repo_env below if you add to this list. */
  #define GIT_DIR_ENVIRONMENT "GIT_DIR"
 +#define GIT_COMMON_DIR_ENVIRONMENT "GIT_COMMON_DIR"
  #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
  #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
  #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
@@@ -432,13 -423,11 +432,13 @@@ extern int is_inside_git_dir(void)
  extern char *git_work_tree_cfg;
  extern int is_inside_work_tree(void);
  extern const char *get_git_dir(void);
 +extern const char *get_git_common_dir(void);
  extern int is_git_directory(const char *path);
  extern char *get_object_directory(void);
  extern char *get_index_file(void);
  extern char *get_graft_file(void);
  extern int set_git_dir(const char *path);
 +extern int get_common_dir(struct strbuf *sb, const char *gitdir);
  extern const char *get_git_namespace(void);
  extern const char *strip_namespace(const char *namespaced_ref);
  extern const char *get_git_work_tree(void);
@@@ -579,7 -568,7 +579,7 @@@ extern void update_index_if_able(struc
  extern int hold_locked_index(struct lock_file *, int);
  extern void set_alternate_index_output(const char *);
  
 -extern int delete_ref(const char *, const unsigned char *sha1, int delopt);
 +extern int delete_ref(const char *, const unsigned char *sha1, unsigned int flags);
  
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
@@@ -623,15 -612,6 +623,15 @@@ extern int core_apply_sparse_checkout
  extern int precomposed_unicode;
  extern int protect_hfs;
  extern int protect_ntfs;
 +extern int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
 +
 +/*
 + * Include broken refs in all ref iterations, which will
 + * generally choke dangerous operations rather than letting
 + * them silently proceed without taking the broken ref into
 + * account.
 + */
 +extern int ref_paranoia;
  
  /*
   * The character that begins a commented line in user-editable file
@@@ -694,19 -674,18 +694,19 @@@ extern int check_repository_format(void
  
  extern char *mksnpath(char *buf, size_t n, const char *fmt, ...)
        __attribute__((format (printf, 3, 4)));
 -extern char *git_snpath(char *buf, size_t n, const char *fmt, ...)
 -      __attribute__((format (printf, 3, 4)));
 +extern void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
 +      __attribute__((format (printf, 2, 3)));
  extern char *git_pathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
  extern char *mkpathdup(const char *fmt, ...)
        __attribute__((format (printf, 1, 2)));
  
  /* Return a statically allocated filename matching the sha1 signature */
 -extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 -extern char *git_path_submodule(const char *path, const char *fmt, ...)
 +extern const char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +extern const char *git_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 +extern const char *git_path_submodule(const char *path, const char *fmt, ...)
        __attribute__((format (printf, 2, 3)));
 +extern void report_linked_checkout_garbage(void);
  
  /*
   * Return the name of the file in the local object database that would
@@@ -731,13 -710,13 +731,13 @@@ extern char *sha1_pack_name(const unsig
  extern char *sha1_pack_index_name(const unsigned char *sha1);
  
  extern const char *find_unique_abbrev(const unsigned char *sha1, int);
 -extern const unsigned char null_sha1[20];
 +extern const unsigned char null_sha1[GIT_SHA1_RAWSZ];
  
  static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2)
  {
        int i;
  
 -      for (i = 0; i < 20; i++, sha1++, sha2++) {
 +      for (i = 0; i < GIT_SHA1_RAWSZ; i++, sha1++, sha2++) {
                if (*sha1 != *sha2)
                        return *sha1 - *sha2;
        }
        return 0;
  }
  
 +static inline int oidcmp(const struct object_id *oid1, const struct object_id *oid2)
 +{
 +      return hashcmp(oid1->hash, oid2->hash);
 +}
 +
  static inline int is_null_sha1(const unsigned char *sha1)
  {
        return !hashcmp(sha1, null_sha1);
  }
  
 +static inline int is_null_oid(const struct object_id *oid)
 +{
 +      return !hashcmp(oid->hash, null_sha1);
 +}
 +
  static inline void hashcpy(unsigned char *sha_dst, const unsigned char *sha_src)
  {
 -      memcpy(sha_dst, sha_src, 20);
 +      memcpy(sha_dst, sha_src, GIT_SHA1_RAWSZ);
 +}
 +
 +static inline void oidcpy(struct object_id *dst, const struct object_id *src)
 +{
 +      hashcpy(dst->hash, src->hash);
  }
 +
  static inline void hashclr(unsigned char *hash)
  {
 -      memset(hash, 0, 20);
 +      memset(hash, 0, GIT_SHA1_RAWSZ);
  }
  
 +static inline void oidclr(struct object_id *oid)
 +{
 +      hashclr(oid->hash);
 +}
 +
 +
  #define EMPTY_TREE_SHA1_HEX \
        "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
  #define EMPTY_TREE_SHA1_BIN_LITERAL \
@@@ -851,7 -808,6 +851,6 @@@ enum scld_error safe_create_leading_dir
  enum scld_error safe_create_leading_directories_const(const char *path);
  
  int mkdir_in_gitdir(const char *path);
- extern void home_config_paths(char **global, char **xdg, char *file);
  extern char *expand_user_path(const char *path);
  const char *enter_repo(const char *path, int strict);
  static inline int is_absolute_path(const char *path)
@@@ -871,6 -827,13 +870,13 @@@ char *strip_path_suffix(const char *pat
  int daemon_avoid_alias(const char *path);
  extern int is_ntfs_dotgit(const char *name);
  
+ /**
+  * Return a newly allocated string with the evaluation of
+  * "$XDG_CONFIG_HOME/git/$filename" if $XDG_CONFIG_HOME is non-empty, otherwise
+  * "$HOME/.config/git/$filename". Return NULL upon error.
+  */
+ extern char *xdg_config_home(const char *filename);
  /* object replacement */
  #define LOOKUP_REPLACE_OBJECT 1
  extern void *read_sha1_file_extended(const unsigned char *sha1, enum object_type *type, unsigned long *size, unsigned flag);
@@@ -909,7 -872,6 +915,7 @@@ static inline const unsigned char *look
  extern int sha1_object_info(const unsigned char *, unsigned long *);
  extern int hash_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *sha1);
  extern int write_sha1_file(const void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
 +extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned flags);
  extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
  extern int force_object_loose(const unsigned char *sha1, time_t mtime);
  extern int git_open_noatime(const char *name);
@@@ -988,10 -950,8 +994,10 @@@ extern int for_each_abbrev(const char *
   * null-terminated string.
   */
  extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 +extern int get_oid_hex(const char *hex, struct object_id *sha1);
  
  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 read_ref_full(const char *refname, int resolve_flags,
                         unsigned char *sha1, int *flags);
  extern int read_ref(const char *refname, unsigned char *sha1);
@@@ -1212,7 -1172,6 +1218,7 @@@ extern struct packed_git 
        int pack_fd;
        unsigned pack_local:1,
                 pack_keep:1,
 +               freshened:1,
                 do_not_close:1;
        unsigned char sha1[20];
        /* something like ".git/objects/pack/xxxxx.pack" */
@@@ -1301,10 -1260,6 +1307,10 @@@ extern int unpack_object_header(struct 
   *
   * Any callback that is NULL will be ignored. Callbacks returning non-zero
   * will end the iteration.
 + *
 + * In the "buf" variant, "path" is a strbuf which will also be used as a
 + * scratch buffer, but restored to its original contents before
 + * the function returns.
   */
  typedef int each_loose_object_fn(const unsigned char *sha1,
                                 const char *path,
@@@ -1320,24 -1275,17 +1326,24 @@@ int for_each_loose_file_in_objdir(cons
                                  each_loose_cruft_fn cruft_cb,
                                  each_loose_subdir_fn subdir_cb,
                                  void *data);
 +int for_each_loose_file_in_objdir_buf(struct strbuf *path,
 +                                    each_loose_object_fn obj_cb,
 +                                    each_loose_cruft_fn cruft_cb,
 +                                    each_loose_subdir_fn subdir_cb,
 +                                    void *data);
  
  /*
   * Iterate over loose and packed objects in both the local
 - * repository and any alternates repositories.
 + * repository and any alternates repositories (unless the
 + * LOCAL_ONLY flag is set).
   */
 +#define FOR_EACH_OBJECT_LOCAL_ONLY 0x1
  typedef int each_packed_object_fn(const unsigned char *sha1,
                                  struct packed_git *pack,
                                  uint32_t pos,
                                  void *data);
 -extern int for_each_loose_object(each_loose_object_fn, void *);
 -extern int for_each_packed_object(each_packed_object_fn, void *);
 +extern int for_each_loose_object(each_loose_object_fn, void *, unsigned flags);
 +extern int for_each_packed_object(each_packed_object_fn, void *, unsigned flags);
  
  struct object_info {
        /* Request */
@@@ -1549,8 -1497,6 +1555,8 @@@ static inline ssize_t write_str_in_full
  {
        return write_in_full(fd, str, strlen(str));
  }
 +__attribute__((format (printf, 3, 4)))
 +extern int write_file(const char *path, int fatal, const char *fmt, ...);
  
  /* pager.c */
  extern void setup_pager(void);
@@@ -1558,7 -1504,7 +1564,7 @@@ extern const char *pager_program
  extern int pager_in_use(void);
  extern int pager_use_color;
  extern int term_columns(void);
 -extern int decimal_width(int);
 +extern int decimal_width(uintmax_t);
  extern int check_pager_config(const char *cmd);
  
  extern const char *editor_program;
@@@ -1620,6 -1566,7 +1626,6 @@@ extern int ws_blank_line(const char *li
  #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
  
  /* ls-files */
 -int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
  char *alias_lookup(const char *alias);
diff --combined config.c
index 6d0098fc80df01ce9df4701119557472430b0619,0d7af9eefe1cf6b43cf6e51d258e1e1b2e54c7f1..ab46462e151dd5cd9d1e5a1fecd23fe5c6607c5d
+++ b/config.c
@@@ -12,7 -12,6 +12,7 @@@
  #include "quote.h"
  #include "hashmap.h"
  #include "string-list.h"
 +#include "utf8.h"
  
  struct config_source {
        struct config_source *prev;
@@@ -50,7 -49,7 +50,7 @@@ static struct config_set the_config_set
  
  static int config_file_fgetc(struct config_source *conf)
  {
 -      return fgetc(conf->u.file);
 +      return getc_unlocked(conf->u.file);
  }
  
  static int config_file_ungetc(int c, struct config_source *conf)
@@@ -74,12 -73,8 +74,12 @@@ static int config_buf_fgetc(struct conf
  
  static int config_buf_ungetc(int c, struct config_source *conf)
  {
 -      if (conf->u.buf.pos > 0)
 -              return conf->u.buf.buf[--conf->u.buf.pos];
 +      if (conf->u.buf.pos > 0) {
 +              conf->u.buf.pos--;
 +              if (conf->u.buf.buf[conf->u.buf.pos] != c)
 +                      die("BUG: config_buf can only ungetc the same character");
 +              return c;
 +      }
  
        return EOF;
  }
@@@ -240,8 -235,7 +240,8 @@@ static int get_next_char(void
                /* DOS like systems */
                c = cf->do_fgetc(cf);
                if (c != '\n') {
 -                      cf->do_ungetc(c, cf);
 +                      if (c != EOF)
 +                              cf->do_ungetc(c, cf);
                        c = '\r';
                }
        }
@@@ -418,7 -412,8 +418,7 @@@ static int git_parse_source(config_fn_
        struct strbuf *var = &cf->var;
  
        /* U+FEFF Byte Order Mark in UTF8 */
 -      static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
 -      const unsigned char *bomptr = utf8_bom;
 +      const char *bomptr = utf8_bom;
  
        for (;;) {
                int c = get_next_char();
                        /* We are at the file beginning; skip UTF8-encoded BOM
                         * if present. Sane editors won't put this in on their
                         * own, but e.g. Windows Notepad will do it happily. */
 -                      if ((unsigned char) c == *bomptr) {
 +                      if (c == (*bomptr & 0377)) {
                                bomptr++;
                                continue;
                        } else {
@@@ -1088,9 -1083,7 +1088,9 @@@ int git_config_from_file(config_fn_t fn
  
        f = fopen(filename, "r");
        if (f) {
 +              flockfile(f);
                ret = do_config_from_file(fn, filename, filename, f, data);
 +              funlockfile(f);
                fclose(f);
        }
        return ret;
@@@ -1187,10 -1180,8 +1187,8 @@@ int git_config_system(void
  int git_config_early(config_fn_t fn, void *data, const char *repo_config)
  {
        int ret = 0, found = 0;
-       char *xdg_config = NULL;
-       char *user_config = NULL;
-       home_config_paths(&user_config, &xdg_config, "config");
+       char *xdg_config = xdg_config_home("config");
+       char *user_config = expand_user_path("~/.gitconfig");
  
        if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
                ret += git_config_from_file(fn, git_etc_gitconfig(),
@@@ -1347,7 -1338,7 +1345,7 @@@ static int configset_add_value(struct c
                string_list_init(&e->value_list, 1);
                hashmap_add(&cs->config_hash, e);
        }
 -      si = string_list_append_nodup(&e->value_list, value ? xstrdup(value) : NULL);
 +      si = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));
  
        ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);
        l_item = &cs->list.items[cs->list.nr++];
diff --combined credential-store.c
index 8b222513cb9ad2b4519c89019566cd2c2080a3a6,8ebfb97c7ffde917da081e65c316673d40571051..f6925096ffa7e036b4e63f65ce372c596c9e1b70
@@@ -145,7 -145,7 +145,7 @@@ static void lookup_credential(const str
  int main(int argc, char **argv)
  {
        const char * const usage[] = {
 -              "git credential-store [options] <action>",
 +              "git credential-store [<options>] <action>",
                NULL
        };
        const char *op;
        } else {
                if ((file = expand_user_path("~/.git-credentials")))
                        string_list_append_nodup(&fns, file);
-               home_config_paths(NULL, &file, "credentials");
+               file = xdg_config_home("credentials");
                if (file)
                        string_list_append_nodup(&fns, file);
        }
diff --combined dir.c
index a3e70734004e5da22dce9ba4062c0d6684061855,cb8f5496ca38f20f430ce852fb9c747c7355e12b..0c38d8601448053f94eca534833d47a975580856
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -12,7 -12,6 +12,7 @@@
  #include "refs.h"
  #include "wildmatch.h"
  #include "pathspec.h"
 +#include "utf8.h"
  
  struct path_simplify {
        int len;
@@@ -378,49 -377,6 +378,49 @@@ int match_pathspec(const struct pathspe
        return negative ? 0 : positive;
  }
  
 +int report_path_error(const char *ps_matched,
 +                    const struct pathspec *pathspec,
 +                    const char *prefix)
 +{
 +      /*
 +       * Make sure all pathspec matched; otherwise it is an error.
 +       */
 +      struct strbuf sb = STRBUF_INIT;
 +      int num, errors = 0;
 +      for (num = 0; num < pathspec->nr; num++) {
 +              int other, found_dup;
 +
 +              if (ps_matched[num])
 +                      continue;
 +              /*
 +               * The caller might have fed identical pathspec
 +               * twice.  Do not barf on such a mistake.
 +               * FIXME: parse_pathspec should have eliminated
 +               * duplicate pathspec.
 +               */
 +              for (found_dup = other = 0;
 +                   !found_dup && other < pathspec->nr;
 +                   other++) {
 +                      if (other == num || !ps_matched[other])
 +                              continue;
 +                      if (!strcmp(pathspec->items[other].original,
 +                                  pathspec->items[num].original))
 +                              /*
 +                               * Ok, we have a match already.
 +                               */
 +                              found_dup = 1;
 +              }
 +              if (found_dup)
 +                      continue;
 +
 +              error("pathspec '%s' did not match any file(s) known to git.",
 +                    pathspec->items[num].original);
 +              errors++;
 +      }
 +      strbuf_release(&sb);
 +      return errors;
 +}
 +
  /*
   * Return the length of the "simple" part of a path match limiter.
   */
@@@ -618,12 -574,7 +618,12 @@@ int add_excludes_from_file_to_list(cons
        }
  
        el->filebuf = buf;
 +
 +      if (skip_utf8_bom(&buf, size))
 +              size -= buf - el->filebuf;
 +
        entry = buf;
 +
        for (i = 0; i < size; i++) {
                if (buf[i] == '\n') {
                        if (entry != buf + i && entry[0] != '#') {
@@@ -1671,14 -1622,11 +1671,11 @@@ int remove_dir_recursively(struct strbu
  void setup_standard_excludes(struct dir_struct *dir)
  {
        const char *path;
-       char *xdg_path;
  
        dir->exclude_per_dir = ".gitignore";
        path = git_path("info/exclude");
-       if (!excludes_file) {
-               home_config_paths(NULL, &xdg_path, "ignore");
-               excludes_file = xdg_path;
-       }
+       if (!excludes_file)
+               excludes_file = xdg_config_home("ignore");
        if (!access_or_warn(path, R_OK, 0))
                add_excludes_from_file(dir, path);
        if (excludes_file && !access_or_warn(excludes_file, R_OK, 0))
diff --combined path.c
index 586f2c90a3c0def34016a692a14e335a46c015ed,2436301ccabaa07513fae556d6624fad4b7b4328..10f4cbf6b78607870461f21dd1cd0f7a2776bc49
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -4,7 -4,6 +4,7 @@@
  #include "cache.h"
  #include "strbuf.h"
  #include "string-list.h"
 +#include "dir.h"
  
  static int get_st_mode_bits(const char *path, int *mode)
  {
  
  static char bad_path[] = "/bad-path/";
  
 -static char *get_pathname(void)
 +static struct strbuf *get_pathname(void)
  {
 -      static char pathname_array[4][PATH_MAX];
 +      static struct strbuf pathname_array[4] = {
 +              STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT
 +      };
        static int index;
 -      return pathname_array[3 & ++index];
 +      struct strbuf *sb = &pathname_array[3 & ++index];
 +      strbuf_reset(sb);
 +      return sb;
  }
  
  static char *cleanup_path(char *path)
        return path;
  }
  
 +static void strbuf_cleanup_path(struct strbuf *sb)
 +{
 +      char *path = cleanup_path(sb->buf);
 +      if (path > sb->buf)
 +              strbuf_remove(sb, 0, path - sb->buf);
 +}
 +
  char *mksnpath(char *buf, size_t n, const char *fmt, ...)
  {
        va_list args;
        return cleanup_path(buf);
  }
  
 -static char *vsnpath(char *buf, size_t n, const char *fmt, va_list args)
 +static int dir_prefix(const char *buf, const char *dir)
  {
 -      const char *git_dir = get_git_dir();
 -      size_t len;
 -
 -      len = strlen(git_dir);
 -      if (n < len + 1)
 -              goto bad;
 -      memcpy(buf, git_dir, len);
 -      if (len && !is_dir_sep(git_dir[len-1]))
 -              buf[len++] = '/';
 -      len += vsnprintf(buf + len, n - len, fmt, args);
 -      if (len >= n)
 -              goto bad;
 -      return cleanup_path(buf);
 -bad:
 -      strlcpy(buf, bad_path, n);
 -      return buf;
 +      int len = strlen(dir);
 +      return !strncmp(buf, dir, len) &&
 +              (is_dir_sep(buf[len]) || buf[len] == '\0');
 +}
 +
 +/* $buf =~ m|$dir/+$file| but without regex */
 +static int is_dir_file(const char *buf, const char *dir, const char *file)
 +{
 +      int len = strlen(dir);
 +      if (strncmp(buf, dir, len) || !is_dir_sep(buf[len]))
 +              return 0;
 +      while (is_dir_sep(buf[len]))
 +              len++;
 +      return !strcmp(buf + len, file);
 +}
 +
 +static void replace_dir(struct strbuf *buf, int len, const char *newdir)
 +{
 +      int newlen = strlen(newdir);
 +      int need_sep = (buf->buf[len] && !is_dir_sep(buf->buf[len])) &&
 +              !is_dir_sep(newdir[newlen - 1]);
 +      if (need_sep)
 +              len--;   /* keep one char, to be replaced with '/'  */
 +      strbuf_splice(buf, 0, len, newdir, newlen);
 +      if (need_sep)
 +              buf->buf[newlen] = '/';
 +}
 +
 +static const char *common_list[] = {
 +      "/branches", "/hooks", "/info", "!/logs", "/lost-found",
 +      "/objects", "/refs", "/remotes", "/worktrees", "/rr-cache", "/svn",
 +      "config", "!gc.pid", "packed-refs", "shallow",
 +      NULL
 +};
 +
 +static void update_common_dir(struct strbuf *buf, int git_dir_len)
 +{
 +      char *base = buf->buf + git_dir_len;
 +      const char **p;
 +
 +      if (is_dir_file(base, "logs", "HEAD") ||
 +          is_dir_file(base, "info", "sparse-checkout"))
 +              return; /* keep this in $GIT_DIR */
 +      for (p = common_list; *p; p++) {
 +              const char *path = *p;
 +              int is_dir = 0;
 +              if (*path == '!')
 +                      path++;
 +              if (*path == '/') {
 +                      path++;
 +                      is_dir = 1;
 +              }
 +              if (is_dir && dir_prefix(base, path)) {
 +                      replace_dir(buf, git_dir_len, get_git_common_dir());
 +                      return;
 +              }
 +              if (!is_dir && !strcmp(base, path)) {
 +                      replace_dir(buf, git_dir_len, get_git_common_dir());
 +                      return;
 +              }
 +      }
 +}
 +
 +void report_linked_checkout_garbage(void)
 +{
 +      struct strbuf sb = STRBUF_INIT;
 +      const char **p;
 +      int len;
 +
 +      if (!git_common_dir_env)
 +              return;
 +      strbuf_addf(&sb, "%s/", get_git_dir());
 +      len = sb.len;
 +      for (p = common_list; *p; p++) {
 +              const char *path = *p;
 +              if (*path == '!')
 +                      continue;
 +              strbuf_setlen(&sb, len);
 +              strbuf_addstr(&sb, path);
 +              if (file_exists(sb.buf))
 +                      report_garbage("unused in linked checkout", sb.buf);
 +      }
 +      strbuf_release(&sb);
  }
  
 -char *git_snpath(char *buf, size_t n, const char *fmt, ...)
 +static void adjust_git_path(struct strbuf *buf, int git_dir_len)
 +{
 +      const char *base = buf->buf + git_dir_len;
 +      if (git_graft_env && is_dir_file(base, "info", "grafts"))
 +              strbuf_splice(buf, 0, buf->len,
 +                            get_graft_file(), strlen(get_graft_file()));
 +      else if (git_index_env && !strcmp(base, "index"))
 +              strbuf_splice(buf, 0, buf->len,
 +                            get_index_file(), strlen(get_index_file()));
 +      else if (git_db_env && dir_prefix(base, "objects"))
 +              replace_dir(buf, git_dir_len + 7, get_object_directory());
 +      else if (git_common_dir_env)
 +              update_common_dir(buf, git_dir_len);
 +}
 +
 +static void do_git_path(struct strbuf *buf, const char *fmt, va_list args)
 +{
 +      int gitdir_len;
 +      strbuf_addstr(buf, get_git_dir());
 +      if (buf->len && !is_dir_sep(buf->buf[buf->len - 1]))
 +              strbuf_addch(buf, '/');
 +      gitdir_len = buf->len;
 +      strbuf_vaddf(buf, fmt, args);
 +      adjust_git_path(buf, gitdir_len);
 +      strbuf_cleanup_path(buf);
 +}
 +
 +void strbuf_git_path(struct strbuf *sb, const char *fmt, ...)
  {
 -      char *ret;
        va_list args;
        va_start(args, fmt);
 -      ret = vsnpath(buf, n, fmt, args);
 +      do_git_path(sb, fmt, args);
        va_end(args);
 -      return ret;
  }
  
 -char *git_pathdup(const char *fmt, ...)
 +const char *git_path(const char *fmt, ...)
  {
 -      char path[PATH_MAX], *ret;
 +      struct strbuf *pathname = get_pathname();
        va_list args;
        va_start(args, fmt);
 -      ret = vsnpath(path, sizeof(path), fmt, args);
 +      do_git_path(pathname, fmt, args);
        va_end(args);
 -      return xstrdup(ret);
 +      return pathname->buf;
  }
  
 -char *mkpathdup(const char *fmt, ...)
 +char *git_pathdup(const char *fmt, ...)
  {
 -      char *path;
 -      struct strbuf sb = STRBUF_INIT;
 +      struct strbuf path = STRBUF_INIT;
        va_list args;
 -
        va_start(args, fmt);
 -      strbuf_vaddf(&sb, fmt, args);
 +      do_git_path(&path, fmt, args);
        va_end(args);
 -      path = xstrdup(cleanup_path(sb.buf));
 -
 -      strbuf_release(&sb);
 -      return path;
 +      return strbuf_detach(&path, NULL);
  }
  
 -char *mkpath(const char *fmt, ...)
 +char *mkpathdup(const char *fmt, ...)
  {
 +      struct strbuf sb = STRBUF_INIT;
        va_list args;
 -      unsigned len;
 -      char *pathname = get_pathname();
 -
        va_start(args, fmt);
 -      len = vsnprintf(pathname, PATH_MAX, fmt, args);
 +      strbuf_vaddf(&sb, fmt, args);
        va_end(args);
 -      if (len >= PATH_MAX)
 -              return bad_path;
 -      return cleanup_path(pathname);
 +      strbuf_cleanup_path(&sb);
 +      return strbuf_detach(&sb, NULL);
  }
  
 -char *git_path(const char *fmt, ...)
 +const char *mkpath(const char *fmt, ...)
  {
 -      char *pathname = get_pathname();
        va_list args;
 -      char *ret;
 -
 +      struct strbuf *pathname = get_pathname();
        va_start(args, fmt);
 -      ret = vsnpath(pathname, PATH_MAX, fmt, args);
 +      strbuf_vaddf(pathname, fmt, args);
        va_end(args);
 -      return ret;
 +      return cleanup_path(pathname->buf);
  }
  
- void home_config_paths(char **global, char **xdg, char *file)
- {
-       char *xdg_home = getenv("XDG_CONFIG_HOME");
-       char *home = getenv("HOME");
-       char *to_free = NULL;
-       if (!home) {
-               if (global)
-                       *global = NULL;
-       } else {
-               if (!xdg_home) {
-                       to_free = mkpathdup("%s/.config", home);
-                       xdg_home = to_free;
-               }
-               if (global)
-                       *global = mkpathdup("%s/.gitconfig", home);
-       }
-       if (xdg) {
-               if (!xdg_home)
-                       *xdg = NULL;
-               else
-                       *xdg = mkpathdup("%s/git/%s", xdg_home, file);
-       }
-       free(to_free);
- }
 -char *git_path_submodule(const char *path, const char *fmt, ...)
 +const char *git_path_submodule(const char *path, const char *fmt, ...)
  {
 -      char *pathname = get_pathname();
 -      struct strbuf buf = STRBUF_INIT;
 +      struct strbuf *buf = get_pathname();
        const char *git_dir;
        va_list args;
 -      unsigned len;
 -
 -      len = strlen(path);
 -      if (len > PATH_MAX-100)
 -              return bad_path;
  
 -      strbuf_addstr(&buf, path);
 -      if (len && path[len-1] != '/')
 -              strbuf_addch(&buf, '/');
 -      strbuf_addstr(&buf, ".git");
 +      strbuf_addstr(buf, path);
 +      if (buf->len && buf->buf[buf->len - 1] != '/')
 +              strbuf_addch(buf, '/');
 +      strbuf_addstr(buf, ".git");
  
 -      git_dir = read_gitfile(buf.buf);
 +      git_dir = read_gitfile(buf->buf);
        if (git_dir) {
 -              strbuf_reset(&buf);
 -              strbuf_addstr(&buf, git_dir);
 +              strbuf_reset(buf);
 +              strbuf_addstr(buf, git_dir);
        }
 -      strbuf_addch(&buf, '/');
 -
 -      if (buf.len >= PATH_MAX)
 -              return bad_path;
 -      memcpy(pathname, buf.buf, buf.len + 1);
 -
 -      strbuf_release(&buf);
 -      len = strlen(pathname);
 +      strbuf_addch(buf, '/');
  
        va_start(args, fmt);
 -      len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
 +      strbuf_vaddf(buf, fmt, args);
        va_end(args);
 -      if (len >= PATH_MAX)
 -              return bad_path;
 -      return cleanup_path(pathname);
 +      strbuf_cleanup_path(buf);
 +      return buf->buf;
  }
  
  int validate_headref(const char *path)
@@@ -383,9 -275,14 +355,9 @@@ return_null
   * (3) "relative/path" to mean cwd relative directory; or
   * (4) "/absolute/path" to mean absolute directory.
   *
 - * Unless "strict" is given, we try access() for existence of "%s.git/.git",
 - * "%s/.git", "%s.git", "%s" in this order.  The first one that exists is
 - * what we try.
 - *
 - * Second, we try chdir() to that.  Upon failure, we return NULL.
 - *
 - * Then, we try if the current directory is a valid git repository.
 - * Upon failure, we return NULL.
 + * Unless "strict" is given, we check "%s/.git", "%s", "%s.git/.git", "%s.git"
 + * in this order. We select the first one that is a valid git repository, and
 + * chdir() to it. If none match, or we fail to chdir, we return NULL.
   *
   * If all goes well, we return the directory we used to chdir() (but
   * before ~user is expanded), avoiding getcwd() resolving symbolic
@@@ -931,3 -828,18 +903,18 @@@ int is_ntfs_dotgit(const char *name
                        len = -1;
                }
  }
+ char *xdg_config_home(const char *filename)
+ {
+       const char *home, *config_home;
+       assert(filename);
+       config_home = getenv("XDG_CONFIG_HOME");
+       if (config_home && *config_home)
+               return mkpathdup("%s/git/%s", config_home, filename);
+       home = getenv("HOME");
+       if (home)
+               return mkpathdup("%s/.config/git/%s", home, filename);
+       return NULL;
+ }