Save errno in handle_alias()
[gitweb.git] / config.c
index a3e14d76e54e05887545765dabda11b1bc2af748..ec44827da4afbcf89a5eed37c7f557a7ffdce35d 100644 (file)
--- a/config.c
+++ b/config.c
@@ -60,6 +60,12 @@ static char *parse_value(void)
                        space = 1;
                        continue;
                }
+               if (!quote) {
+                       if (c == ';' || c == '#') {
+                               comment = 1;
+                               continue;
+                       }
+               }
                if (space) {
                        if (len)
                                value[len++] = ' ';
@@ -93,12 +99,6 @@ static char *parse_value(void)
                        quote = 1-quote;
                        continue;
                }
-               if (!quote) {
-                       if (c == ';' || c == '#') {
-                               comment = 1;
-                               continue;
-                       }
-               }
                value[len++] = c;
        }
 }
@@ -134,6 +134,41 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
        return fn(name, value);
 }
 
+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;
@@ -144,6 +179,8 @@ static int get_base_var(char *name)
                        return -1;
                if (c == ']')
                        return baselen;
+               if (isspace(c))
+                       return get_extended_base_var(name, baselen, c);
                if (!isalnum(c) && c != '.')
                        return -1;
                if (baselen > MAXNAME / 2)
@@ -227,8 +264,13 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
-       if (!strcmp(var, "core.symrefsonly")) {
-               only_use_symrefs = git_config_bool(var, value);
+       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;
        }
 
@@ -238,17 +280,17 @@ int git_default_config(const char *var, const char *value)
        }
 
        if (!strcmp(var, "user.name")) {
-               strncpy(git_default_name, value, sizeof(git_default_name));
+               strlcpy(git_default_name, value, sizeof(git_default_name));
                return 0;
        }
 
        if (!strcmp(var, "user.email")) {
-               strncpy(git_default_email, value, sizeof(git_default_email));
+               strlcpy(git_default_email, value, sizeof(git_default_email));
                return 0;
        }
 
        if (!strcmp(var, "i18n.commitencoding")) {
-               strncpy(git_commit_encoding, value, sizeof(git_commit_encoding));
+               strlcpy(git_commit_encoding, value, sizeof(git_commit_encoding));
                return 0;
        }
 
@@ -275,7 +317,33 @@ int git_config_from_file(config_fn_t fn, const char *filename)
 
 int git_config(config_fn_t fn)
 {
-       return git_config_from_file(fn, git_path("config"));
+       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. */
+       filename = getenv("GIT_CONFIG");
+       if (!filename) {
+               home = getenv("HOME");
+               filename = getenv("GIT_CONFIG_LOCAL");
+               if (!filename)
+                       filename = repo_config = strdup(git_path("config"));
+       }
+
+       if (home) {
+               char *user_config = strdup(mkpath("%s/.gitconfig", home));
+               if (!access(user_config, R_OK))
+                       ret = git_config_from_file(fn, user_config);
+               free(user_config);
+       }
+
+       ret += git_config_from_file(fn, filename);
+       if (repo_config)
+               free(repo_config);
+       return ret;
 }
 
 /*
@@ -335,17 +403,43 @@ static int store_aux(const char* key, const char* value)
                        store.offset[store.seen] = ftell(config_file);
                        store.state = KEY_SEEN;
                        store.seen++;
-               } else if (strrchr(key, '.') - key == store.baselen &&
-                             !strncmp(key, store.key, store.baselen))
+               } else {
+                       if (strrchr(key, '.') - key == store.baselen &&
+                             !strncmp(key, store.key, store.baselen)) {
                                        store.state = SECTION_SEEN;
+                                       store.offset[store.seen] = ftell(config_file);
+                       }
+               }
        }
        return 0;
 }
 
 static void store_write_section(int fd, const char* key)
 {
+       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;
+               }
+       }
+
        write(fd, "[", 1);
-       write(fd, key, store.baselen);
+       write(fd, key, len1);
+       if (len2 >= 0) {
+               write(fd, " \"", 2);
+               while (--len2 >= 0) {
+                       unsigned char c = *++dot;
+                       if (c == '"')
+                               write(fd, "\\", 1);
+                       write(fd, &c, 1);
+               }
+               write(fd, "\"", 1);
+       }
        write(fd, "]\n", 2);
 }
 
@@ -419,13 +513,22 @@ int git_config_set(const char* key, const char* value)
 int git_config_set_multivar(const char* key, const char* value,
        const char* value_regex, int multi_replace)
 {
-       int i;
-       int fd, in_fd;
+       int i, dot;
+       int fd = -1, in_fd;
        int ret;
-       char* config_filename = strdup(git_path("config"));
-       char* lock_file = strdup(git_path("config.lock"));
+       char* config_filename;
+       char* lock_file;
        const char* last_dot = strrchr(key, '.');
 
+       config_filename = getenv("GIT_CONFIG");
+       if (!config_filename) {
+               config_filename = getenv("GIT_CONFIG_LOCAL");
+               if (!config_filename)
+                       config_filename  = git_path("config");
+       }
+       config_filename = strdup(config_filename);
+       lock_file = strdup(mkpath("%s.lock", config_filename));
+
        /*
         * Since "key" actually contains the section name and the real
         * key name separated by a dot, we have to know where the dot is.
@@ -444,16 +547,23 @@ int git_config_set_multivar(const char* key, const char* value,
         * Validate the key and while at it, lower case it for matching.
         */
        store.key = (char*)malloc(strlen(key)+1);
-       for (i = 0; key[i]; i++)
-               if (i != store.baselen &&
-                               ((!isalnum(key[i]) && key[i] != '.') ||
-                                (i == store.baselen+1 && !isalpha(key[i])))) {
-                       fprintf(stderr, "invalid key: %s\n", key);
-                       free(store.key);
-                       ret = 1;
-                       goto out_free;
-               } else
-                       store.key[i] = tolower(key[i]);
+       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) {
+                       if (!isalnum(c) || (i == store.baselen+1 && !isalpha(c))) {
+                               fprintf(stderr, "invalid key: %s\n", key);
+                               free(store.key);
+                               ret = 1;
+                               goto out_free;
+                       }
+                       c = tolower(c);
+               }
+               store.key[i] = c;
+       }
        store.key[i] = 0;
 
        /*
@@ -461,7 +571,7 @@ int git_config_set_multivar(const char* key, const char* value,
         * contents of .git/config will be written into it.
         */
        fd = open(lock_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
-       if (fd < 0) {
+       if (fd < 0 || adjust_shared_perm(lock_file)) {
                fprintf(stderr, "could not lock config file\n");
                free(store.key);
                ret = -1;
@@ -478,15 +588,11 @@ int git_config_set_multivar(const char* key, const char* value,
                if ( ENOENT != errno ) {
                        error("opening %s: %s", config_filename,
                              strerror(errno));
-                       close(fd);
-                       unlink(lock_file);
                        ret = 3; /* same as "invalid config file" */
                        goto out_free;
                }
                /* if nothing to unset, error out */
                if (value == NULL) {
-                       close(fd);
-                       unlink(lock_file);
                        ret = 5;
                        goto out_free;
                }
@@ -529,7 +635,7 @@ int git_config_set_multivar(const char* key, const char* value,
                 * As a side effect, we make sure to transform only a valid
                 * existing config file.
                 */
-               if (git_config(store_aux)) {
+               if (git_config_from_file(store_aux, config_filename)) {
                        fprintf(stderr, "invalid config file\n");
                        free(store.key);
                        if (store.value_regex != NULL) {
@@ -549,8 +655,6 @@ int git_config_set_multivar(const char* key, const char* value,
                /* if nothing to unset, or too many matches, error out */
                if ((store.seen == 0 && value == NULL) ||
                                (store.seen > 1 && multi_replace == 0)) {
-                       close(fd);
-                       unlink(lock_file);
                        ret = 5;
                        goto out_free;
                }
@@ -599,8 +703,6 @@ int git_config_set_multivar(const char* key, const char* value,
                unlink(config_filename);
        }
 
-       close(fd);
-
        if (rename(lock_file, config_filename) < 0) {
                fprintf(stderr, "Could not rename the lock file?\n");
                ret = 4;
@@ -610,10 +712,14 @@ int git_config_set_multivar(const char* key, const char* value,
        ret = 0;
 
 out_free:
+       if (0 <= fd)
+               close(fd);
        if (config_filename)
                free(config_filename);
-       if (lock_file)
+       if (lock_file) {
+               unlink(lock_file);
                free(lock_file);
+       }
        return ret;
 }