2005-11-17 22:32:36 +01:00
|
|
|
/*
|
|
|
|
* GIT - The information manager from hell
|
|
|
|
*
|
|
|
|
* Copyright (C) Linus Torvalds, 2005
|
|
|
|
* Copyright (C) Johannes Schindelin, 2005
|
|
|
|
*
|
|
|
|
*/
|
2005-10-11 01:31:08 +02:00
|
|
|
#include "cache.h"
|
2007-11-13 21:05:06 +01:00
|
|
|
#include "exec_cmd.h"
|
2005-10-11 01:31:08 +02:00
|
|
|
|
|
|
|
#define MAXNAME (256)
|
|
|
|
|
|
|
|
static FILE *config_file;
|
2005-11-28 10:46:15 +01:00
|
|
|
static const char *config_file_name;
|
2005-10-11 01:31:08 +02:00
|
|
|
static int config_linenr;
|
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-09 22:56:50 +02:00
|
|
|
static int zlib_compression_seen;
|
|
|
|
|
2005-10-11 01:31:08 +02:00
|
|
|
static int get_next_char(void)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
FILE *f;
|
|
|
|
|
|
|
|
c = '\n';
|
|
|
|
if ((f = config_file) != NULL) {
|
|
|
|
c = fgetc(f);
|
2005-11-02 22:02:57 +01:00
|
|
|
if (c == '\r') {
|
|
|
|
/* DOS like systems */
|
|
|
|
c = fgetc(f);
|
|
|
|
if (c != '\n') {
|
|
|
|
ungetc(c, f);
|
|
|
|
c = '\r';
|
|
|
|
}
|
|
|
|
}
|
2005-10-11 01:31:08 +02:00
|
|
|
if (c == '\n')
|
|
|
|
config_linenr++;
|
|
|
|
if (c == EOF) {
|
|
|
|
config_file = NULL;
|
|
|
|
c = '\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *parse_value(void)
|
|
|
|
{
|
|
|
|
static char value[1024];
|
|
|
|
int quote = 0, comment = 0, len = 0, space = 0;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
int c = get_next_char();
|
|
|
|
if (len >= sizeof(value))
|
|
|
|
return NULL;
|
|
|
|
if (c == '\n') {
|
|
|
|
if (quote)
|
|
|
|
return NULL;
|
|
|
|
value[len] = 0;
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
if (comment)
|
|
|
|
continue;
|
|
|
|
if (isspace(c) && !quote) {
|
|
|
|
space = 1;
|
|
|
|
continue;
|
|
|
|
}
|
2006-05-02 16:58:37 +02:00
|
|
|
if (!quote) {
|
|
|
|
if (c == ';' || c == '#') {
|
|
|
|
comment = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2005-10-11 01:31:08 +02:00
|
|
|
if (space) {
|
|
|
|
if (len)
|
|
|
|
value[len++] = ' ';
|
|
|
|
space = 0;
|
|
|
|
}
|
|
|
|
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;
|
2005-10-12 00:24:11 +02:00
|
|
|
/* Some characters escape as themselves */
|
|
|
|
case '\\': case '"':
|
|
|
|
break;
|
|
|
|
/* Reject unknown escape sequences */
|
|
|
|
default:
|
|
|
|
return NULL;
|
2005-10-11 01:31:08 +02:00
|
|
|
}
|
|
|
|
value[len++] = c;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (c == '"') {
|
|
|
|
quote = 1-quote;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
value[len++] = c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-10-30 17:25:36 +01:00
|
|
|
static inline int iskeychar(int c)
|
|
|
|
{
|
|
|
|
return isalnum(c) || c == '-';
|
|
|
|
}
|
|
|
|
|
2005-10-11 01:31:08 +02:00
|
|
|
static int get_value(config_fn_t fn, char *name, unsigned int len)
|
|
|
|
{
|
|
|
|
int c;
|
|
|
|
char *value;
|
|
|
|
|
|
|
|
/* Get the full name */
|
|
|
|
for (;;) {
|
|
|
|
c = get_next_char();
|
|
|
|
if (c == EOF)
|
|
|
|
break;
|
2006-10-30 17:25:36 +01:00
|
|
|
if (!iskeychar(c))
|
2005-10-11 01:31:08 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2006-05-09 21:24:02 +02: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;
|
|
|
|
}
|
|
|
|
|
2005-10-11 01:31:08 +02:00
|
|
|
static int get_base_var(char *name)
|
|
|
|
{
|
|
|
|
int baselen = 0;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
int c = get_next_char();
|
|
|
|
if (c == EOF)
|
|
|
|
return -1;
|
|
|
|
if (c == ']')
|
|
|
|
return baselen;
|
2006-05-09 21:24:02 +02:00
|
|
|
if (isspace(c))
|
|
|
|
return get_extended_base_var(name, baselen, c);
|
2006-10-30 17:25:36 +01:00
|
|
|
if (!iskeychar(c) && c != '.')
|
2005-10-11 01:31:08 +02:00
|
|
|
return -1;
|
|
|
|
if (baselen > MAXNAME / 2)
|
|
|
|
return -1;
|
|
|
|
name[baselen++] = tolower(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int git_parse_file(config_fn_t fn)
|
|
|
|
{
|
|
|
|
int comment = 0;
|
|
|
|
int baselen = 0;
|
|
|
|
static char var[MAXNAME];
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
int c = get_next_char();
|
|
|
|
if (c == '\n') {
|
|
|
|
/* EOF? */
|
|
|
|
if (!config_file)
|
|
|
|
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;
|
2005-10-12 03:47:34 +02:00
|
|
|
var[baselen] = tolower(c);
|
2005-10-11 01:31:08 +02:00
|
|
|
if (get_value(fn, var, baselen+1) < 0)
|
|
|
|
break;
|
|
|
|
}
|
2005-11-26 01:03:56 +01:00
|
|
|
die("bad config file line %d in %s", config_linenr, config_file_name);
|
2005-10-11 01:31:08 +02:00
|
|
|
}
|
|
|
|
|
2007-07-12 15:32:26 +02:00
|
|
|
static unsigned long get_unit_factor(const char *end)
|
|
|
|
{
|
|
|
|
if (!*end)
|
|
|
|
return 1;
|
|
|
|
else if (!strcasecmp(end, "k"))
|
|
|
|
return 1024;
|
|
|
|
else if (!strcasecmp(end, "m"))
|
|
|
|
return 1024 * 1024;
|
|
|
|
else if (!strcasecmp(end, "g"))
|
|
|
|
return 1024 * 1024 * 1024;
|
|
|
|
die("unknown unit: '%s'", end);
|
|
|
|
}
|
|
|
|
|
|
|
|
int git_parse_long(const char *value, long *ret)
|
|
|
|
{
|
|
|
|
if (value && *value) {
|
|
|
|
char *end;
|
|
|
|
long val = strtol(value, &end, 0);
|
|
|
|
*ret = val * get_unit_factor(end);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int git_parse_ulong(const char *value, unsigned long *ret)
|
2005-10-11 01:31:08 +02:00
|
|
|
{
|
|
|
|
if (value && *value) {
|
|
|
|
char *end;
|
2007-07-12 15:32:26 +02:00
|
|
|
unsigned long val = strtoul(value, &end, 0);
|
|
|
|
*ret = val * get_unit_factor(end);
|
|
|
|
return 1;
|
2005-10-11 01:31:08 +02:00
|
|
|
}
|
2007-07-12 15:32:26 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int git_config_int(const char *name, const char *value)
|
|
|
|
{
|
|
|
|
long ret;
|
|
|
|
if (!git_parse_long(value, &ret))
|
|
|
|
die("bad config value for '%s' in %s", name, config_file_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 value for '%s' in %s", name, config_file_name);
|
|
|
|
return ret;
|
2005-10-11 01:31:08 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
int git_config_bool(const char *name, const char *value)
|
|
|
|
{
|
|
|
|
if (!value)
|
|
|
|
return 1;
|
|
|
|
if (!*value)
|
|
|
|
return 0;
|
2006-07-04 03:48:23 +02:00
|
|
|
if (!strcasecmp(value, "true") || !strcasecmp(value, "yes"))
|
2005-10-11 01:31:08 +02:00
|
|
|
return 1;
|
2006-07-04 03:48:23 +02:00
|
|
|
if (!strcasecmp(value, "false") || !strcasecmp(value, "no"))
|
2005-10-11 01:31:08 +02:00
|
|
|
return 0;
|
|
|
|
return git_config_int(name, value) != 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int git_default_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;
|
|
|
|
}
|
|
|
|
|
2007-06-25 00:11:24 +02:00
|
|
|
if (!strcmp(var, "core.quotepath")) {
|
|
|
|
quote_path_fully = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-02 22:11:30 +01:00
|
|
|
if (!strcmp(var, "core.symlinks")) {
|
|
|
|
has_symlinks = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-01-07 11:00:28 +01:00
|
|
|
if (!strcmp(var, "core.bare")) {
|
|
|
|
is_bare_repository_cfg = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-02-09 06:15:24 +01:00
|
|
|
if (!strcmp(var, "core.ignorestat")) {
|
|
|
|
assume_unchanged = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-05-02 09:40:24 +02:00
|
|
|
if (!strcmp(var, "core.prefersymlinkrefs")) {
|
|
|
|
prefer_symlink_refs = git_config_bool(var, value);
|
2005-11-15 19:24:19 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-05-17 11:55:40 +02:00
|
|
|
if (!strcmp(var, "core.logallrefupdates")) {
|
|
|
|
log_all_ref_updates = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-03-21 03:45:47 +01:00
|
|
|
if (!strcmp(var, "core.warnambiguousrefs")) {
|
|
|
|
warn_ambiguous_refs = git_config_bool(var, value);
|
|
|
|
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-09 22:56:50 +02:00
|
|
|
if (!strcmp(var, "core.loosecompression")) {
|
2006-07-03 22:11:47 +02:00
|
|
|
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-09 22:56:50 +02: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;
|
2006-07-03 22:11:47 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-12-23 08:34:28 +01:00
|
|
|
if (!strcmp(var, "core.packedgitwindowsize")) {
|
2007-02-14 22:20:41 +01:00
|
|
|
int pgsz_x2 = getpagesize() * 2;
|
2006-12-23 08:34:28 +01:00
|
|
|
packed_git_window_size = git_config_int(var, value);
|
2007-02-14 22:20:41 +01:00
|
|
|
|
|
|
|
/* 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;
|
2006-12-23 08:34:28 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-12-23 08:33:35 +01:00
|
|
|
if (!strcmp(var, "core.packedgitlimit")) {
|
|
|
|
packed_git_limit = git_config_int(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-03-19 06:14:37 +01:00
|
|
|
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-13 20:07:23 +01:00
|
|
|
if (!strcmp(var, "core.autocrlf")) {
|
2007-02-14 03:16:12 +01:00
|
|
|
if (value && !strcasecmp(value, "input")) {
|
|
|
|
auto_crlf = -1;
|
|
|
|
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-13 20:07:23 +01:00
|
|
|
auto_crlf = git_config_bool(var, value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-10-12 03:47:34 +02:00
|
|
|
if (!strcmp(var, "user.name")) {
|
2006-06-24 16:01:25 +02:00
|
|
|
strlcpy(git_default_name, value, sizeof(git_default_name));
|
2005-10-12 03:47:34 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!strcmp(var, "user.email")) {
|
2006-06-24 16:01:25 +02:00
|
|
|
strlcpy(git_default_email, value, sizeof(git_default_email));
|
2005-10-12 03:47:34 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-28 01:09:40 +01:00
|
|
|
if (!strcmp(var, "i18n.commitencoding")) {
|
2007-03-16 02:02:51 +01:00
|
|
|
git_commit_encoding = xstrdup(value);
|
2005-11-28 01:09:40 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-12-28 01:41:33 +01:00
|
|
|
if (!strcmp(var, "i18n.logoutputencoding")) {
|
2007-03-16 02:02:51 +01:00
|
|
|
git_log_output_encoding = xstrdup(value);
|
2006-12-28 01:41:33 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-12-13 10:13:28 +01:00
|
|
|
if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
|
2006-07-30 00:27:43 +02:00
|
|
|
pager_use_color = git_config_bool(var,value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-07-03 20:18:11 +02:00
|
|
|
if (!strcmp(var, "core.pager")) {
|
|
|
|
pager_program = xstrdup(value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-07-20 14:06:09 +02:00
|
|
|
if (!strcmp(var, "core.editor")) {
|
|
|
|
editor_program = xstrdup(value);
|
|
|
|
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 09:05:00 +01:00
|
|
|
if (!strcmp(var, "core.excludesfile")) {
|
|
|
|
if (!value)
|
|
|
|
die("core.excludesfile without value");
|
|
|
|
excludes_file = xstrdup(value);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2006-04-25 00:59:33 +02:00
|
|
|
/* Add other config variables here and to Documentation/config.txt. */
|
2005-10-11 01:31:08 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2005-11-26 01:03:56 +01:00
|
|
|
int git_config_from_file(config_fn_t fn, const char *filename)
|
2005-10-11 01:31:08 +02:00
|
|
|
{
|
|
|
|
int ret;
|
2005-11-26 01:03:56 +01:00
|
|
|
FILE *f = fopen(filename, "r");
|
2005-10-11 01:31:08 +02:00
|
|
|
|
|
|
|
ret = -1;
|
|
|
|
if (f) {
|
|
|
|
config_file = f;
|
2005-11-26 01:03:56 +01:00
|
|
|
config_file_name = filename;
|
2005-10-11 01:31:08 +02:00
|
|
|
config_linenr = 1;
|
|
|
|
ret = git_parse_file(fn);
|
|
|
|
fclose(f);
|
2005-11-26 01:03:56 +01:00
|
|
|
config_file_name = NULL;
|
2005-10-11 01:31:08 +02:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
2005-11-17 22:32:36 +01:00
|
|
|
|
2007-11-13 21:05:05 +01:00
|
|
|
const char *git_etc_gitconfig(void)
|
|
|
|
{
|
2007-11-13 21:05:06 +01:00
|
|
|
static const char *system_wide;
|
|
|
|
if (!system_wide) {
|
|
|
|
system_wide = ETC_GITCONFIG;
|
|
|
|
if (!is_absolute_path(system_wide)) {
|
|
|
|
/* interpret path relative to exec-dir */
|
|
|
|
const char *exec_path = git_exec_path();
|
|
|
|
system_wide = prefix_path(exec_path, strlen(exec_path),
|
|
|
|
system_wide);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return system_wide;
|
2007-11-13 21:05:05 +01:00
|
|
|
}
|
|
|
|
|
2005-11-26 01:03:56 +01:00
|
|
|
int git_config(config_fn_t fn)
|
|
|
|
{
|
Read configuration also from $HOME/.gitconfig
This patch is based on Pasky's, with three notable differences:
- I did not yet update the documentation
- I named it .gitconfig, not .gitrc
- git-repo-config does not barf when a unique key is overridden locally
The last means that if you have something like
[alias]
l = log --stat -M
in ~/.gitconfig, and
[alias]
l = log --stat -M next..
in $GIT_DIR/config, then
git-repo-config alias.l
returns only one value, namely the value from $GIT_DIR/config.
If you set the environment variable GIT_CONFIG, $HOME/.gitconfig is not
read, and neither $GIT_DIR/config, but $GIT_CONFIG instead.
If you set GIT_CONFIG_LOCAL instead, it is interpreted instead of
$GIT_DIR/config, but $HOME/.gitconfig is still read.
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-20 01:48:03 +02:00
|
|
|
int ret = 0;
|
|
|
|
char *repo_config = NULL;
|
|
|
|
const char *home = NULL, *filename;
|
|
|
|
|
|
|
|
/* $GIT_CONFIG makes git read _only_ the given config file,
|
|
|
|
* $GIT_CONFIG_LOCAL will make it process it in addition to the
|
|
|
|
* global config file, the same way it would the per-repository
|
|
|
|
* config file otherwise. */
|
2006-12-19 10:28:15 +01:00
|
|
|
filename = getenv(CONFIG_ENVIRONMENT);
|
Read configuration also from $HOME/.gitconfig
This patch is based on Pasky's, with three notable differences:
- I did not yet update the documentation
- I named it .gitconfig, not .gitrc
- git-repo-config does not barf when a unique key is overridden locally
The last means that if you have something like
[alias]
l = log --stat -M
in ~/.gitconfig, and
[alias]
l = log --stat -M next..
in $GIT_DIR/config, then
git-repo-config alias.l
returns only one value, namely the value from $GIT_DIR/config.
If you set the environment variable GIT_CONFIG, $HOME/.gitconfig is not
read, and neither $GIT_DIR/config, but $GIT_CONFIG instead.
If you set GIT_CONFIG_LOCAL instead, it is interpreted instead of
$GIT_DIR/config, but $HOME/.gitconfig is still read.
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-20 01:48:03 +02:00
|
|
|
if (!filename) {
|
2007-11-13 21:05:05 +01:00
|
|
|
if (!access(git_etc_gitconfig(), R_OK))
|
|
|
|
ret += git_config_from_file(fn, git_etc_gitconfig());
|
Read configuration also from $HOME/.gitconfig
This patch is based on Pasky's, with three notable differences:
- I did not yet update the documentation
- I named it .gitconfig, not .gitrc
- git-repo-config does not barf when a unique key is overridden locally
The last means that if you have something like
[alias]
l = log --stat -M
in ~/.gitconfig, and
[alias]
l = log --stat -M next..
in $GIT_DIR/config, then
git-repo-config alias.l
returns only one value, namely the value from $GIT_DIR/config.
If you set the environment variable GIT_CONFIG, $HOME/.gitconfig is not
read, and neither $GIT_DIR/config, but $GIT_CONFIG instead.
If you set GIT_CONFIG_LOCAL instead, it is interpreted instead of
$GIT_DIR/config, but $HOME/.gitconfig is still read.
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-20 01:48:03 +02:00
|
|
|
home = getenv("HOME");
|
2006-12-19 10:28:15 +01:00
|
|
|
filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
|
Read configuration also from $HOME/.gitconfig
This patch is based on Pasky's, with three notable differences:
- I did not yet update the documentation
- I named it .gitconfig, not .gitrc
- git-repo-config does not barf when a unique key is overridden locally
The last means that if you have something like
[alias]
l = log --stat -M
in ~/.gitconfig, and
[alias]
l = log --stat -M next..
in $GIT_DIR/config, then
git-repo-config alias.l
returns only one value, namely the value from $GIT_DIR/config.
If you set the environment variable GIT_CONFIG, $HOME/.gitconfig is not
read, and neither $GIT_DIR/config, but $GIT_CONFIG instead.
If you set GIT_CONFIG_LOCAL instead, it is interpreted instead of
$GIT_DIR/config, but $HOME/.gitconfig is still read.
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-20 01:48:03 +02:00
|
|
|
if (!filename)
|
2006-09-02 06:16:31 +02:00
|
|
|
filename = repo_config = xstrdup(git_path("config"));
|
2006-06-18 01:23:58 +02:00
|
|
|
}
|
Read configuration also from $HOME/.gitconfig
This patch is based on Pasky's, with three notable differences:
- I did not yet update the documentation
- I named it .gitconfig, not .gitrc
- git-repo-config does not barf when a unique key is overridden locally
The last means that if you have something like
[alias]
l = log --stat -M
in ~/.gitconfig, and
[alias]
l = log --stat -M next..
in $GIT_DIR/config, then
git-repo-config alias.l
returns only one value, namely the value from $GIT_DIR/config.
If you set the environment variable GIT_CONFIG, $HOME/.gitconfig is not
read, and neither $GIT_DIR/config, but $GIT_CONFIG instead.
If you set GIT_CONFIG_LOCAL instead, it is interpreted instead of
$GIT_DIR/config, but $HOME/.gitconfig is still read.
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-20 01:48:03 +02:00
|
|
|
|
|
|
|
if (home) {
|
2006-09-02 06:16:31 +02:00
|
|
|
char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
|
2006-06-20 09:51:09 +02:00
|
|
|
if (!access(user_config, R_OK))
|
Read configuration also from $HOME/.gitconfig
This patch is based on Pasky's, with three notable differences:
- I did not yet update the documentation
- I named it .gitconfig, not .gitrc
- git-repo-config does not barf when a unique key is overridden locally
The last means that if you have something like
[alias]
l = log --stat -M
in ~/.gitconfig, and
[alias]
l = log --stat -M next..
in $GIT_DIR/config, then
git-repo-config alias.l
returns only one value, namely the value from $GIT_DIR/config.
If you set the environment variable GIT_CONFIG, $HOME/.gitconfig is not
read, and neither $GIT_DIR/config, but $GIT_CONFIG instead.
If you set GIT_CONFIG_LOCAL instead, it is interpreted instead of
$GIT_DIR/config, but $HOME/.gitconfig is still read.
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-20 01:48:03 +02:00
|
|
|
ret = git_config_from_file(fn, user_config);
|
|
|
|
free(user_config);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret += git_config_from_file(fn, filename);
|
2006-08-28 06:19:39 +02:00
|
|
|
free(repo_config);
|
Read configuration also from $HOME/.gitconfig
This patch is based on Pasky's, with three notable differences:
- I did not yet update the documentation
- I named it .gitconfig, not .gitrc
- git-repo-config does not barf when a unique key is overridden locally
The last means that if you have something like
[alias]
l = log --stat -M
in ~/.gitconfig, and
[alias]
l = log --stat -M next..
in $GIT_DIR/config, then
git-repo-config alias.l
returns only one value, namely the value from $GIT_DIR/config.
If you set the environment variable GIT_CONFIG, $HOME/.gitconfig is not
read, and neither $GIT_DIR/config, but $GIT_CONFIG instead.
If you set GIT_CONFIG_LOCAL instead, it is interpreted instead of
$GIT_DIR/config, but $HOME/.gitconfig is still read.
Signed-off-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <junkio@cox.net>
2006-06-20 01:48:03 +02:00
|
|
|
return ret;
|
2005-11-26 01:03:56 +01:00
|
|
|
}
|
|
|
|
|
2005-11-17 22:32:36 +01:00
|
|
|
/*
|
|
|
|
* Find all the stuff for git_config_set() below.
|
|
|
|
*/
|
2005-11-20 06:52:22 +01:00
|
|
|
|
|
|
|
#define MAX_MATCHES 512
|
|
|
|
|
2005-11-17 22:32:36 +01:00
|
|
|
static struct {
|
|
|
|
int baselen;
|
|
|
|
char* key;
|
2005-11-20 13:24:18 +01:00
|
|
|
int do_not_match;
|
2005-11-17 22:32:36 +01:00
|
|
|
regex_t* value_regex;
|
2005-11-20 06:52:22 +01:00
|
|
|
int multi_replace;
|
2007-03-07 02:44:37 +01:00
|
|
|
size_t offset[MAX_MATCHES];
|
2005-11-17 22:32:36 +01:00
|
|
|
enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
|
|
|
|
int seen;
|
|
|
|
} store;
|
|
|
|
|
2005-11-20 13:24:18 +01:00
|
|
|
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)));
|
|
|
|
}
|
|
|
|
|
2005-11-17 22:32:36 +01:00
|
|
|
static int store_aux(const char* key, const char* value)
|
|
|
|
{
|
2007-05-13 06:49:33 +02:00
|
|
|
const char *ep;
|
|
|
|
size_t section_len;
|
|
|
|
|
2005-11-17 22:32:36 +01:00
|
|
|
switch (store.state) {
|
|
|
|
case KEY_SEEN:
|
2005-11-20 13:24:18 +01:00
|
|
|
if (matches(key, value)) {
|
2005-11-20 06:52:22 +01:00
|
|
|
if (store.seen == 1 && store.multi_replace == 0) {
|
2005-11-17 22:32:36 +01:00
|
|
|
fprintf(stderr,
|
|
|
|
"Warning: %s has multiple values\n",
|
|
|
|
key);
|
2005-11-20 06:52:22 +01:00
|
|
|
} else if (store.seen >= MAX_MATCHES) {
|
|
|
|
fprintf(stderr, "Too many matches\n");
|
|
|
|
return 1;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
2005-11-20 06:52:22 +01:00
|
|
|
|
|
|
|
store.offset[store.seen] = ftell(config_file);
|
2005-11-17 22:32:36 +01:00
|
|
|
store.seen++;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SECTION_SEEN:
|
2007-05-13 06:49:33 +02:00
|
|
|
/*
|
|
|
|
* 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)) {
|
2005-11-17 22:32:36 +01:00
|
|
|
store.state = SECTION_END_SEEN;
|
|
|
|
break;
|
2007-05-13 06:49:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* 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);
|
2005-11-17 22:32:36 +01:00
|
|
|
/* fallthru */
|
|
|
|
case SECTION_END_SEEN:
|
|
|
|
case START:
|
2005-11-20 13:24:18 +01:00
|
|
|
if (matches(key, value)) {
|
2005-11-20 06:52:22 +01:00
|
|
|
store.offset[store.seen] = ftell(config_file);
|
2005-11-17 22:32:36 +01:00
|
|
|
store.state = KEY_SEEN;
|
|
|
|
store.seen++;
|
2006-05-09 21:24:02 +02:00
|
|
|
} else {
|
|
|
|
if (strrchr(key, '.') - key == store.baselen &&
|
2006-05-06 20:14:02 +02:00
|
|
|
!strncmp(key, store.key, store.baselen)) {
|
2006-05-05 15:49:15 +02:00
|
|
|
store.state = SECTION_SEEN;
|
2006-05-06 20:14:02 +02:00
|
|
|
store.offset[store.seen] = ftell(config_file);
|
2006-05-09 21:24:02 +02:00
|
|
|
}
|
2006-05-06 20:14:02 +02:00
|
|
|
}
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2007-06-13 10:22:51 +02:00
|
|
|
static int write_error(void)
|
2007-01-08 16:58:38 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "Failed to write new configuration file\n");
|
|
|
|
|
|
|
|
/* Same error code as "failed to rename". */
|
|
|
|
return 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int store_write_section(int fd, const char* key)
|
2005-11-17 22:32:36 +01:00
|
|
|
{
|
2006-05-09 21:24:02 +02:00
|
|
|
const char *dot = strchr(key, '.');
|
|
|
|
int len1 = store.baselen, len2 = -1;
|
|
|
|
|
|
|
|
dot = strchr(key, '.');
|
|
|
|
if (dot) {
|
|
|
|
int dotlen = dot - key;
|
|
|
|
if (dotlen < len1) {
|
|
|
|
len2 = len1 - dotlen - 1;
|
|
|
|
len1 = dotlen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-01-08 16:58:38 +01:00
|
|
|
if (write_in_full(fd, "[", 1) != 1 ||
|
|
|
|
write_in_full(fd, key, len1) != len1)
|
|
|
|
return 0;
|
2006-05-09 21:24:02 +02:00
|
|
|
if (len2 >= 0) {
|
2007-01-08 16:58:38 +01:00
|
|
|
if (write_in_full(fd, " \"", 2) != 2)
|
|
|
|
return 0;
|
2006-05-09 21:24:02 +02:00
|
|
|
while (--len2 >= 0) {
|
|
|
|
unsigned char c = *++dot;
|
|
|
|
if (c == '"')
|
2007-01-08 16:58:38 +01:00
|
|
|
if (write_in_full(fd, "\\", 1) != 1)
|
|
|
|
return 0;
|
|
|
|
if (write_in_full(fd, &c, 1) != 1)
|
|
|
|
return 0;
|
2006-05-09 21:24:02 +02:00
|
|
|
}
|
2007-01-08 16:58:38 +01:00
|
|
|
if (write_in_full(fd, "\"", 1) != 1)
|
|
|
|
return 0;
|
2006-05-09 21:24:02 +02:00
|
|
|
}
|
2007-01-08 16:58:38 +01:00
|
|
|
if (write_in_full(fd, "]\n", 2) != 2)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
2007-01-08 16:58:38 +01:00
|
|
|
static int store_write_pair(int fd, const char* key, const char* value)
|
2005-11-17 22:32:36 +01:00
|
|
|
{
|
|
|
|
int i;
|
2007-01-08 16:58:38 +01:00
|
|
|
int length = strlen(key+store.baselen+1);
|
2007-01-09 06:27:41 +01:00
|
|
|
int quote = 0;
|
|
|
|
|
|
|
|
/* Check to see if the value needs to be quoted. */
|
|
|
|
if (value[0] == ' ')
|
|
|
|
quote = 1;
|
|
|
|
for (i = 0; value[i]; i++)
|
|
|
|
if (value[i] == ';' || value[i] == '#')
|
|
|
|
quote = 1;
|
|
|
|
if (value[i-1] == ' ')
|
|
|
|
quote = 1;
|
2005-11-17 22:32:36 +01:00
|
|
|
|
2007-01-08 16:58:38 +01:00
|
|
|
if (write_in_full(fd, "\t", 1) != 1 ||
|
|
|
|
write_in_full(fd, key+store.baselen+1, length) != length ||
|
|
|
|
write_in_full(fd, " = ", 3) != 3)
|
|
|
|
return 0;
|
2007-01-09 06:27:41 +01:00
|
|
|
if (quote && write_in_full(fd, "\"", 1) != 1)
|
|
|
|
return 0;
|
2005-11-17 22:32:36 +01:00
|
|
|
for (i = 0; value[i]; i++)
|
|
|
|
switch (value[i]) {
|
2007-01-08 16:58:38 +01:00
|
|
|
case '\n':
|
|
|
|
if (write_in_full(fd, "\\n", 2) != 2)
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
case '\t':
|
|
|
|
if (write_in_full(fd, "\\t", 2) != 2)
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
case '"':
|
|
|
|
case '\\':
|
|
|
|
if (write_in_full(fd, "\\", 1) != 1)
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
if (write_in_full(fd, value+i, 1) != 1)
|
|
|
|
return 0;
|
|
|
|
break;
|
|
|
|
}
|
2007-01-09 06:27:41 +01:00
|
|
|
if (quote && write_in_full(fd, "\"", 1) != 1)
|
|
|
|
return 0;
|
2007-01-08 16:58:38 +01:00
|
|
|
if (write_in_full(fd, "\n", 1) != 1)
|
|
|
|
return 0;
|
|
|
|
return 1;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
2007-03-07 02:44:37 +01:00
|
|
|
static ssize_t find_beginning_of_line(const char* contents, size_t size,
|
|
|
|
size_t offset_, int* found_bracket)
|
2005-11-20 06:52:22 +01:00
|
|
|
{
|
2007-03-07 02:44:37 +01:00
|
|
|
size_t equal_offset = size, bracket_offset = size;
|
|
|
|
ssize_t offset;
|
2005-11-20 06:52:22 +01:00
|
|
|
|
2007-06-07 09:04:01 +02:00
|
|
|
for (offset = offset_-2; offset > 0
|
2005-11-20 06:52:22 +01:00
|
|
|
&& contents[offset] != '\n'; offset--)
|
|
|
|
switch (contents[offset]) {
|
|
|
|
case '=': equal_offset = offset; break;
|
|
|
|
case ']': bracket_offset = offset; break;
|
|
|
|
}
|
|
|
|
if (bracket_offset < equal_offset) {
|
|
|
|
*found_bracket = 1;
|
|
|
|
offset = bracket_offset+1;
|
|
|
|
} else
|
|
|
|
offset++;
|
|
|
|
|
|
|
|
return offset;
|
|
|
|
}
|
|
|
|
|
2005-11-17 22:32:36 +01:00
|
|
|
int git_config_set(const char* key, const char* value)
|
|
|
|
{
|
2005-11-20 06:52:22 +01:00
|
|
|
return git_config_set_multivar(key, value, NULL, 0);
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If value==NULL, unset in (remove from) config,
|
|
|
|
* if value_regex!=NULL, disregard key/value pairs where value does not match.
|
2005-11-20 06:52:22 +01:00
|
|
|
* 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.
|
2005-11-17 22:32:36 +01:00
|
|
|
*
|
|
|
|
* 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,
|
2005-11-20 06:52:22 +01:00
|
|
|
const char* value_regex, int multi_replace)
|
2005-11-17 22:32:36 +01:00
|
|
|
{
|
2006-05-09 21:24:02 +02:00
|
|
|
int i, dot;
|
2006-05-08 06:27:30 +02:00
|
|
|
int fd = -1, in_fd;
|
2006-04-17 17:14:48 +02:00
|
|
|
int ret;
|
2006-06-20 00:51:58 +02:00
|
|
|
char* config_filename;
|
2007-07-26 18:55:28 +02:00
|
|
|
struct lock_file *lock = NULL;
|
2005-11-20 21:22:19 +01:00
|
|
|
const char* last_dot = strrchr(key, '.');
|
2005-11-20 06:52:22 +01:00
|
|
|
|
2006-12-19 10:28:15 +01:00
|
|
|
config_filename = getenv(CONFIG_ENVIRONMENT);
|
2006-06-20 00:51:58 +02:00
|
|
|
if (!config_filename) {
|
2006-12-19 10:28:15 +01:00
|
|
|
config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
|
2006-06-20 00:51:58 +02:00
|
|
|
if (!config_filename)
|
|
|
|
config_filename = git_path("config");
|
|
|
|
}
|
2006-09-02 06:16:31 +02:00
|
|
|
config_filename = xstrdup(config_filename);
|
2006-06-20 00:51:58 +02:00
|
|
|
|
2005-11-17 22:32:36 +01:00
|
|
|
/*
|
|
|
|
* Since "key" actually contains the section name and the real
|
|
|
|
* key name separated by a dot, we have to know where the dot is.
|
|
|
|
*/
|
2005-11-20 21:22:19 +01:00
|
|
|
|
2006-04-17 17:14:48 +02:00
|
|
|
if (last_dot == NULL) {
|
2005-11-17 22:32:36 +01:00
|
|
|
fprintf(stderr, "key does not contain a section: %s\n", key);
|
2006-04-17 17:14:48 +02:00
|
|
|
ret = 2;
|
|
|
|
goto out_free;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
2005-11-20 21:22:19 +01:00
|
|
|
store.baselen = last_dot - key;
|
|
|
|
|
|
|
|
store.multi_replace = multi_replace;
|
2005-11-17 22:32:36 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate the key and while at it, lower case it for matching.
|
|
|
|
*/
|
2006-09-01 00:32:39 +02:00
|
|
|
store.key = xmalloc(strlen(key) + 1);
|
2006-05-09 21:24:02 +02:00
|
|
|
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 > store.baselen) {
|
2006-10-30 17:25:36 +01:00
|
|
|
if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
|
2006-05-09 21:24:02 +02:00
|
|
|
fprintf(stderr, "invalid key: %s\n", key);
|
|
|
|
free(store.key);
|
|
|
|
ret = 1;
|
|
|
|
goto out_free;
|
|
|
|
}
|
|
|
|
c = tolower(c);
|
2007-01-20 02:25:37 +01:00
|
|
|
} else if (c == '\n') {
|
|
|
|
fprintf(stderr, "invalid key (newline): %s\n", key);
|
|
|
|
free(store.key);
|
|
|
|
ret = 1;
|
|
|
|
goto out_free;
|
2006-05-09 21:24:02 +02:00
|
|
|
}
|
|
|
|
store.key[i] = c;
|
|
|
|
}
|
2005-11-21 11:18:20 +01:00
|
|
|
store.key[i] = 0;
|
2005-11-17 22:32:36 +01:00
|
|
|
|
|
|
|
/*
|
2007-07-26 18:55:28 +02:00
|
|
|
* The lock serves a purpose in addition to locking: the new
|
2005-11-17 22:32:36 +01:00
|
|
|
* contents of .git/config will be written into it.
|
|
|
|
*/
|
2007-07-26 18:55:28 +02:00
|
|
|
lock = xcalloc(sizeof(struct lock_file), 1);
|
|
|
|
fd = hold_lock_file_for_update(lock, config_filename, 0);
|
|
|
|
if (fd < 0) {
|
2005-11-17 22:32:36 +01:00
|
|
|
fprintf(stderr, "could not lock config file\n");
|
|
|
|
free(store.key);
|
2006-04-17 17:14:48 +02:00
|
|
|
ret = -1;
|
|
|
|
goto out_free;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If .git/config does not exist yet, write a minimal version.
|
|
|
|
*/
|
2006-01-05 12:43:34 +01:00
|
|
|
in_fd = open(config_filename, O_RDONLY);
|
|
|
|
if ( in_fd < 0 ) {
|
2005-11-17 22:32:36 +01:00
|
|
|
free(store.key);
|
|
|
|
|
2006-01-05 12:43:34 +01:00
|
|
|
if ( ENOENT != errno ) {
|
|
|
|
error("opening %s: %s", config_filename,
|
|
|
|
strerror(errno));
|
2006-04-17 17:14:48 +02:00
|
|
|
ret = 3; /* same as "invalid config file" */
|
|
|
|
goto out_free;
|
2006-01-05 12:43:34 +01:00
|
|
|
}
|
2005-11-17 22:32:36 +01:00
|
|
|
/* if nothing to unset, error out */
|
|
|
|
if (value == NULL) {
|
2006-04-17 17:14:48 +02:00
|
|
|
ret = 5;
|
|
|
|
goto out_free;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
store.key = (char*)key;
|
2007-01-08 16:58:38 +01:00
|
|
|
if (!store_write_section(fd, key) ||
|
2007-01-11 22:16:26 +01:00
|
|
|
!store_write_pair(fd, key, value))
|
|
|
|
goto write_err_out;
|
|
|
|
} else {
|
2006-01-05 12:43:34 +01:00
|
|
|
struct stat st;
|
2005-11-17 22:32:36 +01:00
|
|
|
char* contents;
|
2007-03-07 02:44:37 +01:00
|
|
|
size_t contents_sz, copy_begin, copy_end;
|
|
|
|
int i, new_line = 0;
|
2005-11-17 22:32:36 +01:00
|
|
|
|
|
|
|
if (value_regex == NULL)
|
|
|
|
store.value_regex = NULL;
|
|
|
|
else {
|
2005-11-20 13:24:18 +01:00
|
|
|
if (value_regex[0] == '!') {
|
|
|
|
store.do_not_match = 1;
|
|
|
|
value_regex++;
|
|
|
|
} else
|
|
|
|
store.do_not_match = 0;
|
|
|
|
|
2006-09-01 00:32:39 +02:00
|
|
|
store.value_regex = (regex_t*)xmalloc(sizeof(regex_t));
|
2005-11-17 22:32:36 +01:00
|
|
|
if (regcomp(store.value_regex, value_regex,
|
|
|
|
REG_EXTENDED)) {
|
2005-12-15 08:47:30 +01:00
|
|
|
fprintf(stderr, "Invalid pattern: %s\n",
|
2005-11-17 22:32:36 +01:00
|
|
|
value_regex);
|
|
|
|
free(store.value_regex);
|
2006-04-17 17:14:48 +02:00
|
|
|
ret = 6;
|
|
|
|
goto out_free;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-11-20 06:52:22 +01:00
|
|
|
store.offset[0] = 0;
|
2005-11-17 22:32:36 +01:00
|
|
|
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.
|
|
|
|
*/
|
2006-06-20 00:51:58 +02:00
|
|
|
if (git_config_from_file(store_aux, config_filename)) {
|
2005-11-17 22:32:36 +01:00
|
|
|
fprintf(stderr, "invalid config file\n");
|
|
|
|
free(store.key);
|
|
|
|
if (store.value_regex != NULL) {
|
|
|
|
regfree(store.value_regex);
|
|
|
|
free(store.value_regex);
|
|
|
|
}
|
2006-04-17 17:14:48 +02:00
|
|
|
ret = 3;
|
|
|
|
goto out_free;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
free(store.key);
|
|
|
|
if (store.value_regex != NULL) {
|
|
|
|
regfree(store.value_regex);
|
|
|
|
free(store.value_regex);
|
|
|
|
}
|
|
|
|
|
2005-11-20 06:52:22 +01:00
|
|
|
/* if nothing to unset, or too many matches, error out */
|
|
|
|
if ((store.seen == 0 && value == NULL) ||
|
|
|
|
(store.seen > 1 && multi_replace == 0)) {
|
2006-04-17 17:14:48 +02:00
|
|
|
ret = 5;
|
|
|
|
goto out_free;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
2006-01-05 12:43:34 +01:00
|
|
|
fstat(in_fd, &st);
|
2007-03-07 02:44:37 +01:00
|
|
|
contents_sz = xsize_t(st.st_size);
|
|
|
|
contents = xmmap(NULL, contents_sz, PROT_READ,
|
2005-11-17 22:32:36 +01:00
|
|
|
MAP_PRIVATE, in_fd, 0);
|
|
|
|
close(in_fd);
|
|
|
|
|
2005-11-20 06:52:22 +01:00
|
|
|
if (store.seen == 0)
|
|
|
|
store.seen = 1;
|
|
|
|
|
|
|
|
for (i = 0, copy_begin = 0; i < store.seen; i++) {
|
|
|
|
if (store.offset[i] == 0) {
|
2007-03-07 02:44:37 +01:00
|
|
|
store.offset[i] = copy_end = contents_sz;
|
2005-11-20 06:52:22 +01:00
|
|
|
} else if (store.state != KEY_SEEN) {
|
|
|
|
copy_end = store.offset[i];
|
2005-11-17 22:32:36 +01:00
|
|
|
} else
|
2005-11-20 06:52:22 +01:00
|
|
|
copy_end = find_beginning_of_line(
|
2007-03-07 02:44:37 +01:00
|
|
|
contents, contents_sz,
|
2005-11-20 06:52:22 +01:00
|
|
|
store.offset[i]-2, &new_line);
|
|
|
|
|
|
|
|
/* write the first part of the config */
|
|
|
|
if (copy_end > copy_begin) {
|
2007-01-11 22:16:26 +01:00
|
|
|
if (write_in_full(fd, contents + copy_begin,
|
|
|
|
copy_end - copy_begin) <
|
|
|
|
copy_end - copy_begin)
|
|
|
|
goto write_err_out;
|
|
|
|
if (new_line &&
|
|
|
|
write_in_full(fd, "\n", 1) != 1)
|
|
|
|
goto write_err_out;
|
2005-11-20 06:52:22 +01:00
|
|
|
}
|
|
|
|
copy_begin = store.offset[i];
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* write the pair (value == NULL means unset) */
|
|
|
|
if (value != NULL) {
|
2007-01-11 22:16:26 +01:00
|
|
|
if (store.state == START) {
|
|
|
|
if (!store_write_section(fd, key))
|
|
|
|
goto write_err_out;
|
2007-01-08 16:58:38 +01:00
|
|
|
}
|
2007-01-11 22:16:26 +01:00
|
|
|
if (!store_write_pair(fd, key, value))
|
|
|
|
goto write_err_out;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/* write the rest of the config */
|
2007-03-07 02:44:37 +01:00
|
|
|
if (copy_begin < contents_sz)
|
2007-01-11 22:16:26 +01:00
|
|
|
if (write_in_full(fd, contents + copy_begin,
|
2007-03-07 02:44:37 +01:00
|
|
|
contents_sz - copy_begin) <
|
|
|
|
contents_sz - copy_begin)
|
2007-01-11 22:16:26 +01:00
|
|
|
goto write_err_out;
|
2005-11-17 22:32:36 +01:00
|
|
|
|
2007-03-07 02:44:37 +01:00
|
|
|
munmap(contents, contents_sz);
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
2007-07-26 18:55:28 +02:00
|
|
|
if (close(fd) || commit_lock_file(lock) < 0) {
|
|
|
|
fprintf(stderr, "Cannot commit config file!\n");
|
2006-04-17 17:14:48 +02:00
|
|
|
ret = 4;
|
|
|
|
goto out_free;
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
2007-07-26 18:55:28 +02:00
|
|
|
/* fd is closed, so don't try to close it below. */
|
|
|
|
fd = -1;
|
|
|
|
/*
|
|
|
|
* 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;
|
2006-04-17 17:14:48 +02:00
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
out_free:
|
2006-05-08 06:27:30 +02:00
|
|
|
if (0 <= fd)
|
|
|
|
close(fd);
|
2007-07-26 18:55:28 +02:00
|
|
|
if (lock)
|
|
|
|
rollback_lock_file(lock);
|
2006-08-28 06:19:39 +02:00
|
|
|
free(config_filename);
|
2006-04-17 17:14:48 +02:00
|
|
|
return ret;
|
2007-01-11 22:16:26 +01:00
|
|
|
|
|
|
|
write_err_out:
|
|
|
|
ret = write_error();
|
|
|
|
goto out_free;
|
|
|
|
|
2005-11-17 22:32:36 +01:00
|
|
|
}
|
|
|
|
|
2007-03-02 21:53:33 +01:00
|
|
|
static int section_name_match (const char *buf, const char *name)
|
|
|
|
{
|
|
|
|
int i = 0, j = 0, dot = 0;
|
|
|
|
for (; 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;
|
|
|
|
}
|
|
|
|
return (buf[i] == ']' && name[j] == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if new_name == NULL, the section is removed instead */
|
2006-12-16 15:14:14 +01:00
|
|
|
int git_config_rename_section(const char *old_name, const char *new_name)
|
|
|
|
{
|
2007-03-02 21:53:33 +01:00
|
|
|
int ret = 0, remove = 0;
|
2006-12-20 06:55:27 +01:00
|
|
|
char *config_filename;
|
2006-12-16 15:14:14 +01:00
|
|
|
struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
|
|
|
|
int out_fd;
|
|
|
|
char buf[1024];
|
|
|
|
|
2006-12-19 10:28:15 +01:00
|
|
|
config_filename = getenv(CONFIG_ENVIRONMENT);
|
2006-12-16 15:14:14 +01:00
|
|
|
if (!config_filename) {
|
2006-12-19 10:28:15 +01:00
|
|
|
config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
|
2006-12-16 15:14:14 +01:00
|
|
|
if (!config_filename)
|
|
|
|
config_filename = git_path("config");
|
|
|
|
}
|
|
|
|
config_filename = xstrdup(config_filename);
|
|
|
|
out_fd = hold_lock_file_for_update(lock, config_filename, 0);
|
2006-12-20 06:55:27 +01:00
|
|
|
if (out_fd < 0) {
|
|
|
|
ret = error("Could not lock config file!");
|
|
|
|
goto out;
|
|
|
|
}
|
2006-12-16 15:14:14 +01:00
|
|
|
|
2006-12-20 06:55:27 +01:00
|
|
|
if (!(config_file = fopen(config_filename, "rb"))) {
|
2007-04-05 16:20:55 +02:00
|
|
|
/* no config file means nothing to rename, no error */
|
|
|
|
goto unlock_and_out;
|
2006-12-20 06:55:27 +01:00
|
|
|
}
|
2006-12-16 15:14:14 +01:00
|
|
|
|
|
|
|
while (fgets(buf, sizeof(buf), config_file)) {
|
|
|
|
int i;
|
2007-01-08 16:58:38 +01:00
|
|
|
int length;
|
2006-12-16 15:14:14 +01:00
|
|
|
for (i = 0; buf[i] && isspace(buf[i]); i++)
|
|
|
|
; /* do nothing */
|
|
|
|
if (buf[i] == '[') {
|
|
|
|
/* it's a section */
|
2007-03-02 21:53:33 +01:00
|
|
|
if (section_name_match (&buf[i+1], old_name)) {
|
|
|
|
ret++;
|
|
|
|
if (new_name == NULL) {
|
|
|
|
remove = 1;
|
2006-12-16 15:14:14 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
store.baselen = strlen(new_name);
|
2007-01-08 16:58:38 +01:00
|
|
|
if (!store_write_section(out_fd, new_name)) {
|
|
|
|
ret = write_error();
|
|
|
|
goto out;
|
|
|
|
}
|
2006-12-16 15:14:14 +01:00
|
|
|
continue;
|
|
|
|
}
|
2007-03-02 21:53:33 +01:00
|
|
|
remove = 0;
|
2006-12-16 15:14:14 +01:00
|
|
|
}
|
2007-03-02 21:53:33 +01:00
|
|
|
if (remove)
|
|
|
|
continue;
|
2007-01-08 16:58:38 +01:00
|
|
|
length = strlen(buf);
|
|
|
|
if (write_in_full(out_fd, buf, length) != length) {
|
|
|
|
ret = write_error();
|
|
|
|
goto out;
|
|
|
|
}
|
2006-12-16 15:14:14 +01:00
|
|
|
}
|
2006-12-20 06:55:27 +01:00
|
|
|
fclose(config_file);
|
2007-04-05 16:20:55 +02:00
|
|
|
unlock_and_out:
|
2006-12-16 15:14:14 +01:00
|
|
|
if (close(out_fd) || commit_lock_file(lock) < 0)
|
2007-01-08 16:58:38 +01:00
|
|
|
ret = error("Cannot commit config file!");
|
2006-12-20 06:55:27 +01:00
|
|
|
out:
|
|
|
|
free(config_filename);
|
2006-12-16 15:14:14 +01:00
|
|
|
return ret;
|
|
|
|
}
|