config: resolve symlinks in conditional include's patterns
[gitweb.git] / config.c
index 2f1aef742ef5c526d6dc1083c490717b1f782d30..12b1b6261edc5a3d9c2ade1fa1fdf3852b0f41c1 100644 (file)
--- a/config.c
+++ b/config.c
@@ -13,6 +13,7 @@
 #include "hashmap.h"
 #include "string-list.h"
 #include "utf8.h"
+#include "dir.h"
 
 struct config_source {
        struct config_source *prev;
@@ -134,7 +135,7 @@ static int handle_path_include(const char *path, struct config_include_data *inc
        if (!path)
                return config_error_nonbool("include.path");
 
-       expanded = expand_user_path(path);
+       expanded = expand_user_path(path, 0);
        if (!expanded)
                return error("could not expand include path '%s'", path);
        path = expanded;
@@ -170,9 +171,94 @@ static int handle_path_include(const char *path, struct config_include_data *inc
        return ret;
 }
 
+static int prepare_include_condition_pattern(struct strbuf *pat)
+{
+       struct strbuf path = STRBUF_INIT;
+       char *expanded;
+       int prefix = 0;
+
+       expanded = expand_user_path(pat->buf, 1);
+       if (expanded) {
+               strbuf_reset(pat);
+               strbuf_addstr(pat, expanded);
+               free(expanded);
+       }
+
+       if (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {
+               const char *slash;
+
+               if (!cf || !cf->path)
+                       return error(_("relative config include "
+                                      "conditionals must come from files"));
+
+               strbuf_realpath(&path, cf->path, 1);
+               slash = find_last_dir_sep(path.buf);
+               if (!slash)
+                       die("BUG: how is this possible?");
+               strbuf_splice(pat, 0, 1, path.buf, slash - path.buf);
+               prefix = slash - path.buf + 1 /* slash */;
+       } else if (!is_absolute_path(pat->buf))
+               strbuf_insert(pat, 0, "**/", 3);
+
+       if (pat->len && is_dir_sep(pat->buf[pat->len - 1]))
+               strbuf_addstr(pat, "**");
+
+       strbuf_release(&path);
+       return prefix;
+}
+
+static int include_by_gitdir(const char *cond, size_t cond_len, int icase)
+{
+       struct strbuf text = STRBUF_INIT;
+       struct strbuf pattern = STRBUF_INIT;
+       int ret = 0, prefix;
+
+       strbuf_realpath(&text, get_git_dir(), 1);
+       strbuf_add(&pattern, cond, cond_len);
+       prefix = prepare_include_condition_pattern(&pattern);
+
+       if (prefix < 0)
+               goto done;
+
+       if (prefix > 0) {
+               /*
+                * perform literal matching on the prefix part so that
+                * any wildcard character in it can't create side effects.
+                */
+               if (text.len < prefix)
+                       goto done;
+               if (!icase && strncmp(pattern.buf, text.buf, prefix))
+                       goto done;
+               if (icase && strncasecmp(pattern.buf, text.buf, prefix))
+                       goto done;
+       }
+
+       ret = !wildmatch(pattern.buf + prefix, text.buf + prefix,
+                        icase ? WM_CASEFOLD : 0, NULL);
+
+done:
+       strbuf_release(&pattern);
+       strbuf_release(&text);
+       return ret;
+}
+
+static int include_condition_is_true(const char *cond, size_t cond_len)
+{
+
+       if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
+               return include_by_gitdir(cond, cond_len, 0);
+       else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
+               return include_by_gitdir(cond, cond_len, 1);
+
+       /* unknown conditionals are always false */
+       return 0;
+}
+
 int git_config_include(const char *var, const char *value, void *data)
 {
        struct config_include_data *inc = data;
+       const char *cond, *key;
+       int cond_len;
        int ret;
 
        /*
@@ -185,6 +271,12 @@ int git_config_include(const char *var, const char *value, void *data)
 
        if (!strcmp(var, "include.path"))
                ret = handle_path_include(value, inc);
+
+       if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) &&
+           (cond && include_condition_is_true(cond, cond_len)) &&
+           !strcmp(key, "path"))
+               ret = handle_path_include(value, inc);
+
        return ret;
 }
 
@@ -765,7 +857,7 @@ int git_config_pathname(const char **dest, const char *var, const char *value)
 {
        if (!value)
                return config_error_nonbool(var);
-       *dest = expand_user_path(value);
+       *dest = expand_user_path(value, 0);
        if (!*dest)
                die(_("failed to expand user dir in: '%s'"), value);
        return 0;
@@ -826,7 +918,12 @@ static int git_default_core_config(const char *var, const char *value)
        }
 
        if (!strcmp(var, "core.logallrefupdates")) {
-               log_all_ref_updates = git_config_bool(var, value);
+               if (value && !strcasecmp(value, "always"))
+                       log_all_ref_updates = LOG_REFS_ALWAYS;
+               else if (git_config_bool(var, value))
+                       log_all_ref_updates = LOG_REFS_NORMAL;
+               else
+                       log_all_ref_updates = LOG_REFS_NONE;
                return 0;
        }
 
@@ -836,10 +933,16 @@ static int git_default_core_config(const char *var, const char *value)
        }
 
        if (!strcmp(var, "core.abbrev")) {
-               int abbrev = git_config_int(var, value);
-               if (abbrev < minimum_abbrev || abbrev > 40)
-                       return -1;
-               default_abbrev = abbrev;
+               if (!value)
+                       return config_error_nonbool(var);
+               if (!strcasecmp(value, "auto"))
+                       default_abbrev = -1;
+               else {
+                       int abbrev = git_config_int(var, value);
+                       if (abbrev < minimum_abbrev || abbrev > 40)
+                               return error("abbrev length out of range: %d", abbrev);
+                       default_abbrev = abbrev;
+               }
                return 0;
        }
 
@@ -1230,10 +1333,10 @@ int git_config_from_mem(config_fn_t fn, const enum config_origin_type origin_typ
        return do_config_from(&top, fn, data);
 }
 
-static int git_config_from_blob_sha1(config_fn_t fn,
-                                    const char *name,
-                                    const unsigned char *sha1,
-                                    void *data)
+int git_config_from_blob_sha1(config_fn_t fn,
+                             const char *name,
+                             const unsigned char *sha1,
+                             void *data)
 {
        enum object_type type;
        char *buf;
@@ -1304,7 +1407,7 @@ static int do_git_config_sequence(config_fn_t fn, void *data)
 {
        int ret = 0;
        char *xdg_config = xdg_config_home("config");
-       char *user_config = expand_user_path("~/.gitconfig");
+       char *user_config = expand_user_path("~/.gitconfig", 0);
        char *repo_config = have_git_dir() ? git_pathdup("config") : NULL;
 
        current_parsing_scope = CONFIG_SCOPE_SYSTEM;
@@ -2210,7 +2313,12 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
                        goto out_free;
                }
 
-               fstat(in_fd, &st);
+               if (fstat(in_fd, &st) == -1) {
+                       error_errno(_("fstat on %s failed"), config_filename);
+                       ret = CONFIG_INVALID_FILE;
+                       goto out_free;
+               }
+
                contents_sz = xsize_t(st.st_size);
                contents = xmmap_gently(NULL, contents_sz, PROT_READ,
                                        MAP_PRIVATE, in_fd, 0);
@@ -2412,7 +2520,7 @@ int git_config_rename_section_in_file(const char *config_filename,
 
        if (new_name && !section_name_is_ok(new_name)) {
                ret = error("invalid section name: %s", new_name);
-               goto out;
+               goto out_no_rollback;
        }
 
        if (!config_filename)
@@ -2427,10 +2535,13 @@ int git_config_rename_section_in_file(const char *config_filename,
 
        if (!(config_file = fopen(config_filename, "rb"))) {
                /* no config file means nothing to rename, no error */
-               goto unlock_and_out;
+               goto commit_and_out;
        }
 
-       fstat(fileno(config_file), &st);
+       if (fstat(fileno(config_file), &st) == -1) {
+               ret = error_errno(_("fstat on %s failed"), config_filename);
+               goto out;
+       }
 
        if (chmod(get_lock_file_path(lock), st.st_mode & 07777) < 0) {
                ret = error_errno("chmod on %s failed",
@@ -2486,11 +2597,13 @@ int git_config_rename_section_in_file(const char *config_filename,
                }
        }
        fclose(config_file);
-unlock_and_out:
+commit_and_out:
        if (commit_lock_file(lock) < 0)
                ret = error_errno("could not write config file %s",
                                  config_filename);
 out:
+       rollback_lock_file(lock);
+out_no_rollback:
        free(filename_buf);
        return ret;
 }