Merge branch 'js/config-parse' into jch
The parsing routines for the configuration files have been split into a separate file. * js/config-parse: config-parse: split library out of config.[c|h] config.c: accept config_parse_options in git_config_from_stdin config: report config parse errors using cb config: split do_event() into start and flush operations config: split out config_parse_options
This commit is contained in:
1
Makefile
1
Makefile
@ -995,6 +995,7 @@ LIB_OBJS += compat/obstack.o
|
||||
LIB_OBJS += compat/terminal.o
|
||||
LIB_OBJS += compat/zlib-uncompress2.o
|
||||
LIB_OBJS += config.o
|
||||
LIB_OBJS += config-parse.o
|
||||
LIB_OBJS += connect.o
|
||||
LIB_OBJS += connected.o
|
||||
LIB_OBJS += convert.o
|
||||
|
@ -40,7 +40,9 @@ static int actions, type;
|
||||
static char *default_value;
|
||||
static int end_nul;
|
||||
static int respect_includes_opt = -1;
|
||||
static struct config_options config_options;
|
||||
static struct config_options config_options = {
|
||||
.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE)
|
||||
};
|
||||
static int show_origin;
|
||||
static int show_scope;
|
||||
static int fixed_value;
|
||||
|
@ -237,9 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,
|
||||
struct bundle_list *list)
|
||||
{
|
||||
int result;
|
||||
struct config_options opts = {
|
||||
.error_action = CONFIG_ERROR_ERROR,
|
||||
};
|
||||
struct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);
|
||||
|
||||
if (!list->baseURI) {
|
||||
struct strbuf baseURI = STRBUF_INIT;
|
||||
|
601
config-parse.c
Normal file
601
config-parse.c
Normal file
@ -0,0 +1,601 @@
|
||||
#include "git-compat-util.h"
|
||||
#include "strbuf.h"
|
||||
#include "gettext.h"
|
||||
#include "hashmap.h"
|
||||
#include "utf8.h"
|
||||
#include "config-parse.h"
|
||||
|
||||
static int config_file_fgetc(struct config_source *conf)
|
||||
{
|
||||
return getc_unlocked(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) {
|
||||
conf->u.buf.pos--;
|
||||
if (conf->u.buf.buf[conf->u.buf.pos] != c)
|
||||
BUG("config_buf can only ungetc the same character");
|
||||
return c;
|
||||
}
|
||||
|
||||
return EOF;
|
||||
}
|
||||
|
||||
static long config_buf_ftell(struct config_source *conf)
|
||||
{
|
||||
return conf->u.buf.pos;
|
||||
}
|
||||
|
||||
static inline int iskeychar(int c)
|
||||
{
|
||||
return isalnum(c) || c == '-';
|
||||
}
|
||||
|
||||
/*
|
||||
* Auxiliary function to sanity-check and split the key into the section
|
||||
* identifier and variable name.
|
||||
*
|
||||
* Returns 0 on success, -CONFIG_INVALID_KEY when there is an invalid character
|
||||
* in the key and -CONFIG_NO_SECTION_OR_NAME if there is no section name in the
|
||||
* key.
|
||||
*
|
||||
* store_key - pointer to char* which will hold a copy of the key with
|
||||
* lowercase section and variable name
|
||||
* baselen - pointer to size_t which will hold the length of the
|
||||
* section + subsection part, can be NULL
|
||||
*/
|
||||
int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)
|
||||
{
|
||||
size_t i, baselen;
|
||||
int dot;
|
||||
const char *last_dot = strrchr(key, '.');
|
||||
|
||||
/*
|
||||
* Since "key" actually contains the section name and the real
|
||||
* key name separated by a dot, we have to know where the dot is.
|
||||
*/
|
||||
|
||||
if (last_dot == NULL || last_dot == key) {
|
||||
error(_("key does not contain a section: %s"), key);
|
||||
return -CONFIG_NO_SECTION_OR_NAME;
|
||||
}
|
||||
|
||||
if (!last_dot[1]) {
|
||||
error(_("key does not contain variable name: %s"), key);
|
||||
return -CONFIG_NO_SECTION_OR_NAME;
|
||||
}
|
||||
|
||||
baselen = last_dot - key;
|
||||
if (baselen_)
|
||||
*baselen_ = baselen;
|
||||
|
||||
/*
|
||||
* Validate the key and while at it, lower case it for matching.
|
||||
*/
|
||||
*store_key = xmallocz(strlen(key));
|
||||
|
||||
dot = 0;
|
||||
for (i = 0; key[i]; i++) {
|
||||
unsigned char c = key[i];
|
||||
if (c == '.')
|
||||
dot = 1;
|
||||
/* Leave the extended basename untouched.. */
|
||||
if (!dot || i > baselen) {
|
||||
if (!iskeychar(c) ||
|
||||
(i == baselen + 1 && !isalpha(c))) {
|
||||
error(_("invalid key: %s"), key);
|
||||
goto out_free_ret_1;
|
||||
}
|
||||
c = tolower(c);
|
||||
} else if (c == '\n') {
|
||||
error(_("invalid key (newline): %s"), key);
|
||||
goto out_free_ret_1;
|
||||
}
|
||||
(*store_key)[i] = c;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_ret_1:
|
||||
FREE_AND_NULL(*store_key);
|
||||
return -CONFIG_INVALID_KEY;
|
||||
}
|
||||
|
||||
static int get_next_char(struct config_source *cs)
|
||||
{
|
||||
int c = cs->do_fgetc(cs);
|
||||
|
||||
if (c == '\r') {
|
||||
/* DOS like systems */
|
||||
c = cs->do_fgetc(cs);
|
||||
if (c != '\n') {
|
||||
if (c != EOF)
|
||||
cs->do_ungetc(c, cs);
|
||||
c = '\r';
|
||||
}
|
||||
}
|
||||
|
||||
if (c != EOF && ++cs->total_len > INT_MAX) {
|
||||
/*
|
||||
* This is an absurdly long config file; refuse to parse
|
||||
* further in order to protect downstream code from integer
|
||||
* overflows. Note that we can't return an error specifically,
|
||||
* but we can mark EOF and put trash in the return value,
|
||||
* which will trigger a parse error.
|
||||
*/
|
||||
cs->eof = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
cs->linenr++;
|
||||
if (c == EOF) {
|
||||
cs->eof = 1;
|
||||
cs->linenr++;
|
||||
c = '\n';
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static char *parse_value(struct config_source *cs)
|
||||
{
|
||||
int quote = 0, comment = 0, space = 0;
|
||||
|
||||
strbuf_reset(&cs->value);
|
||||
for (;;) {
|
||||
int c = get_next_char(cs);
|
||||
if (c == '\n') {
|
||||
if (quote) {
|
||||
cs->linenr--;
|
||||
return NULL;
|
||||
}
|
||||
return cs->value.buf;
|
||||
}
|
||||
if (comment)
|
||||
continue;
|
||||
if (isspace(c) && !quote) {
|
||||
if (cs->value.len)
|
||||
space++;
|
||||
continue;
|
||||
}
|
||||
if (!quote) {
|
||||
if (c == ';' || c == '#') {
|
||||
comment = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (; space; space--)
|
||||
strbuf_addch(&cs->value, ' ');
|
||||
if (c == '\\') {
|
||||
c = get_next_char(cs);
|
||||
switch (c) {
|
||||
case '\n':
|
||||
continue;
|
||||
case 't':
|
||||
c = '\t';
|
||||
break;
|
||||
case 'b':
|
||||
c = '\b';
|
||||
break;
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
/* Some characters escape as themselves */
|
||||
case '\\': case '"':
|
||||
break;
|
||||
/* Reject unknown escape sequences */
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
strbuf_addch(&cs->value, c);
|
||||
continue;
|
||||
}
|
||||
if (c == '"') {
|
||||
quote = 1-quote;
|
||||
continue;
|
||||
}
|
||||
strbuf_addch(&cs->value, c);
|
||||
}
|
||||
}
|
||||
|
||||
static int get_value(struct config_source *cs, struct key_value_info *kvi,
|
||||
config_fn_t fn, void *data, struct strbuf *name)
|
||||
{
|
||||
int c;
|
||||
char *value;
|
||||
int ret;
|
||||
struct config_context ctx = {
|
||||
.kvi = kvi,
|
||||
};
|
||||
|
||||
/* Get the full name */
|
||||
for (;;) {
|
||||
c = get_next_char(cs);
|
||||
if (cs->eof)
|
||||
break;
|
||||
if (!iskeychar(c))
|
||||
break;
|
||||
strbuf_addch(name, tolower(c));
|
||||
}
|
||||
|
||||
while (c == ' ' || c == '\t')
|
||||
c = get_next_char(cs);
|
||||
|
||||
value = NULL;
|
||||
if (c != '\n') {
|
||||
if (c != '=')
|
||||
return -1;
|
||||
value = parse_value(cs);
|
||||
if (!value)
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* We already consumed the \n, but we need linenr to point to
|
||||
* the line we just parsed during the call to fn to get
|
||||
* accurate line number in error messages.
|
||||
*/
|
||||
cs->linenr--;
|
||||
kvi->linenr = cs->linenr;
|
||||
ret = fn(name->buf, value, &ctx, data);
|
||||
if (ret >= 0)
|
||||
cs->linenr++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
|
||||
int c)
|
||||
{
|
||||
cs->subsection_case_sensitive = 0;
|
||||
do {
|
||||
if (c == '\n')
|
||||
goto error_incomplete_line;
|
||||
c = get_next_char(cs);
|
||||
} while (isspace(c));
|
||||
|
||||
/* We require the format to be '[base "extension"]' */
|
||||
if (c != '"')
|
||||
return -1;
|
||||
strbuf_addch(name, '.');
|
||||
|
||||
for (;;) {
|
||||
int c = get_next_char(cs);
|
||||
if (c == '\n')
|
||||
goto error_incomplete_line;
|
||||
if (c == '"')
|
||||
break;
|
||||
if (c == '\\') {
|
||||
c = get_next_char(cs);
|
||||
if (c == '\n')
|
||||
goto error_incomplete_line;
|
||||
}
|
||||
strbuf_addch(name, c);
|
||||
}
|
||||
|
||||
/* Final ']' */
|
||||
if (get_next_char(cs) != ']')
|
||||
return -1;
|
||||
return 0;
|
||||
error_incomplete_line:
|
||||
cs->linenr--;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int get_base_var(struct config_source *cs, struct strbuf *name)
|
||||
{
|
||||
cs->subsection_case_sensitive = 1;
|
||||
for (;;) {
|
||||
int c = get_next_char(cs);
|
||||
if (cs->eof)
|
||||
return -1;
|
||||
if (c == ']')
|
||||
return 0;
|
||||
if (isspace(c))
|
||||
return get_extended_base_var(cs, name, c);
|
||||
if (!iskeychar(c) && c != '.')
|
||||
return -1;
|
||||
strbuf_addch(name, tolower(c));
|
||||
}
|
||||
}
|
||||
|
||||
struct parse_event_data {
|
||||
enum config_event_t previous_type;
|
||||
size_t previous_offset;
|
||||
const struct config_parse_options *opts;
|
||||
};
|
||||
|
||||
static size_t get_corrected_offset(struct config_source *cs,
|
||||
enum config_event_t type)
|
||||
{
|
||||
size_t offset = cs->do_ftell(cs);
|
||||
|
||||
/*
|
||||
* At EOF, the parser always "inserts" an extra '\n', therefore
|
||||
* the end offset of the event is the current file position, otherwise
|
||||
* we will already have advanced to the next event.
|
||||
*/
|
||||
if (type != CONFIG_EVENT_EOF)
|
||||
offset--;
|
||||
return offset;
|
||||
}
|
||||
|
||||
static void start_event(struct config_source *cs, enum config_event_t type,
|
||||
struct parse_event_data *data)
|
||||
{
|
||||
data->previous_type = type;
|
||||
data->previous_offset = get_corrected_offset(cs, type);
|
||||
}
|
||||
|
||||
static int flush_event(struct config_source *cs, enum config_event_t type,
|
||||
struct parse_event_data *data)
|
||||
{
|
||||
if (!data->opts || !data->opts->event_fn)
|
||||
return 0;
|
||||
|
||||
if (type == CONFIG_EVENT_WHITESPACE &&
|
||||
data->previous_type == type)
|
||||
return 0;
|
||||
|
||||
if (data->previous_type != CONFIG_EVENT_EOF &&
|
||||
data->opts->event_fn(data->previous_type, data->previous_offset,
|
||||
get_corrected_offset(cs, type), cs,
|
||||
data->opts->event_fn_data) < 0)
|
||||
return -1;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int do_event(struct config_source *cs, enum config_event_t type,
|
||||
struct parse_event_data *data)
|
||||
{
|
||||
int maybe_ret;
|
||||
|
||||
if ((maybe_ret = flush_event(cs, type, data)) < 1)
|
||||
return maybe_ret;
|
||||
|
||||
start_event(cs, type, data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int do_event_and_flush(struct config_source *cs,
|
||||
enum config_event_t type,
|
||||
struct parse_event_data *data)
|
||||
{
|
||||
int maybe_ret;
|
||||
|
||||
if ((maybe_ret = flush_event(cs, type, data)) < 1)
|
||||
return maybe_ret;
|
||||
|
||||
start_event(cs, type, data);
|
||||
|
||||
if ((maybe_ret = flush_event(cs, type, data)) < 1)
|
||||
return maybe_ret;
|
||||
|
||||
/*
|
||||
* Not actually EOF, but this indicates we don't have a valid event
|
||||
* to flush next time around.
|
||||
*/
|
||||
data->previous_type = CONFIG_EVENT_EOF;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kvi_from_source(struct config_source *cs,
|
||||
enum config_scope scope,
|
||||
struct key_value_info *out)
|
||||
{
|
||||
out->filename = strintern(cs->name);
|
||||
out->origin_type = cs->origin_type;
|
||||
out->linenr = cs->linenr;
|
||||
out->scope = scope;
|
||||
out->path = cs->path;
|
||||
}
|
||||
|
||||
static int git_parse_source(struct config_source *cs, config_fn_t fn,
|
||||
struct key_value_info *kvi, void *data,
|
||||
const struct config_parse_options *opts)
|
||||
{
|
||||
int comment = 0;
|
||||
size_t baselen = 0;
|
||||
struct strbuf *var = &cs->var;
|
||||
|
||||
/* U+FEFF Byte Order Mark in UTF8 */
|
||||
const char *bomptr = utf8_bom;
|
||||
|
||||
/* For the parser event callback */
|
||||
struct parse_event_data event_data = {
|
||||
CONFIG_EVENT_EOF, 0, opts
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
int c;
|
||||
|
||||
c = get_next_char(cs);
|
||||
if (bomptr && *bomptr) {
|
||||
/* 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 (c == (*bomptr & 0377)) {
|
||||
bomptr++;
|
||||
continue;
|
||||
} else {
|
||||
/* Do not tolerate partial BOM. */
|
||||
if (bomptr != utf8_bom)
|
||||
break;
|
||||
/* No BOM at file beginning. Cool. */
|
||||
bomptr = NULL;
|
||||
}
|
||||
}
|
||||
if (c == '\n') {
|
||||
if (cs->eof) {
|
||||
if (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
|
||||
return -1;
|
||||
comment = 0;
|
||||
continue;
|
||||
}
|
||||
if (comment)
|
||||
continue;
|
||||
if (isspace(c)) {
|
||||
if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
if (c == '#' || c == ';') {
|
||||
if (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)
|
||||
return -1;
|
||||
comment = 1;
|
||||
continue;
|
||||
}
|
||||
if (c == '[') {
|
||||
if (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)
|
||||
return -1;
|
||||
|
||||
/* Reset prior to determining a new stem */
|
||||
strbuf_reset(var);
|
||||
if (get_base_var(cs, var) < 0 || var->len < 1)
|
||||
break;
|
||||
strbuf_addch(var, '.');
|
||||
baselen = var->len;
|
||||
continue;
|
||||
}
|
||||
if (!isalpha(c))
|
||||
break;
|
||||
|
||||
if (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Truncate the var name back to the section header
|
||||
* stem prior to grabbing the suffix part of the name
|
||||
* and the value.
|
||||
*/
|
||||
strbuf_setlen(var, baselen);
|
||||
strbuf_addch(var, tolower(c));
|
||||
if (get_value(cs, kvi, fn, data, var) < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
do_event_and_flush(cs, CONFIG_EVENT_ERROR, &event_data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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, enum config_scope scope,
|
||||
const struct config_parse_options *opts)
|
||||
{
|
||||
struct key_value_info kvi = KVI_INIT;
|
||||
int ret;
|
||||
|
||||
/* push config-file parsing state stack */
|
||||
top->linenr = 1;
|
||||
top->eof = 0;
|
||||
top->total_len = 0;
|
||||
strbuf_init(&top->value, 1024);
|
||||
strbuf_init(&top->var, 1024);
|
||||
kvi_from_source(top, scope, &kvi);
|
||||
|
||||
ret = git_parse_source(top, fn, &kvi, data, opts);
|
||||
|
||||
strbuf_release(&top->value);
|
||||
strbuf_release(&top->var);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_config_from_file(config_fn_t fn,
|
||||
const enum config_origin_type origin_type,
|
||||
const char *name, const char *path, FILE *f,
|
||||
void *data, enum config_scope scope,
|
||||
const struct config_parse_options *opts)
|
||||
{
|
||||
struct config_source top = CONFIG_SOURCE_INIT;
|
||||
int ret;
|
||||
|
||||
top.u.file = f;
|
||||
top.origin_type = origin_type;
|
||||
top.name = name;
|
||||
top.path = path;
|
||||
top.do_fgetc = config_file_fgetc;
|
||||
top.do_ungetc = config_file_ungetc;
|
||||
top.do_ftell = config_file_ftell;
|
||||
|
||||
flockfile(f);
|
||||
ret = do_config_from(&top, fn, data, scope, opts);
|
||||
funlockfile(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,
|
||||
const struct config_parse_options *config_opts)
|
||||
{
|
||||
return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
|
||||
data, scope, config_opts);
|
||||
}
|
||||
|
||||
int git_config_from_file_with_options(config_fn_t fn, const char *filename,
|
||||
void *data, enum config_scope scope,
|
||||
const struct config_parse_options *opts)
|
||||
{
|
||||
int ret = -1;
|
||||
FILE *f;
|
||||
|
||||
if (!filename)
|
||||
BUG("filename cannot be NULL");
|
||||
f = fopen_or_warn(filename, "r");
|
||||
if (f) {
|
||||
ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
|
||||
filename, f, data, scope, opts);
|
||||
fclose(f);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int git_config_from_mem(config_fn_t fn,
|
||||
const enum config_origin_type origin_type,
|
||||
const char *name, const char *buf, size_t len,
|
||||
void *data, enum config_scope scope,
|
||||
const struct config_parse_options *opts)
|
||||
{
|
||||
struct config_source top = CONFIG_SOURCE_INIT;
|
||||
|
||||
top.u.buf.buf = buf;
|
||||
top.u.buf.len = len;
|
||||
top.u.buf.pos = 0;
|
||||
top.origin_type = origin_type;
|
||||
top.name = name;
|
||||
top.path = NULL;
|
||||
top.do_fgetc = config_buf_fgetc;
|
||||
top.do_ungetc = config_buf_ungetc;
|
||||
top.do_ftell = config_buf_ftell;
|
||||
|
||||
return do_config_from(&top, fn, data, scope, opts);
|
||||
}
|
155
config-parse.h
Normal file
155
config-parse.h
Normal file
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Low level config parsing.
|
||||
*/
|
||||
#ifndef CONFIG_PARSE_H
|
||||
#define CONFIG_PARSE_H
|
||||
|
||||
#include "strbuf.h"
|
||||
|
||||
/* git_config_parse_key() returns these negated: */
|
||||
#define CONFIG_INVALID_KEY 1
|
||||
#define CONFIG_NO_SECTION_OR_NAME 2
|
||||
|
||||
int git_config_parse_key(const char *, char **, size_t *);
|
||||
|
||||
enum config_scope {
|
||||
CONFIG_SCOPE_UNKNOWN = 0,
|
||||
CONFIG_SCOPE_SYSTEM,
|
||||
CONFIG_SCOPE_GLOBAL,
|
||||
CONFIG_SCOPE_LOCAL,
|
||||
CONFIG_SCOPE_WORKTREE,
|
||||
CONFIG_SCOPE_COMMAND,
|
||||
CONFIG_SCOPE_SUBMODULE,
|
||||
};
|
||||
const char *config_scope_name(enum config_scope scope);
|
||||
|
||||
enum config_origin_type {
|
||||
CONFIG_ORIGIN_UNKNOWN = 0,
|
||||
CONFIG_ORIGIN_BLOB,
|
||||
CONFIG_ORIGIN_FILE,
|
||||
CONFIG_ORIGIN_STDIN,
|
||||
CONFIG_ORIGIN_SUBMODULE_BLOB,
|
||||
CONFIG_ORIGIN_CMDLINE
|
||||
};
|
||||
|
||||
enum config_event_t {
|
||||
CONFIG_EVENT_SECTION,
|
||||
CONFIG_EVENT_ENTRY,
|
||||
CONFIG_EVENT_WHITESPACE,
|
||||
CONFIG_EVENT_COMMENT,
|
||||
CONFIG_EVENT_EOF,
|
||||
CONFIG_EVENT_ERROR
|
||||
};
|
||||
|
||||
struct config_source;
|
||||
/*
|
||||
* The parser event function (if not NULL) is called with the event type and
|
||||
* the begin/end offsets of the parsed elements.
|
||||
*
|
||||
* Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline
|
||||
* character is considered part of the element.
|
||||
*/
|
||||
typedef int (*config_parser_event_fn_t)(enum config_event_t type,
|
||||
size_t begin_offset, size_t end_offset,
|
||||
struct config_source *cs,
|
||||
void *event_fn_data);
|
||||
|
||||
struct config_parse_options {
|
||||
/*
|
||||
* event_fn and event_fn_data are for internal use only. Handles events
|
||||
* emitted by the config parser.
|
||||
*/
|
||||
config_parser_event_fn_t event_fn;
|
||||
void *event_fn_data;
|
||||
};
|
||||
|
||||
struct config_source {
|
||||
struct config_source *prev;
|
||||
union {
|
||||
FILE *file;
|
||||
struct config_buf {
|
||||
const char *buf;
|
||||
size_t len;
|
||||
size_t pos;
|
||||
} buf;
|
||||
} u;
|
||||
enum config_origin_type origin_type;
|
||||
const char *name;
|
||||
const char *path;
|
||||
int linenr;
|
||||
int eof;
|
||||
size_t total_len;
|
||||
struct strbuf value;
|
||||
struct strbuf var;
|
||||
unsigned subsection_case_sensitive : 1;
|
||||
|
||||
int (*do_fgetc)(struct config_source *c);
|
||||
int (*do_ungetc)(int c, struct config_source *conf);
|
||||
long (*do_ftell)(struct config_source *c);
|
||||
};
|
||||
#define CONFIG_SOURCE_INIT { 0 }
|
||||
|
||||
/* Config source metadata for a given config key-value pair */
|
||||
struct key_value_info {
|
||||
const char *filename;
|
||||
int linenr;
|
||||
enum config_origin_type origin_type;
|
||||
enum config_scope scope;
|
||||
const char *path;
|
||||
};
|
||||
#define KVI_INIT { \
|
||||
.filename = NULL, \
|
||||
.linenr = -1, \
|
||||
.origin_type = CONFIG_ORIGIN_UNKNOWN, \
|
||||
.scope = CONFIG_SCOPE_UNKNOWN, \
|
||||
.path = NULL, \
|
||||
}
|
||||
|
||||
/* Captures additional information that a config callback can use. */
|
||||
struct config_context {
|
||||
/* Config source metadata for key and value. */
|
||||
const struct key_value_info *kvi;
|
||||
};
|
||||
#define CONFIG_CONTEXT_INIT { 0 }
|
||||
|
||||
/**
|
||||
* A config callback function takes four parameters:
|
||||
*
|
||||
* - the name of the parsed variable. This is in canonical "flat" form: the
|
||||
* section, subsection, and variable segments will be separated by dots,
|
||||
* and the section and variable segments will be all lowercase. E.g.,
|
||||
* `core.ignorecase`, `diff.SomeType.textconv`.
|
||||
*
|
||||
* - the value of the found variable, as a string. If the variable had no
|
||||
* value specified, the value will be NULL (typically this means it
|
||||
* should be interpreted as boolean true).
|
||||
*
|
||||
* - the 'config context', that is, additional information about the config
|
||||
* iteration operation provided by the config machinery. For example, this
|
||||
* includes information about the config source being parsed (e.g. the
|
||||
* filename).
|
||||
*
|
||||
* - a void pointer passed in by the caller of the config API; this can
|
||||
* contain callback-specific data
|
||||
*
|
||||
* A config callback should return 0 for success, or -1 if the variable
|
||||
* could not be parsed properly.
|
||||
*/
|
||||
typedef int (*config_fn_t)(const char *, const char *,
|
||||
const struct config_context *, void *);
|
||||
|
||||
int git_config_from_file_with_options(config_fn_t fn, const char *,
|
||||
void *, enum config_scope,
|
||||
const struct config_parse_options *);
|
||||
|
||||
int git_config_from_mem(config_fn_t fn,
|
||||
const enum config_origin_type,
|
||||
const char *name,
|
||||
const char *buf, size_t len,
|
||||
void *data, enum config_scope scope,
|
||||
const struct config_parse_options *opts);
|
||||
|
||||
int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,
|
||||
const struct config_parse_options *config_opts);
|
||||
|
||||
#endif /* CONFIG_PARSE_H */
|
658
config.c
658
config.c
@ -42,33 +42,6 @@
|
||||
#include "ws.h"
|
||||
#include "write-or-die.h"
|
||||
|
||||
struct config_source {
|
||||
struct config_source *prev;
|
||||
union {
|
||||
FILE *file;
|
||||
struct config_buf {
|
||||
const char *buf;
|
||||
size_t len;
|
||||
size_t pos;
|
||||
} buf;
|
||||
} u;
|
||||
enum config_origin_type origin_type;
|
||||
const char *name;
|
||||
const char *path;
|
||||
enum config_error_action default_error_action;
|
||||
int linenr;
|
||||
int eof;
|
||||
size_t total_len;
|
||||
struct strbuf value;
|
||||
struct strbuf var;
|
||||
unsigned subsection_case_sensitive : 1;
|
||||
|
||||
int (*do_fgetc)(struct config_source *c);
|
||||
int (*do_ungetc)(int c, struct config_source *conf);
|
||||
long (*do_ftell)(struct config_source *c);
|
||||
};
|
||||
#define CONFIG_SOURCE_INIT { 0 }
|
||||
|
||||
static int pack_compression_seen;
|
||||
static int zlib_compression_seen;
|
||||
|
||||
@ -83,47 +56,6 @@ static int zlib_compression_seen;
|
||||
*/
|
||||
static struct config_set protected_config;
|
||||
|
||||
static int config_file_fgetc(struct config_source *conf)
|
||||
{
|
||||
return getc_unlocked(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) {
|
||||
conf->u.buf.pos--;
|
||||
if (conf->u.buf.buf[conf->u.buf.pos] != c)
|
||||
BUG("config_buf can only ungetc the same character");
|
||||
return c;
|
||||
}
|
||||
|
||||
return EOF;
|
||||
}
|
||||
|
||||
static long config_buf_ftell(struct config_source *conf)
|
||||
{
|
||||
return conf->u.buf.pos;
|
||||
}
|
||||
|
||||
struct config_include_data {
|
||||
int depth;
|
||||
config_fn_t fn;
|
||||
@ -185,13 +117,15 @@ static int handle_path_include(const struct key_value_info *kvi,
|
||||
}
|
||||
|
||||
if (!access_or_die(path, R_OK, 0)) {
|
||||
struct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);
|
||||
|
||||
if (++inc->depth > MAX_INCLUDE_DEPTH)
|
||||
die(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,
|
||||
!kvi ? "<unknown>" :
|
||||
kvi->filename ? kvi->filename :
|
||||
"the command line");
|
||||
ret = git_config_from_file_with_options(git_config_include, path, inc,
|
||||
kvi->scope, NULL);
|
||||
kvi->scope, &config_opts);
|
||||
inc->depth--;
|
||||
}
|
||||
cleanup:
|
||||
@ -339,7 +273,9 @@ static int add_remote_url(const char *var, const char *value,
|
||||
|
||||
static void populate_remote_urls(struct config_include_data *inc)
|
||||
{
|
||||
struct config_options opts;
|
||||
struct config_options opts = {
|
||||
.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),
|
||||
};
|
||||
|
||||
opts = *inc->opts;
|
||||
opts.unconditional_remote_url = 1;
|
||||
@ -525,80 +461,6 @@ void git_config_push_env(const char *spec)
|
||||
free(key);
|
||||
}
|
||||
|
||||
static inline int iskeychar(int c)
|
||||
{
|
||||
return isalnum(c) || c == '-';
|
||||
}
|
||||
|
||||
/*
|
||||
* Auxiliary function to sanity-check and split the key into the section
|
||||
* identifier and variable name.
|
||||
*
|
||||
* Returns 0 on success, -1 when there is an invalid character in the key and
|
||||
* -2 if there is no section name in the key.
|
||||
*
|
||||
* store_key - pointer to char* which will hold a copy of the key with
|
||||
* lowercase section and variable name
|
||||
* baselen - pointer to size_t which will hold the length of the
|
||||
* section + subsection part, can be NULL
|
||||
*/
|
||||
int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)
|
||||
{
|
||||
size_t i, baselen;
|
||||
int dot;
|
||||
const char *last_dot = strrchr(key, '.');
|
||||
|
||||
/*
|
||||
* Since "key" actually contains the section name and the real
|
||||
* key name separated by a dot, we have to know where the dot is.
|
||||
*/
|
||||
|
||||
if (last_dot == NULL || last_dot == key) {
|
||||
error(_("key does not contain a section: %s"), key);
|
||||
return -CONFIG_NO_SECTION_OR_NAME;
|
||||
}
|
||||
|
||||
if (!last_dot[1]) {
|
||||
error(_("key does not contain variable name: %s"), key);
|
||||
return -CONFIG_NO_SECTION_OR_NAME;
|
||||
}
|
||||
|
||||
baselen = last_dot - key;
|
||||
if (baselen_)
|
||||
*baselen_ = baselen;
|
||||
|
||||
/*
|
||||
* Validate the key and while at it, lower case it for matching.
|
||||
*/
|
||||
*store_key = xmallocz(strlen(key));
|
||||
|
||||
dot = 0;
|
||||
for (i = 0; key[i]; i++) {
|
||||
unsigned char c = key[i];
|
||||
if (c == '.')
|
||||
dot = 1;
|
||||
/* Leave the extended basename untouched.. */
|
||||
if (!dot || i > baselen) {
|
||||
if (!iskeychar(c) ||
|
||||
(i == baselen + 1 && !isalpha(c))) {
|
||||
error(_("invalid key: %s"), key);
|
||||
goto out_free_ret_1;
|
||||
}
|
||||
c = tolower(c);
|
||||
} else if (c == '\n') {
|
||||
error(_("invalid key (newline): %s"), key);
|
||||
goto out_free_ret_1;
|
||||
}
|
||||
(*store_key)[i] = c;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
out_free_ret_1:
|
||||
FREE_AND_NULL(*store_key);
|
||||
return -CONFIG_INVALID_KEY;
|
||||
}
|
||||
|
||||
static int config_parse_pair(const char *key, const char *value,
|
||||
struct key_value_info *kvi,
|
||||
config_fn_t fn, void *data)
|
||||
@ -783,343 +645,16 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_next_char(struct config_source *cs)
|
||||
int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,
|
||||
size_t end_offset UNUSED, struct config_source *cs,
|
||||
void *data)
|
||||
{
|
||||
int c = cs->do_fgetc(cs);
|
||||
|
||||
if (c == '\r') {
|
||||
/* DOS like systems */
|
||||
c = cs->do_fgetc(cs);
|
||||
if (c != '\n') {
|
||||
if (c != EOF)
|
||||
cs->do_ungetc(c, cs);
|
||||
c = '\r';
|
||||
}
|
||||
}
|
||||
|
||||
if (c != EOF && ++cs->total_len > INT_MAX) {
|
||||
/*
|
||||
* This is an absurdly long config file; refuse to parse
|
||||
* further in order to protect downstream code from integer
|
||||
* overflows. Note that we can't return an error specifically,
|
||||
* but we can mark EOF and put trash in the return value,
|
||||
* which will trigger a parse error.
|
||||
*/
|
||||
cs->eof = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (c == '\n')
|
||||
cs->linenr++;
|
||||
if (c == EOF) {
|
||||
cs->eof = 1;
|
||||
cs->linenr++;
|
||||
c = '\n';
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static char *parse_value(struct config_source *cs)
|
||||
{
|
||||
int quote = 0, comment = 0, space = 0;
|
||||
|
||||
strbuf_reset(&cs->value);
|
||||
for (;;) {
|
||||
int c = get_next_char(cs);
|
||||
if (c == '\n') {
|
||||
if (quote) {
|
||||
cs->linenr--;
|
||||
return NULL;
|
||||
}
|
||||
return cs->value.buf;
|
||||
}
|
||||
if (comment)
|
||||
continue;
|
||||
if (isspace(c) && !quote) {
|
||||
if (cs->value.len)
|
||||
space++;
|
||||
continue;
|
||||
}
|
||||
if (!quote) {
|
||||
if (c == ';' || c == '#') {
|
||||
comment = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (; space; space--)
|
||||
strbuf_addch(&cs->value, ' ');
|
||||
if (c == '\\') {
|
||||
c = get_next_char(cs);
|
||||
switch (c) {
|
||||
case '\n':
|
||||
continue;
|
||||
case 't':
|
||||
c = '\t';
|
||||
break;
|
||||
case 'b':
|
||||
c = '\b';
|
||||
break;
|
||||
case 'n':
|
||||
c = '\n';
|
||||
break;
|
||||
/* Some characters escape as themselves */
|
||||
case '\\': case '"':
|
||||
break;
|
||||
/* Reject unknown escape sequences */
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
strbuf_addch(&cs->value, c);
|
||||
continue;
|
||||
}
|
||||
if (c == '"') {
|
||||
quote = 1-quote;
|
||||
continue;
|
||||
}
|
||||
strbuf_addch(&cs->value, c);
|
||||
}
|
||||
}
|
||||
|
||||
static int get_value(struct config_source *cs, struct key_value_info *kvi,
|
||||
config_fn_t fn, void *data, struct strbuf *name)
|
||||
{
|
||||
int c;
|
||||
char *value;
|
||||
int ret;
|
||||
struct config_context ctx = {
|
||||
.kvi = kvi,
|
||||
};
|
||||
|
||||
/* Get the full name */
|
||||
for (;;) {
|
||||
c = get_next_char(cs);
|
||||
if (cs->eof)
|
||||
break;
|
||||
if (!iskeychar(c))
|
||||
break;
|
||||
strbuf_addch(name, tolower(c));
|
||||
}
|
||||
|
||||
while (c == ' ' || c == '\t')
|
||||
c = get_next_char(cs);
|
||||
|
||||
value = NULL;
|
||||
if (c != '\n') {
|
||||
if (c != '=')
|
||||
return -1;
|
||||
value = parse_value(cs);
|
||||
if (!value)
|
||||
return -1;
|
||||
}
|
||||
/*
|
||||
* We already consumed the \n, but we need linenr to point to
|
||||
* the line we just parsed during the call to fn to get
|
||||
* accurate line number in error messages.
|
||||
*/
|
||||
cs->linenr--;
|
||||
kvi->linenr = cs->linenr;
|
||||
ret = fn(name->buf, value, &ctx, data);
|
||||
if (ret >= 0)
|
||||
cs->linenr++;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_extended_base_var(struct config_source *cs, struct strbuf *name,
|
||||
int c)
|
||||
{
|
||||
cs->subsection_case_sensitive = 0;
|
||||
do {
|
||||
if (c == '\n')
|
||||
goto error_incomplete_line;
|
||||
c = get_next_char(cs);
|
||||
} while (isspace(c));
|
||||
|
||||
/* We require the format to be '[base "extension"]' */
|
||||
if (c != '"')
|
||||
return -1;
|
||||
strbuf_addch(name, '.');
|
||||
|
||||
for (;;) {
|
||||
int c = get_next_char(cs);
|
||||
if (c == '\n')
|
||||
goto error_incomplete_line;
|
||||
if (c == '"')
|
||||
break;
|
||||
if (c == '\\') {
|
||||
c = get_next_char(cs);
|
||||
if (c == '\n')
|
||||
goto error_incomplete_line;
|
||||
}
|
||||
strbuf_addch(name, c);
|
||||
}
|
||||
|
||||
/* Final ']' */
|
||||
if (get_next_char(cs) != ']')
|
||||
return -1;
|
||||
return 0;
|
||||
error_incomplete_line:
|
||||
cs->linenr--;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int get_base_var(struct config_source *cs, struct strbuf *name)
|
||||
{
|
||||
cs->subsection_case_sensitive = 1;
|
||||
for (;;) {
|
||||
int c = get_next_char(cs);
|
||||
if (cs->eof)
|
||||
return -1;
|
||||
if (c == ']')
|
||||
return 0;
|
||||
if (isspace(c))
|
||||
return get_extended_base_var(cs, name, c);
|
||||
if (!iskeychar(c) && c != '.')
|
||||
return -1;
|
||||
strbuf_addch(name, tolower(c));
|
||||
}
|
||||
}
|
||||
|
||||
struct parse_event_data {
|
||||
enum config_event_t previous_type;
|
||||
size_t previous_offset;
|
||||
const struct config_options *opts;
|
||||
};
|
||||
|
||||
static int do_event(struct config_source *cs, enum config_event_t type,
|
||||
struct parse_event_data *data)
|
||||
{
|
||||
size_t offset;
|
||||
|
||||
if (!data->opts || !data->opts->event_fn)
|
||||
return 0;
|
||||
|
||||
if (type == CONFIG_EVENT_WHITESPACE &&
|
||||
data->previous_type == type)
|
||||
return 0;
|
||||
|
||||
offset = cs->do_ftell(cs);
|
||||
/*
|
||||
* At EOF, the parser always "inserts" an extra '\n', therefore
|
||||
* the end offset of the event is the current file position, otherwise
|
||||
* we will already have advanced to the next event.
|
||||
*/
|
||||
if (type != CONFIG_EVENT_EOF)
|
||||
offset--;
|
||||
|
||||
if (data->previous_type != CONFIG_EVENT_EOF &&
|
||||
data->opts->event_fn(data->previous_type, data->previous_offset,
|
||||
offset, cs, data->opts->event_fn_data) < 0)
|
||||
return -1;
|
||||
|
||||
data->previous_type = type;
|
||||
data->previous_offset = offset;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void kvi_from_source(struct config_source *cs,
|
||||
enum config_scope scope,
|
||||
struct key_value_info *out)
|
||||
{
|
||||
out->filename = strintern(cs->name);
|
||||
out->origin_type = cs->origin_type;
|
||||
out->linenr = cs->linenr;
|
||||
out->scope = scope;
|
||||
out->path = cs->path;
|
||||
}
|
||||
|
||||
static int git_parse_source(struct config_source *cs, config_fn_t fn,
|
||||
struct key_value_info *kvi, void *data,
|
||||
const struct config_options *opts)
|
||||
{
|
||||
int comment = 0;
|
||||
size_t baselen = 0;
|
||||
struct strbuf *var = &cs->var;
|
||||
int error_return = 0;
|
||||
char *error_msg = NULL;
|
||||
int error_return = 0;
|
||||
enum config_error_action *action = data;
|
||||
|
||||
/* U+FEFF Byte Order Mark in UTF8 */
|
||||
const char *bomptr = utf8_bom;
|
||||
|
||||
/* For the parser event callback */
|
||||
struct parse_event_data event_data = {
|
||||
CONFIG_EVENT_EOF, 0, opts
|
||||
};
|
||||
|
||||
for (;;) {
|
||||
int c;
|
||||
|
||||
c = get_next_char(cs);
|
||||
if (bomptr && *bomptr) {
|
||||
/* 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 (c == (*bomptr & 0377)) {
|
||||
bomptr++;
|
||||
continue;
|
||||
} else {
|
||||
/* Do not tolerate partial BOM. */
|
||||
if (bomptr != utf8_bom)
|
||||
break;
|
||||
/* No BOM at file beginning. Cool. */
|
||||
bomptr = NULL;
|
||||
}
|
||||
}
|
||||
if (c == '\n') {
|
||||
if (cs->eof) {
|
||||
if (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
|
||||
return -1;
|
||||
comment = 0;
|
||||
continue;
|
||||
}
|
||||
if (comment)
|
||||
continue;
|
||||
if (isspace(c)) {
|
||||
if (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
if (c == '#' || c == ';') {
|
||||
if (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)
|
||||
return -1;
|
||||
comment = 1;
|
||||
continue;
|
||||
}
|
||||
if (c == '[') {
|
||||
if (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)
|
||||
return -1;
|
||||
|
||||
/* Reset prior to determining a new stem */
|
||||
strbuf_reset(var);
|
||||
if (get_base_var(cs, var) < 0 || var->len < 1)
|
||||
break;
|
||||
strbuf_addch(var, '.');
|
||||
baselen = var->len;
|
||||
continue;
|
||||
}
|
||||
if (!isalpha(c))
|
||||
break;
|
||||
|
||||
if (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)
|
||||
return -1;
|
||||
|
||||
/*
|
||||
* Truncate the var name back to the section header
|
||||
* stem prior to grabbing the suffix part of the name
|
||||
* and the value.
|
||||
*/
|
||||
strbuf_setlen(var, baselen);
|
||||
strbuf_addch(var, tolower(c));
|
||||
if (get_value(cs, kvi, fn, data, var) < 0)
|
||||
break;
|
||||
}
|
||||
|
||||
if (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)
|
||||
return -1;
|
||||
if (type != CONFIG_EVENT_ERROR)
|
||||
return 0;
|
||||
|
||||
switch (cs->origin_type) {
|
||||
case CONFIG_ORIGIN_BLOB:
|
||||
@ -1147,20 +682,13 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,
|
||||
cs->linenr, cs->name);
|
||||
}
|
||||
|
||||
switch (opts && opts->error_action ?
|
||||
opts->error_action :
|
||||
cs->default_error_action) {
|
||||
switch (*action) {
|
||||
case CONFIG_ERROR_DIE:
|
||||
die("%s", error_msg);
|
||||
break;
|
||||
case CONFIG_ERROR_ERROR:
|
||||
error_return = error("%s", error_msg);
|
||||
break;
|
||||
case CONFIG_ERROR_SILENT:
|
||||
error_return = -1;
|
||||
break;
|
||||
case CONFIG_ERROR_UNSET:
|
||||
BUG("config error action unset");
|
||||
}
|
||||
|
||||
free(error_msg);
|
||||
@ -1830,109 +1358,12 @@ int git_default_config(const char *var, const char *value,
|
||||
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, enum config_scope scope,
|
||||
const struct config_options *opts)
|
||||
{
|
||||
struct key_value_info kvi = KVI_INIT;
|
||||
int ret;
|
||||
|
||||
/* push config-file parsing state stack */
|
||||
top->linenr = 1;
|
||||
top->eof = 0;
|
||||
top->total_len = 0;
|
||||
strbuf_init(&top->value, 1024);
|
||||
strbuf_init(&top->var, 1024);
|
||||
kvi_from_source(top, scope, &kvi);
|
||||
|
||||
ret = git_parse_source(top, fn, &kvi, data, opts);
|
||||
|
||||
strbuf_release(&top->value);
|
||||
strbuf_release(&top->var);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_config_from_file(config_fn_t fn,
|
||||
const enum config_origin_type origin_type,
|
||||
const char *name, const char *path, FILE *f,
|
||||
void *data, enum config_scope scope,
|
||||
const struct config_options *opts)
|
||||
{
|
||||
struct config_source top = CONFIG_SOURCE_INIT;
|
||||
int ret;
|
||||
|
||||
top.u.file = f;
|
||||
top.origin_type = origin_type;
|
||||
top.name = name;
|
||||
top.path = path;
|
||||
top.default_error_action = CONFIG_ERROR_DIE;
|
||||
top.do_fgetc = config_file_fgetc;
|
||||
top.do_ungetc = config_file_ungetc;
|
||||
top.do_ftell = config_file_ftell;
|
||||
|
||||
flockfile(f);
|
||||
ret = do_config_from(&top, fn, data, scope, opts);
|
||||
funlockfile(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int git_config_from_stdin(config_fn_t fn, void *data,
|
||||
enum config_scope scope)
|
||||
{
|
||||
return do_config_from_file(fn, CONFIG_ORIGIN_STDIN, "", NULL, stdin,
|
||||
data, scope, NULL);
|
||||
}
|
||||
|
||||
int git_config_from_file_with_options(config_fn_t fn, const char *filename,
|
||||
void *data, enum config_scope scope,
|
||||
const struct config_options *opts)
|
||||
{
|
||||
int ret = -1;
|
||||
FILE *f;
|
||||
|
||||
if (!filename)
|
||||
BUG("filename cannot be NULL");
|
||||
f = fopen_or_warn(filename, "r");
|
||||
if (f) {
|
||||
ret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,
|
||||
filename, f, data, scope, opts);
|
||||
fclose(f);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int git_config_from_file(config_fn_t fn, const char *filename, void *data)
|
||||
{
|
||||
struct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);
|
||||
|
||||
return git_config_from_file_with_options(fn, filename, data,
|
||||
CONFIG_SCOPE_UNKNOWN, NULL);
|
||||
}
|
||||
|
||||
int git_config_from_mem(config_fn_t fn,
|
||||
const enum config_origin_type origin_type,
|
||||
const char *name, const char *buf, size_t len,
|
||||
void *data, enum config_scope scope,
|
||||
const struct config_options *opts)
|
||||
{
|
||||
struct config_source top = CONFIG_SOURCE_INIT;
|
||||
|
||||
top.u.buf.buf = buf;
|
||||
top.u.buf.len = len;
|
||||
top.u.buf.pos = 0;
|
||||
top.origin_type = origin_type;
|
||||
top.name = name;
|
||||
top.path = NULL;
|
||||
top.default_error_action = CONFIG_ERROR_ERROR;
|
||||
top.do_fgetc = config_buf_fgetc;
|
||||
top.do_ungetc = config_buf_ungetc;
|
||||
top.do_ftell = config_buf_ftell;
|
||||
|
||||
return do_config_from(&top, fn, data, scope, opts);
|
||||
CONFIG_SCOPE_UNKNOWN, &config_opts);
|
||||
}
|
||||
|
||||
int git_config_from_blob_oid(config_fn_t fn,
|
||||
@ -1946,6 +1377,7 @@ int git_config_from_blob_oid(config_fn_t fn,
|
||||
char *buf;
|
||||
unsigned long size;
|
||||
int ret;
|
||||
struct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);
|
||||
|
||||
buf = repo_read_object_file(repo, oid, &type, &size);
|
||||
if (!buf)
|
||||
@ -1956,7 +1388,7 @@ int git_config_from_blob_oid(config_fn_t fn,
|
||||
}
|
||||
|
||||
ret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,
|
||||
data, scope, NULL);
|
||||
data, scope, &config_opts);
|
||||
free(buf);
|
||||
|
||||
return ret;
|
||||
@ -2035,29 +1467,32 @@ static int do_git_config_sequence(const struct config_options *opts,
|
||||
opts->system_gently ? ACCESS_EACCES_OK : 0))
|
||||
ret += git_config_from_file_with_options(fn, system_config,
|
||||
data, CONFIG_SCOPE_SYSTEM,
|
||||
NULL);
|
||||
&opts->parse_options);
|
||||
|
||||
git_global_config(&user_config, &xdg_config);
|
||||
|
||||
if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
|
||||
ret += git_config_from_file_with_options(fn, xdg_config, data,
|
||||
CONFIG_SCOPE_GLOBAL, NULL);
|
||||
CONFIG_SCOPE_GLOBAL,
|
||||
&opts->parse_options);
|
||||
|
||||
if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))
|
||||
ret += git_config_from_file_with_options(fn, user_config, data,
|
||||
CONFIG_SCOPE_GLOBAL, NULL);
|
||||
CONFIG_SCOPE_GLOBAL,
|
||||
&opts->parse_options);
|
||||
|
||||
if (!opts->ignore_repo && repo_config &&
|
||||
!access_or_die(repo_config, R_OK, 0))
|
||||
ret += git_config_from_file_with_options(fn, repo_config, data,
|
||||
CONFIG_SCOPE_LOCAL, NULL);
|
||||
CONFIG_SCOPE_LOCAL,
|
||||
&opts->parse_options);
|
||||
|
||||
if (!opts->ignore_worktree && worktree_config &&
|
||||
repo && repo->repository_format_worktree_config &&
|
||||
!access_or_die(worktree_config, R_OK, 0)) {
|
||||
ret += git_config_from_file_with_options(fn, worktree_config, data,
|
||||
CONFIG_SCOPE_WORKTREE,
|
||||
NULL);
|
||||
&opts->parse_options);
|
||||
}
|
||||
|
||||
if (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)
|
||||
@ -2094,11 +1529,12 @@ int config_with_options(config_fn_t fn, void *data,
|
||||
* regular lookup sequence.
|
||||
*/
|
||||
if (config_source && config_source->use_stdin) {
|
||||
ret = git_config_from_stdin(fn, data, config_source->scope);
|
||||
ret = git_config_from_stdin(fn, data, config_source->scope,
|
||||
&opts->parse_options);
|
||||
} else if (config_source && config_source->file) {
|
||||
ret = git_config_from_file_with_options(fn, config_source->file,
|
||||
data, config_source->scope,
|
||||
NULL);
|
||||
&opts->parse_options);
|
||||
} else if (config_source && config_source->blob) {
|
||||
ret = git_config_from_blob_ref(fn, repo, config_source->blob,
|
||||
data, config_source->scope);
|
||||
@ -2136,9 +1572,11 @@ static void configset_iter(struct config_set *set, config_fn_t fn, void *data)
|
||||
|
||||
void read_early_config(config_fn_t cb, void *data)
|
||||
{
|
||||
struct config_options opts = {0};
|
||||
struct strbuf commondir = STRBUF_INIT;
|
||||
struct strbuf gitdir = STRBUF_INIT;
|
||||
struct config_options opts = {
|
||||
.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),
|
||||
};
|
||||
|
||||
opts.respect_includes = 1;
|
||||
|
||||
@ -2170,13 +1608,14 @@ void read_early_config(config_fn_t cb, void *data)
|
||||
*/
|
||||
void read_very_early_config(config_fn_t cb, void *data)
|
||||
{
|
||||
struct config_options opts = { 0 };
|
||||
|
||||
opts.respect_includes = 1;
|
||||
opts.ignore_repo = 1;
|
||||
opts.ignore_worktree = 1;
|
||||
opts.ignore_cmdline = 1;
|
||||
opts.system_gently = 1;
|
||||
struct config_options opts = {
|
||||
.respect_includes = 1,
|
||||
.ignore_repo = 1,
|
||||
.ignore_worktree = 1,
|
||||
.ignore_cmdline = 1,
|
||||
.system_gently = 1,
|
||||
.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),
|
||||
};
|
||||
|
||||
config_with_options(cb, data, NULL, NULL, &opts);
|
||||
}
|
||||
@ -2461,7 +1900,9 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch
|
||||
/* Functions use to read configuration from a repository */
|
||||
static void repo_read_config(struct repository *repo)
|
||||
{
|
||||
struct config_options opts = { 0 };
|
||||
struct config_options opts = {
|
||||
.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),
|
||||
};
|
||||
|
||||
opts.respect_includes = 1;
|
||||
opts.commondir = repo->commondir;
|
||||
@ -2612,8 +2053,10 @@ static void read_protected_config(void)
|
||||
.ignore_repo = 1,
|
||||
.ignore_worktree = 1,
|
||||
.system_gently = 1,
|
||||
.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),
|
||||
};
|
||||
|
||||
|
||||
git_configset_init(&protected_config);
|
||||
config_with_options(config_set_callback, &protected_config, NULL,
|
||||
NULL, &opts);
|
||||
@ -2824,6 +2267,7 @@ struct config_store_data {
|
||||
enum config_event_t type;
|
||||
int is_keys_section;
|
||||
} *parsed;
|
||||
enum config_error_action error_action;
|
||||
unsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;
|
||||
unsigned int key_seen:1, section_seen:1, is_keys_section:1;
|
||||
};
|
||||
@ -2891,6 +2335,10 @@ static int store_aux_event(enum config_event_t type, size_t begin, size_t end,
|
||||
store->seen[store->seen_nr] = store->parsed_nr;
|
||||
}
|
||||
}
|
||||
if (type == CONFIG_EVENT_ERROR) {
|
||||
return git_config_err_fn(type, begin, end, cs,
|
||||
&store->error_action);
|
||||
}
|
||||
|
||||
store->parsed_nr++;
|
||||
|
||||
@ -3228,7 +2676,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
|
||||
struct stat st;
|
||||
size_t copy_begin, copy_end;
|
||||
int i, new_line = 0;
|
||||
struct config_options opts;
|
||||
struct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);
|
||||
|
||||
if (!value_pattern)
|
||||
store.value_pattern = NULL;
|
||||
@ -3255,8 +2703,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
|
||||
|
||||
ALLOC_GROW(store.parsed, 1, store.parsed_alloc);
|
||||
store.parsed[0].end = 0;
|
||||
store.error_action = CONFIG_ERROR_DIE;
|
||||
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
opts.event_fn = store_aux_event;
|
||||
opts.event_fn_data = &store;
|
||||
|
||||
|
133
config.h
133
config.h
@ -5,6 +5,7 @@
|
||||
#include "string-list.h"
|
||||
#include "repository.h"
|
||||
#include "parse.h"
|
||||
#include "config-parse.h"
|
||||
|
||||
/**
|
||||
* The config API gives callers a way to access Git configuration files
|
||||
@ -23,9 +24,6 @@
|
||||
|
||||
struct object_id;
|
||||
|
||||
/* git_config_parse_key() returns these negated: */
|
||||
#define CONFIG_INVALID_KEY 1
|
||||
#define CONFIG_NO_SECTION_OR_NAME 2
|
||||
/* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */
|
||||
#define CONFIG_NO_LOCK -1
|
||||
#define CONFIG_INVALID_FILE 3
|
||||
@ -36,17 +34,6 @@ struct object_id;
|
||||
|
||||
#define CONFIG_REGEX_NONE ((void *)1)
|
||||
|
||||
enum config_scope {
|
||||
CONFIG_SCOPE_UNKNOWN = 0,
|
||||
CONFIG_SCOPE_SYSTEM,
|
||||
CONFIG_SCOPE_GLOBAL,
|
||||
CONFIG_SCOPE_LOCAL,
|
||||
CONFIG_SCOPE_WORKTREE,
|
||||
CONFIG_SCOPE_COMMAND,
|
||||
CONFIG_SCOPE_SUBMODULE,
|
||||
};
|
||||
const char *config_scope_name(enum config_scope scope);
|
||||
|
||||
struct git_config_source {
|
||||
unsigned int use_stdin:1;
|
||||
const char *file;
|
||||
@ -54,36 +41,10 @@ struct git_config_source {
|
||||
enum config_scope scope;
|
||||
};
|
||||
|
||||
enum config_origin_type {
|
||||
CONFIG_ORIGIN_UNKNOWN = 0,
|
||||
CONFIG_ORIGIN_BLOB,
|
||||
CONFIG_ORIGIN_FILE,
|
||||
CONFIG_ORIGIN_STDIN,
|
||||
CONFIG_ORIGIN_SUBMODULE_BLOB,
|
||||
CONFIG_ORIGIN_CMDLINE
|
||||
};
|
||||
|
||||
enum config_event_t {
|
||||
CONFIG_EVENT_SECTION,
|
||||
CONFIG_EVENT_ENTRY,
|
||||
CONFIG_EVENT_WHITESPACE,
|
||||
CONFIG_EVENT_COMMENT,
|
||||
CONFIG_EVENT_EOF,
|
||||
CONFIG_EVENT_ERROR
|
||||
};
|
||||
|
||||
struct config_source;
|
||||
/*
|
||||
* The parser event function (if not NULL) is called with the event type and
|
||||
* the begin/end offsets of the parsed elements.
|
||||
*
|
||||
* Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline
|
||||
* character is considered part of the element.
|
||||
*/
|
||||
typedef int (*config_parser_event_fn_t)(enum config_event_t type,
|
||||
size_t begin_offset, size_t end_offset,
|
||||
struct config_source *cs,
|
||||
void *event_fn_data);
|
||||
#define CP_OPTS_INIT(error_action) { \
|
||||
.event_fn = git_config_err_fn, \
|
||||
.event_fn_data = (enum config_error_action []){(error_action)}, \
|
||||
}
|
||||
|
||||
struct config_options {
|
||||
unsigned int respect_includes : 1;
|
||||
@ -92,6 +53,9 @@ struct config_options {
|
||||
unsigned int ignore_cmdline : 1;
|
||||
unsigned int system_gently : 1;
|
||||
|
||||
const char *commondir;
|
||||
const char *git_dir;
|
||||
struct config_parse_options parse_options;
|
||||
/*
|
||||
* For internal use. Include all includeif.hasremoteurl paths without
|
||||
* checking if the repo has that remote URL, and when doing so, verify
|
||||
@ -99,75 +63,18 @@ struct config_options {
|
||||
* themselves.
|
||||
*/
|
||||
unsigned int unconditional_remote_url : 1;
|
||||
|
||||
const char *commondir;
|
||||
const char *git_dir;
|
||||
/*
|
||||
* event_fn and event_fn_data are for internal use only. Handles events
|
||||
* emitted by the config parser.
|
||||
*/
|
||||
config_parser_event_fn_t event_fn;
|
||||
void *event_fn_data;
|
||||
enum config_error_action {
|
||||
CONFIG_ERROR_UNSET = 0, /* use source-specific default */
|
||||
CONFIG_ERROR_DIE, /* die() on error */
|
||||
CONFIG_ERROR_ERROR, /* error() on error, return -1 */
|
||||
CONFIG_ERROR_SILENT, /* return -1 */
|
||||
} error_action;
|
||||
};
|
||||
|
||||
/* Config source metadata for a given config key-value pair */
|
||||
struct key_value_info {
|
||||
const char *filename;
|
||||
int linenr;
|
||||
enum config_origin_type origin_type;
|
||||
enum config_scope scope;
|
||||
const char *path;
|
||||
enum config_error_action {
|
||||
CONFIG_ERROR_DIE, /* die() on error */
|
||||
CONFIG_ERROR_ERROR, /* error() on error, return -1 */
|
||||
};
|
||||
#define KVI_INIT { \
|
||||
.filename = NULL, \
|
||||
.linenr = -1, \
|
||||
.origin_type = CONFIG_ORIGIN_UNKNOWN, \
|
||||
.scope = CONFIG_SCOPE_UNKNOWN, \
|
||||
.path = NULL, \
|
||||
}
|
||||
|
||||
/* Captures additional information that a config callback can use. */
|
||||
struct config_context {
|
||||
/* Config source metadata for key and value. */
|
||||
const struct key_value_info *kvi;
|
||||
};
|
||||
#define CONFIG_CONTEXT_INIT { 0 }
|
||||
|
||||
/**
|
||||
* A config callback function takes four parameters:
|
||||
*
|
||||
* - the name of the parsed variable. This is in canonical "flat" form: the
|
||||
* section, subsection, and variable segments will be separated by dots,
|
||||
* and the section and variable segments will be all lowercase. E.g.,
|
||||
* `core.ignorecase`, `diff.SomeType.textconv`.
|
||||
*
|
||||
* - the value of the found variable, as a string. If the variable had no
|
||||
* value specified, the value will be NULL (typically this means it
|
||||
* should be interpreted as boolean true).
|
||||
*
|
||||
* - the 'config context', that is, additional information about the config
|
||||
* iteration operation provided by the config machinery. For example, this
|
||||
* includes information about the config source being parsed (e.g. the
|
||||
* filename).
|
||||
*
|
||||
* - a void pointer passed in by the caller of the config API; this can
|
||||
* contain callback-specific data
|
||||
*
|
||||
* A config callback should return 0 for success, or -1 if the variable
|
||||
* could not be parsed properly.
|
||||
*/
|
||||
typedef int (*config_fn_t)(const char *, const char *,
|
||||
const struct config_context *, void *);
|
||||
|
||||
int git_config_err_fn(enum config_event_t type, size_t begin_offset,
|
||||
size_t end_offset, struct config_source *cs,
|
||||
void *event_fn_data);
|
||||
int git_default_config(const char *, const char *,
|
||||
const struct config_context *, void *);
|
||||
|
||||
/**
|
||||
* Read a specific file in git-config format.
|
||||
* This function takes the same callback and data parameters as `git_config`.
|
||||
@ -175,16 +82,6 @@ int git_default_config(const char *, const char *,
|
||||
* Unlike git_config(), this function does not respect includes.
|
||||
*/
|
||||
int git_config_from_file(config_fn_t fn, const char *, void *);
|
||||
|
||||
int git_config_from_file_with_options(config_fn_t fn, const char *,
|
||||
void *, enum config_scope,
|
||||
const struct config_options *);
|
||||
int git_config_from_mem(config_fn_t fn,
|
||||
const enum config_origin_type,
|
||||
const char *name,
|
||||
const char *buf, size_t len,
|
||||
void *data, enum config_scope scope,
|
||||
const struct config_options *opts);
|
||||
int git_config_from_blob_oid(config_fn_t fn, const char *name,
|
||||
struct repository *repo,
|
||||
const struct object_id *oid, void *data,
|
||||
@ -312,8 +209,6 @@ int repo_config_set_worktree_gently(struct repository *, const char *, const cha
|
||||
*/
|
||||
void git_config_set(const char *, const char *);
|
||||
|
||||
int git_config_parse_key(const char *, char **, size_t *);
|
||||
|
||||
/*
|
||||
* The following macros specify flag bits that alter the behavior
|
||||
* of the git_config_set_multivar*() methods.
|
||||
|
4
fsck.c
4
fsck.c
@ -1240,7 +1240,6 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
|
||||
return 0;
|
||||
|
||||
if (oidset_contains(&options->gitmodules_found, oid)) {
|
||||
struct config_options config_opts = { 0 };
|
||||
struct fsck_gitmodules_data data;
|
||||
|
||||
oidset_insert(&options->gitmodules_done, oid);
|
||||
@ -1259,10 +1258,9 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
|
||||
data.oid = oid;
|
||||
data.options = options;
|
||||
data.ret = 0;
|
||||
config_opts.error_action = CONFIG_ERROR_SILENT;
|
||||
if (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,
|
||||
".gitmodules", buf, size, &data,
|
||||
CONFIG_SCOPE_UNKNOWN, &config_opts))
|
||||
CONFIG_SCOPE_UNKNOWN, NULL))
|
||||
data.ret |= report(options, oid, OBJ_BLOB,
|
||||
FSCK_MSG_GITMODULES_PARSE,
|
||||
"could not parse gitmodules blob");
|
||||
|
@ -564,6 +564,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,
|
||||
enum object_type type;
|
||||
const struct submodule *submodule = NULL;
|
||||
struct parse_config_parameter parameter;
|
||||
struct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);
|
||||
|
||||
|
||||
/*
|
||||
* If any parameter except the cache is a NULL pointer just
|
||||
@ -607,7 +609,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,
|
||||
parameter.gitmodules_oid = &oid;
|
||||
parameter.overwrite = 0;
|
||||
git_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,
|
||||
config, config_size, ¶meter, CONFIG_SCOPE_UNKNOWN, NULL);
|
||||
config, config_size, ¶meter,
|
||||
CONFIG_SCOPE_UNKNOWN, &config_opts);
|
||||
strbuf_release(&rev);
|
||||
free(config);
|
||||
|
||||
@ -651,7 +654,9 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void
|
||||
struct git_config_source config_source = {
|
||||
0, .scope = CONFIG_SCOPE_SUBMODULE
|
||||
};
|
||||
const struct config_options opts = { 0 };
|
||||
struct config_options opts = {
|
||||
.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),
|
||||
};
|
||||
struct object_id oid;
|
||||
char *file;
|
||||
char *oidstr = NULL;
|
||||
|
Reference in New Issue
Block a user