Merge branch 'pt/xdg-config-path' into maint
authorJunio C Hamano <gitster@pobox.com>
Fri, 5 Jun 2015 19:00:03 +0000 (12:00 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 5 Jun 2015 19:00:04 +0000 (12:00 -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()
t0302: "unreadable" test needs POSIXPERM
t0302: test credential-store support for XDG_CONFIG_HOME
git-credential-store: support XDG_CONFIG_HOME
git-credential-store: support multiple credential files

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 da79ac4bc7a7247017e2f952b35642a1976c8101,b4aaaab5bc908d440ab195072ea0b7aa4fac15d4..c2ebea4ed31d13cfe48603042edfb0ca63f5de6e
  #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
  };
  
@@@ -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 bfd3016e83f3fd4e5f4f2ec8f4d6f4c5a24e711f,2f8bf7d7cfdb36029046c1d5a28af56ad708a9c4..a58f99c2d78f8e06a2e9df7af155af7aa8eaf4c8
@@@ -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
  };
  
@@@ -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 e42be4b11ba114a1ae01b9c5f6825d23bbf61eba,aa8d3774483b17128b7f44eaaeb2b58a0bc91883..badf3da3405dab75ecb03b6fa2e885182ba56475
+++ b/cache.h
@@@ -568,7 -568,7 +568,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;
@@@ -613,14 -613,6 +613,14 @@@ extern int precomposed_unicode
  extern int protect_hfs;
  extern int protect_ntfs;
  
 +/*
 + * 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
   * that is subject to stripspace.
@@@ -816,7 -808,6 +816,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)
@@@ -836,6 -827,13 +835,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);
@@@ -874,7 -872,6 +880,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);
@@@ -1175,7 -1172,6 +1181,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" */
@@@ -1264,10 -1260,6 +1270,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,
@@@ -1283,24 -1275,17 +1289,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 */
@@@ -1519,7 -1504,7 +1525,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;
@@@ -1581,6 -1566,7 +1587,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 c4424c01388496b5995e19f9601f0c87b9fdd3c0,0d7af9eefe1cf6b43cf6e51d258e1e1b2e54c7f1..286907b97e5a378e2be9677ee5b6a4fe7f6169af
+++ 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;
@@@ -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 {
@@@ -1185,10 -1180,8 +1185,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(),
@@@ -1345,7 -1338,7 +1343,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 925d3f40247d7f41d709b4e6b3eaabaf646572b6,8ebfb97c7ffde917da081e65c316673d40571051..f6925096ffa7e036b4e63f65ce372c596c9e1b70
@@@ -6,7 -6,7 +6,7 @@@
  
  static struct lock_file credential_lock;
  
- static void parse_credential_file(const char *fn,
+ static int parse_credential_file(const char *fn,
                                  struct credential *c,
                                  void (*match_cb)(struct credential *),
                                  void (*other_cb)(struct strbuf *))
        FILE *fh;
        struct strbuf line = STRBUF_INIT;
        struct credential entry = CREDENTIAL_INIT;
+       int found_credential = 0;
  
        fh = fopen(fn, "r");
        if (!fh) {
-               if (errno != ENOENT)
+               if (errno != ENOENT && errno != EACCES)
                        die_errno("unable to open %s", fn);
-               return;
+               return found_credential;
        }
  
        while (strbuf_getline(&line, fh, '\n') != EOF) {
                credential_from_url(&entry, line.buf);
                if (entry.username && entry.password &&
                    credential_match(c, &entry)) {
+                       found_credential = 1;
                        if (match_cb) {
                                match_cb(&entry);
                                break;
@@@ -38,6 -40,7 +40,7 @@@
        credential_clear(&entry);
        strbuf_release(&line);
        fclose(fh);
+       return found_credential;
  }
  
  static void print_entry(struct credential *c)
@@@ -64,21 -67,10 +67,10 @@@ static void rewrite_credential_file(con
                die_errno("unable to commit credential store");
  }
  
- static void store_credential(const char *fn, struct credential *c)
+ static void store_credential_file(const char *fn, struct credential *c)
  {
        struct strbuf buf = STRBUF_INIT;
  
-       /*
-        * Sanity check that what we are storing is actually sensible.
-        * In particular, we can't make a URL without a protocol field.
-        * Without either a host or pathname (depending on the scheme),
-        * we have no primary key. And without a username and password,
-        * we are not actually storing a credential.
-        */
-       if (!c->protocol || !(c->host || c->path) ||
-           !c->username || !c->password)
-               return;
        strbuf_addf(&buf, "%s://", c->protocol);
        strbuf_addstr_urlencode(&buf, c->username, 1);
        strbuf_addch(&buf, ':');
        strbuf_release(&buf);
  }
  
- static void remove_credential(const char *fn, struct credential *c)
+ static void store_credential(const struct string_list *fns, struct credential *c)
+ {
+       struct string_list_item *fn;
+       /*
+        * Sanity check that what we are storing is actually sensible.
+        * In particular, we can't make a URL without a protocol field.
+        * Without either a host or pathname (depending on the scheme),
+        * we have no primary key. And without a username and password,
+        * we are not actually storing a credential.
+        */
+       if (!c->protocol || !(c->host || c->path) || !c->username || !c->password)
+               return;
+       for_each_string_list_item(fn, fns)
+               if (!access(fn->string, F_OK)) {
+                       store_credential_file(fn->string, c);
+                       return;
+               }
+       /*
+        * Write credential to the filename specified by fns->items[0], thus
+        * creating it
+        */
+       if (fns->nr)
+               store_credential_file(fns->items[0].string, c);
+ }
+ static void remove_credential(const struct string_list *fns, struct credential *c)
  {
+       struct string_list_item *fn;
        /*
         * Sanity check that we actually have something to match
         * against. The input we get is a restrictive pattern,
         * to empty input. So explicitly disallow it, and require that the
         * pattern have some actual content to match.
         */
-       if (c->protocol || c->host || c->path || c->username)
-               rewrite_credential_file(fn, c, NULL);
+       if (!c->protocol && !c->host && !c->path && !c->username)
+               return;
+       for_each_string_list_item(fn, fns)
+               if (!access(fn->string, F_OK))
+                       rewrite_credential_file(fn->string, c, NULL);
  }
  
- static int lookup_credential(const char *fn, struct credential *c)
+ static void lookup_credential(const struct string_list *fns, struct credential *c)
  {
-       parse_credential_file(fn, c, print_entry, NULL);
-       return c->username && c->password;
+       struct string_list_item *fn;
+       for_each_string_list_item(fn, fns)
+               if (parse_credential_file(fn->string, c, print_entry, NULL))
+                       return; /* Found credential */
  }
  
  int main(int argc, char **argv)
  {
        const char * const usage[] = {
 -              "git credential-store [options] <action>",
 +              "git credential-store [<options>] <action>",
                NULL
        };
        const char *op;
        struct credential c = CREDENTIAL_INIT;
+       struct string_list fns = STRING_LIST_INIT_DUP;
        char *file = NULL;
        struct option options[] = {
                OPT_STRING(0, "file", &file, "path",
                usage_with_options(usage, options);
        op = argv[0];
  
-       if (!file)
-               file = expand_user_path("~/.git-credentials");
-       if (!file)
+       if (file) {
+               string_list_append(&fns, file);
+       } else {
+               if ((file = expand_user_path("~/.git-credentials")))
+                       string_list_append_nodup(&fns, file);
+               file = xdg_config_home("credentials");
+               if (file)
+                       string_list_append_nodup(&fns, file);
+       }
+       if (!fns.nr)
                die("unable to set up default path; use --file");
  
        if (credential_read(&c, stdin) < 0)
                die("unable to read credential");
  
        if (!strcmp(op, "get"))
-               lookup_credential(file, &c);
+               lookup_credential(&fns, &c);
        else if (!strcmp(op, "erase"))
-               remove_credential(file, &c);
+               remove_credential(&fns, &c);
        else if (!strcmp(op, "store"))
-               store_credential(file, &c);
+               store_credential(&fns, &c);
        else
                ; /* Ignore unknown operation. */
  
+       string_list_clear(&fns, 0);
        return 0;
  }
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 595da81ca67096bae9592d9455bdade442b92628,2436301ccabaa07513fae556d6624fad4b7b4328..6b537ccfff452b7fbbe41446c001c8fda0679bea
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -130,34 -130,6 +130,6 @@@ char *git_path(const char *fmt, ...
        return ret;
  }
  
- 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, ...)
  {
        char *pathname = get_pathname();
@@@ -303,9 -275,14 +275,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
@@@ -851,3 -828,18 +823,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;
+ }