git/config.c

1498 lines
34 KiB
C
Raw Normal View History

/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
* Copyright (C) Johannes Schindelin, 2005
*
*/
#include "cache.h"
#include "exec_cmd.h"
#include "strbuf.h"
#include "quote.h"
#define MAXNAME (256)
static FILE *config_file;
static const char *config_file_name;
static int config_linenr;
static int config_file_eof;
Custom compression levels for objects and packs Add config variables pack.compression and core.loosecompression , and switch --compression=level to pack-objects. Loose objects will be compressed using core.loosecompression if set, else core.compression if set, else Z_BEST_SPEED. Packed objects will be compressed using --compression=level if seen, else pack.compression if set, else core.compression if set, else Z_DEFAULT_COMPRESSION. This is the "pack compression level". Loose objects added to a pack undeltified will be recompressed to the pack compression level if it is unequal to the current loose compression level by the preceding rules, or if the loose object was written while core.legacyheaders = true. Newly deltified loose objects are always compressed to the current pack compression level. Previously packed objects added to a pack are recompressed to the current pack compression level exactly when their deltification status changes, since the previous pack data cannot be reused. In either case, the --no-reuse-object switch from the first patch below will always force recompression to the current pack compression level, instead of assuming the pack compression level hasn't changed and pack data can be reused when possible. This applies on top of the following patches from Nicolas Pitre: [PATCH] allow for undeltified objects not to be reused [PATCH] make "repack -f" imply "pack-objects --no-reuse-object" Signed-off-by: Dana L. How <danahow@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-05-10 04:56:50 +08:00
static int zlib_compression_seen;
const char *config_exclusive_filename = NULL;
static void lowercase(char *p)
{
for (; *p; p++)
*p = tolower(*p);
}
void git_config_push_parameter(const char *text)
{
struct strbuf env = STRBUF_INIT;
const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
if (old) {
strbuf_addstr(&env, old);
strbuf_addch(&env, ' ');
}
sq_quote_buf(&env, text);
setenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);
strbuf_release(&env);
}
static int git_config_parse_parameter(const char *text,
config_fn_t fn, void *data)
{
struct strbuf tmp = STRBUF_INIT;
struct strbuf **pair;
strbuf_addstr(&tmp, text);
pair = strbuf_split(&tmp, '=');
if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=')
strbuf_setlen(pair[0], pair[0]->len - 1);
strbuf_trim(pair[0]);
if (!pair[0]->len) {
strbuf_list_free(pair);
return error("bogus config parameter: %s", text);
}
lowercase(pair[0]->buf);
if (fn(pair[0]->buf, pair[1] ? pair[1]->buf : NULL, data) < 0) {
strbuf_list_free(pair);
return -1;
}
strbuf_list_free(pair);
return 0;
}
int git_config_from_parameters(config_fn_t fn, void *data)
{
const char *env = getenv(CONFIG_DATA_ENVIRONMENT);
char *envw;
const char **argv = NULL;
int nr = 0, alloc = 0;
int i;
if (!env)
return 0;
/* sq_dequote will write over it */
envw = xstrdup(env);
if (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {
free(envw);
return error("bogus format in " CONFIG_DATA_ENVIRONMENT);
}
for (i = 0; i < nr; i++) {
if (git_config_parse_parameter(argv[i], fn, data) < 0) {
free(argv);
free(envw);
return -1;
}
}
free(argv);
free(envw);
return nr > 0;
}
static int get_next_char(void)
{
int c;
FILE *f;
c = '\n';
if ((f = config_file) != NULL) {
c = fgetc(f);
if (c == '\r') {
/* DOS like systems */
c = fgetc(f);
if (c != '\n') {
ungetc(c, f);
c = '\r';
}
}
if (c == '\n')
config_linenr++;
if (c == EOF) {
config_file_eof = 1;
c = '\n';
}
}
return c;
}
static char *parse_value(void)
{
static struct strbuf value = STRBUF_INIT;
int quote = 0, comment = 0, space = 0;
strbuf_reset(&value);
for (;;) {
int c = get_next_char();
if (c == '\n') {
if (quote)
return NULL;
return value.buf;
}
if (comment)
continue;
if (isspace(c) && !quote) {
if (value.len)
space++;
continue;
}
if (!quote) {
if (c == ';' || c == '#') {
comment = 1;
continue;
}
}
for (; space; space--)
strbuf_addch(&value, ' ');
if (c == '\\') {
c = get_next_char();
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(&value, c);
continue;
}
if (c == '"') {
quote = 1-quote;
continue;
}
strbuf_addch(&value, c);
}
}
static inline int iskeychar(int c)
{
return isalnum(c) || c == '-';
}
static int get_value(config_fn_t fn, void *data, char *name, unsigned int len)
{
int c;
char *value;
/* Get the full name */
for (;;) {
c = get_next_char();
if (config_file_eof)
break;
if (!iskeychar(c))
break;
name[len++] = tolower(c);
if (len >= MAXNAME)
return -1;
}
name[len] = 0;
while (c == ' ' || c == '\t')
c = get_next_char();
value = NULL;
if (c != '\n') {
if (c != '=')
return -1;
value = parse_value();
if (!value)
return -1;
}
return fn(name, value, data);
}
git config syntax updates This updates the hierarchical section name syntax to [section<space>+"<randomstring>"] where the only rule for "randomstring" is that it can't contain a newline, and if you really want to insert a double-quote, you do it with \". It turns that into the section name "secion.randomstring". The "section" part is still case insensitive, but the "randomstring" part is case sensitive. So you could use this for things like [email "torvalds@osdl.org"] name = Linus Torvalds if you wanted to do the "email->name" conversion as part of the config file format (I'm not claiming that is sensible, I'm just giving it as an insane example). That would show up as the association email.torvalds@osdl.org.name -> Linus Torvalds which is easy to parse (the "." in the email _looks_ ambiguous, but it isn't: you know that there will always be a single key-name, so you find the key name with "strrchr(name, '.')" and things are entirely unambiguous). Repo-config is updated to be able to parse the new format, and also write things out in the new format. [jc: rolled two patches from Linus and one fix-up from Sean into one, with additional adjustments for t/t1300 test to check the case insensitiveness of section base and variable and case sensitiveness of the extended section part. Then stripped some part off to make the result applicable to the stale 1.3.X series that does not have recent enhancements. ] Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Sean Estabrooks <seanlkml@sympatico.ca> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-05-10 03:24:02 +08:00
static int get_extended_base_var(char *name, int baselen, int c)
{
do {
if (c == '\n')
return -1;
c = get_next_char();
} while (isspace(c));
/* We require the format to be '[base "extension"]' */
if (c != '"')
return -1;
name[baselen++] = '.';
for (;;) {
int c = get_next_char();
if (c == '\n')
return -1;
if (c == '"')
break;
if (c == '\\') {
c = get_next_char();
if (c == '\n')
return -1;
}
name[baselen++] = c;
if (baselen > MAXNAME / 2)
return -1;
}
/* Final ']' */
if (get_next_char() != ']')
return -1;
return baselen;
}
static int get_base_var(char *name)
{
int baselen = 0;
for (;;) {
int c = get_next_char();
if (config_file_eof)
return -1;
if (c == ']')
return baselen;
git config syntax updates This updates the hierarchical section name syntax to [section<space>+"<randomstring>"] where the only rule for "randomstring" is that it can't contain a newline, and if you really want to insert a double-quote, you do it with \". It turns that into the section name "secion.randomstring". The "section" part is still case insensitive, but the "randomstring" part is case sensitive. So you could use this for things like [email "torvalds@osdl.org"] name = Linus Torvalds if you wanted to do the "email->name" conversion as part of the config file format (I'm not claiming that is sensible, I'm just giving it as an insane example). That would show up as the association email.torvalds@osdl.org.name -> Linus Torvalds which is easy to parse (the "." in the email _looks_ ambiguous, but it isn't: you know that there will always be a single key-name, so you find the key name with "strrchr(name, '.')" and things are entirely unambiguous). Repo-config is updated to be able to parse the new format, and also write things out in the new format. [jc: rolled two patches from Linus and one fix-up from Sean into one, with additional adjustments for t/t1300 test to check the case insensitiveness of section base and variable and case sensitiveness of the extended section part. Then stripped some part off to make the result applicable to the stale 1.3.X series that does not have recent enhancements. ] Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Sean Estabrooks <seanlkml@sympatico.ca> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-05-10 03:24:02 +08:00
if (isspace(c))
return get_extended_base_var(name, baselen, c);
if (!iskeychar(c) && c != '.')
return -1;
if (baselen > MAXNAME / 2)
return -1;
name[baselen++] = tolower(c);
}
}
static int git_parse_file(config_fn_t fn, void *data)
{
int comment = 0;
int baselen = 0;
static char var[MAXNAME];
/* U+FEFF Byte Order Mark in UTF8 */
static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
const unsigned char *bomptr = utf8_bom;
for (;;) {
int c = get_next_char();
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 ((unsigned char) c == *bomptr) {
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 (config_file_eof)
return 0;
comment = 0;
continue;
}
if (comment || isspace(c))
continue;
if (c == '#' || c == ';') {
comment = 1;
continue;
}
if (c == '[') {
baselen = get_base_var(var);
if (baselen <= 0)
break;
var[baselen++] = '.';
var[baselen] = 0;
continue;
}
if (!isalpha(c))
break;
var[baselen] = tolower(c);
if (get_value(fn, data, var, baselen+1) < 0)
break;
}
die("bad config file line %d in %s", config_linenr, config_file_name);
}
static int parse_unit_factor(const char *end, unsigned long *val)
{
if (!*end)
return 1;
else if (!strcasecmp(end, "k")) {
*val *= 1024;
return 1;
}
else if (!strcasecmp(end, "m")) {
*val *= 1024 * 1024;
return 1;
}
else if (!strcasecmp(end, "g")) {
*val *= 1024 * 1024 * 1024;
return 1;
}
return 0;
}
static int git_parse_long(const char *value, long *ret)
{
if (value && *value) {
char *end;
long val = strtol(value, &end, 0);
unsigned long factor = 1;
if (!parse_unit_factor(end, &factor))
return 0;
*ret = val * factor;
return 1;
}
return 0;
}
int git_parse_ulong(const char *value, unsigned long *ret)
{
if (value && *value) {
char *end;
unsigned long val = strtoul(value, &end, 0);
if (!parse_unit_factor(end, &val))
return 0;
*ret = val;
return 1;
}
return 0;
}
static void die_bad_config(const char *name)
{
if (config_file_name)
die("bad config value for '%s' in %s", name, config_file_name);
die("bad config value for '%s'", name);
}
int git_config_int(const char *name, const char *value)
{
long ret = 0;
if (!git_parse_long(value, &ret))
die_bad_config(name);
return ret;
}
unsigned long git_config_ulong(const char *name, const char *value)
{
unsigned long ret;
if (!git_parse_ulong(value, &ret))
die_bad_config(name);
return ret;
}
static int git_config_maybe_bool_text(const char *name, const char *value)
{
if (!value)
return 1;
if (!*value)
return 0;
if (!strcasecmp(value, "true")
|| !strcasecmp(value, "yes")
|| !strcasecmp(value, "on"))
return 1;
if (!strcasecmp(value, "false")
|| !strcasecmp(value, "no")
|| !strcasecmp(value, "off"))
return 0;
return -1;
}
int git_config_maybe_bool(const char *name, const char *value)
{
long v = git_config_maybe_bool_text(name, value);
if (0 <= v)
return v;
if (git_parse_long(value, &v))
return !!v;
return -1;
}
int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
{
int v = git_config_maybe_bool_text(name, value);
if (0 <= v) {
*is_bool = 1;
return v;
}
*is_bool = 0;
return git_config_int(name, value);
}
int git_config_bool(const char *name, const char *value)
{
int discard;
return !!git_config_bool_or_int(name, value, &discard);
}
int git_config_string(const char **dest, const char *var, const char *value)
{
if (!value)
return config_error_nonbool(var);
*dest = xstrdup(value);
return 0;
}
int git_config_pathname(const char **dest, const char *var, const char *value)
{
if (!value)
return config_error_nonbool(var);
*dest = expand_user_path(value);
if (!*dest)
die("Failed to expand user dir in: '%s'", value);
return 0;
}
static int git_default_core_config(const char *var, const char *value)
{
/* This needs a better name */
if (!strcmp(var, "core.filemode")) {
trust_executable_bit = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.trustctime")) {
trust_ctime = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.quotepath")) {
quote_path_fully = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.symlinks")) {
has_symlinks = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.ignorecase")) {
ignore_case = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.bare")) {
is_bare_repository_cfg = git_config_bool(var, value);
return 0;
}
"Assume unchanged" git This adds "assume unchanged" logic, started by this message in the list discussion recently: <Pine.LNX.4.64.0601311807470.7301@g5.osdl.org> This is a workaround for filesystems that do not have lstat() that is quick enough for the index mechanism to take advantage of. On the paths marked as "assumed to be unchanged", the user needs to explicitly use update-index to register the object name to be in the next commit. You can use two new options to update-index to set and reset the CE_VALID bit: git-update-index --assume-unchanged path... git-update-index --no-assume-unchanged path... These forms manipulate only the CE_VALID bit; it does not change the object name recorded in the index file. Nor they add a new entry to the index. When the configuration variable "core.ignorestat = true" is set, the index entries are marked with CE_VALID bit automatically after: - update-index to explicitly register the current object name to the index file. - when update-index --refresh finds the path to be up-to-date. - when tools like read-tree -u and apply --index update the working tree file and register the current object name to the index file. The flag is dropped upon read-tree that does not check out the index entry. This happens regardless of the core.ignorestat settings. Index entries marked with CE_VALID bit are assumed to be unchanged most of the time. However, there are cases that CE_VALID bit is ignored for the sake of safety and usability: - while "git-read-tree -m" or git-apply need to make sure that the paths involved in the merge do not have local modifications. This sacrifices performance for safety. - when git-checkout-index -f -q -u -a tries to see if it needs to checkout the paths. Otherwise you can never check anything out ;-). - when git-update-index --really-refresh (a new flag) tries to see if the index entry is up to date. You can start with everything marked as CE_VALID and run this once to drop CE_VALID bit for paths that are modified. Most notably, "update-index --refresh" honours CE_VALID and does not actively stat, so after you modified a file in the working tree, update-index --refresh would not notice until you tell the index about it with "git-update-index path" or "git-update-index --no-assume-unchanged path". This version is not expected to be perfect. I think diff between index and/or tree and working files may need some adjustment, and there probably needs other cases we should automatically unmark paths that are marked to be CE_VALID. But the basics seem to work, and ready to be tested by people who asked for this feature. Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-02-09 13:15:24 +08:00
if (!strcmp(var, "core.ignorestat")) {
assume_unchanged = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.prefersymlinkrefs")) {
prefer_symlink_refs = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.logallrefupdates")) {
log_all_ref_updates = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.warnambiguousrefs")) {
warn_ambiguous_refs = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.abbrev")) {
int abbrev = git_config_int(var, value);
if (abbrev < minimum_abbrev || abbrev > 40)
return -1;
default_abbrev = abbrev;
return 0;
}
Custom compression levels for objects and packs Add config variables pack.compression and core.loosecompression , and switch --compression=level to pack-objects. Loose objects will be compressed using core.loosecompression if set, else core.compression if set, else Z_BEST_SPEED. Packed objects will be compressed using --compression=level if seen, else pack.compression if set, else core.compression if set, else Z_DEFAULT_COMPRESSION. This is the "pack compression level". Loose objects added to a pack undeltified will be recompressed to the pack compression level if it is unequal to the current loose compression level by the preceding rules, or if the loose object was written while core.legacyheaders = true. Newly deltified loose objects are always compressed to the current pack compression level. Previously packed objects added to a pack are recompressed to the current pack compression level exactly when their deltification status changes, since the previous pack data cannot be reused. In either case, the --no-reuse-object switch from the first patch below will always force recompression to the current pack compression level, instead of assuming the pack compression level hasn't changed and pack data can be reused when possible. This applies on top of the following patches from Nicolas Pitre: [PATCH] allow for undeltified objects not to be reused [PATCH] make "repack -f" imply "pack-objects --no-reuse-object" Signed-off-by: Dana L. How <danahow@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-05-10 04:56:50 +08:00
if (!strcmp(var, "core.loosecompression")) {
int level = git_config_int(var, value);
if (level == -1)
level = Z_DEFAULT_COMPRESSION;
else if (level < 0 || level > Z_BEST_COMPRESSION)
die("bad zlib compression level %d", level);
zlib_compression_level = level;
Custom compression levels for objects and packs Add config variables pack.compression and core.loosecompression , and switch --compression=level to pack-objects. Loose objects will be compressed using core.loosecompression if set, else core.compression if set, else Z_BEST_SPEED. Packed objects will be compressed using --compression=level if seen, else pack.compression if set, else core.compression if set, else Z_DEFAULT_COMPRESSION. This is the "pack compression level". Loose objects added to a pack undeltified will be recompressed to the pack compression level if it is unequal to the current loose compression level by the preceding rules, or if the loose object was written while core.legacyheaders = true. Newly deltified loose objects are always compressed to the current pack compression level. Previously packed objects added to a pack are recompressed to the current pack compression level exactly when their deltification status changes, since the previous pack data cannot be reused. In either case, the --no-reuse-object switch from the first patch below will always force recompression to the current pack compression level, instead of assuming the pack compression level hasn't changed and pack data can be reused when possible. This applies on top of the following patches from Nicolas Pitre: [PATCH] allow for undeltified objects not to be reused [PATCH] make "repack -f" imply "pack-objects --no-reuse-object" Signed-off-by: Dana L. How <danahow@gmail.com> Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-05-10 04:56:50 +08:00
zlib_compression_seen = 1;
return 0;
}
if (!strcmp(var, "core.compression")) {
int level = git_config_int(var, value);
if (level == -1)
level = Z_DEFAULT_COMPRESSION;
else if (level < 0 || level > Z_BEST_COMPRESSION)
die("bad zlib compression level %d", level);
core_compression_level = level;
core_compression_seen = 1;
if (!zlib_compression_seen)
zlib_compression_level = level;
return 0;
}
Fully activate the sliding window pack access. This finally turns on the sliding window behavior for packfile data access by mapping limited size windows and chaining them under the packed_git->windows list. We consider a given byte offset to be within the window only if there would be at least 20 bytes (one hash worth of data) accessible after the requested offset. This range selection relates to the contract that use_pack() makes with its callers, allowing them to access one hash or one object header without needing to call use_pack() for every byte of data obtained. In the worst case scenario we will map the same page of data twice into memory: once at the end of one window and once again at the start of the next window. This duplicate page mapping will happen only when an object header or a delta base reference is spanned over the end of a window and is always limited to just one page of duplication, as no sane operating system will ever have a page size smaller than a hash. I am assuming that the possible wasted page of virtual address space is going to perform faster than the alternatives, which would be to copy the object header or ref delta into a temporary buffer prior to parsing, or to check the window range on every byte during header parsing. We may decide to revisit this decision in the future since this is just a gut instinct decision and has not actually been proven out by experimental testing. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-12-23 15:34:28 +08:00
if (!strcmp(var, "core.packedgitwindowsize")) {
int pgsz_x2 = getpagesize() * 2;
Fully activate the sliding window pack access. This finally turns on the sliding window behavior for packfile data access by mapping limited size windows and chaining them under the packed_git->windows list. We consider a given byte offset to be within the window only if there would be at least 20 bytes (one hash worth of data) accessible after the requested offset. This range selection relates to the contract that use_pack() makes with its callers, allowing them to access one hash or one object header without needing to call use_pack() for every byte of data obtained. In the worst case scenario we will map the same page of data twice into memory: once at the end of one window and once again at the start of the next window. This duplicate page mapping will happen only when an object header or a delta base reference is spanned over the end of a window and is always limited to just one page of duplication, as no sane operating system will ever have a page size smaller than a hash. I am assuming that the possible wasted page of virtual address space is going to perform faster than the alternatives, which would be to copy the object header or ref delta into a temporary buffer prior to parsing, or to check the window range on every byte during header parsing. We may decide to revisit this decision in the future since this is just a gut instinct decision and has not actually been proven out by experimental testing. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-12-23 15:34:28 +08:00
packed_git_window_size = git_config_int(var, value);
/* This value must be multiple of (pagesize * 2) */
packed_git_window_size /= pgsz_x2;
if (packed_git_window_size < 1)
packed_git_window_size = 1;
packed_git_window_size *= pgsz_x2;
Fully activate the sliding window pack access. This finally turns on the sliding window behavior for packfile data access by mapping limited size windows and chaining them under the packed_git->windows list. We consider a given byte offset to be within the window only if there would be at least 20 bytes (one hash worth of data) accessible after the requested offset. This range selection relates to the contract that use_pack() makes with its callers, allowing them to access one hash or one object header without needing to call use_pack() for every byte of data obtained. In the worst case scenario we will map the same page of data twice into memory: once at the end of one window and once again at the start of the next window. This duplicate page mapping will happen only when an object header or a delta base reference is spanned over the end of a window and is always limited to just one page of duplication, as no sane operating system will ever have a page size smaller than a hash. I am assuming that the possible wasted page of virtual address space is going to perform faster than the alternatives, which would be to copy the object header or ref delta into a temporary buffer prior to parsing, or to check the window range on every byte during header parsing. We may decide to revisit this decision in the future since this is just a gut instinct decision and has not actually been proven out by experimental testing. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-12-23 15:34:28 +08:00
return 0;
}
if (!strcmp(var, "core.bigfilethreshold")) {
long n = git_config_int(var, value);
big_file_threshold = 0 < n ? n : 0;
return 0;
}
if (!strcmp(var, "core.packedgitlimit")) {
packed_git_limit = git_config_int(var, value);
return 0;
}
if (!strcmp(var, "core.deltabasecachelimit")) {
delta_base_cache_limit = git_config_int(var, value);
return 0;
}
Lazy man's auto-CRLF It currently does NOT know about file attributes, so it does its conversion purely based on content. Maybe that is more in the "git philosophy" anyway, since content is king, but I think we should try to do the file attributes to turn it off on demand. Anyway, BY DEFAULT it is off regardless, because it requires a [core] AutoCRLF = true in your config file to be enabled. We could make that the default for Windows, of course, the same way we do some other things (filemode etc). But you can actually enable it on UNIX, and it will cause: - "git update-index" will write blobs without CRLF - "git diff" will diff working tree files without CRLF - "git checkout" will write files to the working tree _with_ CRLF and things work fine. Funnily, it actually shows an odd file in git itself: git clone -n git test-crlf cd test-crlf git config core.autocrlf true git checkout git diff shows a diff for "Documentation/docbook-xsl.css". Why? Because we have actually checked in that file *with* CRLF! So when "core.autocrlf" is true, we'll always generate a *different* hash for it in the index, because the index hash will be for the content _without_ CRLF. Is this complete? I dunno. It seems to work for me. It doesn't use the filename at all right now, and that's probably a deficiency (we could certainly make the "is_binary()" heuristics also take standard filename heuristics into account). I don't pass in the filename at all for the "index_fd()" case (git-update-index), so that would need to be passed around, but this actually works fine. NOTE NOTE NOTE! The "is_binary()" heuristics are totally made-up by yours truly. I will not guarantee that they work at all reasonable. Caveat emptor. But it _is_ simple, and it _is_ safe, since it's all off by default. The patch is pretty simple - the biggest part is the new "convert.c" file, but even that is really just basic stuff that anybody can write in "Teaching C 101" as a final project for their first class in programming. Not to say that it's bug-free, of course - but at least we're not talking about rocket surgery here. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-02-14 03:07:23 +08:00
if (!strcmp(var, "core.autocrlf")) {
if (value && !strcasecmp(value, "input")) {
if (core_eol == EOL_CRLF)
return error("core.autocrlf=input conflicts with core.eol=crlf");
auto_crlf = AUTO_CRLF_INPUT;
return 0;
}
Lazy man's auto-CRLF It currently does NOT know about file attributes, so it does its conversion purely based on content. Maybe that is more in the "git philosophy" anyway, since content is king, but I think we should try to do the file attributes to turn it off on demand. Anyway, BY DEFAULT it is off regardless, because it requires a [core] AutoCRLF = true in your config file to be enabled. We could make that the default for Windows, of course, the same way we do some other things (filemode etc). But you can actually enable it on UNIX, and it will cause: - "git update-index" will write blobs without CRLF - "git diff" will diff working tree files without CRLF - "git checkout" will write files to the working tree _with_ CRLF and things work fine. Funnily, it actually shows an odd file in git itself: git clone -n git test-crlf cd test-crlf git config core.autocrlf true git checkout git diff shows a diff for "Documentation/docbook-xsl.css". Why? Because we have actually checked in that file *with* CRLF! So when "core.autocrlf" is true, we'll always generate a *different* hash for it in the index, because the index hash will be for the content _without_ CRLF. Is this complete? I dunno. It seems to work for me. It doesn't use the filename at all right now, and that's probably a deficiency (we could certainly make the "is_binary()" heuristics also take standard filename heuristics into account). I don't pass in the filename at all for the "index_fd()" case (git-update-index), so that would need to be passed around, but this actually works fine. NOTE NOTE NOTE! The "is_binary()" heuristics are totally made-up by yours truly. I will not guarantee that they work at all reasonable. Caveat emptor. But it _is_ simple, and it _is_ safe, since it's all off by default. The patch is pretty simple - the biggest part is the new "convert.c" file, but even that is really just basic stuff that anybody can write in "Teaching C 101" as a final project for their first class in programming. Not to say that it's bug-free, of course - but at least we're not talking about rocket surgery here. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> Signed-off-by: Junio C Hamano <junkio@cox.net>
2007-02-14 03:07:23 +08:00
auto_crlf = git_config_bool(var, value);
return 0;
}
safecrlf: Add mechanism to warn about irreversible crlf conversions CRLF conversion bears a slight chance of corrupting data. autocrlf=true will convert CRLF to LF during commit and LF to CRLF during checkout. A file that contains a mixture of LF and CRLF before the commit cannot be recreated by git. For text files this is the right thing to do: it corrects line endings such that we have only LF line endings in the repository. But for binary files that are accidentally classified as text the conversion can corrupt data. If you recognize such corruption early you can easily fix it by setting the conversion type explicitly in .gitattributes. Right after committing you still have the original file in your work tree and this file is not yet corrupted. You can explicitly tell git that this file is binary and git will handle the file appropriately. Unfortunately, the desired effect of cleaning up text files with mixed line endings and the undesired effect of corrupting binary files cannot be distinguished. In both cases CRLFs are removed in an irreversible way. For text files this is the right thing to do because CRLFs are line endings, while for binary files converting CRLFs corrupts data. This patch adds a mechanism that can either warn the user about an irreversible conversion or can even refuse to convert. The mechanism is controlled by the variable core.safecrlf, with the following values: - false: disable safecrlf mechanism - warn: warn about irreversible conversions - true: refuse irreversible conversions The default is to warn. Users are only affected by this default if core.autocrlf is set. But the current default of git is to leave core.autocrlf unset, so users will not see warnings unless they deliberately chose to activate the autocrlf mechanism. The safecrlf mechanism's details depend on the git command. The general principles when safecrlf is active (not false) are: - we warn/error out if files in the work tree can modified in an irreversible way without giving the user a chance to backup the original file. - for read-only operations that do not modify files in the work tree we do not not print annoying warnings. There are exceptions. Even though... - "git add" itself does not touch the files in the work tree, the next checkout would, so the safety triggers; - "git apply" to update a text file with a patch does touch the files in the work tree, but the operation is about text files and CRLF conversion is about fixing the line ending inconsistencies, so the safety does not trigger; - "git diff" itself does not touch the files in the work tree, it is often run to inspect the changes you intend to next "git add". To catch potential problems early, safety triggers. The concept of a safety check was originally proposed in a similar way by Linus Torvalds. Thanks to Dimitry Potapov for insisting on getting the naked LF/autocrlf=true case right. Signed-off-by: Steffen Prohaska <prohaska@zib.de>
2008-02-06 19:25:58 +08:00
if (!strcmp(var, "core.safecrlf")) {
if (value && !strcasecmp(value, "warn")) {
safe_crlf = SAFE_CRLF_WARN;
return 0;
}
safe_crlf = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.eol")) {
if (value && !strcasecmp(value, "lf"))
core_eol = EOL_LF;
else if (value && !strcasecmp(value, "crlf"))
core_eol = EOL_CRLF;
else if (value && !strcasecmp(value, "native"))
core_eol = EOL_NATIVE;
else
core_eol = EOL_UNSET;
if (core_eol == EOL_CRLF && auto_crlf == AUTO_CRLF_INPUT)
return error("core.autocrlf=input conflicts with core.eol=crlf");
return 0;
}
if (!strcmp(var, "core.notesref")) {
notes_ref_name = xstrdup(value);
return 0;
}
if (!strcmp(var, "core.pager"))
return git_config_string(&pager_program, var, value);
if (!strcmp(var, "core.editor"))
return git_config_string(&editor_program, var, value);
if (!strcmp(var, "core.askpass"))
return git_config_string(&askpass_program, var, value);
if (!strcmp(var, "core.excludesfile"))
return git_config_pathname(&excludes_file, var, value);
if (!strcmp(var, "core.whitespace")) {
if (!value)
return config_error_nonbool(var);
whitespace_rule_cfg = parse_whitespace_rule(value);
return 0;
}
if (!strcmp(var, "core.fsyncobjectfiles")) {
fsync_object_files = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.preloadindex")) {
core_preload_index = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "core.createobject")) {
if (!strcmp(value, "rename"))
object_creation_mode = OBJECT_CREATION_USES_RENAMES;
else if (!strcmp(value, "link"))
object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;
else
die("Invalid mode for object creation: %s", value);
return 0;
}
if (!strcmp(var, "core.sparsecheckout")) {
core_apply_sparse_checkout = git_config_bool(var, value);
return 0;
}
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
static int git_default_user_config(const char *var, const char *value)
{
if (!strcmp(var, "user.name")) {
if (!value)
return config_error_nonbool(var);
strlcpy(git_default_name, value, sizeof(git_default_name));
user_ident_explicitly_given |= IDENT_NAME_GIVEN;
return 0;
}
if (!strcmp(var, "user.email")) {
if (!value)
return config_error_nonbool(var);
strlcpy(git_default_email, value, sizeof(git_default_email));
user_ident_explicitly_given |= IDENT_MAIL_GIVEN;
return 0;
}
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
static int git_default_i18n_config(const char *var, const char *value)
{
if (!strcmp(var, "i18n.commitencoding"))
return git_config_string(&git_commit_encoding, var, value);
if (!strcmp(var, "i18n.logoutputencoding"))
return git_config_string(&git_log_output_encoding, var, value);
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
core.excludesfile clean-up There are inconsistencies in the way commands currently handle the core.excludesfile configuration variable. The problem is the variable is too new to be noticed by anything other than git-add and git-status. * git-ls-files does not notice any of the "ignore" files by default, as it predates the standardized set of ignore files. The calling scripts established the convention to use .git/info/exclude, .gitignore, and later core.excludesfile. * git-add and git-status know about it because they call add_excludes_from_file() directly with their own notion of which standard set of ignore files to use. This is just a stupid duplication of code that need to be updated every time the definition of the standard set of ignore files is changed. * git-read-tree takes --exclude-per-directory=<gitignore>, not because the flexibility was needed. Again, this was because the option predates the standardization of the ignore files. * git-merge-recursive uses hardcoded per-directory .gitignore and nothing else. git-clean (scripted version) does not honor core.* because its call to underlying ls-files does not know about it. git-clean in C (parked in 'pu') doesn't either. We probably could change git-ls-files to use the standard set when no excludes are specified on the command line and ignore processing was asked, or something like that, but that will be a change in semantics and might break people's scripts in a subtle way. I am somewhat reluctant to make such a change. On the other hand, I think it makes perfect sense to fix git-read-tree, git-merge-recursive and git-clean to follow the same rule as other commands. I do not think of a valid use case to give an exclude-per-directory that is nonstandard to read-tree command, outside a "negative" test in the t1004 test script. This patch is the first step to untangle this mess. The next step would be to teach read-tree, merge-recursive and clean (in C) to use setup_standard_excludes(). Signed-off-by: Junio C Hamano <gitster@pobox.com>
2007-11-14 16:05:00 +08:00
static int git_default_branch_config(const char *var, const char *value)
{
if (!strcmp(var, "branch.autosetupmerge")) {
if (value && !strcasecmp(value, "always")) {
git_branch_track = BRANCH_TRACK_ALWAYS;
return 0;
}
git_branch_track = git_config_bool(var, value);
return 0;
}
if (!strcmp(var, "branch.autosetuprebase")) {
if (!value)
return config_error_nonbool(var);
else if (!strcmp(value, "never"))
autorebase = AUTOREBASE_NEVER;
else if (!strcmp(value, "local"))
autorebase = AUTOREBASE_LOCAL;
else if (!strcmp(value, "remote"))
autorebase = AUTOREBASE_REMOTE;
else if (!strcmp(value, "always"))
autorebase = AUTOREBASE_ALWAYS;
else
return error("Malformed value for %s", var);
return 0;
}
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
static int git_default_push_config(const char *var, const char *value)
{
if (!strcmp(var, "push.default")) {
if (!value)
return config_error_nonbool(var);
else if (!strcmp(value, "nothing"))
push_default = PUSH_DEFAULT_NOTHING;
else if (!strcmp(value, "matching"))
push_default = PUSH_DEFAULT_MATCHING;
else if (!strcmp(value, "upstream"))
push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "tracking")) /* deprecated */
push_default = PUSH_DEFAULT_UPSTREAM;
else if (!strcmp(value, "current"))
push_default = PUSH_DEFAULT_CURRENT;
else {
error("Malformed value for %s: %s", var, value);
return error("Must be one of nothing, matching, "
"tracking or current.");
}
return 0;
}
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
static int git_default_mailmap_config(const char *var, const char *value)
{
if (!strcmp(var, "mailmap.file"))
return git_config_string(&git_mailmap_file, var, value);
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
int git_default_config(const char *var, const char *value, void *dummy)
{
if (!prefixcmp(var, "core."))
return git_default_core_config(var, value);
if (!prefixcmp(var, "user."))
return git_default_user_config(var, value);
if (!prefixcmp(var, "i18n."))
return git_default_i18n_config(var, value);
if (!prefixcmp(var, "branch."))
return git_default_branch_config(var, value);
if (!prefixcmp(var, "push."))
return git_default_push_config(var, value);
if (!prefixcmp(var, "mailmap."))
return git_default_mailmap_config(var, value);
if (!prefixcmp(var, "advice."))
return git_default_advice_config(var, value);
if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
pager_use_color = git_config_bool(var,value);
return 0;
}
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
int git_config_from_file(config_fn_t fn, const char *filename, void *data)
{
int ret;
FILE *f = fopen(filename, "r");
ret = -1;
if (f) {
config_file = f;
config_file_name = filename;
config_linenr = 1;
config_file_eof = 0;
ret = git_parse_file(fn, data);
fclose(f);
config_file_name = NULL;
}
return ret;
}
const char *git_etc_gitconfig(void)
{
static const char *system_wide;
if (!system_wide)
system_wide = system_path(ETC_GITCONFIG);
return system_wide;
}
int git_env_bool(const char *k, int def)
{
const char *v = getenv(k);
return v ? git_config_bool(k, v) : def;
}
int git_config_system(void)
{
return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
}
int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
const char *home = NULL;
/* Setting $GIT_CONFIG makes git read _only_ the given config file. */
if (config_exclusive_filename)
return git_config_from_file(fn, config_exclusive_filename, data);
if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
data);
found += 1;
}
home = getenv("HOME");
if (home) {
char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
if (!access(user_config, R_OK)) {
ret += git_config_from_file(fn, user_config, data);
found += 1;
}
free(user_config);
}
if (repo_config && !access(repo_config, R_OK)) {
ret += git_config_from_file(fn, repo_config, data);
found += 1;
}
switch (git_config_from_parameters(fn, data)) {
case -1: /* error */
ret--;
break;
case 0: /* found nothing */
break;
default: /* found at least one item */
found++;
break;
}
return ret == 0 ? found : ret;
}
int git_config(config_fn_t fn, void *data)
{
char *repo_config = NULL;
int ret;
repo_config = git_pathdup("config");
ret = git_config_early(fn, data, repo_config);
if (repo_config)
free(repo_config);
return ret;
}
/*
* Find all the stuff for git_config_set() below.
*/
#define MAX_MATCHES 512
static struct {
int baselen;
char *key;
int do_not_match;
regex_t *value_regex;
int multi_replace;
size_t offset[MAX_MATCHES];
enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
int seen;
} store;
static int matches(const char *key, const char *value)
{
return !strcmp(key, store.key) &&
(store.value_regex == NULL ||
(store.do_not_match ^
!regexec(store.value_regex, value, 0, NULL, 0)));
}
static int store_aux(const char *key, const char *value, void *cb)
{
const char *ep;
size_t section_len;
switch (store.state) {
case KEY_SEEN:
if (matches(key, value)) {
if (store.seen == 1 && store.multi_replace == 0) {
warning("%s has multiple values", key);
} else if (store.seen >= MAX_MATCHES) {
error("too many matches for %s", key);
return 1;
}
store.offset[store.seen] = ftell(config_file);
store.seen++;
}
break;
case SECTION_SEEN:
/*
* What we are looking for is in store.key (both
* section and var), and its section part is baselen
* long. We found key (again, both section and var).
* We would want to know if this key is in the same
* section as what we are looking for. We already
* know we are in the same section as what should
* hold store.key.
*/
ep = strrchr(key, '.');
section_len = ep - key;
if ((section_len != store.baselen) ||
memcmp(key, store.key, section_len+1)) {
store.state = SECTION_END_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(config_file);
/* fallthru */
case SECTION_END_SEEN:
case START:
if (matches(key, value)) {
store.offset[store.seen] = ftell(config_file);
store.state = KEY_SEEN;
store.seen++;
git config syntax updates This updates the hierarchical section name syntax to [section<space>+"<randomstring>"] where the only rule for "randomstring" is that it can't contain a newline, and if you really want to insert a double-quote, you do it with \". It turns that into the section name "secion.randomstring". The "section" part is still case insensitive, but the "randomstring" part is case sensitive. So you could use this for things like [email "torvalds@osdl.org"] name = Linus Torvalds if you wanted to do the "email->name" conversion as part of the config file format (I'm not claiming that is sensible, I'm just giving it as an insane example). That would show up as the association email.torvalds@osdl.org.name -> Linus Torvalds which is easy to parse (the "." in the email _looks_ ambiguous, but it isn't: you know that there will always be a single key-name, so you find the key name with "strrchr(name, '.')" and things are entirely unambiguous). Repo-config is updated to be able to parse the new format, and also write things out in the new format. [jc: rolled two patches from Linus and one fix-up from Sean into one, with additional adjustments for t/t1300 test to check the case insensitiveness of section base and variable and case sensitiveness of the extended section part. Then stripped some part off to make the result applicable to the stale 1.3.X series that does not have recent enhancements. ] Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Sean Estabrooks <seanlkml@sympatico.ca> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-05-10 03:24:02 +08:00
} else {
if (strrchr(key, '.') - key == store.baselen &&
!strncmp(key, store.key, store.baselen)) {
store.state = SECTION_SEEN;
store.offset[store.seen] = ftell(config_file);
git config syntax updates This updates the hierarchical section name syntax to [section<space>+"<randomstring>"] where the only rule for "randomstring" is that it can't contain a newline, and if you really want to insert a double-quote, you do it with \". It turns that into the section name "secion.randomstring". The "section" part is still case insensitive, but the "randomstring" part is case sensitive. So you could use this for things like [email "torvalds@osdl.org"] name = Linus Torvalds if you wanted to do the "email->name" conversion as part of the config file format (I'm not claiming that is sensible, I'm just giving it as an insane example). That would show up as the association email.torvalds@osdl.org.name -> Linus Torvalds which is easy to parse (the "." in the email _looks_ ambiguous, but it isn't: you know that there will always be a single key-name, so you find the key name with "strrchr(name, '.')" and things are entirely unambiguous). Repo-config is updated to be able to parse the new format, and also write things out in the new format. [jc: rolled two patches from Linus and one fix-up from Sean into one, with additional adjustments for t/t1300 test to check the case insensitiveness of section base and variable and case sensitiveness of the extended section part. Then stripped some part off to make the result applicable to the stale 1.3.X series that does not have recent enhancements. ] Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Sean Estabrooks <seanlkml@sympatico.ca> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-05-10 03:24:02 +08:00
}
}
}
return 0;
}
static int write_error(const char *filename)
{
error("failed to write new configuration file %s", filename);
/* Same error code as "failed to rename". */
return 4;
}
static int store_write_section(int fd, const char *key)
{
const char *dot;
int i, success;
struct strbuf sb = STRBUF_INIT;
git config syntax updates This updates the hierarchical section name syntax to [section<space>+"<randomstring>"] where the only rule for "randomstring" is that it can't contain a newline, and if you really want to insert a double-quote, you do it with \". It turns that into the section name "secion.randomstring". The "section" part is still case insensitive, but the "randomstring" part is case sensitive. So you could use this for things like [email "torvalds@osdl.org"] name = Linus Torvalds if you wanted to do the "email->name" conversion as part of the config file format (I'm not claiming that is sensible, I'm just giving it as an insane example). That would show up as the association email.torvalds@osdl.org.name -> Linus Torvalds which is easy to parse (the "." in the email _looks_ ambiguous, but it isn't: you know that there will always be a single key-name, so you find the key name with "strrchr(name, '.')" and things are entirely unambiguous). Repo-config is updated to be able to parse the new format, and also write things out in the new format. [jc: rolled two patches from Linus and one fix-up from Sean into one, with additional adjustments for t/t1300 test to check the case insensitiveness of section base and variable and case sensitiveness of the extended section part. Then stripped some part off to make the result applicable to the stale 1.3.X series that does not have recent enhancements. ] Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Sean Estabrooks <seanlkml@sympatico.ca> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-05-10 03:24:02 +08:00
dot = memchr(key, '.', store.baselen);
git config syntax updates This updates the hierarchical section name syntax to [section<space>+"<randomstring>"] where the only rule for "randomstring" is that it can't contain a newline, and if you really want to insert a double-quote, you do it with \". It turns that into the section name "secion.randomstring". The "section" part is still case insensitive, but the "randomstring" part is case sensitive. So you could use this for things like [email "torvalds@osdl.org"] name = Linus Torvalds if you wanted to do the "email->name" conversion as part of the config file format (I'm not claiming that is sensible, I'm just giving it as an insane example). That would show up as the association email.torvalds@osdl.org.name -> Linus Torvalds which is easy to parse (the "." in the email _looks_ ambiguous, but it isn't: you know that there will always be a single key-name, so you find the key name with "strrchr(name, '.')" and things are entirely unambiguous). Repo-config is updated to be able to parse the new format, and also write things out in the new format. [jc: rolled two patches from Linus and one fix-up from Sean into one, with additional adjustments for t/t1300 test to check the case insensitiveness of section base and variable and case sensitiveness of the extended section part. Then stripped some part off to make the result applicable to the stale 1.3.X series that does not have recent enhancements. ] Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Sean Estabrooks <seanlkml@sympatico.ca> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-05-10 03:24:02 +08:00
if (dot) {
strbuf_addf(&sb, "[%.*s \"", (int)(dot - key), key);
for (i = dot - key + 1; i < store.baselen; i++) {
if (key[i] == '"' || key[i] == '\\')
strbuf_addch(&sb, '\\');
strbuf_addch(&sb, key[i]);
git config syntax updates This updates the hierarchical section name syntax to [section<space>+"<randomstring>"] where the only rule for "randomstring" is that it can't contain a newline, and if you really want to insert a double-quote, you do it with \". It turns that into the section name "secion.randomstring". The "section" part is still case insensitive, but the "randomstring" part is case sensitive. So you could use this for things like [email "torvalds@osdl.org"] name = Linus Torvalds if you wanted to do the "email->name" conversion as part of the config file format (I'm not claiming that is sensible, I'm just giving it as an insane example). That would show up as the association email.torvalds@osdl.org.name -> Linus Torvalds which is easy to parse (the "." in the email _looks_ ambiguous, but it isn't: you know that there will always be a single key-name, so you find the key name with "strrchr(name, '.')" and things are entirely unambiguous). Repo-config is updated to be able to parse the new format, and also write things out in the new format. [jc: rolled two patches from Linus and one fix-up from Sean into one, with additional adjustments for t/t1300 test to check the case insensitiveness of section base and variable and case sensitiveness of the extended section part. Then stripped some part off to make the result applicable to the stale 1.3.X series that does not have recent enhancements. ] Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Sean Estabrooks <seanlkml@sympatico.ca> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-05-10 03:24:02 +08:00
}
strbuf_addstr(&sb, "\"]\n");
} else {
strbuf_addf(&sb, "[%.*s]\n", store.baselen, key);
git config syntax updates This updates the hierarchical section name syntax to [section<space>+"<randomstring>"] where the only rule for "randomstring" is that it can't contain a newline, and if you really want to insert a double-quote, you do it with \". It turns that into the section name "secion.randomstring". The "section" part is still case insensitive, but the "randomstring" part is case sensitive. So you could use this for things like [email "torvalds@osdl.org"] name = Linus Torvalds if you wanted to do the "email->name" conversion as part of the config file format (I'm not claiming that is sensible, I'm just giving it as an insane example). That would show up as the association email.torvalds@osdl.org.name -> Linus Torvalds which is easy to parse (the "." in the email _looks_ ambiguous, but it isn't: you know that there will always be a single key-name, so you find the key name with "strrchr(name, '.')" and things are entirely unambiguous). Repo-config is updated to be able to parse the new format, and also write things out in the new format. [jc: rolled two patches from Linus and one fix-up from Sean into one, with additional adjustments for t/t1300 test to check the case insensitiveness of section base and variable and case sensitiveness of the extended section part. Then stripped some part off to make the result applicable to the stale 1.3.X series that does not have recent enhancements. ] Signed-off-by: Linus Torvalds <torvalds@osdl.org> Signed-off-by: Sean Estabrooks <seanlkml@sympatico.ca> Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-05-10 03:24:02 +08:00
}
success = write_in_full(fd, sb.buf, sb.len) == sb.len;
strbuf_release(&sb);
return success;
}
static int store_write_pair(int fd, const char *key, const char *value)
{
int i, success;
int length = strlen(key + store.baselen + 1);
const char *quote = "";
struct strbuf sb = STRBUF_INIT;
/*
* Check to see if the value needs to be surrounded with a dq pair.
* Note that problematic characters are always backslash-quoted; this
* check is about not losing leading or trailing SP and strings that
* follow beginning-of-comment characters (i.e. ';' and '#') by the
* configuration parser.
*/
if (value[0] == ' ')
quote = "\"";
for (i = 0; value[i]; i++)
if (value[i] == ';' || value[i] == '#')
quote = "\"";
if (i && value[i - 1] == ' ')
quote = "\"";
strbuf_addf(&sb, "\t%.*s = %s",
length, key + store.baselen + 1, quote);
for (i = 0; value[i]; i++)
switch (value[i]) {
case '\n':
strbuf_addstr(&sb, "\\n");
break;
case '\t':
strbuf_addstr(&sb, "\\t");
break;
case '"':
case '\\':
strbuf_addch(&sb, '\\');
default:
strbuf_addch(&sb, value[i]);
break;
}
strbuf_addf(&sb, "%s\n", quote);
success = write_in_full(fd, sb.buf, sb.len) == sb.len;
strbuf_release(&sb);
return success;
}
static ssize_t find_beginning_of_line(const char *contents, size_t size,
size_t offset_, int *found_bracket)
{
size_t equal_offset = size, bracket_offset = size;
ssize_t offset;
contline:
for (offset = offset_-2; offset > 0
&& contents[offset] != '\n'; offset--)
switch (contents[offset]) {
case '=': equal_offset = offset; break;
case ']': bracket_offset = offset; break;
}
if (offset > 0 && contents[offset-1] == '\\') {
offset_ = offset;
goto contline;
}
if (bracket_offset < equal_offset) {
*found_bracket = 1;
offset = bracket_offset+1;
} else
offset++;
return offset;
}
int git_config_set(const char *key, const char *value)
{
return git_config_set_multivar(key, value, NULL, 0);
}
/*
* Auxiliary function to sanity-check and split the key into the section
* identifier and variable name.
*
* Returns 0 on success, -1 when there is an invalid character in the key and
* -2 if there is no section name in the key.
*
* store_key - pointer to char* which will hold a copy of the key with
* lowercase section and variable name
* baselen - pointer to int which will hold the length of the
* section + subsection part, can be NULL
*/
int git_config_parse_key(const char *key, char **store_key, int *baselen_)
{
int i, dot, baselen;
const char *last_dot = strrchr(key, '.');
/*
* Since "key" actually contains the section name and the real
* key name separated by a dot, we have to know where the dot is.
*/
if (last_dot == NULL || last_dot == key) {
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 = xmalloc(strlen(key) + 1);
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;
}
(*store_key)[i] = 0;
return 0;
out_free_ret_1:
free(*store_key);
return -CONFIG_INVALID_KEY;
}
/*
* If value==NULL, unset in (remove from) config,
* if value_regex!=NULL, disregard key/value pairs where value does not match.
* if multi_replace==0, nothing, or only one matching key/value is replaced,
* else all matching key/values (regardless how many) are removed,
* before the new pair is written.
*
* Returns 0 on success.
*
* This function does this:
*
* - it locks the config file by creating ".git/config.lock"
*
* - it then parses the config using store_aux() as validator to find
* the position on the key/value pair to replace. If it is to be unset,
* it must be found exactly once.
*
* - the config file is mmap()ed and the part before the match (if any) is
* written to the lock file, then the changed part and the rest.
*
* - the config file is removed and the lock file rename()d to it.
*
*/
int git_config_set_multivar(const char *key, const char *value,
const char *value_regex, int multi_replace)
{
int fd = -1, in_fd;
int ret;
char *config_filename;
struct lock_file *lock = NULL;
if (config_exclusive_filename)
config_filename = xstrdup(config_exclusive_filename);
else
config_filename = git_pathdup("config");
/* parse-key returns negative; flip the sign to feed exit(3) */
ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
if (ret)
goto out_free;
store.multi_replace = multi_replace;
/*
* The lock serves a purpose in addition to locking: the new
* contents of .git/config will be written into it.
*/
lock = xcalloc(sizeof(struct lock_file), 1);
fd = hold_lock_file_for_update(lock, config_filename, 0);
if (fd < 0) {
error("could not lock config file %s: %s", config_filename, strerror(errno));
free(store.key);
ret = CONFIG_NO_LOCK;
goto out_free;
}
/*
* If .git/config does not exist yet, write a minimal version.
*/
in_fd = open(config_filename, O_RDONLY);
if ( in_fd < 0 ) {
free(store.key);
if ( ENOENT != errno ) {
error("opening %s: %s", config_filename,
strerror(errno));
ret = CONFIG_INVALID_FILE; /* same as "invalid config file" */
goto out_free;
}
/* if nothing to unset, error out */
if (value == NULL) {
ret = CONFIG_NOTHING_SET;
goto out_free;
}
store.key = (char *)key;
if (!store_write_section(fd, key) ||
!store_write_pair(fd, key, value))
goto write_err_out;
} else {
struct stat st;
char *contents;
size_t contents_sz, copy_begin, copy_end;
int i, new_line = 0;
if (value_regex == NULL)
store.value_regex = NULL;
else {
if (value_regex[0] == '!') {
store.do_not_match = 1;
value_regex++;
} else
store.do_not_match = 0;
store.value_regex = (regex_t*)xmalloc(sizeof(regex_t));
if (regcomp(store.value_regex, value_regex,
REG_EXTENDED)) {
error("invalid pattern: %s", value_regex);
free(store.value_regex);
ret = CONFIG_INVALID_PATTERN;
goto out_free;
}
}
store.offset[0] = 0;
store.state = START;
store.seen = 0;
/*
* After this, store.offset will contain the *end* offset
* of the last match, or remain at 0 if no match was found.
* As a side effect, we make sure to transform only a valid
* existing config file.
*/
if (git_config_from_file(store_aux, config_filename, NULL)) {
error("invalid config file %s", config_filename);
free(store.key);
if (store.value_regex != NULL) {
regfree(store.value_regex);
free(store.value_regex);
}
ret = CONFIG_INVALID_FILE;
goto out_free;
}
free(store.key);
if (store.value_regex != NULL) {
regfree(store.value_regex);
free(store.value_regex);
}
/* if nothing to unset, or too many matches, error out */
if ((store.seen == 0 && value == NULL) ||
(store.seen > 1 && multi_replace == 0)) {
ret = CONFIG_NOTHING_SET;
goto out_free;
}
fstat(in_fd, &st);
contents_sz = xsize_t(st.st_size);
contents = xmmap(NULL, contents_sz, PROT_READ,
MAP_PRIVATE, in_fd, 0);
close(in_fd);
if (store.seen == 0)
store.seen = 1;
for (i = 0, copy_begin = 0; i < store.seen; i++) {
if (store.offset[i] == 0) {
store.offset[i] = copy_end = contents_sz;
} else if (store.state != KEY_SEEN) {
copy_end = store.offset[i];
} else
copy_end = find_beginning_of_line(
contents, contents_sz,
store.offset[i]-2, &new_line);
if (copy_end > 0 && contents[copy_end-1] != '\n')
new_line = 1;
/* write the first part of the config */
if (copy_end > copy_begin) {
if (write_in_full(fd, contents + copy_begin,
copy_end - copy_begin) <
copy_end - copy_begin)
goto write_err_out;
if (new_line &&
write_str_in_full(fd, "\n") != 1)
goto write_err_out;
}
copy_begin = store.offset[i];
}
/* write the pair (value == NULL means unset) */
if (value != NULL) {
if (store.state == START) {
if (!store_write_section(fd, key))
goto write_err_out;
}
if (!store_write_pair(fd, key, value))
goto write_err_out;
}
/* write the rest of the config */
if (copy_begin < contents_sz)
if (write_in_full(fd, contents + copy_begin,
contents_sz - copy_begin) <
contents_sz - copy_begin)
goto write_err_out;
munmap(contents, contents_sz);
}
if (commit_lock_file(lock) < 0) {
error("could not commit config file %s", config_filename);
ret = CONFIG_NO_WRITE;
goto out_free;
}
/*
* lock is committed, so don't try to roll it back below.
* NOTE: Since lockfile.c keeps a linked list of all created
* lock_file structures, it isn't safe to free(lock). It's
* better to just leave it hanging around.
*/
lock = NULL;
ret = 0;
out_free:
if (lock)
rollback_lock_file(lock);
free(config_filename);
return ret;
write_err_out:
ret = write_error(lock->filename);
goto out_free;
}
static int section_name_match (const char *buf, const char *name)
{
int i = 0, j = 0, dot = 0;
if (buf[i] != '[')
return 0;
for (i = 1; buf[i] && buf[i] != ']'; i++) {
if (!dot && isspace(buf[i])) {
dot = 1;
if (name[j++] != '.')
break;
for (i++; isspace(buf[i]); i++)
; /* do nothing */
if (buf[i] != '"')
break;
continue;
}
if (buf[i] == '\\' && dot)
i++;
else if (buf[i] == '"' && dot) {
for (i++; isspace(buf[i]); i++)
; /* do_nothing */
break;
}
if (buf[i] != name[j++])
break;
}
if (buf[i] == ']' && name[j] == 0) {
/*
* We match, now just find the right length offset by
* gobbling up any whitespace after it, as well
*/
i++;
for (; buf[i] && isspace(buf[i]); i++)
; /* do nothing */
return i;
}
return 0;
}
/* if new_name == NULL, the section is removed instead */
int git_config_rename_section(const char *old_name, const char *new_name)
{
int ret = 0, remove = 0;
char *config_filename;
struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
int out_fd;
char buf[1024];
if (config_exclusive_filename)
config_filename = xstrdup(config_exclusive_filename);
else
config_filename = git_pathdup("config");
out_fd = hold_lock_file_for_update(lock, config_filename, 0);
if (out_fd < 0) {
ret = error("could not lock config file %s", config_filename);
goto out;
}
if (!(config_file = fopen(config_filename, "rb"))) {
/* no config file means nothing to rename, no error */
goto unlock_and_out;
}
while (fgets(buf, sizeof(buf), config_file)) {
int i;
int length;
char *output = buf;
for (i = 0; buf[i] && isspace(buf[i]); i++)
; /* do nothing */
if (buf[i] == '[') {
/* it's a section */
int offset = section_name_match(&buf[i], old_name);
if (offset > 0) {
ret++;
if (new_name == NULL) {
remove = 1;
continue;
}
store.baselen = strlen(new_name);
if (!store_write_section(out_fd, new_name)) {
ret = write_error(lock->filename);
goto out;
}
/*
* We wrote out the new section, with
* a newline, now skip the old
* section's length
*/
output += offset + i;
if (strlen(output) > 0) {
/*
* More content means there's
* a declaration to put on the
* next line; indent with a
* tab
*/
output -= 1;
output[0] = '\t';
}
}
remove = 0;
}
if (remove)
continue;
length = strlen(output);
if (write_in_full(out_fd, output, length) != length) {
ret = write_error(lock->filename);
goto out;
}
}
fclose(config_file);
unlock_and_out:
if (commit_lock_file(lock) < 0)
ret = error("could not commit config file %s", config_filename);
out:
free(config_filename);
return ret;
}
/*
* Call this to report error for your variable that should not
* get a boolean value (i.e. "[my] var" means "true").
*/
int config_error_nonbool(const char *var)
{
return error("Missing value for '%s'", var);
}