*
*/
#include "cache.h"
-#include <regex.h>
+#include "exec_cmd.h"
#define MAXNAME (256)
static FILE *config_file;
static const char *config_file_name;
static int config_linenr;
+static int zlib_compression_seen;
+
static int get_next_char(void)
{
int c;
die("bad config file line %d in %s", config_linenr, config_file_name);
}
-int git_config_int(const char *name, const char *value)
+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)
{
if (value && *value) {
char *end;
- int val = strtol(value, &end, 0);
- if (!*end)
- return val;
+ unsigned long val = strtoul(value, &end, 0);
+ *ret = val * get_unit_factor(end);
+ return 1;
}
- die("bad config value for '%s' in %s", name, config_file_name);
+ 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;
}
int git_config_bool(const char *name, const char *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.bare")) {
+ is_bare_repository_cfg = git_config_bool(var, value);
+ return 0;
+ }
+
if (!strcmp(var, "core.ignorestat")) {
assume_unchanged = git_config_bool(var, value);
return 0;
return 0;
}
- if (!strcmp(var, "core.legacyheaders")) {
- use_legacy_headers = git_config_bool(var, value);
+ 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;
+ zlib_compression_seen = 1;
return 0;
}
level = Z_DEFAULT_COMPRESSION;
else if (level < 0 || level > Z_BEST_COMPRESSION)
die("bad zlib compression level %d", level);
- zlib_compression_level = level;
+ core_compression_level = level;
+ core_compression_seen = 1;
+ if (!zlib_compression_seen)
+ zlib_compression_level = level;
+ return 0;
+ }
+
+ if (!strcmp(var, "core.packedgitwindowsize")) {
+ int pgsz_x2 = getpagesize() * 2;
+ 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;
+ 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;
+ }
+
+ if (!strcmp(var, "core.autocrlf")) {
+ if (value && !strcasecmp(value, "input")) {
+ auto_crlf = -1;
+ return 0;
+ }
+ auto_crlf = git_config_bool(var, value);
return 0;
}
}
if (!strcmp(var, "i18n.commitencoding")) {
- strlcpy(git_commit_encoding, value, sizeof(git_commit_encoding));
+ git_commit_encoding = xstrdup(value);
return 0;
}
- if (!strcmp(var, "pager.color")) {
+ if (!strcmp(var, "i18n.logoutputencoding")) {
+ git_log_output_encoding = xstrdup(value);
+ return 0;
+ }
+
+
+ if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
pager_use_color = git_config_bool(var,value);
return 0;
}
+ if (!strcmp(var, "core.pager")) {
+ pager_program = xstrdup(value);
+ return 0;
+ }
+
+ if (!strcmp(var, "core.editor")) {
+ editor_program = xstrdup(value);
+ return 0;
+ }
+
+ if (!strcmp(var, "core.excludesfile")) {
+ if (!value)
+ die("core.excludesfile without value");
+ excludes_file = xstrdup(value);
+ return 0;
+ }
+
/* Add other config variables here and to Documentation/config.txt. */
return 0;
}
return ret;
}
+const char *git_etc_gitconfig(void)
+{
+ 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;
+}
+
int git_config(config_fn_t fn)
{
int ret = 0;
* $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. */
- filename = getenv("GIT_CONFIG");
+ filename = getenv(CONFIG_ENVIRONMENT);
if (!filename) {
+ if (!access(git_etc_gitconfig(), R_OK))
+ ret += git_config_from_file(fn, git_etc_gitconfig());
home = getenv("HOME");
- filename = getenv("GIT_CONFIG_LOCAL");
+ filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
if (!filename)
filename = repo_config = xstrdup(git_path("config"));
}
int do_not_match;
regex_t* value_regex;
int multi_replace;
- off_t offset[MAX_MATCHES];
+ size_t offset[MAX_MATCHES];
enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
int seen;
} store;
static int store_aux(const char* key, const char* value)
{
+ const char *ep;
+ size_t section_len;
+
switch (store.state) {
case KEY_SEEN:
if (matches(key, value)) {
}
break;
case SECTION_SEEN:
- if (strncmp(key, store.key, store.baselen+1)) {
+ /*
+ * 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;
- } else
- /* do not increment matches: this is no match */
- store.offset[store.seen] = ftell(config_file);
+ }
+
+ /*
+ * 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:
return 0;
}
-static void store_write_section(int fd, const char* key)
+static int write_error(void)
+{
+ 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)
{
const char *dot = strchr(key, '.');
int len1 = store.baselen, len2 = -1;
}
}
- write(fd, "[", 1);
- write(fd, key, len1);
+ if (write_in_full(fd, "[", 1) != 1 ||
+ write_in_full(fd, key, len1) != len1)
+ return 0;
if (len2 >= 0) {
- write(fd, " \"", 2);
+ if (write_in_full(fd, " \"", 2) != 2)
+ return 0;
while (--len2 >= 0) {
unsigned char c = *++dot;
if (c == '"')
- write(fd, "\\", 1);
- write(fd, &c, 1);
+ if (write_in_full(fd, "\\", 1) != 1)
+ return 0;
+ if (write_in_full(fd, &c, 1) != 1)
+ return 0;
}
- write(fd, "\"", 1);
+ if (write_in_full(fd, "\"", 1) != 1)
+ return 0;
}
- write(fd, "]\n", 2);
+ if (write_in_full(fd, "]\n", 2) != 2)
+ return 0;
+
+ return 1;
}
-static void store_write_pair(int fd, const char* key, const char* value)
+static int store_write_pair(int fd, const char* key, const char* value)
{
int i;
+ int length = strlen(key+store.baselen+1);
+ int quote = 0;
- write(fd, "\t", 1);
- write(fd, key+store.baselen+1,
- strlen(key+store.baselen+1));
- write(fd, " = ", 3);
+ /* 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;
+
+ 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;
+ if (quote && write_in_full(fd, "\"", 1) != 1)
+ return 0;
for (i = 0; value[i]; i++)
switch (value[i]) {
- case '\n': write(fd, "\\n", 2); break;
- case '\t': write(fd, "\\t", 2); break;
- case '"': case '\\': write(fd, "\\", 1);
- default: write(fd, value+i, 1);
- }
- write(fd, "\n", 1);
+ 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;
+ }
+ if (quote && write_in_full(fd, "\"", 1) != 1)
+ return 0;
+ if (write_in_full(fd, "\n", 1) != 1)
+ return 0;
+ return 1;
}
-static int find_beginning_of_line(const char* contents, int size,
- int offset_, int* found_bracket)
+static ssize_t find_beginning_of_line(const char* contents, size_t size,
+ size_t offset_, int* found_bracket)
{
- int equal_offset = size, bracket_offset = size;
- int offset;
+ size_t equal_offset = size, bracket_offset = size;
+ ssize_t offset;
- for (offset = offset_-2; offset > 0
+ for (offset = offset_-2; offset > 0
&& contents[offset] != '\n'; offset--)
switch (contents[offset]) {
case '=': equal_offset = offset; break;
int fd = -1, in_fd;
int ret;
char* config_filename;
- char* lock_file;
+ struct lock_file *lock = NULL;
const char* last_dot = strrchr(key, '.');
- config_filename = getenv("GIT_CONFIG");
+ config_filename = getenv(CONFIG_ENVIRONMENT);
if (!config_filename) {
- config_filename = getenv("GIT_CONFIG_LOCAL");
+ config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
if (!config_filename)
config_filename = git_path("config");
}
config_filename = xstrdup(config_filename);
- lock_file = xstrdup(mkpath("%s.lock", config_filename));
/*
* Since "key" actually contains the section name and the real
goto out_free;
}
c = tolower(c);
+ } else if (c == '\n') {
+ fprintf(stderr, "invalid key (newline): %s\n", key);
+ free(store.key);
+ ret = 1;
+ goto out_free;
}
store.key[i] = c;
}
store.key[i] = 0;
/*
- * The lock_file serves a purpose in addition to locking: the new
+ * The lock serves a purpose in addition to locking: the new
* contents of .git/config will be written into it.
*/
- fd = open(lock_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
- if (fd < 0 || adjust_shared_perm(lock_file)) {
+ lock = xcalloc(sizeof(struct lock_file), 1);
+ fd = hold_lock_file_for_update(lock, config_filename, 0);
+ if (fd < 0) {
fprintf(stderr, "could not lock config file\n");
free(store.key);
ret = -1;
}
store.key = (char*)key;
- store_write_section(fd, key);
- store_write_pair(fd, key, value);
- } else{
+ if (!store_write_section(fd, key) ||
+ !store_write_pair(fd, key, value))
+ goto write_err_out;
+ } else {
struct stat st;
char* contents;
- int i, copy_begin, copy_end, new_line = 0;
+ size_t contents_sz, copy_begin, copy_end;
+ int i, new_line = 0;
if (value_regex == NULL)
store.value_regex = NULL;
}
fstat(in_fd, &st);
- contents = mmap(NULL, st.st_size, PROT_READ,
+ contents_sz = xsize_t(st.st_size);
+ contents = xmmap(NULL, contents_sz, PROT_READ,
MAP_PRIVATE, in_fd, 0);
close(in_fd);
for (i = 0, copy_begin = 0; i < store.seen; i++) {
if (store.offset[i] == 0) {
- store.offset[i] = copy_end = st.st_size;
+ 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, st.st_size,
+ contents, contents_sz,
store.offset[i]-2, &new_line);
/* write the first part of the config */
if (copy_end > copy_begin) {
- write(fd, contents + copy_begin,
- copy_end - copy_begin);
- if (new_line)
- write(fd, "\n", 1);
+ 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;
}
copy_begin = store.offset[i];
}
/* write the pair (value == NULL means unset) */
if (value != NULL) {
- if (store.state == START)
- store_write_section(fd, key);
- store_write_pair(fd, key, value);
+ 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 < st.st_size)
- write(fd, contents + copy_begin,
- st.st_size - copy_begin);
+ 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, st.st_size);
- unlink(config_filename);
+ munmap(contents, contents_sz);
}
- if (rename(lock_file, config_filename) < 0) {
- fprintf(stderr, "Could not rename the lock file?\n");
+ if (close(fd) || commit_lock_file(lock) < 0) {
+ fprintf(stderr, "Cannot commit config file!\n");
ret = 4;
goto out_free;
}
+ /* 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;
ret = 0;
out_free:
if (0 <= fd)
close(fd);
+ if (lock)
+ rollback_lock_file(lock);
free(config_filename);
- if (lock_file) {
- unlink(lock_file);
- free(lock_file);
- }
return ret;
+
+write_err_out:
+ ret = write_error();
+ goto out_free;
+
+}
+
+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 */
+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];
+
+ config_filename = getenv(CONFIG_ENVIRONMENT);
+ if (!config_filename) {
+ config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
+ if (!config_filename)
+ config_filename = git_path("config");
+ }
+ config_filename = xstrdup(config_filename);
+ out_fd = hold_lock_file_for_update(lock, config_filename, 0);
+ if (out_fd < 0) {
+ ret = error("Could not lock config file!");
+ 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;
+ for (i = 0; buf[i] && isspace(buf[i]); i++)
+ ; /* do nothing */
+ if (buf[i] == '[') {
+ /* it's a section */
+ if (section_name_match (&buf[i+1], old_name)) {
+ ret++;
+ if (new_name == NULL) {
+ remove = 1;
+ continue;
+ }
+ store.baselen = strlen(new_name);
+ if (!store_write_section(out_fd, new_name)) {
+ ret = write_error();
+ goto out;
+ }
+ continue;
+ }
+ remove = 0;
+ }
+ if (remove)
+ continue;
+ length = strlen(buf);
+ if (write_in_full(out_fd, buf, length) != length) {
+ ret = write_error();
+ goto out;
+ }
+ }
+ fclose(config_file);
+ unlock_and_out:
+ if (close(out_fd) || commit_lock_file(lock) < 0)
+ ret = error("Cannot commit config file!");
+ out:
+ free(config_filename);
+ return ret;
+}