Merge branch 'hv/config-from-blob'
authorJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 18:24:09 +0000 (11:24 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 18:24:09 +0000 (11:24 -0700)
Allow configuration data to be read from in-tree blob objects,
which would help working in a bare repository and submodule
updates.

* hv/config-from-blob:
do not die when error in config parsing of buf occurs
teach config --blob option to parse config from database
config: make parsing stack struct independent from actual data source
config: drop cf validity check in get_next_char()
config: factor out config file stack management

1  2 
Documentation/git-config.txt
builtin/config.c
cache.h
config.c
index 99dc497b6f25243dbe01ce47d6e53ec6952d1737,f0e179e38d8ea50f113e39ab97c430a572335d4e..34b089464627fc66c5a734adc7699fcceaff7c4f
@@@ -82,7 -82,7 +82,7 @@@ OPTION
  --get::
        Get the value for a given key (optionally filtered by a regex
        matching the value). Returns error code 1 if the key was not
 -      found and error code 2 if multiple key values were found.
 +      found and the last value if multiple key values were found.
  
  --get-all::
        Like get, but does not fail if the number of values for the key
@@@ -114,19 -114,17 +114,26 @@@ rather than from all available files
  +
  See also <<FILES>>.
  
 +--local::
 +      For writing options: write to the repository .git/config file.
 +      This is the default behavior.
 ++
 +For reading options: read only from the repository .git/config rather than
 +from all available files.
 ++
 +See also <<FILES>>.
 +
  -f config-file::
  --file config-file::
        Use the given config file instead of the one specified by GIT_CONFIG.
  
+ --blob blob::
+       Similar to '--file' but use the given blob instead of a file. E.g.
+       you can use 'master:.gitmodules' to read values from the file
+       '.gitmodules' in the master branch. See "SPECIFYING REVISIONS"
+       section in linkgit:gitrevisions[7] for a more complete list of
+       ways to spell blob names.
  --remove-section::
        Remove the given section from the configuration file.
  
        Opens an editor to modify the specified config file; either
        '--system', '--global', or repository (default).
  
 ---includes::
 ---no-includes::
 +--[no-]includes::
        Respect `include.*` directives in config files when looking up
        values. Defaults to on.
  
@@@ -206,8 -205,12 +213,8 @@@ FILE
  If not set explicitly with '--file', there are four files where
  'git config' will search for configuration options:
  
 -$GIT_DIR/config::
 -      Repository specific configuration file.
 -
 -~/.gitconfig::
 -      User-specific configuration file. Also called "global"
 -      configuration file.
 +$(prefix)/etc/gitconfig::
 +      System-wide configuration file.
  
  $XDG_CONFIG_HOME/git/config::
        Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
        you sometimes use older versions of Git, as support for this
        file was added fairly recently.
  
 -$(prefix)/etc/gitconfig::
 -      System-wide configuration file.
 +~/.gitconfig::
 +      User-specific configuration file. Also called "global"
 +      configuration file.
 +
 +$GIT_DIR/config::
 +      Repository specific configuration file.
  
  If no further options are given, all reading options will read all of these
  files that are available. If the global or the system-wide configuration
@@@ -230,10 -229,6 +237,10 @@@ file are not available they will be ign
  file is not available or readable, 'git config' will exit with a non-zero
  error code. However, in neither case will an error message be issued.
  
 +The files are read in the order given above, with last value found taking
 +precedence over values read earlier.  When multiple values are taken then all
 +values of a key from all files will be used.
 +
  All writing options will per default write to the repository specific
  configuration file. Note that this also affects options like '--replace-all'
  and '--unset'. *'git config' will only ever change one file at a time*.
diff --combined builtin/config.c
index 7759671eb8d44ad341831eeffc96c4469d0652e8,8d01b7ab461b6db3a2e28499f9846739a133ed3b..4010c4320a585d84bdb0914d8a9bbc27d38b7f18
@@@ -21,6 -21,7 +21,7 @@@ static char term = '\n'
  
  static int use_global_config, use_system_config, use_local_config;
  static const char *given_config_file;
+ static const char *given_config_blob;
  static int actions, types;
  static const char *get_color_slot, *get_colorbool_slot;
  static int end_null;
@@@ -53,6 -54,7 +54,7 @@@ static struct option builtin_config_opt
        OPT_BOOLEAN(0, "system", &use_system_config, N_("use system config file")),
        OPT_BOOLEAN(0, "local", &use_local_config, N_("use repository config file")),
        OPT_STRING('f', "file", &given_config_file, N_("file"), N_("use given config file")),
+       OPT_STRING(0, "blob", &given_config_blob, N_("blob-id"), N_("read config from given blob object")),
        OPT_GROUP(N_("Action")),
        OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET),
        OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL),
@@@ -218,7 -220,8 +220,8 @@@ static int get_value(const char *key_, 
        }
  
        git_config_with_options(collect_config, &values,
-                               given_config_file, respect_includes);
+                               given_config_file, given_config_blob,
+                               respect_includes);
  
        ret = !values.nr;
  
@@@ -302,7 -305,8 +305,8 @@@ static void get_color(const char *def_c
        get_color_found = 0;
        parsed_color[0] = '\0';
        git_config_with_options(git_get_color_config, NULL,
-                               given_config_file, respect_includes);
+                               given_config_file, given_config_blob,
+                               respect_includes);
  
        if (!get_color_found && def_color)
                color_parse(def_color, "command line", parsed_color);
@@@ -329,9 -333,9 +333,10 @@@ static int get_colorbool(int print
  {
        get_colorbool_found = -1;
        get_diff_color_found = -1;
 +      get_color_ui_found = -1;
        git_config_with_options(git_get_colorbool_config, NULL,
-                               given_config_file, respect_includes);
+                               given_config_file, given_config_blob,
+                               respect_includes);
  
        if (get_colorbool_found < 0) {
                if (!strcmp(get_colorbool_slot, "color.diff"))
                        get_colorbool_found = get_color_ui_found;
        }
  
 +      if (get_colorbool_found < 0)
 +              /* default value if none found in config */
 +              get_colorbool_found = GIT_COLOR_AUTO;
 +
        get_colorbool_found = want_color(get_colorbool_found);
  
        if (print) {
                return get_colorbool_found ? 0 : 1;
  }
  
+ static void check_blob_write(void)
+ {
+       if (given_config_blob)
+               die("writing config blobs is not supported");
+ }
  int cmd_config(int argc, const char **argv, const char *prefix)
  {
        int nongit = !startup_info->have_repository;
                             builtin_config_usage,
                             PARSE_OPT_STOP_AT_NON_OPTION);
  
-       if (use_global_config + use_system_config + use_local_config + !!given_config_file > 1) {
+       if (use_global_config + use_system_config + use_local_config +
+           !!given_config_file + !!given_config_blob > 1) {
                error("only one config file at a time.");
                usage_with_options(builtin_config_usage, builtin_config_options);
        }
                         */
                        die("$HOME not set");
  
 -              if (access_or_warn(user_config, R_OK) &&
 -                  xdg_config && !access_or_warn(xdg_config, R_OK))
 +              if (access_or_warn(user_config, R_OK, 0) &&
 +                  xdg_config && !access_or_warn(xdg_config, R_OK, 0))
                        given_config_file = xdg_config;
                else
                        given_config_file = user_config;
                check_argc(argc, 0, 0);
                if (git_config_with_options(show_all_config, NULL,
                                            given_config_file,
+                                           given_config_blob,
                                            respect_includes) < 0) {
                        if (given_config_file)
                                die_errno("unable to read config file '%s'",
                check_argc(argc, 0, 0);
                if (!given_config_file && nongit)
                        die("not in a git directory");
+               if (given_config_blob)
+                       die("editing blobs is not supported");
                git_config(git_default_config, NULL);
                launch_editor(given_config_file ?
                              given_config_file : git_path("config"),
        }
        else if (actions == ACTION_SET) {
                int ret;
+               check_blob_write();
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
                ret = git_config_set_in_file(given_config_file, argv[0], value);
                return ret;
        }
        else if (actions == ACTION_SET_ALL) {
+               check_blob_write();
                check_argc(argc, 2, 3);
                value = normalize_value(argv[0], argv[1]);
                return git_config_set_multivar_in_file(given_config_file,
                                                       argv[0], value, argv[2], 0);
        }
        else if (actions == ACTION_ADD) {
+               check_blob_write();
                check_argc(argc, 2, 2);
                value = normalize_value(argv[0], argv[1]);
                return git_config_set_multivar_in_file(given_config_file,
                                                       argv[0], value, "^$", 0);
        }
        else if (actions == ACTION_REPLACE_ALL) {
+               check_blob_write();
                check_argc(argc, 2, 3);
                value = normalize_value(argv[0], argv[1]);
                return git_config_set_multivar_in_file(given_config_file,
                return get_value(argv[0], argv[1]);
        }
        else if (actions == ACTION_UNSET) {
+               check_blob_write();
                check_argc(argc, 1, 2);
                if (argc == 2)
                        return git_config_set_multivar_in_file(given_config_file,
                                                      argv[0], NULL);
        }
        else if (actions == ACTION_UNSET_ALL) {
+               check_blob_write();
                check_argc(argc, 1, 2);
                return git_config_set_multivar_in_file(given_config_file,
                                                       argv[0], NULL, argv[1], 1);
        }
        else if (actions == ACTION_RENAME_SECTION) {
                int ret;
+               check_blob_write();
                check_argc(argc, 2, 2);
                ret = git_config_rename_section_in_file(given_config_file,
                                                        argv[0], argv[1]);
        }
        else if (actions == ACTION_REMOVE_SECTION) {
                int ret;
+               check_blob_write();
                check_argc(argc, 1, 1);
                ret = git_config_rename_section_in_file(given_config_file,
                                                        argv[0], NULL);
diff --combined cache.h
index 48e147f8ba8591bb7577c76e3ab6947580c515da,be48c4bd217b22493f512c99e3c1b2ae65c5cb92..3142b6c372361e93d1d16ce080f68aaf0bb51267
+++ b/cache.h
@@@ -119,19 -119,15 +119,19 @@@ struct cache_time 
        unsigned int nsec;
  };
  
 +struct stat_data {
 +      struct cache_time sd_ctime;
 +      struct cache_time sd_mtime;
 +      unsigned int sd_dev;
 +      unsigned int sd_ino;
 +      unsigned int sd_uid;
 +      unsigned int sd_gid;
 +      unsigned int sd_size;
 +};
 +
  struct cache_entry {
 -      struct cache_time ce_ctime;
 -      struct cache_time ce_mtime;
 -      unsigned int ce_dev;
 -      unsigned int ce_ino;
 +      struct stat_data ce_stat_data;
        unsigned int ce_mode;
 -      unsigned int ce_uid;
 -      unsigned int ce_gid;
 -      unsigned int ce_size;
        unsigned int ce_flags;
        unsigned int ce_namelen;
        unsigned char sha1[20];
   * another. But we never change the name, or the hash state!
   */
  #define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
 -static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
 +static inline void copy_cache_entry(struct cache_entry *dst,
 +                                  const struct cache_entry *src)
  {
        unsigned int state = dst->ce_flags & CE_STATE_MASK;
  
@@@ -227,8 -222,7 +227,8 @@@ static inline unsigned int create_ce_mo
                return S_IFGITLINK;
        return S_IFREG | ce_permissions(mode);
  }
 -static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
 +static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,
 +                                           unsigned int mode)
  {
        extern int trust_executable_bit, has_symlinks;
        if (!has_symlinks && S_ISREG(mode) &&
@@@ -425,8 -419,6 +425,8 @@@ extern int path_inside_repo(const char 
  extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
  extern int init_db(const char *template_dir, unsigned int flags);
  
 +extern void sanitize_stdfds(void);
 +
  #define alloc_nr(x) (((x)+16)*3/2)
  
  /*
@@@ -478,7 -470,7 +478,7 @@@ extern int remove_file_from_index(struc
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
 -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern int index_name_is_other(const struct index_state *, const char *, int);
  extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
  
  #define CE_MATCH_RACY_IS_DIRTY                02
  /* do stat comparison even if CE_SKIP_WORKTREE is true */
  #define CE_MATCH_IGNORE_SKIP_WORKTREE 04
 -extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 -extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
 +extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  
  #define PATHSPEC_ONESTAR 1    /* the pathspec pattern sastisfies GFNM_ONESTAR */
  
@@@ -517,21 -509,6 +517,21 @@@ extern int limit_pathspec_to_literal(vo
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
  extern int index_path(unsigned char *sha1, const char *path, struct stat *st, unsigned flags);
 +
 +/*
 + * Record to sd the data from st that we use to check whether a file
 + * might have changed.
 + */
 +extern void fill_stat_data(struct stat_data *sd, struct stat *st);
 +
 +/*
 + * Return 0 if st is consistent with a file not having been changed
 + * since sd was filled.  If there are differences, return a
 + * combination of MTIME_CHANGED, CTIME_CHANGED, OWNER_CHANGED,
 + * INODE_CHANGED, and DATA_CHANGED.
 + */
 +extern int match_stat_data(const struct stat_data *sd, struct stat *st);
 +
  extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
  
  #define REFRESH_REALLY                0x0001  /* ignore_valid */
@@@ -795,6 -772,9 +795,6 @@@ extern int parse_sha1_header(const cha
  /* global flag to enable extra checks when accessing packed objects */
  extern int do_check_packed_object_crc;
  
 -/* for development: log offset of pack access */
 -extern const char *log_pack_access;
 -
  extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
  
  extern int move_temp_to_file(const char *tmpfile, const char *filename);
@@@ -930,7 -910,6 +930,7 @@@ void show_date_relative(unsigned long t
                        struct strbuf *timebuf);
  int parse_date(const char *date, char *buf, int bufsize);
  int parse_date_basic(const char *date, unsigned long *timestamp, int *offset);
 +int parse_expiry_date(const char *date, unsigned long *timestamp);
  void datestamp(char *buf, int bufsize);
  #define approxidate(s) approxidate_careful((s), NULL)
  unsigned long approxidate_careful(const char *, int *);
@@@ -1045,21 -1024,9 +1045,21 @@@ struct ref 
        unsigned int
                force:1,
                forced_update:1,
 -              merge:1,
                deletion:1,
                matched:1;
 +
 +      /*
 +       * Order is important here, as we write to FETCH_HEAD
 +       * in numeric order. And the default NOT_FOR_MERGE
 +       * should be 0, so that xcalloc'd structures get it
 +       * by default.
 +       */
 +      enum {
 +              FETCH_HEAD_MERGE = -1,
 +              FETCH_HEAD_NOT_FOR_MERGE = 0,
 +              FETCH_HEAD_IGNORE = 1
 +      } fetch_head_status;
 +
        enum {
                REF_STATUS_NONE = 0,
                REF_STATUS_OK,
@@@ -1132,7 -1099,6 +1132,7 @@@ extern int unpack_object_header(struct 
  struct object_info {
        /* Request */
        unsigned long *sizep;
 +      unsigned long *disk_sizep;
  
        /* Response */
        enum {
@@@ -1176,11 -1142,15 +1176,15 @@@ extern int update_server_info(int)
  typedef int (*config_fn_t)(const char *, const char *, void *);
  extern int git_default_config(const char *, const char *, void *);
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
+ extern int git_config_from_buf(config_fn_t fn, const char *name,
+                              const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern int git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
-                                  const char *filename, int respect_includes);
+                                  const char *filename,
+                                  const char *blob_ref,
+                                  int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
@@@ -1360,31 -1330,4 +1364,31 @@@ int checkout_fast_forward(const unsigne
  
  int sane_execvp(const char *file, char *const argv[]);
  
 +/*
 + * A struct to encapsulate the concept of whether a file has changed
 + * since we last checked it. This uses criteria similar to those used
 + * for the index.
 + */
 +struct stat_validity {
 +      struct stat_data *sd;
 +};
 +
 +void stat_validity_clear(struct stat_validity *sv);
 +
 +/*
 + * Returns 1 if the path is a regular file (or a symlink to a regular
 + * file) and matches the saved stat_validity, 0 otherwise.  A missing
 + * or inaccessible file is considered a match if the struct was just
 + * initialized, or if the previous update found an inaccessible file.
 + */
 +int stat_validity_check(struct stat_validity *sv, const char *path);
 +
 +/*
 + * Update the stat_validity from a file opened at descriptor fd. If
 + * the file is missing, inaccessible, or not a regular file, then
 + * future calls to stat_validity_check will match iff one of those
 + * conditions continues to be true.
 + */
 +void stat_validity_update(struct stat_validity *sv, int fd);
 +
  #endif /* CACHE_H */
diff --combined config.c
index d04e8157abc415c6956bb8a237c8a17bd9624f8c,680dd6d685029a4c8f7ff0b6bc151e788b120d0b..e13a7b65e7615da7d788fddd001716a17baef5db
+++ b/config.c
  #include "strbuf.h"
  #include "quote.h"
  
- typedef struct config_file {
-       struct config_file *prev;
-       FILE *f;
+ struct config_source {
+       struct config_source *prev;
+       union {
+               FILE *file;
+               struct config_buf {
+                       const char *buf;
+                       size_t len;
+                       size_t pos;
+               } buf;
+       } u;
        const char *name;
+       int die_on_error;
        int linenr;
        int eof;
        struct strbuf value;
        struct strbuf var;
- } config_file;
  
- static config_file *cf;
+       int (*fgetc)(struct config_source *c);
+       int (*ungetc)(int c, struct config_source *conf);
+       long (*ftell)(struct config_source *c);
+ };
+ static struct config_source *cf;
  
  static int zlib_compression_seen;
  
+ static int config_file_fgetc(struct config_source *conf)
+ {
+       return fgetc(conf->u.file);
+ }
+ static int config_file_ungetc(int c, struct config_source *conf)
+ {
+       return ungetc(c, conf->u.file);
+ }
+ static long config_file_ftell(struct config_source *conf)
+ {
+       return ftell(conf->u.file);
+ }
+ static int config_buf_fgetc(struct config_source *conf)
+ {
+       if (conf->u.buf.pos < conf->u.buf.len)
+               return conf->u.buf.buf[conf->u.buf.pos++];
+       return EOF;
+ }
+ 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];
+       return EOF;
+ }
+ static long config_buf_ftell(struct config_source *conf)
+ {
+       return conf->u.buf.pos;
+ }
  #define MAX_INCLUDE_DEPTH 10
  static const char include_depth_advice[] =
  "exceeded maximum include depth (%d) while including\n"
@@@ -58,7 -107,7 +107,7 @@@ static int handle_path_include(const ch
                path = buf.buf;
        }
  
 -      if (!access_or_die(path, R_OK)) {
 +      if (!access_or_die(path, R_OK, 0)) {
                if (++inc->depth > MAX_INCLUDE_DEPTH)
                        die(include_depth_advice, MAX_INCLUDE_DEPTH, path,
                            cf && cf->name ? cf->name : "the command line");
@@@ -168,27 -217,22 +217,22 @@@ int git_config_from_parameters(config_f
  
  static int get_next_char(void)
  {
-       int c;
-       FILE *f;
-       c = '\n';
-       if (cf && ((f = cf->f) != NULL)) {
-               c = fgetc(f);
-               if (c == '\r') {
-                       /* DOS like systems */
-                       c = fgetc(f);
-                       if (c != '\n') {
-                               ungetc(c, f);
-                               c = '\r';
-                       }
-               }
-               if (c == '\n')
-                       cf->linenr++;
-               if (c == EOF) {
-                       cf->eof = 1;
-                       c = '\n';
+       int c = cf->fgetc(cf);
+       if (c == '\r') {
+               /* DOS like systems */
+               c = cf->fgetc(cf);
+               if (c != '\n') {
+                       cf->ungetc(c, cf);
+                       c = '\r';
                }
        }
+       if (c == '\n')
+               cf->linenr++;
+       if (c == EOF) {
+               cf->eof = 1;
+               c = '\n';
+       }
        return c;
  }
  
@@@ -339,7 -383,7 +383,7 @@@ static int get_base_var(struct strbuf *
        }
  }
  
- static int git_parse_file(config_fn_t fn, void *data)
+ static int git_parse_source(config_fn_t fn, void *data)
  {
        int comment = 0;
        int baselen = 0;
                if (get_value(fn, data, var) < 0)
                        break;
        }
-       die("bad config file line %d in %s", cf->linenr, cf->name);
+       if (cf->die_on_error)
+               die("bad config file line %d in %s", cf->linenr, cf->name);
+       else
+               return error("bad config file line %d in %s", cf->linenr, cf->name);
  }
  
  static int parse_unit_factor(const char *end, uintmax_t *val)
@@@ -566,20 -613,7 +613,20 @@@ static int git_default_core_config(cons
                trust_ctime = git_config_bool(var, value);
                return 0;
        }
 -      if (!strcmp(var, "core.statinfo")) {
 +      if (!strcmp(var, "core.statinfo") ||
 +          !strcmp(var, "core.checkstat")) {
 +              /*
 +               * NEEDSWORK: statinfo was a typo in v1.8.2 that has
 +               * never been advertised.  we will remove it at Git
 +               * 2.0 boundary.
 +               */
 +              if (!strcmp(var, "core.statinfo")) {
 +                      static int warned;
 +                      if (!warned++) {
 +                              warning("'core.statinfo' will be removed in Git 2.0; "
 +                                      "use 'core.checkstat' instead.");
 +                      }
 +              }
                if (!strcasecmp(value, "default"))
                        check_stat = 1;
                else if (!strcasecmp(value, "minimal"))
                return 0;
        }
  
 -      if (!strcmp(var, "core.logpackaccess"))
 -              return git_config_string(&log_pack_access, var, value);
 -
        if (!strcmp(var, "core.autocrlf")) {
                if (value && !strcasecmp(value, "input")) {
                        if (core_eol == EOL_CRLF)
@@@ -906,6 -943,33 +953,33 @@@ int git_default_config(const char *var
        return 0;
  }
  
+ /*
+  * All source specific fields in the union, die_on_error, name and the callbacks
+  * fgetc, ungetc, ftell of top need to be initialized before calling
+  * this function.
+  */
+ static int do_config_from(struct config_source *top, config_fn_t fn, void *data)
+ {
+       int ret;
+       /* push config-file parsing state stack */
+       top->prev = cf;
+       top->linenr = 1;
+       top->eof = 0;
+       strbuf_init(&top->value, 1024);
+       strbuf_init(&top->var, 1024);
+       cf = top;
+       ret = git_parse_source(fn, data);
+       /* pop config-file parsing state stack */
+       strbuf_release(&top->value);
+       strbuf_release(&top->var);
+       cf = top->prev;
+       return ret;
+ }
  int git_config_from_file(config_fn_t fn, const char *filename, void *data)
  {
        int ret;
  
        ret = -1;
        if (f) {
-               config_file top;
+               struct config_source top;
  
-               /* push config-file parsing state stack */
-               top.prev = cf;
-               top.f = f;
+               top.u.file = f;
                top.name = filename;
-               top.linenr = 1;
-               top.eof = 0;
-               strbuf_init(&top.value, 1024);
-               strbuf_init(&top.var, 1024);
-               cf = &top;
-               ret = git_parse_file(fn, data);
+               top.die_on_error = 1;
+               top.fgetc = config_file_fgetc;
+               top.ungetc = config_file_ungetc;
+               top.ftell = config_file_ftell;
  
-               /* pop config-file parsing state stack */
-               strbuf_release(&top.value);
-               strbuf_release(&top.var);
-               cf = top.prev;
+               ret = do_config_from(&top, fn, data);
  
                fclose(f);
        }
        return ret;
  }
  
+ int git_config_from_buf(config_fn_t fn, const char *name, const char *buf,
+                       size_t len, void *data)
+ {
+       struct config_source top;
+       top.u.buf.buf = buf;
+       top.u.buf.len = len;
+       top.u.buf.pos = 0;
+       top.name = name;
+       top.die_on_error = 0;
+       top.fgetc = config_buf_fgetc;
+       top.ungetc = config_buf_ungetc;
+       top.ftell = config_buf_ftell;
+       return do_config_from(&top, fn, data);
+ }
+ static int git_config_from_blob_sha1(config_fn_t fn,
+                                    const char *name,
+                                    const unsigned char *sha1,
+                                    void *data)
+ {
+       enum object_type type;
+       char *buf;
+       unsigned long size;
+       int ret;
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               return error("unable to load config blob object '%s'", name);
+       if (type != OBJ_BLOB) {
+               free(buf);
+               return error("reference '%s' does not point to a blob", name);
+       }
+       ret = git_config_from_buf(fn, name, buf, size, data);
+       free(buf);
+       return ret;
+ }
+ static int git_config_from_blob_ref(config_fn_t fn,
+                                   const char *name,
+                                   void *data)
+ {
+       unsigned char sha1[20];
+       if (get_sha1(name, sha1) < 0)
+               return error("unable to resolve config blob '%s'", name);
+       return git_config_from_blob_sha1(fn, name, sha1, data);
+ }
  const char *git_etc_gitconfig(void)
  {
        static const char *system_wide;
@@@ -964,23 -1072,23 +1082,23 @@@ int git_config_early(config_fn_t fn, vo
  
        home_config_paths(&user_config, &xdg_config, "config");
  
 -      if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK)) {
 +      if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
                ret += git_config_from_file(fn, git_etc_gitconfig(),
                                            data);
                found += 1;
        }
  
 -      if (xdg_config && !access_or_die(xdg_config, R_OK)) {
 +      if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) {
                ret += git_config_from_file(fn, xdg_config, data);
                found += 1;
        }
  
 -      if (user_config && !access_or_die(user_config, R_OK)) {
 +      if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) {
                ret += git_config_from_file(fn, user_config, data);
                found += 1;
        }
  
 -      if (repo_config && !access_or_die(repo_config, R_OK)) {
 +      if (repo_config && !access_or_die(repo_config, R_OK, 0)) {
                ret += git_config_from_file(fn, repo_config, data);
                found += 1;
        }
  }
  
  int git_config_with_options(config_fn_t fn, void *data,
-                           const char *filename, int respect_includes)
+                           const char *filename,
+                           const char *blob_ref,
+                           int respect_includes)
  {
        char *repo_config = NULL;
        int ret;
         */
        if (filename)
                return git_config_from_file(fn, filename, data);
+       else if (blob_ref)
+               return git_config_from_blob_ref(fn, blob_ref, data);
  
        repo_config = git_pathdup("config");
        ret = git_config_early(fn, data, repo_config);
  
  int git_config(config_fn_t fn, void *data)
  {
-       return git_config_with_options(fn, data, NULL, 1);
+       return git_config_with_options(fn, data, NULL, NULL, 1);
  }
  
  /*
@@@ -1063,7 -1175,6 +1185,6 @@@ static int store_aux(const char *key, c
  {
        const char *ep;
        size_t section_len;
-       FILE *f = cf->f;
  
        switch (store.state) {
        case KEY_SEEN:
                                return 1;
                        }
  
-                       store.offset[store.seen] = ftell(f);
+                       store.offset[store.seen] = cf->ftell(cf);
                        store.seen++;
                }
                break;
                 * Do not increment matches: this is no match, but we
                 * just made sure we are in the desired section.
                 */
-               store.offset[store.seen] = ftell(f);
+               store.offset[store.seen] = cf->ftell(cf);
                /* fallthru */
        case SECTION_END_SEEN:
        case START:
                if (matches(key, value)) {
-                       store.offset[store.seen] = ftell(f);
+                       store.offset[store.seen] = cf->ftell(cf);
                        store.state = KEY_SEEN;
                        store.seen++;
                } else {
                        if (strrchr(key, '.') - key == store.baselen &&
                              !strncmp(key, store.key, store.baselen)) {
                                        store.state = SECTION_SEEN;
-                                       store.offset[store.seen] = ftell(f);
+                                       store.offset[store.seen] = cf->ftell(cf);
                        }
                }
        }