Teach git diff about Objective-C syntax
[gitweb.git] / config.c
index 062449459e1a4cfc2a605c065ed281669e0e7452..18d305c890d3f4ba00962cfa4033ff9ed68cf95b 100644 (file)
--- a/config.c
+++ b/config.c
@@ -16,6 +16,8 @@ static int config_linenr;
 static int config_file_eof;
 static int zlib_compression_seen;
 
+const char *config_exclusive_filename = NULL;
+
 static int get_next_char(void)
 {
        int c;
@@ -111,7 +113,7 @@ static inline int iskeychar(int c)
        return isalnum(c) || c == '-';
 }
 
-static int get_value(config_fn_t fn, char *name, unsigned int len)
+static int get_value(config_fn_t fn, void *data, char *name, unsigned int len)
 {
        int c;
        char *value;
@@ -139,7 +141,7 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
                if (!value)
                        return -1;
        }
-       return fn(name, value);
+       return fn(name, value, data);
 }
 
 static int get_extended_base_var(char *name, int baselen, int c)
@@ -197,14 +199,33 @@ static int get_base_var(char *name)
        }
 }
 
-static int git_parse_file(config_fn_t fn)
+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;
@@ -228,7 +249,7 @@ static int git_parse_file(config_fn_t fn)
                if (!isalpha(c))
                        break;
                var[baselen] = tolower(c);
-               if (get_value(fn, var, baselen+1) < 0)
+               if (get_value(fn, data, var, baselen+1) < 0)
                        break;
        }
        die("bad config file line %d in %s", config_linenr, config_file_name);
@@ -253,7 +274,7 @@ static int parse_unit_factor(const char *end, unsigned long *val)
        return 0;
 }
 
-int git_parse_long(const char *value, long *ret)
+static int git_parse_long(const char *value, long *ret)
 {
        if (value && *value) {
                char *end;
@@ -289,7 +310,7 @@ static void die_bad_config(const char *name)
 
 int git_config_int(const char *name, const char *value)
 {
-       long ret;
+       long ret = 0;
        if (!git_parse_long(value, &ret))
                die_bad_config(name);
        return ret;
@@ -303,8 +324,9 @@ unsigned long git_config_ulong(const char *name, const char *value)
        return ret;
 }
 
-int git_config_bool(const char *name, const char *value)
+int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
 {
+       *is_bool = 1;
        if (!value)
                return 1;
        if (!*value)
@@ -313,7 +335,14 @@ int git_config_bool(const char *name, const char *value)
                return 1;
        if (!strcasecmp(value, "false") || !strcasecmp(value, "no"))
                return 0;
-       return git_config_int(name, value) != 0;
+       *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)
@@ -324,13 +353,17 @@ int git_config_string(const char **dest, const char *var, const char *value)
        return 0;
 }
 
-int git_default_config(const char *var, const char *value)
+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);
@@ -342,6 +375,11 @@ int git_default_config(const char *var, const char *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;
@@ -431,10 +469,39 @@ int git_default_config(const char *var, const char *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.excludesfile"))
+               return git_config_string(&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;
+       }
+
+       /* 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));
+               if (git_default_email[0])
+                       user_ident_explicitly_given = 1;
                return 0;
        }
 
@@ -442,35 +509,29 @@ int git_default_config(const char *var, const char *value)
                if (!value)
                        return config_error_nonbool(var);
                strlcpy(git_default_email, value, sizeof(git_default_email));
+               if (git_default_name[0])
+                       user_ident_explicitly_given = 1;
                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);
 
-       if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
-               pager_use_color = git_config_bool(var,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.excludesfile"))
-               return git_config_string(&excludes_file, var, value);
+       /* Add other config variables here and to Documentation/config.txt. */
+       return 0;
+}
 
-       if (!strcmp(var, "core.whitespace")) {
-               if (!value)
-                       return config_error_nonbool(var);
-               whitespace_rule_cfg = parse_whitespace_rule(value);
-               return 0;
-       }
+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;
@@ -479,12 +540,50 @@ int git_default_config(const char *var, const char *value)
                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;
+}
+
+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 (!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)
+int git_config_from_file(config_fn_t fn, const char *filename, void *data)
 {
        int ret;
        FILE *f = fopen(filename, "r");
@@ -495,7 +594,7 @@ int git_config_from_file(config_fn_t fn, const char *filename)
                config_file_name = filename;
                config_linenr = 1;
                config_file_eof = 0;
-               ret = git_parse_file(fn);
+               ret = git_parse_file(fn, data);
                fclose(f);
                config_file_name = NULL;
        }
@@ -505,19 +604,12 @@ int git_config_from_file(config_fn_t fn, const char *filename)
 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 */
-                       struct strbuf d = STRBUF_INIT;
-                       strbuf_addf(&d, "%s/%s", git_exec_path(), system_wide);
-                       system_wide = strbuf_detach(&d, NULL);
-               }
-       }
+       if (!system_wide)
+               system_wide = system_path(ETC_GITCONFIG);
        return system_wide;
 }
 
-int git_env_bool(const char *k, int def)
+static int git_env_bool(const char *k, int def)
 {
        const char *v = getenv(k);
        return v ? git_config_bool(k, v) : def;
@@ -533,34 +625,32 @@ int git_config_global(void)
        return !git_env_bool("GIT_CONFIG_NOGLOBAL", 0);
 }
 
-int git_config(config_fn_t fn)
+int git_config(config_fn_t fn, void *data)
 {
        int ret = 0;
        char *repo_config = NULL;
-       const char *home = NULL, *filename;
+       const char *home = NULL;
 
        /* $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(CONFIG_ENVIRONMENT);
-       if (!filename) {
-               if (git_config_system() && !access(git_etc_gitconfig(), R_OK))
-                       ret += git_config_from_file(fn, git_etc_gitconfig());
-               home = getenv("HOME");
-               filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
-               if (!filename)
-                       filename = repo_config = xstrdup(git_path("config"));
-       }
+       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);
 
+       home = getenv("HOME");
        if (git_config_global() && home) {
                char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
                if (!access(user_config, R_OK))
-                       ret = git_config_from_file(fn, user_config);
+                       ret += git_config_from_file(fn, user_config, data);
                free(user_config);
        }
 
-       ret += git_config_from_file(fn, filename);
+       repo_config = xstrdup(git_path("config"));
+       ret += git_config_from_file(fn, repo_config, data);
        free(repo_config);
        return ret;
 }
@@ -590,7 +680,7 @@ static int matches(const char* key, const char* value)
                  !regexec(store.value_regex, value, 0, NULL, 0)));
 }
 
-static int store_aux(const char* key, const char* value)
+static int store_aux(const char* key, const char* value, void *cb)
 {
        const char *ep;
        size_t section_len;
@@ -599,11 +689,9 @@ static int store_aux(const char* key, const char* value)
        case KEY_SEEN:
                if (matches(key, value)) {
                        if (store.seen == 1 && store.multi_replace == 0) {
-                               fprintf(stderr,
-                                       "Warning: %s has multiple values\n",
-                                       key);
+                               warning("%s has multiple values", key);
                        } else if (store.seen >= MAX_MATCHES) {
-                               fprintf(stderr, "Too many matches\n");
+                               error("too many matches for %s", key);
                                return 1;
                        }
 
@@ -653,9 +741,9 @@ static int store_aux(const char* key, const char* value)
        return 0;
 }
 
-static int write_error(void)
+static int write_error(const char *filename)
 {
-       fprintf(stderr, "Failed to write new configuration file\n");
+       error("failed to write new configuration file %s", filename);
 
        /* Same error code as "failed to rename". */
        return 4;
@@ -672,7 +760,7 @@ static int store_write_section(int fd, const char* key)
        if (dot) {
                strbuf_addf(&sb, "[%.*s \"", (int)(dot - key), key);
                for (i = dot - key + 1; i < store.baselen; i++) {
-                       if (key[i] == '"')
+                       if (key[i] == '"' || key[i] == '\\')
                                strbuf_addch(&sb, '\\');
                        strbuf_addch(&sb, key[i]);
                }
@@ -800,13 +888,10 @@ int git_config_set_multivar(const char* key, const char* value,
        struct lock_file *lock = NULL;
        const char* last_dot = strrchr(key, '.');
 
-       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);
+       if (config_exclusive_filename)
+               config_filename = xstrdup(config_exclusive_filename);
+       else
+               config_filename = xstrdup(git_path("config"));
 
        /*
         * Since "key" actually contains the section name and the real
@@ -814,7 +899,7 @@ int git_config_set_multivar(const char* key, const char* value,
         */
 
        if (last_dot == NULL) {
-               fprintf(stderr, "key does not contain a section: %s\n", key);
+               error("key does not contain a section: %s", key);
                ret = 2;
                goto out_free;
        }
@@ -834,14 +919,14 @@ int git_config_set_multivar(const char* key, const char* value,
                /* Leave the extended basename untouched.. */
                if (!dot || i > store.baselen) {
                        if (!iskeychar(c) || (i == store.baselen+1 && !isalpha(c))) {
-                               fprintf(stderr, "invalid key: %s\n", key);
+                               error("invalid key: %s", key);
                                free(store.key);
                                ret = 1;
                                goto out_free;
                        }
                        c = tolower(c);
                } else if (c == '\n') {
-                       fprintf(stderr, "invalid key (newline): %s\n", key);
+                       error("invalid key (newline): %s", key);
                        free(store.key);
                        ret = 1;
                        goto out_free;
@@ -857,7 +942,7 @@ int git_config_set_multivar(const char* key, const char* value,
        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");
+               error("could not lock config file %s", config_filename);
                free(store.key);
                ret = -1;
                goto out_free;
@@ -904,8 +989,7 @@ int git_config_set_multivar(const char* key, const char* value,
                        store.value_regex = (regex_t*)xmalloc(sizeof(regex_t));
                        if (regcomp(store.value_regex, value_regex,
                                        REG_EXTENDED)) {
-                               fprintf(stderr, "Invalid pattern: %s\n",
-                                       value_regex);
+                               error("invalid pattern: %s", value_regex);
                                free(store.value_regex);
                                ret = 6;
                                goto out_free;
@@ -922,8 +1006,8 @@ 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_from_file(store_aux, config_filename)) {
-                       fprintf(stderr, "invalid config file\n");
+               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);
@@ -1002,7 +1086,7 @@ int git_config_set_multivar(const char* key, const char* value,
        }
 
        if (commit_lock_file(lock) < 0) {
-               fprintf(stderr, "Cannot commit config file!\n");
+               error("could not commit config file %s", config_filename);
                ret = 4;
                goto out_free;
        }
@@ -1023,7 +1107,7 @@ int git_config_set_multivar(const char* key, const char* value,
        return ret;
 
 write_err_out:
-       ret = write_error();
+       ret = write_error(lock->filename);
        goto out_free;
 
 }
@@ -1064,16 +1148,13 @@ int git_config_rename_section(const char *old_name, const char *new_name)
        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);
+       if (config_exclusive_filename)
+               config_filename = xstrdup(config_exclusive_filename);
+       else
+               config_filename = xstrdup(git_path("config"));
        out_fd = hold_lock_file_for_update(lock, config_filename, 0);
        if (out_fd < 0) {
-               ret = error("Could not lock config file!");
+               ret = error("could not lock config file %s", config_filename);
                goto out;
        }
 
@@ -1097,7 +1178,7 @@ int git_config_rename_section(const char *old_name, const char *new_name)
                                }
                                store.baselen = strlen(new_name);
                                if (!store_write_section(out_fd, new_name)) {
-                                       ret = write_error();
+                                       ret = write_error(lock->filename);
                                        goto out;
                                }
                                continue;
@@ -1108,14 +1189,14 @@ int git_config_rename_section(const char *old_name, const char *new_name)
                        continue;
                length = strlen(buf);
                if (write_in_full(out_fd, buf, length) != length) {
-                       ret = write_error();
+                       ret = write_error(lock->filename);
                        goto out;
                }
        }
        fclose(config_file);
  unlock_and_out:
        if (commit_lock_file(lock) < 0)
-                       ret = error("Cannot commit config file!");
+               ret = error("could not commit config file %s", config_filename);
  out:
        free(config_filename);
        return ret;