Diff options
| -rw-r--r-- | source/cgit.c | 134 | |
| -rw-r--r-- | source/config.c | 44 | |
| -rw-r--r-- | source/config.h | 20 | |
| -rw-r--r-- | source/scan-tree.c | 8 | |
| -rwxr-xr-x | tests/t0205-config.sh | 36 |
5 files changed, 172 insertions, 70 deletions
diff --git a/source/cgit.c b/source/cgit.c index 17a79a2..180cd82 100644 --- a/source/cgit.c +++ b/source/cgit.c @@ -590,77 +590,77 @@ static void apply_config(const char *name, const char *value) else if (!strcmp(name, "virtual-root")) ctx.cfg.virtual_root = ensure_end(value, '/'); else if (!strcmp(name, "enable-plain-email")) - ctx.cfg.enable_plain_email = atoi(value); + ctx.cfg.enable_plain_email = config_int(name, value); else if (!strcmp(name, "enable-header")) - ctx.cfg.enable_header = atoi(value); + ctx.cfg.enable_header = config_int(name, value); else if (!strcmp(name, "snapshots")) ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); else if (!strcmp(name, "trust-scan-config")) - ctx.cfg.trust_scan_config = atoi(value); + ctx.cfg.trust_scan_config = config_int(name, value); else if (!strcmp(name, "enable-follow-links")) - ctx.cfg.enable_follow_links = atoi(value); + ctx.cfg.enable_follow_links = config_int(name, value); else if (!strcmp(name, "enable-http-clone")) - ctx.cfg.enable_http_clone = atoi(value); + ctx.cfg.enable_http_clone = config_int(name, value); else if (!strcmp(name, "enable-index-links")) - ctx.cfg.enable_index_links = atoi(value); + ctx.cfg.enable_index_links = config_int(name, value); else if (!strcmp(name, "enable-index-owner")) - ctx.cfg.enable_index_owner = atoi(value); + ctx.cfg.enable_index_owner = config_int(name, value); else if (!strcmp(name, "enable-blame")) - ctx.cfg.enable_blame = atoi(value); + ctx.cfg.enable_blame = config_int(name, value); else if (!strcmp(name, "enable-commit-graph")) - ctx.cfg.enable_commit_graph = atoi(value); + ctx.cfg.enable_commit_graph = config_int(name, value); else if (!strcmp(name, "enable-gitmodules-links")) - ctx.cfg.enable_gitmodules_links = atoi(value); + ctx.cfg.enable_gitmodules_links = config_int(name, value); else if (!strcmp(name, "enable-log-filecount")) - ctx.cfg.enable_log_filecount = atoi(value); + ctx.cfg.enable_log_filecount = config_int(name, value); else if (!strcmp(name, "enable-log-linecount")) - ctx.cfg.enable_log_linecount = atoi(value); + ctx.cfg.enable_log_linecount = config_int(name, value); else if (!strcmp(name, "enable-mailmap")) - ctx.cfg.enable_mailmap = atoi(value); + ctx.cfg.enable_mailmap = config_int(name, value); else if (!strcmp(name, "enable-relative-dates")) - ctx.cfg.enable_relative_dates = atoi(value); + ctx.cfg.enable_relative_dates = config_int(name, value); else if (!strcmp(name, "enable-remote-branches")) - ctx.cfg.enable_remote_branches = atoi(value); + ctx.cfg.enable_remote_branches = config_int(name, value); else if (!strcmp(name, "enable-subject-links")) - ctx.cfg.enable_subject_links = atoi(value); + ctx.cfg.enable_subject_links = config_int(name, value); else if (!strcmp(name, "enable-commit-trailers")) - ctx.cfg.enable_commit_trailers = atoi(value); + ctx.cfg.enable_commit_trailers = config_int(name, value); else if (!strcmp(name, "enable-html-serving")) - ctx.cfg.enable_html_serving = atoi(value); + ctx.cfg.enable_html_serving = config_int(name, value); else if (!strcmp(name, "enable-tree-linenumbers")) - ctx.cfg.enable_tree_linenumbers = atoi(value); + ctx.cfg.enable_tree_linenumbers = config_int(name, value); else if (!strcmp(name, "enable-version-info")) - ctx.cfg.enable_version_info = atoi(value); + ctx.cfg.enable_version_info = config_int(name, value); else if (!strcmp(name, "enable-tree-group-dirs")) - ctx.cfg.enable_tree_group_dirs = atoi(value); + ctx.cfg.enable_tree_group_dirs = config_int(name, value); else if (!strcmp(name, "enable-git-config")) - ctx.cfg.enable_git_config = atoi(value); + ctx.cfg.enable_git_config = config_int(name, value); else if (!strcmp(name, "enable-cache-list")) - ctx.cfg.enable_cache_list = atoi(value); + ctx.cfg.enable_cache_list = config_int(name, value); else if (!strcmp(name, "max-stats")) ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); else if (!strcmp(name, "cache-size")) - ctx.cfg.cache_size = atoi(value); + ctx.cfg.cache_size = config_int(name, value); else if (!strcmp(name, "cache-root")) ctx.cfg.cache_root = cgit_strdup_first_line(cgit_expand_macros(value)); else if (!strcmp(name, "cache-index-ttl")) - ctx.cfg.cache_index_ttl = atoi(value); + ctx.cfg.cache_index_ttl = config_int(name, value); else if (!strcmp(name, "cache-summary-ttl")) - ctx.cfg.cache_summary_ttl = atoi(value); + ctx.cfg.cache_summary_ttl = config_int(name, value); else if (!strcmp(name, "cache-scan-ttl")) - ctx.cfg.cache_scan_ttl = atoi(value); + ctx.cfg.cache_scan_ttl = config_int(name, value); else if (!strcmp(name, "cache-static-ttl")) - ctx.cfg.cache_static_ttl = atoi(value); + ctx.cfg.cache_static_ttl = config_int(name, value); else if (!strcmp(name, "cache-dynamic-ttl")) - ctx.cfg.cache_dynamic_ttl = atoi(value); + ctx.cfg.cache_dynamic_ttl = config_int(name, value); else if (!strcmp(name, "cache-about-ttl")) - ctx.cfg.cache_about_ttl = atoi(value); + ctx.cfg.cache_about_ttl = config_int(name, value); else if (!strcmp(name, "cache-snapshot-ttl")) - ctx.cfg.cache_snapshot_ttl = atoi(value); + ctx.cfg.cache_snapshot_ttl = config_int(name, value); else if (!strcmp(name, "cache-max-slot-size")) - ctx.cfg.cache_max_slot_size = atoi(value); + ctx.cfg.cache_max_slot_size = config_int(name, value); else if (!strcmp(name, "case-sensitive-sort")) - ctx.cfg.case_sensitive_sort = atoi(value); + ctx.cfg.case_sensitive_sort = config_int(name, value); else if (!strcmp(name, "about-filter")) ctx.cfg.about_filter = cgit_new_filter(value, ABOUT); else if (!strcmp(name, "commit-filter")) @@ -672,29 +672,29 @@ static void apply_config(const char *name, const char *value) else if (!strcmp(name, "auth-filter")) ctx.cfg.auth_filter = cgit_new_filter(value, AUTH); else if (!strcmp(name, "embedded")) - ctx.cfg.embedded = atoi(value); + ctx.cfg.embedded = config_int(name, value); else if (!strcmp(name, "max-atom-items")) - ctx.cfg.max_atom_items = atoi(value); + ctx.cfg.max_atom_items = config_int(name, value); else if (!strcmp(name, "max-message-length")) - ctx.cfg.max_msg_len = atoi(value); + ctx.cfg.max_msg_len = config_int(name, value); else if (!strcmp(name, "max-repodesc-length")) - ctx.cfg.max_repodesc_len = atoi(value); + ctx.cfg.max_repodesc_len = config_int(name, value); else if (!strcmp(name, "max-blob-size")) - ctx.cfg.max_blob_size = atoi(value); + ctx.cfg.max_blob_size = config_int(name, value); else if (!strcmp(name, "max-diff-files")) - ctx.cfg.max_diff_files = atoi(value); + ctx.cfg.max_diff_files = config_int(name, value); else if (!strcmp(name, "max-diff-lines")) - ctx.cfg.max_diff_lines = atoi(value); + ctx.cfg.max_diff_lines = config_int(name, value); else if (!strcmp(name, "max-ref-count")) - ctx.cfg.max_ref_count = atoi(value); + ctx.cfg.max_ref_count = config_int(name, value); else if (!strcmp(name, "max-repo-count")) { - ctx.cfg.max_repo_count = atoi(value); + ctx.cfg.max_repo_count = config_int(name, value); if (ctx.cfg.max_repo_count <= 0) ctx.cfg.max_repo_count = INT_MAX; } else if (!strcmp(name, "max-commit-count")) - ctx.cfg.max_commit_count = atoi(value); + ctx.cfg.max_commit_count = config_int(name, value); else if (!strcmp(name, "max-patch-count")) - ctx.cfg.max_patch_count = atoi(value); + ctx.cfg.max_patch_count = config_int(name, value); else if (!strcmp(name, "project-list")) ctx.cfg.project_list = cgit_strdup_first_line(cgit_expand_macros(value)); else if (!strcmp(name, "scan-path")) { @@ -706,31 +706,31 @@ static void apply_config(const char *name, const char *value) // it may have moved, and a repo key after it belongs to nothing. ctx.repo = NULL; } else if (!strcmp(name, "scan-hidden-path")) - ctx.cfg.scan_hidden_path = atoi(value); + ctx.cfg.scan_hidden_path = config_int(name, value); else if (!strcmp(name, "section-from-path")) - ctx.cfg.section_from_path = atoi(value); + ctx.cfg.section_from_path = config_int(name, value); else if (!strcmp(name, "repository-sort")) ctx.cfg.repository_sort = cgit_strdup_first_line(value); else if (!strcmp(name, "section-sort")) - ctx.cfg.section_sort = atoi(value); + ctx.cfg.section_sort = config_int(name, value); else if (!strcmp(name, "source-filter")) ctx.cfg.source_filter = cgit_new_filter(value, SOURCE); else if (!strcmp(name, "summary-log")) - ctx.cfg.summary_log = atoi(value); + ctx.cfg.summary_log = config_int(name, value); else if (!strcmp(name, "summary-branches")) - ctx.cfg.summary_branches = atoi(value); + ctx.cfg.summary_branches = config_int(name, value); else if (!strcmp(name, "summary-tags")) - ctx.cfg.summary_tags = atoi(value); + ctx.cfg.summary_tags = config_int(name, value); else if (!strcmp(name, "side-by-side-diffs")) - ctx.cfg.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED; + ctx.cfg.difftype = config_int(name, value) ? DIFF_SSDIFF : DIFF_UNIFIED; else if (!strcmp(name, "age-file")) ctx.cfg.agefile = cgit_strdup_first_line(value); else if (!strcmp(name, "mimetype-file")) ctx.cfg.mimetype_file = cgit_strdup_first_line(value); else if (!strcmp(name, "rename-limit")) - ctx.cfg.renamelimit = atoi(value); + ctx.cfg.renamelimit = config_int(name, value); else if (!strcmp(name, "remove-suffix")) - ctx.cfg.remove_suffix = atoi(value); + ctx.cfg.remove_suffix = config_int(name, value); else if (!strcmp(name, "robots")) ctx.cfg.robots = cgit_strdup_first_line(value); else if (!strcmp(name, "clone-prefix")) @@ -738,7 +738,7 @@ static void apply_config(const char *name, const char *value) else if (!strcmp(name, "clone-url")) ctx.cfg.clone_url = cgit_strdup_first_line(value); else if (!strcmp(name, "local-time")) - ctx.cfg.local_time = atoi(value); + ctx.cfg.local_time = config_int(name, value); else if (!strcmp(name, "date-format")) parse_date_setting(value, &ctx.cfg.date_mode); else if (!strcmp(name, "commit-sort")) { @@ -1296,27 +1296,27 @@ void cgit_repo_config(struct cgit_repo *repo, const char *name, const char *valu else if (!strcmp(name, "snapshots")) repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-blame")) - repo->enable_blame = atoi(value); + repo->enable_blame = config_int(name, value); else if (!strcmp(name, "enable-commit-graph")) - repo->enable_commit_graph = atoi(value); + repo->enable_commit_graph = config_int(name, value); else if (!strcmp(name, "enable-follow-links")) - repo->enable_follow_links = atoi(value); + repo->enable_follow_links = config_int(name, value); else if (!strcmp(name, "enable-gitmodules-links")) - repo->enable_gitmodules_links = atoi(value); + repo->enable_gitmodules_links = config_int(name, value); else if (!strcmp(name, "enable-log-filecount")) - repo->enable_log_filecount = atoi(value); + repo->enable_log_filecount = config_int(name, value); else if (!strcmp(name, "enable-log-linecount")) - repo->enable_log_linecount = atoi(value); + repo->enable_log_linecount = config_int(name, value); else if (!strcmp(name, "enable-mailmap")) - repo->enable_mailmap = atoi(value); + repo->enable_mailmap = config_int(name, value); else if (!strcmp(name, "enable-remote-branches")) - repo->enable_remote_branches = atoi(value); + repo->enable_remote_branches = config_int(name, value); else if (!strcmp(name, "enable-subject-links")) - repo->enable_subject_links = atoi(value); + repo->enable_subject_links = config_int(name, value); else if (!strcmp(name, "enable-commit-trailers")) - repo->enable_commit_trailers = atoi(value); + repo->enable_commit_trailers = config_int(name, value); else if (!strcmp(name, "enable-html-serving")) - repo->enable_html_serving = atoi(value); + repo->enable_html_serving = config_int(name, value); else if (!strcmp(name, "branch-sort")) { if (!strcmp(value, "age")) repo->branch_sort = 1; @@ -1345,9 +1345,9 @@ void cgit_repo_config(struct cgit_repo *repo, const char *name, const char *valu else if (!strcmp(name, "logo-link")) repo->logo_link = cgit_strdup_first_line(value); else if (!strcmp(name, "hide")) - repo->hide = atoi(value); + repo->hide = config_int(name, value); else if (!strcmp(name, "ignore")) - repo->ignore = atoi(value); + repo->ignore = config_int(name, value); else if (!strcmp(name, "about-filter")) repo->about_filter = cgit_new_filter(value, ABOUT); else if (!strcmp(name, "commit-filter")) diff --git a/source/config.c b/source/config.c index ca7c688..bfd31b6 100644 --- a/source/config.c +++ b/source/config.c @@ -11,6 +11,12 @@ #define MAX_INCLUDE_NESTING 8 +struct config_origin config_origin; + +// The newlines read so far from the file being parsed, which place each entry +// on its line. +static int lines_read; + /* * Folds away a carriage return that sits right before a newline so a file * saved with DOS line endings parses the same as one saved with Unix endings. @@ -26,6 +32,8 @@ static int next_char(FILE *f) c = '\r'; } } + if (c == '\n') + lines_read++; return c; } @@ -52,7 +60,7 @@ static int next_content_char(FILE *f) } } -static int read_entry(FILE *f, struct strbuf *name, struct strbuf *value) +static int read_entry(FILE *f, struct strbuf *name, struct strbuf *value, int *line) { int c; @@ -63,6 +71,7 @@ static int read_entry(FILE *f, struct strbuf *name, struct strbuf *value) if (c == EOF) return 0; + *line = lines_read + 1; strbuf_reset(name); while (c != '=' && c != '\n' && c != EOF) { strbuf_addch(name, c); @@ -88,6 +97,10 @@ static int read_entry(FILE *f, struct strbuf *name, struct strbuf *value) int config_file_parse(const char *filename, config_file_value_fn fn) { static int nesting; + // An include is parsed from inside a line of the file that names it, + // which carries on from that line once the include is done. + struct config_origin outer = config_origin; + int outer_lines_read = lines_read; struct strbuf name = STRBUF_INIT; struct strbuf value = STRBUF_INIT; FILE *f; @@ -99,15 +112,42 @@ int config_file_parse(const char *filename, config_file_value_fn fn) return 0; if (!(f = fopen(filename, "r"))) return errno; + config_origin.file = filename; + lines_read = 0; nesting++; - while (read_entry(f, &name, &value)) + while (read_entry(f, &name, &value, &config_origin.line)) fn(name.buf, value.buf); // A directory opens like a file and fails only once it is read. if (ferror(f)) err = errno; nesting--; fclose(f); + config_origin = outer; + lines_read = outer_lines_read; strbuf_release(&name); strbuf_release(&value); return err; } + +int config_int(const char *name, const char *value) +{ + int number = atoi(value); + char *end; + + // Space may follow the digits strtol finds, but nothing else. + strtol(value, &end, 10); + while (end != value && isspace(*end)) + end++; + if (end != value && !*end) + return number; + // The first line alone, since a git config value can hold a newline + // that would forge a log line of its own. + if (config_origin.file) + fprintf(stderr, "[cgit] Reading %s=%.*s in %s line %d as %d, since it is not a whole number\n", + name, (int)strcspn(value, "\n"), value, config_origin.file, + config_origin.line, number); + else + fprintf(stderr, "[cgit] Reading %s=%.*s as %d, since it is not a whole number\n", + name, (int)strcspn(value, "\n"), value, number); + return number; +} diff --git a/source/config.h b/source/config.h index 30cf74d..b6dfa2b 100644 --- a/source/config.h +++ b/source/config.h @@ -10,9 +10,29 @@ typedef void (*config_file_value_fn)(const char *name, const char *value); /* + * Where the setting being applied was written, for a warning about it to name. + * The file is NULL while no config file is being read. + */ +struct config_origin { + const char *file; + int line; +}; + +extern struct config_origin config_origin; + +/* * Returns 0, or an errno value when filename could not be opened or read. A * missing file is ordinary for some callers, so reporting one is left to them. + * filename has to stay as it is until the parse returns, since a warning about + * a setting in the file names it. */ extern int config_file_parse(const char *filename, config_file_value_fn fn); +/* + * Read value as atoi does, which is what a number in cgitrc has always meant, + * and warn on stderr when it is not a plain whole number, since atoi turns a + * word such as "yes" into 0 without a sound. + */ +extern int config_int(const char *name, const char *value); + #endif // CGIT_CONFIG_H diff --git a/source/scan-tree.c b/source/scan-tree.c index 91db419..6c1f854 100644 --- a/source/scan-tree.c +++ b/source/scan-tree.c @@ -75,13 +75,18 @@ static int trusted_key(const char *name, const char *value) } static int apply_gitconfig(const char *key, const char *value, - const struct config_context *cfg_ctx UNUSED, void *cb UNUSED) + const struct config_context *cfg_ctx, void *cb UNUSED) { + // The scan runs from inside a line of cgitrc, which a warning about + // this value must not be pinned to. + struct config_origin outer = config_origin; const char *name; // A key with no value is legal git config and nothing here can use it. if (!value) return 0; + config_origin.file = cfg_ctx->kvi->filename; + config_origin.line = cfg_ctx->kvi->linenr; if (!strcmp(key, "gitweb.owner")) cgit_repo_config(current_repo, "owner", value); else if (!strcmp(key, "gitweb.description")) @@ -90,6 +95,7 @@ static int apply_gitconfig(const char *key, const char *value, cgit_repo_config(current_repo, "section", value); else if (skip_prefix(key, "cgit.", &name) && trusted_key(name, value)) cgit_repo_config(current_repo, name, value); + config_origin = outer; return 0; } diff --git a/tests/t0205-config.sh b/tests/t0205-config.sh index 12087e7..3b892e9 100755 --- a/tests/t0205-config.sh +++ b/tests/t0205-config.sh @@ -143,4 +143,40 @@ test_expect_success 'a scan says nothing of the files it may lack' ' test_must_be_empty err ' +# A number is read as atoi reads it, which makes a word 0 without a sound, so +# a value that is not a whole number is logged with the file and line it came +# from, and still read the way it always was. An include in between leaves +# the line count of the file around it alone. +test_expect_success 'a word where a number belongs is reported' ' + cgitrc_repo wordrc word enable-index-owner=no "max-repo-count= 20 " \ + "repo.path=$PWD/repos/foo/.git" repo.hide=yes && + cgit_rc wordrc "url=" >tmp 2>err && + grep ">word</a>" tmp && + ! grep "col-owner" tmp && + grep "Reading enable-index-owner=no in $PWD/wordrc line 3 as 0, since it is not a whole number" err && + grep "Reading hide=yes in $PWD/wordrc line 7 as 0, since it is not a whole number" err && + ! grep "max-repo-count" err && + { + echo "include=$PWD/wordrc" && + echo "enable-blame=on" + } >outerrc && + cgit_rc outerrc "url=" >tmp 2>err && + grep "Reading enable-blame=on in $PWD/outerrc line 2 as 0, since it is not a whole number" err +' + +test_expect_success 'a word in a scanned git config is reported' ' + git clone -q --bare repos/foo/.git wordscan/foo.git && + git -C wordscan/foo.git config cgit.hide true && + line=$(grep -n "hide = true" wordscan/foo.git/config | cut -d: -f1) && + { + echo "virtual-root=/" && + echo "cache-size=0" && + echo "enable-git-config=1" && + echo "scan-path=$PWD/wordscan" + } >wordscanrc && + cgit_rc wordscanrc "url=" >tmp 2>err && + grep ">foo.git</a>" tmp && + grep "Reading hide=true in $PWD/wordscan/foo.git/config line $line as 0, since it is not a whole number" err +' + test_done |
