diff options
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | NOTES_70 | 35 | ||||
-rw-r--r-- | cgit.h | 2 | ||||
-rw-r--r-- | cgit_70.c | 1063 | ||||
-rw-r--r-- | cgit_70.mk | 149 | ||||
-rw-r--r-- | cmd_70.c | 209 | ||||
-rw-r--r-- | html.c | 1 | ||||
-rw-r--r-- | shared_70.c | 578 | ||||
-rw-r--r-- | ui-shared_70.c | 40 | ||||
-rw-r--r-- | ui-shared_70.h | 13 | ||||
-rw-r--r-- | ui_70-refs.c | 224 | ||||
-rw-r--r-- | ui_70-repolist.c | 309 | ||||
-rw-r--r-- | ui_70-shared.c | 1315 | ||||
-rw-r--r-- | ui_70-shared.h | 130 | ||||
-rw-r--r-- | ui_70-summary.c | 146 | ||||
-rw-r--r-- | ui_70_repolist.c | 379 |
16 files changed, 4604 insertions, 5 deletions
@@ -2,9 +2,9 @@ all:: CGIT_VERSION = v1.2 CGIT_SCRIPT_NAME = cgit.cgi -CGIT_SCRIPT_PATH = /var/www/htdocs/cgit +CGIT_SCRIPT_PATH = /var/www/htdocs/cgit_70 CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) -CGIT_CONFIG = /etc/cgitrc +CGIT_CONFIG = /var/www/htdocs/etc/cgitrc CACHE_ROOT = /var/cache/cgit prefix = /usr/local libdir = $(prefix)/lib @@ -70,10 +70,15 @@ endif .SUFFIXES: +#all:: cgit +# +#cgit: +# $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk ../cgit $(EXTRA_GIT_TARGETS) NO_CURL=1 + all:: cgit -cgit: - $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk ../cgit $(EXTRA_GIT_TARGETS) NO_CURL=1 +cgit: + $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit_70.mk ../cgit_70 $(EXTRA_GIT_TARGETS) NO_CURL=1 sparse: $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk NO_CURL=1 cgit-sparse @@ -162,7 +167,8 @@ get-git: tags: $(QUIET_TAGS)find . -name '*.[ch]' | xargs ctags -.PHONY: all cgit git get-git +#.PHONY: all cgit git get-git +.PHONY: all cgit_70 git get-git .PHONY: clean clean-doc cleanall .PHONY: doc doc-html doc-man doc-pdf .PHONY: install install-doc install-html install-man install-pdf diff --git a/NOTES_70 b/NOTES_70 new file mode 100644 index 0000000..9ddbab8 --- /dev/null +++ b/NOTES_70 @@ -0,0 +1,35 @@ +## cgit-gopher + +The plan is to equip cgit with a Gopher (RFC 1436) front-end. This would +be possible by replacing the functions in ui-*.c with appropriate +functions to generate Gopher contents. In practice, all the html-related +stuff should be replaced by simple text, and the output simplified (we +can't have more than one link per line in Gopher). + +It seems that ui-tree.c is a good place to start for a proof-of-concept. + +(20180724-16:19) + + The PoC works. Now we should produce proper selectors for the stuff in + the tree page. In particular: + + - Distinguish between files and directories/links + - construct an appropriate selector path (see cgit_tree_link in + ui-shared.c) + + N.B.: We don't need to support all the stuff in cgit. In particular, + the 'virtual-root' variable might be a bit cumbersome to implement, + since we need an explicit way to signal the Gopher server that we + need the script (i.e., the presence of a '?' after the name of the CGI + script). + +(20180725 - 9:30) + + The easiest way to inject a Gopher interface seems to be through + cmd.c, since the functions to be called for each action are defined in + there. It should be sufficient to provide a gopher-cmd.c and to + replace all the ui-*.c files with the corresponding ui70-*.c + counterparts which implement the Gopher interface. + + Again, we should start from ui-repolist.c and ui-tree.c, which are the + two necessary bits for a viable proof-of-concept. @@ -298,8 +298,10 @@ struct cgit_environment { const char *http_referer; unsigned int content_length; int authenticated; + const char *gopher_search; }; + struct cgit_context { struct cgit_environment env; struct cgit_query qry; diff --git a/cgit_70.c b/cgit_70.c new file mode 100644 index 0000000..b2baab0 --- /dev/null +++ b/cgit_70.c @@ -0,0 +1,1063 @@ +/* cgit.c: cgi for the git scm + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "cache.h" +#include "cmd.h" +#include "configfile.h" +#include "html.h" +#include "ui-shared.h" +#include "ui-stats.h" +#include "ui-blob.h" +#include "ui-summary.h" +#include "scan-tree.h" + +const char *cgit_version = CGIT_VERSION; + +static void add_mimetype(const char *name, const char *value) +{ + struct string_list_item *item; + + item = string_list_insert(&ctx.cfg.mimetypes, name); + item->util = xstrdup(value); +} + +static void process_cached_repolist(const char *path); + +static void repo_config(struct cgit_repo *repo, const char *name, const char *value) +{ + const char *path; + struct string_list_item *item; + + if (!strcmp(name, "name")) + repo->name = xstrdup(value); + else if (!strcmp(name, "clone-url")) + repo->clone_url = xstrdup(value); + else if (!strcmp(name, "desc")) + repo->desc = xstrdup(value); + else if (!strcmp(name, "owner")) + repo->owner = xstrdup(value); + else if (!strcmp(name, "homepage")) + repo->homepage = xstrdup(value); + else if (!strcmp(name, "defbranch")) + repo->defbranch = xstrdup(value); + else if (!strcmp(name, "extra-head-content")) + repo->extra_head_content = xstrdup(value); + else if (!strcmp(name, "snapshots")) + repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); + else if (!strcmp(name, "enable-commit-graph")) + repo->enable_commit_graph = atoi(value); + else if (!strcmp(name, "enable-log-filecount")) + repo->enable_log_filecount = atoi(value); + else if (!strcmp(name, "enable-log-linecount")) + repo->enable_log_linecount = atoi(value); + else if (!strcmp(name, "enable-remote-branches")) + repo->enable_remote_branches = atoi(value); + else if (!strcmp(name, "enable-subject-links")) + repo->enable_subject_links = atoi(value); + else if (!strcmp(name, "enable-html-serving")) + repo->enable_html_serving = atoi(value); + else if (!strcmp(name, "branch-sort")) { + if (!strcmp(value, "age")) + repo->branch_sort = 1; + if (!strcmp(value, "name")) + repo->branch_sort = 0; + } else if (!strcmp(name, "commit-sort")) { + if (!strcmp(value, "date")) + repo->commit_sort = 1; + if (!strcmp(value, "topo")) + repo->commit_sort = 2; + } else if (!strcmp(name, "max-stats")) + repo->max_stats = cgit_find_stats_period(value, NULL); + else if (!strcmp(name, "module-link")) + repo->module_link= xstrdup(value); + else if (skip_prefix(name, "module-link.", &path)) { + item = string_list_append(&repo->submodules, xstrdup(path)); + item->util = xstrdup(value); + } else if (!strcmp(name, "section")) + repo->section = xstrdup(value); + else if (!strcmp(name, "snapshot-prefix")) + repo->snapshot_prefix = xstrdup(value); + else if (!strcmp(name, "readme") && value != NULL) { + if (repo->readme.items == ctx.cfg.readme.items) + memset(&repo->readme, 0, sizeof(repo->readme)); + string_list_append(&repo->readme, xstrdup(value)); + } else if (!strcmp(name, "logo") && value != NULL) + repo->logo = xstrdup(value); + else if (!strcmp(name, "logo-link") && value != NULL) + repo->logo_link = xstrdup(value); + else if (!strcmp(name, "hide")) + repo->hide = atoi(value); + else if (!strcmp(name, "ignore")) + repo->ignore = atoi(value); + else if (ctx.cfg.enable_filter_overrides) { + if (!strcmp(name, "about-filter")) + repo->about_filter = cgit_new_filter(value, ABOUT); + else if (!strcmp(name, "commit-filter")) + repo->commit_filter = cgit_new_filter(value, COMMIT); + else if (!strcmp(name, "source-filter")) + repo->source_filter = cgit_new_filter(value, SOURCE); + else if (!strcmp(name, "email-filter")) + repo->email_filter = cgit_new_filter(value, EMAIL); + else if (!strcmp(name, "owner-filter")) + repo->owner_filter = cgit_new_filter(value, OWNER); + } +} + +static void config_cb(const char *name, const char *value) +{ + const char *arg; + + if (!strcmp(name, "section")) + ctx.cfg.section = xstrdup(value); + else if (!strcmp(name, "repo.url")) + ctx.repo = cgit_add_repo(value); + else if (ctx.repo && !strcmp(name, "repo.path")) + ctx.repo->path = trim_end(value, '/'); + else if (ctx.repo && skip_prefix(name, "repo.", &arg)) + repo_config(ctx.repo, arg, value); + else if (!strcmp(name, "readme")) + string_list_append(&ctx.cfg.readme, xstrdup(value)); + else if (!strcmp(name, "root-title")) + ctx.cfg.root_title = xstrdup(value); + else if (!strcmp(name, "root-desc")) + ctx.cfg.root_desc = xstrdup(value); + else if (!strcmp(name, "root-readme")) + ctx.cfg.root_readme = xstrdup(value); + else if (!strcmp(name, "css")) + ctx.cfg.css = xstrdup(value); + else if (!strcmp(name, "favicon")) + ctx.cfg.favicon = xstrdup(value); + else if (!strcmp(name, "footer")) + ctx.cfg.footer = xstrdup(value); + else if (!strcmp(name, "head-include")) + ctx.cfg.head_include = xstrdup(value); + else if (!strcmp(name, "header")) + ctx.cfg.header = xstrdup(value); + else if (!strcmp(name, "logo")) + ctx.cfg.logo = xstrdup(value); + else if (!strcmp(name, "logo-link")) + ctx.cfg.logo_link = xstrdup(value); + else if (!strcmp(name, "module-link")) + ctx.cfg.module_link = xstrdup(value); + else if (!strcmp(name, "strict-export")) + ctx.cfg.strict_export = xstrdup(value); + else if (!strcmp(name, "virtual-root")) + ctx.cfg.virtual_root = ensure_end(value, '/'); + else if (!strcmp(name, "noplainemail")) + ctx.cfg.noplainemail = atoi(value); + else if (!strcmp(name, "noheader")) + ctx.cfg.noheader = atoi(value); + else if (!strcmp(name, "snapshots")) + ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); + else if (!strcmp(name, "enable-filter-overrides")) + ctx.cfg.enable_filter_overrides = atoi(value); + else if (!strcmp(name, "enable-follow-links")) + ctx.cfg.enable_follow_links = atoi(value); + else if (!strcmp(name, "enable-http-clone")) + ctx.cfg.enable_http_clone = atoi(value); + else if (!strcmp(name, "enable-index-links")) + ctx.cfg.enable_index_links = atoi(value); + else if (!strcmp(name, "enable-index-owner")) + ctx.cfg.enable_index_owner = atoi(value); + else if (!strcmp(name, "enable-blame")) + ctx.cfg.enable_blame = atoi(value); + else if (!strcmp(name, "enable-commit-graph")) + ctx.cfg.enable_commit_graph = atoi(value); + else if (!strcmp(name, "enable-log-filecount")) + ctx.cfg.enable_log_filecount = atoi(value); + else if (!strcmp(name, "enable-log-linecount")) + ctx.cfg.enable_log_linecount = atoi(value); + else if (!strcmp(name, "enable-remote-branches")) + ctx.cfg.enable_remote_branches = atoi(value); + else if (!strcmp(name, "enable-subject-links")) + ctx.cfg.enable_subject_links = atoi(value); + else if (!strcmp(name, "enable-html-serving")) + ctx.cfg.enable_html_serving = atoi(value); + else if (!strcmp(name, "enable-tree-linenumbers")) + ctx.cfg.enable_tree_linenumbers = atoi(value); + else if (!strcmp(name, "enable-git-config")) + ctx.cfg.enable_git_config = atoi(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); + else if (!strcmp(name, "cache-root")) + ctx.cfg.cache_root = xstrdup(expand_macros(value)); + else if (!strcmp(name, "cache-root-ttl")) + ctx.cfg.cache_root_ttl = atoi(value); + else if (!strcmp(name, "cache-repo-ttl")) + ctx.cfg.cache_repo_ttl = atoi(value); + else if (!strcmp(name, "cache-scanrc-ttl")) + ctx.cfg.cache_scanrc_ttl = atoi(value); + else if (!strcmp(name, "cache-static-ttl")) + ctx.cfg.cache_static_ttl = atoi(value); + else if (!strcmp(name, "cache-dynamic-ttl")) + ctx.cfg.cache_dynamic_ttl = atoi(value); + else if (!strcmp(name, "cache-about-ttl")) + ctx.cfg.cache_about_ttl = atoi(value); + else if (!strcmp(name, "cache-snapshot-ttl")) + ctx.cfg.cache_snapshot_ttl = atoi(value); + else if (!strcmp(name, "case-sensitive-sort")) + ctx.cfg.case_sensitive_sort = atoi(value); + else if (!strcmp(name, "about-filter")) + ctx.cfg.about_filter = cgit_new_filter(value, ABOUT); + else if (!strcmp(name, "commit-filter")) + ctx.cfg.commit_filter = cgit_new_filter(value, COMMIT); + else if (!strcmp(name, "email-filter")) + ctx.cfg.email_filter = cgit_new_filter(value, EMAIL); + else if (!strcmp(name, "owner-filter")) + ctx.cfg.owner_filter = cgit_new_filter(value, OWNER); + 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); + else if (!strcmp(name, "max-atom-items")) + ctx.cfg.max_atom_items = atoi(value); + else if (!strcmp(name, "max-message-length")) + ctx.cfg.max_msg_len = atoi(value); + else if (!strcmp(name, "max-repodesc-length")) + ctx.cfg.max_repodesc_len = atoi(value); + else if (!strcmp(name, "max-blob-size")) + ctx.cfg.max_blob_size = atoi(value); + else if (!strcmp(name, "max-repo-count")) + ctx.cfg.max_repo_count = atoi(value); + else if (!strcmp(name, "max-commit-count")) + ctx.cfg.max_commit_count = atoi(value); + else if (!strcmp(name, "project-list")) + ctx.cfg.project_list = xstrdup(expand_macros(value)); + else if (!strcmp(name, "scan-path")) + if (ctx.cfg.cache_size) + process_cached_repolist(expand_macros(value)); + else if (ctx.cfg.project_list) + scan_projects(expand_macros(value), + ctx.cfg.project_list, repo_config); + else + scan_tree(expand_macros(value), repo_config); + else if (!strcmp(name, "scan-hidden-path")) + ctx.cfg.scan_hidden_path = atoi(value); + else if (!strcmp(name, "section-from-path")) + ctx.cfg.section_from_path = atoi(value); + else if (!strcmp(name, "repository-sort")) + ctx.cfg.repository_sort = xstrdup(value); + else if (!strcmp(name, "section-sort")) + ctx.cfg.section_sort = atoi(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); + else if (!strcmp(name, "summary-branches")) + ctx.cfg.summary_branches = atoi(value); + else if (!strcmp(name, "summary-tags")) + ctx.cfg.summary_tags = atoi(value); + else if (!strcmp(name, "side-by-side-diffs")) + ctx.cfg.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED; + else if (!strcmp(name, "agefile")) + ctx.cfg.agefile = xstrdup(value); + else if (!strcmp(name, "mimetype-file")) + ctx.cfg.mimetype_file = xstrdup(value); + else if (!strcmp(name, "renamelimit")) + ctx.cfg.renamelimit = atoi(value); + else if (!strcmp(name, "remove-suffix")) + ctx.cfg.remove_suffix = atoi(value); + else if (!strcmp(name, "robots")) + ctx.cfg.robots = xstrdup(value); + else if (!strcmp(name, "clone-prefix")) + ctx.cfg.clone_prefix = xstrdup(value); + else if (!strcmp(name, "clone-url")) + ctx.cfg.clone_url = xstrdup(value); + else if (!strcmp(name, "local-time")) + ctx.cfg.local_time = atoi(value); + else if (!strcmp(name, "commit-sort")) { + if (!strcmp(value, "date")) + ctx.cfg.commit_sort = 1; + if (!strcmp(value, "topo")) + ctx.cfg.commit_sort = 2; + } else if (!strcmp(name, "branch-sort")) { + if (!strcmp(value, "age")) + ctx.cfg.branch_sort = 1; + if (!strcmp(value, "name")) + ctx.cfg.branch_sort = 0; + } else if (skip_prefix(name, "mimetype.", &arg)) + add_mimetype(arg, value); + else if (!strcmp(name, "include")) + parse_configfile(expand_macros(value), config_cb); +} + +static void querystring_cb(const char *name, const char *value) +{ + if (!value) + value = ""; + + if (!strcmp(name,"r")) { + ctx.qry.repo = xstrdup(value); + ctx.repo = cgit_get_repoinfo(value); + } else if (!strcmp(name, "p")) { + ctx.qry.page = xstrdup(value); + } else if (!strcmp(name, "url")) { + if (*value == '/') + value++; + ctx.qry.url = xstrdup(value); + fprintf(stderr, " --- got url: %s\n", value); + cgit_parse_url(value); + } else if (!strcmp(name, "qt")) { + ctx.qry.grep = xstrdup(value); + } else if (!strcmp(name, "q")) { + ctx.qry.search = xstrdup(value); + } else if (!strcmp(name, "h")) { + ctx.qry.head = xstrdup(value); + ctx.qry.has_symref = 1; + } else if (!strcmp(name, "id")) { + ctx.qry.sha1 = xstrdup(value); + ctx.qry.has_sha1 = 1; + } else if (!strcmp(name, "id2")) { + ctx.qry.sha2 = xstrdup(value); + ctx.qry.has_sha1 = 1; + } else if (!strcmp(name, "ofs")) { + ctx.qry.ofs = atoi(value); + } else if (!strcmp(name, "path")) { + ctx.qry.path = trim_end(value, '/'); + } else if (!strcmp(name, "name")) { + ctx.qry.name = xstrdup(value); + } else if (!strcmp(name, "s")) { + ctx.qry.sort = xstrdup(value); + } else if (!strcmp(name, "showmsg")) { + ctx.qry.showmsg = atoi(value); + } else if (!strcmp(name, "period")) { + ctx.qry.period = xstrdup(value); + } else if (!strcmp(name, "dt")) { + ctx.qry.difftype = atoi(value); + ctx.qry.has_difftype = 1; + } else if (!strcmp(name, "ss")) { + /* No longer generated, but there may be links out there. */ + ctx.qry.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED; + ctx.qry.has_difftype = 1; + } else if (!strcmp(name, "all")) { + ctx.qry.show_all = atoi(value); + } else if (!strcmp(name, "context")) { + ctx.qry.context = atoi(value); + } else if (!strcmp(name, "ignorews")) { + ctx.qry.ignorews = atoi(value); + } else if (!strcmp(name, "follow")) { + ctx.qry.follow = atoi(value); + } +} + +static void prepare_context(void) +{ + memset(&ctx, 0, sizeof(ctx)); + ctx.cfg.agefile = "info/web/last-modified"; + ctx.cfg.cache_size = 0; + ctx.cfg.cache_max_create_time = 5; + ctx.cfg.cache_root = CGIT_CACHE_ROOT; + ctx.cfg.cache_about_ttl = 15; + ctx.cfg.cache_snapshot_ttl = 5; + ctx.cfg.cache_repo_ttl = 5; + ctx.cfg.cache_root_ttl = 5; + ctx.cfg.cache_scanrc_ttl = 15; + ctx.cfg.cache_dynamic_ttl = 5; + ctx.cfg.cache_static_ttl = -1; + ctx.cfg.case_sensitive_sort = 1; + ctx.cfg.branch_sort = 0; + ctx.cfg.commit_sort = 0; + ctx.cfg.css = "/cgit.css"; + ctx.cfg.logo = "/cgit.png"; + ctx.cfg.favicon = "/favicon.ico"; + ctx.cfg.local_time = 0; + ctx.cfg.enable_http_clone = 1; + ctx.cfg.enable_index_owner = 1; + ctx.cfg.enable_tree_linenumbers = 1; + ctx.cfg.enable_git_config = 0; + ctx.cfg.max_repo_count = 50; + ctx.cfg.max_commit_count = 50; + ctx.cfg.max_lock_attempts = 5; + ctx.cfg.max_msg_len = 80; + ctx.cfg.max_repodesc_len = 80; + ctx.cfg.max_blob_size = 0; + ctx.cfg.max_stats = 0; + ctx.cfg.project_list = NULL; + ctx.cfg.renamelimit = -1; + ctx.cfg.remove_suffix = 0; + ctx.cfg.robots = "index, nofollow"; + ctx.cfg.root_title = "Git repository browser"; + ctx.cfg.root_desc = "a fast Gopher interface for the git dscm"; + ctx.cfg.scan_hidden_path = 0; + ctx.cfg.script_name = CGIT_SCRIPT_NAME; + ctx.cfg.section = ""; + ctx.cfg.repository_sort = "name"; + ctx.cfg.section_sort = 1; + ctx.cfg.summary_branches = 10; + ctx.cfg.summary_log = 10; + ctx.cfg.summary_tags = 10; + ctx.cfg.max_atom_items = 10; + ctx.cfg.difftype = DIFF_UNIFIED; + ctx.env.cgit_config = getenv("CGIT_CONFIG"); + ctx.env.http_host = getenv("HTTP_HOST"); + ctx.env.https = getenv("HTTPS"); + ctx.env.no_http = getenv("NO_HTTP"); + ctx.env.path_info = getenv("PATH_INFO"); + ctx.env.query_string = getenv("QUERY_STRING"); + ctx.env.request_method = getenv("REQUEST_METHOD"); + ctx.env.script_name = getenv("SCRIPT_NAME"); + ctx.env.server_name = getenv("SERVER_NAME"); + ctx.env.server_port = getenv("SERVER_PORT"); + ctx.env.http_cookie = getenv("HTTP_COOKIE"); + ctx.env.http_referer = getenv("HTTP_REFERER"); + ctx.env.content_length = getenv("CONTENT_LENGTH") ? strtoul(getenv("CONTENT_LENGTH"), NULL, 10) : 0; + ctx.env.authenticated = 0; + ctx.page.mimetype = "text/html"; + ctx.page.charset = PAGE_ENCODING; + ctx.page.filename = NULL; + ctx.page.size = 0; + ctx.page.modified = time(NULL); + ctx.page.expires = ctx.page.modified; + ctx.page.etag = NULL; + string_list_init(&ctx.cfg.mimetypes, 1); + if (ctx.env.script_name) + ctx.cfg.script_name = xstrdup(ctx.env.script_name); + if (ctx.env.query_string) + ctx.qry.raw = xstrdup(ctx.env.query_string); + if (!ctx.env.cgit_config) + ctx.env.cgit_config = CGIT_CONFIG; +} + +struct refmatch { + char *req_ref; + char *first_ref; + int match; +}; + +static int find_current_ref(const char *refname, const struct object_id *oid, + int flags, void *cb_data) +{ + struct refmatch *info; + + info = (struct refmatch *)cb_data; + if (!strcmp(refname, info->req_ref)) + info->match = 1; + if (!info->first_ref) + info->first_ref = xstrdup(refname); + return info->match; +} + +static void free_refmatch_inner(struct refmatch *info) +{ + if (info->first_ref) + free(info->first_ref); +} + +static char *find_default_branch(struct cgit_repo *repo) +{ + struct refmatch info; + char *ref; + + info.req_ref = repo->defbranch; + info.first_ref = NULL; + info.match = 0; + for_each_branch_ref(find_current_ref, &info); + if (info.match) + ref = info.req_ref; + else + ref = info.first_ref; + if (ref) + ref = xstrdup(ref); + free_refmatch_inner(&info); + + return ref; +} + +static char *guess_defbranch(void) +{ + const char *ref, *refname; + struct object_id oid; + + ref = resolve_ref_unsafe("HEAD", 0, &oid, NULL); + if (!ref || !skip_prefix(ref, "refs/heads/", &refname)) + return "master"; + return xstrdup(refname); +} + +/* The caller must free filename and ref after calling this. */ +static inline void parse_readme(const char *readme, char **filename, char **ref, struct cgit_repo *repo) +{ + const char *colon; + + *filename = NULL; + *ref = NULL; + + if (!readme || !readme[0]) + return; + + /* Check if the readme is tracked in the git repo. */ + colon = strchr(readme, ':'); + if (colon && strlen(colon) > 1) { + /* If it starts with a colon, we want to use + * the default branch */ + if (colon == readme && repo->defbranch) + *ref = xstrdup(repo->defbranch); + else + *ref = xstrndup(readme, colon - readme); + readme = colon + 1; + } + + /* Prepend repo path to relative readme path unless tracked. */ + if (!(*ref) && readme[0] != '/') + *filename = fmtalloc("%s/%s", repo->path, readme); + else + *filename = xstrdup(readme); +} +static void choose_readme(struct cgit_repo *repo) +{ + int found; + char *filename, *ref; + struct string_list_item *entry; + + if (!repo->readme.nr) + return; + + found = 0; + for_each_string_list_item(entry, &repo->readme) { + parse_readme(entry->string, &filename, &ref, repo); + if (!filename) { + free(filename); + free(ref); + continue; + } + if (ref) { + if (cgit_ref_path_exists(filename, ref, 1)) { + found = 1; + break; + } + } + else if (!access(filename, R_OK)) { + found = 1; + break; + } + free(filename); + free(ref); + } + repo->readme.strdup_strings = 1; + string_list_clear(&repo->readme, 0); + repo->readme.strdup_strings = 0; + if (found) + string_list_append(&repo->readme, filename)->util = ref; +} + +static void print_no_repo_clone_urls(const char *url) +{ + html("<tr><td><a rel='vcs-git' href='"); + html_url_path(url); + html("' title='"); + html_attr(ctx.repo->name); + html(" Git repository'>"); + html_txt(url); + html("</a></td></tr>\n"); +} + +static void prepare_repo_env(int *nongit) +{ + /* The path to the git repository. */ + setenv("GIT_DIR", ctx.repo->path, 1); + + /* Do not look in /etc/ for gitconfig and gitattributes. */ + setenv("GIT_CONFIG_NOSYSTEM", "1", 1); + setenv("GIT_ATTR_NOSYSTEM", "1", 1); + unsetenv("HOME"); + unsetenv("XDG_CONFIG_HOME"); + + /* Setup the git directory and initialize the notes system. Both of these + * load local configuration from the git repository, so we do them both while + * the HOME variables are unset. */ + setup_git_directory_gently(nongit); + init_display_notes(NULL); +} +static int prepare_repo_cmd(int nongit) +{ + struct object_id oid; + int rc; + + if (nongit) { + const char *name = ctx.repo->name; + rc = errno; + ctx.page.title = fmtalloc("%s - %s", ctx.cfg.root_title, + "config error"); + ctx.repo = NULL; + cgit_print_http_headers(); + cgit_print_docstart(); + cgit_print_pageheader(); + cgit_print_error("Failed to open %s: %s", name, + rc ? strerror(rc) : "Not a valid git repository"); + cgit_print_docend(); + return 1; + } + ctx.page.title = fmtalloc("%s - %s", ctx.repo->name, ctx.repo->desc); + + if (!ctx.repo->defbranch) + ctx.repo->defbranch = guess_defbranch(); + + if (!ctx.qry.head) { + ctx.qry.nohead = 1; + ctx.qry.head = find_default_branch(ctx.repo); + } + + if (!ctx.qry.head) { + cgit_print_http_headers(); + cgit_print_docstart(); + cgit_print_pageheader(); + cgit_print_error("Repository seems to be empty"); + if (!strcmp(ctx.qry.page, "summary")) { + html("<table class='list'><tr class='nohover'><td> </td></tr><tr class='nohover'><th class='left'>Clone</th></tr>\n"); + cgit_prepare_repo_env(ctx.repo); + cgit_add_clone_urls(print_no_repo_clone_urls); + html("</table>\n"); + } + cgit_print_docend(); + return 1; + } + + if (get_oid(ctx.qry.head, &oid)) { + char *old_head = ctx.qry.head; + ctx.qry.head = xstrdup(ctx.repo->defbranch); + cgit_print_error_page(404, "Not found", + "Invalid branch: %s", old_head); + free(old_head); + return 1; + } + string_list_sort(&ctx.repo->submodules); + cgit_prepare_repo_env(ctx.repo); + choose_readme(ctx.repo); + return 0; +} + +static inline void open_auth_filter(const char *function) +{ + cgit_open_filter(ctx.cfg.auth_filter, function, + ctx.env.http_cookie ? ctx.env.http_cookie : "", + ctx.env.request_method ? ctx.env.request_method : "", + ctx.env.query_string ? ctx.env.query_string : "", + ctx.env.http_referer ? ctx.env.http_referer : "", + ctx.env.path_info ? ctx.env.path_info : "", + ctx.env.http_host ? ctx.env.http_host : "", + ctx.env.https ? ctx.env.https : "", + ctx.qry.repo ? ctx.qry.repo : "", + ctx.qry.page ? ctx.qry.page : "", + ctx.qry.url ? ctx.qry.url : "", + cgit_loginurl()); +} + +/* We intentionally keep this rather small, instead of looping and + * feeding it to the filter a couple bytes at a time. This way, the + * filter itself does not need to handle any denial of service or + * buffer bloat issues. If this winds up being too small, people + * will complain on the mailing list, and we'll increase it as needed. */ +#define MAX_AUTHENTICATION_POST_BYTES 4096 +/* The filter is expected to spit out "Status: " and all headers. */ +static inline void authenticate_post(void) +{ + char buffer[MAX_AUTHENTICATION_POST_BYTES]; + ssize_t len; + + open_auth_filter("authenticate-post"); + len = ctx.env.content_length; + if (len > MAX_AUTHENTICATION_POST_BYTES) + len = MAX_AUTHENTICATION_POST_BYTES; + if ((len = read(STDIN_FILENO, buffer, len)) < 0) + die_errno("Could not read POST from stdin"); + if (write(STDOUT_FILENO, buffer, len) < 0) + die_errno("Could not write POST to stdout"); + cgit_close_filter(ctx.cfg.auth_filter); + exit(0); +} + +static inline void authenticate_cookie(void) +{ + /* If we don't have an auth_filter, consider all cookies valid, and thus return early. */ + if (!ctx.cfg.auth_filter) { + ctx.env.authenticated = 1; + return; + } + + /* If we're having something POST'd to /login, we're authenticating POST, + * instead of the cookie, so call authenticate_post and bail out early. + * This pattern here should match /?p=login with POST. */ + if (ctx.env.request_method && ctx.qry.page && !ctx.repo && \ + !strcmp(ctx.env.request_method, "POST") && !strcmp(ctx.qry.page, "login")) { + authenticate_post(); + return; + } + + /* If we've made it this far, we're authenticating the cookie for real, so do that. */ + open_auth_filter("authenticate-cookie"); + ctx.env.authenticated = cgit_close_filter(ctx.cfg.auth_filter); +} + +static void process_request(void) +{ + struct cgit_cmd *cmd; + int nongit = 0; + + /* If we're not yet authenticated, no matter what page we're on, + * display the authentication body from the auth_filter. This should + * never be cached. */ +/* if (!ctx.env.authenticated) { + ctx.page.title = "Authentication Required"; + cgit_print_http_headers(); + cgit_print_docstart(); + cgit_print_pageheader(); + open_auth_filter("body"); + cgit_close_filter(ctx.cfg.auth_filter); + cgit_print_docend(); + return; + } +*/ + if (ctx.repo) + prepare_repo_env(&nongit); + + cmd = cgit_get_cmd(); + if (!cmd) { + ctx.page.title = "cgit error"; + cgit_print_error_page(404, "Not found", "Invalid request"); + return; + } + + if (!ctx.cfg.enable_http_clone && cmd->is_clone) { + ctx.page.title = "cgit error"; + cgit_print_error_page(404, "Not found", "Invalid request"); + return; + } + + if (cmd->want_repo && !ctx.repo) { + cgit_print_error_page(400, "Bad request", + "No repository selected"); + return; + } + + /* If cmd->want_vpath is set, assume ctx.qry.path contains a "virtual" + * in-project path limit to be made available at ctx.qry.vpath. + * Otherwise, no path limit is in effect (ctx.qry.vpath = NULL). + */ + ctx.qry.vpath = cmd->want_vpath ? ctx.qry.path : NULL; + + if (ctx.repo && prepare_repo_cmd(nongit)) + return; + + cmd->fn(); +} + +static int cmp_repos(const void *a, const void *b) +{ + const struct cgit_repo *ra = a, *rb = b; + return strcmp(ra->url, rb->url); +} + +static char *build_snapshot_setting(int bitmap) +{ + const struct cgit_snapshot_format *f; + struct strbuf result = STRBUF_INIT; + + for (f = cgit_snapshot_formats; f->suffix; f++) { + if (cgit_snapshot_format_bit(f) & bitmap) { + if (result.len) + strbuf_addch(&result, ' '); + strbuf_addstr(&result, f->suffix); + } + } + return strbuf_detach(&result, NULL); +} + +static char *get_first_line(char *txt) +{ + char *t = xstrdup(txt); + char *p = strchr(t, '\n'); + if (p) + *p = '\0'; + return t; +} + +static void print_repo(FILE *f, struct cgit_repo *repo) +{ + struct string_list_item *item; + fprintf(f, "repo.url=%s\n", repo->url); + fprintf(f, "repo.name=%s\n", repo->name); + fprintf(f, "repo.path=%s\n", repo->path); + if (repo->owner) + fprintf(f, "repo.owner=%s\n", repo->owner); + if (repo->desc) { + char *tmp = get_first_line(repo->desc); + fprintf(f, "repo.desc=%s\n", tmp); + free(tmp); + } + for_each_string_list_item(item, &repo->readme) { + if (item->util) + fprintf(f, "repo.readme=%s:%s\n", (char *)item->util, item->string); + else + fprintf(f, "repo.readme=%s\n", item->string); + } + if (repo->defbranch) + fprintf(f, "repo.defbranch=%s\n", repo->defbranch); + if (repo->extra_head_content) + fprintf(f, "repo.extra-head-content=%s\n", repo->extra_head_content); + if (repo->module_link) + fprintf(f, "repo.module-link=%s\n", repo->module_link); + if (repo->section) + fprintf(f, "repo.section=%s\n", repo->section); + if (repo->homepage) + fprintf(f, "repo.homepage=%s\n", repo->homepage); + if (repo->clone_url) + fprintf(f, "repo.clone-url=%s\n", repo->clone_url); + fprintf(f, "repo.enable-commit-graph=%d\n", + repo->enable_commit_graph); + fprintf(f, "repo.enable-log-filecount=%d\n", + repo->enable_log_filecount); + fprintf(f, "repo.enable-log-linecount=%d\n", + repo->enable_log_linecount); + if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) + cgit_fprintf_filter(repo->about_filter, f, "repo.about-filter="); + if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) + cgit_fprintf_filter(repo->commit_filter, f, "repo.commit-filter="); + if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) + cgit_fprintf_filter(repo->source_filter, f, "repo.source-filter="); + if (repo->email_filter && repo->email_filter != ctx.cfg.email_filter) + cgit_fprintf_filter(repo->email_filter, f, "repo.email-filter="); + if (repo->owner_filter && repo->owner_filter != ctx.cfg.owner_filter) + cgit_fprintf_filter(repo->owner_filter, f, "repo.owner-filter="); + if (repo->snapshots != ctx.cfg.snapshots) { + char *tmp = build_snapshot_setting(repo->snapshots); + fprintf(f, "repo.snapshots=%s\n", tmp ? tmp : ""); + free(tmp); + } + if (repo->snapshot_prefix) + fprintf(f, "repo.snapshot-prefix=%s\n", repo->snapshot_prefix); + if (repo->max_stats != ctx.cfg.max_stats) + fprintf(f, "repo.max-stats=%s\n", + cgit_find_stats_periodname(repo->max_stats)); + if (repo->logo) + fprintf(f, "repo.logo=%s\n", repo->logo); + if (repo->logo_link) + fprintf(f, "repo.logo-link=%s\n", repo->logo_link); + fprintf(f, "repo.enable-remote-branches=%d\n", repo->enable_remote_branches); + fprintf(f, "repo.enable-subject-links=%d\n", repo->enable_subject_links); + fprintf(f, "repo.enable-html-serving=%d\n", repo->enable_html_serving); + if (repo->branch_sort == 1) + fprintf(f, "repo.branch-sort=age\n"); + if (repo->commit_sort) { + if (repo->commit_sort == 1) + fprintf(f, "repo.commit-sort=date\n"); + else if (repo->commit_sort == 2) + fprintf(f, "repo.commit-sort=topo\n"); + } + fprintf(f, "repo.hide=%d\n", repo->hide); + fprintf(f, "repo.ignore=%d\n", repo->ignore); + fprintf(f, "\n"); +} + +static void print_repolist(FILE *f, struct cgit_repolist *list, int start) +{ + int i; + + for (i = start; i < list->count; i++) + print_repo(f, &list->repos[i]); +} + +/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' + * and return 0 on success. + */ +static int generate_cached_repolist(const char *path, const char *cached_rc) +{ + struct strbuf locked_rc = STRBUF_INIT; + int result = 0; + int idx; + FILE *f; + + strbuf_addf(&locked_rc, "%s.lock", cached_rc); + f = fopen(locked_rc.buf, "wx"); + if (!f) { + /* Inform about the error unless the lockfile already existed, + * since that only means we've got concurrent requests. + */ + result = errno; + if (result != EEXIST) + fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", + locked_rc.buf, strerror(result), result); + goto out; + } + idx = cgit_repolist.count; + if (ctx.cfg.project_list) + scan_projects(path, ctx.cfg.project_list, repo_config); + else + scan_tree(path, repo_config); + print_repolist(f, &cgit_repolist, idx); + if (rename(locked_rc.buf, cached_rc)) + fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", + locked_rc.buf, cached_rc, strerror(errno), errno); + fclose(f); +out: + strbuf_release(&locked_rc); + return result; +} + +static void process_cached_repolist(const char *path) +{ + struct stat st; + struct strbuf cached_rc = STRBUF_INIT; + time_t age; + unsigned long hash; + + hash = hash_str(path); + if (ctx.cfg.project_list) + hash += hash_str(ctx.cfg.project_list); + strbuf_addf(&cached_rc, "%s/rc-%8lx", ctx.cfg.cache_root, hash); + + if (stat(cached_rc.buf, &st)) { + /* Nothing is cached, we need to scan without forking. And + * if we fail to generate a cached repolist, we need to + * invoke scan_tree manually. + */ + if (generate_cached_repolist(path, cached_rc.buf)) { + if (ctx.cfg.project_list) + scan_projects(path, ctx.cfg.project_list, + repo_config); + else + scan_tree(path, repo_config); + } + goto out; + } + + parse_configfile(cached_rc.buf, config_cb); + + /* If the cached configfile hasn't expired, lets exit now */ + age = time(NULL) - st.st_mtime; + if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) + goto out; + + /* The cached repolist has been parsed, but it was old. So lets + * rescan the specified path and generate a new cached repolist + * in a child-process to avoid latency for the current request. + */ + if (fork()) + goto out; + + exit(generate_cached_repolist(path, cached_rc.buf)); +out: + strbuf_release(&cached_rc); +} + +static void cgit_parse_args(int argc, const char **argv) +{ + while (--argc > 4); + fprintf(stderr, " -- argc: %d\n", argc); + switch (++argc){ + case 5: + ctx.env.server_port = xstrdup(argv[4]); + fprintf(stderr, "server_port: %s\n", ctx.env.server_port); + case 4: + ctx.env.server_name = xstrdup(argv[3]); + fprintf(stderr, "server_name: %s\n", ctx.env.server_name); + case 3: + ctx.env.query_string = xstrdup(argv[2]); + ctx.qry.raw = xstrdup(argv[2]); + fprintf(stderr, "query_string: %s\n", ctx.env.query_string); + case 2: + ctx.env.gopher_search = xstrdup(argv[1]); + fprintf(stderr, "gopher_search: %s\n", ctx.env.gopher_search); + case 1: + ctx.env.script_name = xstrdup(argv[0]); + fprintf(stderr, "script_name: %s\n", ctx.env.script_name); + } + +} + +static int calc_ttl(void) +{ + if (!ctx.repo) + return ctx.cfg.cache_root_ttl; + + if (!ctx.qry.page) + return ctx.cfg.cache_repo_ttl; + + if (!strcmp(ctx.qry.page, "about")) + return ctx.cfg.cache_about_ttl; + + if (!strcmp(ctx.qry.page, "snapshot")) + return ctx.cfg.cache_snapshot_ttl; + + if (ctx.qry.has_sha1) + return ctx.cfg.cache_static_ttl; + + if (ctx.qry.has_symref) + return ctx.cfg.cache_dynamic_ttl; + + return ctx.cfg.cache_repo_ttl; +} + +int cmd_main(int argc, const char **argv) +{ + const char *path; + int err, ttl; + + + prepare_context(); + cgit_repolist.length = 0; + cgit_repolist.count = 0; + cgit_repolist.repos = NULL; + + cgit_parse_args(argc, argv); + parse_configfile(expand_macros(ctx.env.cgit_config), config_cb); + ctx.repo = NULL; + fprintf(stderr, " -- cmd_main -- ctx.qry.raw: %s\n", ctx.qry.raw); + http_parse_querystring(ctx.qry.raw, querystring_cb); + + fprintf(stderr, " -- cmd_main -- got url: %s\n", ctx.qry.url); + + /* If virtual-root isn't specified in cgitrc, lets pretend + * that virtual-root equals SCRIPT_NAME, minus any possibly + * trailing slashes. + */ + if (!ctx.cfg.virtual_root && ctx.cfg.script_name) + ctx.cfg.virtual_root = ensure_end(ctx.cfg.script_name, '/'); + + /* If no url parameter is specified on the querystring, lets + * use PATH_INFO as url. This allows cgit to work with virtual + * urls without the need for rewriterules in the webserver (as + * long as PATH_INFO is included in the cache lookup key). + */ + path = ctx.env.path_info; + if (!ctx.qry.url && path) { + if (path[0] == '/') + path++; + ctx.qry.url = xstrdup(path); + if (ctx.qry.raw) { + char *newqry = fmtalloc("%s?%s", path, ctx.qry.raw); + free(ctx.qry.raw); + ctx.qry.raw = newqry; + } else + ctx.qry.raw = xstrdup(ctx.qry.url); + cgit_parse_url(ctx.qry.url); + } + + /* Before we go any further, we set ctx.env.authenticated by checking to see + * if the supplied cookie is valid. All cookies are valid if there is no + * auth_filter. If there is an auth_filter, the filter decides. */ + /* authenticate_cookie(); */ + + ttl = calc_ttl(); + if (ttl < 0) + ctx.page.expires += 10 * 365 * 24 * 60 * 60; /* 10 years */ + else + ctx.page.expires += ttl * 60; + /*if (!ctx.env.authenticated || (ctx.env.request_method && * !strcmp(ctx.env.request_method, "HEAD")))*/ + if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) + ctx.cfg.cache_size = 0; + /* cache_process is the function that does the work...*/ + err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, + ctx.qry.raw, ttl, process_request); + if (err) + cgit_print_error("Error processing page: %s (%d)", + strerror(err), err); + return err; +} diff --git a/cgit_70.mk b/cgit_70.mk new file mode 100644 index 0000000..4e6857a --- /dev/null +++ b/cgit_70.mk @@ -0,0 +1,149 @@ +# This Makefile is run in the "git" directory in order to re-use Git's +# build variables and operating system detection. Hence all files in +# CGit's directory must be prefixed with "../". +include Makefile + +CGIT_PREFIX = ../ + +-include $(CGIT_PREFIX)cgit.conf + +# The CGIT_* variables are inherited when this file is called from the +# main Makefile - they are defined there. + +$(CGIT_PREFIX)VERSION: force-version + @cd $(CGIT_PREFIX) && '$(SHELL_PATH_SQ)' ./gen-version.sh "$(CGIT_VERSION)" +-include $(CGIT_PREFIX)VERSION +.PHONY: force-version + +# CGIT_CFLAGS is a separate variable so that we can track it separately +# and avoid rebuilding all of Git when these variables change. +CGIT_CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' +CGIT_CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' +CGIT_CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' +CGIT_CFLAGS += -g + +PKG_CONFIG ?= pkg-config + +ifdef NO_C99_FORMAT + CFLAGS += -DNO_C99_FORMAT +endif + +ifdef NO_LUA + LUA_MESSAGE := linking without specified Lua support + CGIT_CFLAGS += -DNO_LUA +else +ifeq ($(LUA_PKGCONFIG),) + LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \ + $(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \ + done) + LUA_MODE := autodetected +else + LUA_MODE := specified +endif +ifneq ($(LUA_PKGCONFIG),) + LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG) + LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null) + LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null) + CGIT_LIBS += $(LUA_LIBS) + CGIT_CFLAGS += $(LUA_CFLAGS) +else + LUA_MESSAGE := linking without autodetected Lua support + NO_LUA := YesPlease + CGIT_CFLAGS += -DNO_LUA +endif + +endif + +# Add -ldl to linker flags on systems that commonly use GNU libc. +ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD)) + CGIT_LIBS += -ldl +endif + +# glibc 2.1+ offers sendfile which the most common C library on Linux +ifeq ($(uname_S),Linux) + HAVE_LINUX_SENDFILE = YesPlease +endif + +ifdef HAVE_LINUX_SENDFILE + CGIT_CFLAGS += -DHAVE_LINUX_SENDFILE +endif + +#CGIT_OBJ_NAMES += cgit.o +CGIT_OBJ_NAMES += cgit_70.o +CGIT_OBJ_NAMES += cache.o +#CGIT_OBJ_NAMES += cmd.o +CGIT_OBJ_NAMES += cmd_70.o +CGIT_OBJ_NAMES += configfile.o +CGIT_OBJ_NAMES += filter.o +CGIT_OBJ_NAMES += html.o +CGIT_OBJ_NAMES += parsing.o +CGIT_OBJ_NAMES += scan-tree.o +CGIT_OBJ_NAMES += shared.o +CGIT_OBJ_NAMES += ui-atom.o +CGIT_OBJ_NAMES += ui-blame.o +CGIT_OBJ_NAMES += ui-blob.o +CGIT_OBJ_NAMES += ui-clone.o +CGIT_OBJ_NAMES += ui-commit.o +CGIT_OBJ_NAMES += ui-diff.o +CGIT_OBJ_NAMES += ui-log.o +CGIT_OBJ_NAMES += ui-patch.o +CGIT_OBJ_NAMES += ui-plain.o +##CGIT_OBJ_NAMES += ui-refs.o +CGIT_OBJ_NAMES += ui_70-refs.o +##CGIT_OBJ_NAMES += ui-repolist.o +CGIT_OBJ_NAMES += ui_70-repolist.o +##CGIT_OBJ_NAMES += ui-shared.o +CGIT_OBJ_NAMES += ui_70-shared.o +CGIT_OBJ_NAMES += ui-snapshot.o +CGIT_OBJ_NAMES += ui-ssdiff.o +CGIT_OBJ_NAMES += ui-stats.o +##CGIT_OBJ_NAMES += ui-summary.o +CGIT_OBJ_NAMES += ui_70-summary.o +CGIT_OBJ_NAMES += ui-tag.o +CGIT_OBJ_NAMES += ui-tree.o + +CGIT_OBJS := $(addprefix $(CGIT_PREFIX),$(CGIT_OBJ_NAMES)) + +# Only cgit.c reference CGIT_VERSION so we only rebuild its objects when the +# version changes. +##CGIT_VERSION_OBJS := $(addprefix $(CGIT_PREFIX),cgit.o cgit.sp) +CGIT_VERSION_OBJS := $(addprefix $(CGIT_PREFIX),cgit_70.o cgit.sp) +$(CGIT_VERSION_OBJS): $(CGIT_PREFIX)VERSION +$(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \ + -DCGIT_VERSION='"$(CGIT_VERSION)"' + +# Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not +# handled by that and we must handle them ourselves. +cgit_dep_files := $(foreach f,$(CGIT_OBJS),$(dir $f).depend/$(notdir $f).d) +cgit_dep_files_present := $(wildcard $(cgit_dep_files)) +ifneq ($(cgit_dep_files_present),) +include $(cgit_dep_files_present) +endif + +ifeq ($(wildcard $(CGIT_PREFIX).depend),) +missing_dep_dirs += $(CGIT_PREFIX).depend +endif + +$(CGIT_PREFIX).depend: + @mkdir -p $@ + +$(CGIT_PREFIX)CGIT-CFLAGS: FORCE + @FLAGS='$(subst ','\'',$(CGIT_CFLAGS))'; \ + if test x"$$FLAGS" != x"`cat ../CGIT-CFLAGS 2>/dev/null`" ; then \ + echo 1>&2 " * new CGit build flags"; \ + echo "$$FLAGS" >$(CGIT_PREFIX)CGIT-CFLAGS; \ + fi + +$(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs) + $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $< + +$(CGIT_PREFIX)cgit_70: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS) + @echo 1>&1 " * $(LUA_MESSAGE)" + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS) + +CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS)) + +$(CGIT_SP_OBJS): %.sp: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS FORCE + $(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $(SPARSE_FLAGS) $< + +cgit-sparse: $(CGIT_SP_OBJS) diff --git a/cmd_70.c b/cmd_70.c new file mode 100644 index 0000000..8478cab --- /dev/null +++ b/cmd_70.c @@ -0,0 +1,209 @@ +/* cmd.c: the cgit command dispatcher + * + * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "cmd.h" +#include "cache.h" +#include "ui-shared.h" +#include "ui-atom.h" +#include "ui-blame.h" +#include "ui-blob.h" +#include "ui-clone.h" +#include "ui-commit.h" +#include "ui-diff.h" +#include "ui-log.h" +#include "ui-patch.h" +#include "ui-plain.h" +#include "ui-refs.h" +#include "ui-repolist.h" +#include "ui-snapshot.h" +#include "ui-stats.h" +#include "ui-summary.h" +#include "ui-tag.h" +#include "ui-tree.h" + +static void HEAD_fn(void) +{ + cgit_clone_head(); +} + +static void atom_fn(void) +{ + cgit_print_atom(ctx.qry.head, ctx.qry.path, ctx.cfg.max_atom_items); +} + +static void about_fn(void) +{ + if (ctx.repo) { + size_t path_info_len = ctx.env.path_info ? strlen(ctx.env.path_info) : 0; + if (!ctx.qry.path && + ctx.qry.url[strlen(ctx.qry.url) - 1] != '/' && + (!path_info_len || ctx.env.path_info[path_info_len - 1] != '/')) { + char *currenturl = cgit_currenturl(); + char *redirect = fmtalloc("%s/", currenturl); + cgit_redirect(redirect, true); + free(currenturl); + free(redirect); + } else if (ctx.repo->readme.nr) + cgit_print_repo_readme(ctx.qry.path); + else if (ctx.repo->homepage) + cgit_redirect(ctx.repo->homepage, false); + else { + char *currenturl = cgit_currenturl(); + char *redirect = fmtalloc("%s../", currenturl); + cgit_redirect(redirect, false); + free(currenturl); + free(redirect); + } + } else + cgit_print_site_readme(); +} + +static void blame_fn(void) +{ + if (ctx.cfg.enable_blame) + cgit_print_blame(); + else + cgit_print_error_page(403, "Forbidden", "Blame is disabled"); +} + +static void blob_fn(void) +{ + cgit_print_blob(ctx.qry.sha1, ctx.qry.path, ctx.qry.head, 0); +} + +static void commit_fn(void) +{ + cgit_print_commit(ctx.qry.sha1, ctx.qry.path); +} + +static void diff_fn(void) +{ + cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 0); +} + +static void rawdiff_fn(void) +{ + cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 1); +} + +static void info_fn(void) +{ + cgit_clone_info(); +} + +static void log_fn(void) +{ + cgit_print_log(ctx.qry.sha1, ctx.qry.ofs, ctx.cfg.max_commit_count, + ctx.qry.grep, ctx.qry.search, ctx.qry.path, 1, + ctx.repo->enable_commit_graph, + ctx.repo->commit_sort); +} + +static void ls_cache_fn(void) +{ + ctx.page.mimetype = "text/plain"; + ctx.page.filename = "ls-cache.txt"; + cgit_print_http_headers(); + cache_ls(ctx.cfg.cache_root); +} + +static void objects_fn(void) +{ + cgit_clone_objects(); +} + +static void repolist_fn(void) +{ + cgit_print_repolist(); +} + +static void patch_fn(void) +{ + cgit_print_patch(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path); +} + +static void plain_fn(void) +{ + cgit_print_plain(); +} + +static void refs_fn(void) +{ + cgit_print_refs(); +} + +static void snapshot_fn(void) +{ + cgit_print_snapshot(ctx.qry.head, ctx.qry.sha1, ctx.qry.path, + ctx.qry.nohead); +} + +static void stats_fn(void) +{ + cgit_show_stats(); +} + +static void summary_fn(void) +{ + fprintf(stderr, " ---- selected function: cgit_print_summary\n"); + cgit_print_summary(); +} + +static void tag_fn(void) +{ + cgit_print_tag(ctx.qry.sha1); +} + +static void tree_fn(void) +{ + cgit_print_tree(ctx.qry.sha1, ctx.qry.path); +} + +#define def_cmd(name, want_repo, want_vpath, is_clone) \ + {#name, name##_fn, want_repo, want_vpath, is_clone} + +struct cgit_cmd *cgit_get_cmd(void) +{ + static struct cgit_cmd cmds[] = { + def_cmd(HEAD, 1, 0, 1), + def_cmd(atom, 1, 0, 0), + def_cmd(about, 0, 0, 0), + def_cmd(blame, 1, 1, 0), + def_cmd(blob, 1, 0, 0), + def_cmd(commit, 1, 1, 0), + def_cmd(diff, 1, 1, 0), + def_cmd(info, 1, 0, 1), + def_cmd(log, 1, 1, 0), + def_cmd(ls_cache, 0, 0, 0), + def_cmd(objects, 1, 0, 1), + def_cmd(patch, 1, 1, 0), + def_cmd(plain, 1, 0, 0), + def_cmd(rawdiff, 1, 1, 0), + def_cmd(refs, 1, 0, 0), + def_cmd(repolist, 0, 0, 0), + def_cmd(snapshot, 1, 0, 0), + def_cmd(stats, 1, 1, 0), + def_cmd(summary, 1, 0, 0), + def_cmd(tag, 1, 0, 0), + def_cmd(tree, 1, 1, 0), + }; + int i; + + if (ctx.qry.page == NULL) { + if (ctx.repo) + ctx.qry.page = "summary"; + else + ctx.qry.page = "repolist"; + } + + for (i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) + if (!strcmp(ctx.qry.page, cmds[i].name)) + return &cmds[i]; + return NULL; +} @@ -335,6 +335,7 @@ void http_parse_querystring(const char *txt, void (*fn)(const char *name, const while (t && *t) { char *name = url_decode_parameter_name(&t); if (*name) { + fprintf(stderr, "http_parse_querystring -- name: %s\n", name); char *value = url_decode_parameter_value(&t); fn(name, value); free(value); diff --git a/shared_70.c b/shared_70.c new file mode 100644 index 0000000..609bd2a --- /dev/null +++ b/shared_70.c @@ -0,0 +1,578 @@ +/* shared.c: global vars + some callback functions + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" + +struct cgit_repolist cgit_repolist; +struct cgit_context ctx; + +int chk_zero(int result, char *msg) +{ + if (result != 0) + die_errno("%s", msg); + return result; +} + +int chk_positive(int result, char *msg) +{ + if (result <= 0) + die_errno("%s", msg); + return result; +} + +int chk_non_negative(int result, char *msg) +{ + if (result < 0) + die_errno("%s", msg); + return result; +} + +char *cgit_default_repo_desc = "[no description]"; +struct cgit_repo *cgit_add_repo(const char *url) +{ + struct cgit_repo *ret; + + if (++cgit_repolist.count > cgit_repolist.length) { + if (cgit_repolist.length == 0) + cgit_repolist.length = 8; + else + cgit_repolist.length *= 2; + cgit_repolist.repos = xrealloc(cgit_repolist.repos, + cgit_repolist.length * + sizeof(struct cgit_repo)); + } + + ret = &cgit_repolist.repos[cgit_repolist.count-1]; + memset(ret, 0, sizeof(struct cgit_repo)); + ret->url = trim_end(url, '/'); + ret->name = ret->url; + ret->path = NULL; + ret->desc = cgit_default_repo_desc; + ret->extra_head_content = NULL; + ret->owner = NULL; + ret->homepage = NULL; + ret->section = ctx.cfg.section; + ret->snapshots = ctx.cfg.snapshots; + ret->enable_commit_graph = ctx.cfg.enable_commit_graph; + ret->enable_log_filecount = ctx.cfg.enable_log_filecount; + ret->enable_log_linecount = ctx.cfg.enable_log_linecount; + ret->enable_remote_branches = ctx.cfg.enable_remote_branches; + ret->enable_subject_links = ctx.cfg.enable_subject_links; + ret->enable_html_serving = ctx.cfg.enable_html_serving; + ret->max_stats = ctx.cfg.max_stats; + ret->branch_sort = ctx.cfg.branch_sort; + ret->commit_sort = ctx.cfg.commit_sort; + ret->module_link = ctx.cfg.module_link; + ret->readme = ctx.cfg.readme; + ret->mtime = -1; + ret->about_filter = ctx.cfg.about_filter; + ret->commit_filter = ctx.cfg.commit_filter; + ret->source_filter = ctx.cfg.source_filter; + ret->email_filter = ctx.cfg.email_filter; + ret->owner_filter = ctx.cfg.owner_filter; + ret->clone_url = ctx.cfg.clone_url; + ret->submodules.strdup_strings = 1; + ret->hide = ret->ignore = 0; + return ret; +} + +struct cgit_repo *cgit_get_repoinfo(const char *url) +{ + int i; + struct cgit_repo *repo; + + for (i = 0; i < cgit_repolist.count; i++) { + repo = &cgit_repolist.repos[i]; + if (repo->ignore) + continue; + if (!strcmp(repo->url, url)) + return repo; + } + return NULL; +} + +void cgit_free_commitinfo(struct commitinfo *info) +{ + free(info->author); + free(info->author_email); + free(info->committer); + free(info->committer_email); + free(info->subject); + free(info->msg); + free(info->msg_encoding); + free(info); +} + +char *trim_end(const char *str, char c) +{ + int len; + + if (str == NULL) + return NULL; + len = strlen(str); + while (len > 0 && str[len - 1] == c) + len--; + if (len == 0) + return NULL; + return xstrndup(str, len); +} + +char *ensure_end(const char *str, char c) +{ + size_t len = strlen(str); + char *result; + + if (len && str[len - 1] == c) + return xstrndup(str, len); + + result = xmalloc(len + 2); + memcpy(result, str, len); + result[len] = '/'; + result[len + 1] = '\0'; + return result; +} + +void strbuf_ensure_end(struct strbuf *sb, char c) +{ + if (!sb->len || sb->buf[sb->len - 1] != c) + strbuf_addch(sb, c); +} + +void cgit_add_ref(struct reflist *list, struct refinfo *ref) +{ + size_t size; + + if (list->count >= list->alloc) { + list->alloc += (list->alloc ? list->alloc : 4); + size = list->alloc * sizeof(struct refinfo *); + list->refs = xrealloc(list->refs, size); + } + list->refs[list->count++] = ref; +} + +static struct refinfo *cgit_mk_refinfo(const char *refname, const struct object_id *oid) +{ + struct refinfo *ref; + + ref = xmalloc(sizeof (struct refinfo)); + ref->refname = xstrdup(refname); + ref->object = parse_object(oid); + switch (ref->object->type) { + case OBJ_TAG: + ref->tag = cgit_parse_tag((struct tag *)ref->object); + break; + case OBJ_COMMIT: + ref->commit = cgit_parse_commit((struct commit *)ref->object); + break; + } + return ref; +} + +void cgit_free_taginfo(struct taginfo *tag) +{ + if (tag->tagger) + free(tag->tagger); + if (tag->tagger_email) + free(tag->tagger_email); + if (tag->msg) + free(tag->msg); + free(tag); +} + +static void cgit_free_refinfo(struct refinfo *ref) +{ + if (ref->refname) + free((char *)ref->refname); + switch (ref->object->type) { + case OBJ_TAG: + cgit_free_taginfo(ref->tag); + break; + case OBJ_COMMIT: + cgit_free_commitinfo(ref->commit); + break; + } + free(ref); +} + +void cgit_free_reflist_inner(struct reflist *list) +{ + int i; + + for (i = 0; i < list->count; i++) { + cgit_free_refinfo(list->refs[i]); + } + free(list->refs); +} + +int cgit_refs_cb(const char *refname, const struct object_id *oid, int flags, + void *cb_data) +{ + struct reflist *list = (struct reflist *)cb_data; + struct refinfo *info = cgit_mk_refinfo(refname, oid); + + if (info) + cgit_add_ref(list, info); + return 0; +} + +void cgit_diff_tree_cb(struct diff_queue_struct *q, + struct diff_options *options, void *data) +{ + int i; + + for (i = 0; i < q->nr; i++) { + if (q->queue[i]->status == 'U') + continue; + ((filepair_fn)data)(q->queue[i]); + } +} + +static int load_mmfile(mmfile_t *file, const struct object_id *oid) +{ + enum object_type type; + + if (is_null_oid(oid)) { + file->ptr = (char *)""; + file->size = 0; + } else { + file->ptr = read_object_file(oid, &type, + (unsigned long *)&file->size); + } + return 1; +} + +/* + * Receive diff-buffers from xdiff and concatenate them as + * needed across multiple callbacks. + * + * This is basically a copy of xdiff-interface.c/xdiff_outf(), + * ripped from git and modified to use globals instead of + * a special callback-struct. + */ +static char *diffbuf = NULL; +static int buflen = 0; + +static int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) +{ + int i; + + for (i = 0; i < nbuf; i++) { + if (mb[i].ptr[mb[i].size-1] != '\n') { + /* Incomplete line */ + diffbuf = xrealloc(diffbuf, buflen + mb[i].size); + memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); + buflen += mb[i].size; + continue; + } + + /* we have a complete line */ + if (!diffbuf) { + ((linediff_fn)priv)(mb[i].ptr, mb[i].size); + continue; + } + diffbuf = xrealloc(diffbuf, buflen + mb[i].size); + memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); + ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); + free(diffbuf); + diffbuf = NULL; + buflen = 0; + } + if (diffbuf) { + ((linediff_fn)priv)(diffbuf, buflen); + free(diffbuf); + diffbuf = NULL; + buflen = 0; + } + return 0; +} + +int cgit_diff_files(const struct object_id *old_oid, + const struct object_id *new_oid, unsigned long *old_size, + unsigned long *new_size, int *binary, int context, + int ignorews, linediff_fn fn) +{ + mmfile_t file1, file2; + xpparam_t diff_params; + xdemitconf_t emit_params; + xdemitcb_t emit_cb; + + if (!load_mmfile(&file1, old_oid) || !load_mmfile(&file2, new_oid)) + return 1; + + *old_size = file1.size; + *new_size = file2.size; + + if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || + (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { + *binary = 1; + if (file1.size) + free(file1.ptr); + if (file2.size) + free(file2.ptr); + return 0; + } + + memset(&diff_params, 0, sizeof(diff_params)); + memset(&emit_params, 0, sizeof(emit_params)); + memset(&emit_cb, 0, sizeof(emit_cb)); + diff_params.flags = XDF_NEED_MINIMAL; + if (ignorews) + diff_params.flags |= XDF_IGNORE_WHITESPACE; + emit_params.ctxlen = context > 0 ? context : 3; + emit_params.flags = XDL_EMIT_FUNCNAMES; + emit_cb.outf = filediff_cb; + emit_cb.priv = fn; + xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); + if (file1.size) + free(file1.ptr); + if (file2.size) + free(file2.ptr); + return 0; +} + +void cgit_diff_tree(const struct object_id *old_oid, + const struct object_id *new_oid, + filepair_fn fn, const char *prefix, int ignorews) +{ + struct diff_options opt; + struct pathspec_item item; + + memset(&item, 0, sizeof(item)); + diff_setup(&opt); + opt.output_format = DIFF_FORMAT_CALLBACK; + opt.detect_rename = 1; + opt.rename_limit = ctx.cfg.renamelimit; + opt.flags.recursive = 1; + if (ignorews) + DIFF_XDL_SET(&opt, IGNORE_WHITESPACE); + opt.format_callback = cgit_diff_tree_cb; + opt.format_callback_data = fn; + if (prefix) { + item.match = xstrdup(prefix); + item.len = strlen(prefix); + opt.pathspec.nr = 1; + opt.pathspec.items = &item; + } + diff_setup_done(&opt); + + if (old_oid && !is_null_oid(old_oid)) + diff_tree_oid(old_oid, new_oid, "", &opt); + else + diff_root_tree_oid(new_oid, "", &opt); + diffcore_std(&opt); + diff_flush(&opt); + + free(item.match); +} + +void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix) +{ + const struct object_id *old_oid = NULL; + + if (commit->parents) + old_oid = &commit->parents->item->object.oid; + cgit_diff_tree(old_oid, &commit->object.oid, fn, prefix, + ctx.qry.ignorews); +} + +int cgit_parse_snapshots_mask(const char *str) +{ + struct string_list tokens = STRING_LIST_INIT_DUP; + struct string_list_item *item; + const struct cgit_snapshot_format *f; + int rv = 0; + + /* favor legacy setting */ + if (atoi(str)) + return 1; + + if (strcmp(str, "all") == 0) + return INT_MAX; + + string_list_split(&tokens, str, ' ', -1); + string_list_remove_empty_items(&tokens, 0); + + for_each_string_list_item(item, &tokens) { + for (f = cgit_snapshot_formats; f->suffix; f++) { + if (!strcmp(item->string, f->suffix) || + !strcmp(item->string, f->suffix + 1)) { + rv |= cgit_snapshot_format_bit(f); + break; + } + } + } + + string_list_clear(&tokens, 0); + return rv; +} + +typedef struct { + char * name; + char * value; +} cgit_env_var; + +void cgit_prepare_repo_env(struct cgit_repo * repo) +{ + cgit_env_var env_vars[] = { + { .name = "CGIT_REPO_URL", .value = repo->url }, + { .name = "CGIT_REPO_NAME", .value = repo->name }, + { .name = "CGIT_REPO_PATH", .value = repo->path }, + { .name = "CGIT_REPO_OWNER", .value = repo->owner }, + { .name = "CGIT_REPO_DEFBRANCH", .value = repo->defbranch }, + { .name = "CGIT_REPO_SECTION", .value = repo->section }, + { .name = "CGIT_REPO_CLONE_URL", .value = repo->clone_url } + }; + int env_var_count = ARRAY_SIZE(env_vars); + cgit_env_var *p, *q; + static char *warn = "cgit warning: failed to set env: %s=%s\n"; + + p = env_vars; + q = p + env_var_count; + for (; p < q; p++) + if (p->value && setenv(p->name, p->value, 1)) + fprintf(stderr, warn, p->name, p->value); +} + +/* Read the content of the specified file into a newly allocated buffer, + * zeroterminate the buffer and return 0 on success, errno otherwise. + */ +int readfile(const char *path, char **buf, size_t *size) +{ + int fd, e; + struct stat st; + + fd = open(path, O_RDONLY); + if (fd == -1) + return errno; + if (fstat(fd, &st)) { + e = errno; + close(fd); + return e; + } + if (!S_ISREG(st.st_mode)) { + close(fd); + return EISDIR; + } + *buf = xmalloc(st.st_size + 1); + *size = read_in_full(fd, *buf, st.st_size); + e = errno; + (*buf)[*size] = '\0'; + close(fd); + return (*size == st.st_size ? 0 : e); +} + +static int is_token_char(char c) +{ + return isalnum(c) || c == '_'; +} + +/* Replace name with getenv(name), return pointer to zero-terminating char + */ +static char *expand_macro(char *name, int maxlength) +{ + char *value; + size_t len; + + len = 0; + value = getenv(name); + if (value) { + len = strlen(value) + 1; + if (len > maxlength) + len = maxlength; + strlcpy(name, value, len); + --len; + } + return name + len; +} + +#define EXPBUFSIZE (1024 * 8) + +/* Replace all tokens prefixed by '$' in the specified text with the + * value of the named environment variable. + * NB: the return value is a static buffer, i.e. it must be strdup'd + * by the caller. + */ +char *expand_macros(const char *txt) +{ + static char result[EXPBUFSIZE]; + char *p, *start; + int len; + + p = result; + start = NULL; + while (p < result + EXPBUFSIZE - 1 && txt && *txt) { + *p = *txt; + if (start) { + if (!is_token_char(*txt)) { + if (p - start > 0) { + *p = '\0'; + len = result + EXPBUFSIZE - start - 1; + p = expand_macro(start, len) - 1; + } + start = NULL; + txt--; + } + p++; + txt++; + continue; + } + if (*txt == '$') { + start = p; + txt++; + continue; + } + p++; + txt++; + } + *p = '\0'; + if (start && p - start > 0) { + len = result + EXPBUFSIZE - start - 1; + p = expand_macro(start, len); + *p = '\0'; + } + return result; +} + +char *get_mimetype_for_filename(const char *filename) +{ + char *ext, *mimetype, *token, line[1024], *saveptr; + FILE *file; + struct string_list_item *mime; + + if (!filename) + return NULL; + + ext = strrchr(filename, '.'); + if (!ext) + return NULL; + ++ext; + if (!ext[0]) + return NULL; + mime = string_list_lookup(&ctx.cfg.mimetypes, ext); + if (mime) + return xstrdup(mime->util); + + if (!ctx.cfg.mimetype_file) + return NULL; + file = fopen(ctx.cfg.mimetype_file, "r"); + if (!file) + return NULL; + while (fgets(line, sizeof(line), file)) { + if (!line[0] || line[0] == '#') + continue; + mimetype = strtok_r(line, " \t\r\n", &saveptr); + while ((token = strtok_r(NULL, " \t\r\n", &saveptr))) { + if (!strcasecmp(ext, token)) { + fclose(file); + return xstrdup(mimetype); + } + } + } + fclose(file); + return NULL; +} diff --git a/ui-shared_70.c b/ui-shared_70.c new file mode 100644 index 0000000..54ea099 --- /dev/null +++ b/ui-shared_70.c @@ -0,0 +1,40 @@ +/* +* +* Gopher-related functions +* +*/ + +#include <stdio.h> + +void cgit_gopher_selector(char type, char *str, char *sel, char *host, int port){ + + printf("%c%s\t%s\t%s\t%d\r\n", + type, str, sel, host, port); +} + + +void cgit_gopher_info(char *msg, char *host, int port){ + cgit_gopher_selector('i', msg, "", host, port); +} + +void cgit_gopher_menu(char *descr, char *sel, char *host, int port){ + + cgit_gopher_selector('1', descr, sel, host, port); +} + +void cgit_gopher_textfile(char *descr, char *sel, char *host, int port){ + + cgit_gopher_selector('0', descr, sel, host, port); +} + +void cgit_gopher_error(char *msg, char *host, int port){ + cgit_gopher_selector('3', msg, "Err", host, port); +} + +void cgit_tree_link_gopher(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path) +{ + + + +} diff --git a/ui-shared_70.h b/ui-shared_70.h new file mode 100644 index 0000000..3c5ef55 --- /dev/null +++ b/ui-shared_70.h @@ -0,0 +1,13 @@ +#ifndef GOPHER_H +#define GOPHER_H + +#define CGIT_GOPHER_HOST "localhost" +#define CGIT_GOPHER_PORT 1500 + +void cgit_gopher_selector(char type, char *str, char *sel, char *host, int port); +void cgit_gopher_info(char *msg, char *host, int port); +void cgit_gopher_menu(char *descr, char *sel, char *host, int port); +void cgit_gopher_textfile(char *descr, char *sel, char *host, int port); +void cgit_gopher_error(char *descr, char *host, int port); + +#endif /* GOPHER_H */ diff --git a/ui_70-refs.c b/ui_70-refs.c new file mode 100644 index 0000000..da18f8a --- /dev/null +++ b/ui_70-refs.c @@ -0,0 +1,224 @@ +/* ui-refs.c: browse symbolic refs + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-refs.h" +#include "html.h" +#include "ui_70-shared.h" + +static inline int cmp_age(int age1, int age2) +{ + /* age1 and age2 are assumed to be non-negative */ + return age2 - age1; +} + +static int cmp_ref_name(const void *a, const void *b) +{ + struct refinfo *r1 = *(struct refinfo **)a; + struct refinfo *r2 = *(struct refinfo **)b; + + return strcmp(r1->refname, r2->refname); +} + +static int cmp_branch_age(const void *a, const void *b) +{ + struct refinfo *r1 = *(struct refinfo **)a; + struct refinfo *r2 = *(struct refinfo **)b; + + return cmp_age(r1->commit->committer_date, r2->commit->committer_date); +} + +static int get_ref_age(struct refinfo *ref) +{ + if (!ref->object) + return 0; + switch (ref->object->type) { + case OBJ_TAG: + return ref->tag ? ref->tag->tagger_date : 0; + case OBJ_COMMIT: + return ref->commit ? ref->commit->committer_date : 0; + } + return 0; +} + +static int cmp_tag_age(const void *a, const void *b) +{ + struct refinfo *r1 = *(struct refinfo **)a; + struct refinfo *r2 = *(struct refinfo **)b; + + return cmp_age(get_ref_age(r1), get_ref_age(r2)); +} + +static int print_branch(struct refinfo *ref) +{ + struct commitinfo *info = ref->commit; + char *name = (char *)ref->refname; + + if (!info) + return 1; + cgit_gopher_start_selector(GOPHER_MENU); + cgit_gopher_text_pad(name, GOPHER_SUMMARY_NAME_LEN); + if (ref->object->type == OBJ_COMMIT) { + cgit_gopher_text_pad(info->subject, GOPHER_SUMMARY_DESC_LEN); + cgit_gopher_text_pad(info->author, GOPHER_SUMMARY_AUTH_LEN); + cgit_print_age(info->committer_date, info->committer_tz, -1); + } else { + html("</td><td></td><td>"); + cgit_object_link(ref->object); + } + cgit_gopher_text("\t"); + cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, + ctx.qry.showmsg, 0); + cgit_gopher_text("\t"); + cgit_gopher_end_selector(); + return 0; +} + + +static int print_tag(struct refinfo *ref) +{ + struct tag *tag = NULL; + struct taginfo *info = NULL; + char *name = (char *)ref->refname; + struct object *obj = ref->object; + + if (obj->type == OBJ_TAG) { + tag = (struct tag *)obj; + obj = tag->tagged; + info = ref->tag; + if (!info) + return 1; + } + + cgit_gopher_start_selector(GOPHER_MENU); + cgit_gopher_text_pad(name, GOPHER_SUMMARY_NAME_LEN); + cgit_gopher_text_pad("@@@ commit id here @@@", GOPHER_SUMMARY_DESC_LEN); + if (info) { + if (info->tagger) { + cgit_gopher_text_pad(info->tagger, GOPHER_SUMMARY_AUTH_LEN); + } + } else if (ref->object->type == OBJ_COMMIT) { + cgit_gopher_text_pad(ref->commit->author, GOPHER_SUMMARY_AUTH_LEN); + } + if (info) { + if (info->tagger_date > 0) + cgit_print_age(info->tagger_date, info->tagger_tz, -1); + } else if (ref->object->type == OBJ_COMMIT) { + cgit_print_age(ref->commit->commit->date, 0, -1); + } + cgit_gopher_text("\t"); + cgit_tag_link(name, NULL, NULL, name); + cgit_gopher_end_selector(); + return 0; +} + +static void print_refs_link(char *path) +{ + html("<tr class='nohover'><td colspan='5'>"); + cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); + html("</td></tr>"); +} + +void print_branch_header(void){ + + cgit_gopher_start_selector(GOPHER_INFO); + cgit_gopher_text_pad("Branch", GOPHER_SUMMARY_NAME_LEN); + cgit_gopher_text_pad("Commit message", GOPHER_SUMMARY_DESC_LEN); + cgit_gopher_text_pad("Author", GOPHER_SUMMARY_AUTH_LEN); + cgit_gopher_text_pad("Age", GOPHER_SUMMARY_AGE_LEN); + cgit_gopher_text("\t"); + cgit_gopher_selector_link("Err"); + cgit_gopher_end_selector(); + +} + + +void cgit_print_branches(int maxcount) +{ + struct reflist list; + int i; + + print_branch_header(); + list.refs = NULL; + list.alloc = list.count = 0; + for_each_branch_ref(cgit_refs_cb, &list); + if (ctx.repo->enable_remote_branches) + for_each_remote_ref(cgit_refs_cb, &list); + + if (maxcount == 0 || maxcount > list.count) + maxcount = list.count; + + qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); + if (ctx.repo->branch_sort == 0) + qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); + + for (i = 0; i < maxcount; i++) + print_branch(list.refs[i]); + + if (maxcount < list.count) + print_refs_link("heads"); + + cgit_free_reflist_inner(&list); +} + +static void print_tag_header(void){ + + cgit_gopher_start_selector(GOPHER_INFO); + cgit_gopher_text_pad("Tag", GOPHER_SUMMARY_NAME_LEN); + cgit_gopher_text_pad("Commit", GOPHER_SUMMARY_DESC_LEN); + cgit_gopher_text_pad("Author", GOPHER_SUMMARY_AUTH_LEN); + cgit_gopher_text_pad("Age", GOPHER_SUMMARY_AGE_LEN); + cgit_gopher_text("\t"); + cgit_gopher_selector_link("Err"); + cgit_gopher_end_selector(); + +} + + +void cgit_print_tags(int maxcount) +{ + struct reflist list; + int i; + + list.refs = NULL; + list.alloc = list.count = 0; + for_each_tag_ref(cgit_refs_cb, &list); + if (list.count == 0) + return; + qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); + if (!maxcount) + maxcount = list.count; + else if (maxcount > list.count) + maxcount = list.count; + print_tag_header(); + for (i = 0; i < maxcount; i++) + print_tag(list.refs[i]); + + if (maxcount < list.count) + print_refs_link("tags"); + + cgit_free_reflist_inner(&list); +} + +void cgit_print_refs(void) +{ + cgit_print_layout_start(); + html("<table class='list nowrap'>"); + + if (ctx.qry.path && starts_with(ctx.qry.path, "heads")) + cgit_print_branches(0); + else if (ctx.qry.path && starts_with(ctx.qry.path, "tags")) + cgit_print_tags(0); + else { + cgit_print_branches(0); + html("<tr class='nohover'><td colspan='5'> </td></tr>"); + cgit_print_tags(0); + } + html("</table>"); + cgit_print_layout_end(); +} diff --git a/ui_70-repolist.c b/ui_70-repolist.c new file mode 100644 index 0000000..e03acb5 --- /dev/null +++ b/ui_70-repolist.c @@ -0,0 +1,309 @@ +/* ui-repolist.c: functions for generating the repolist page + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-repolist.h" +#include "html.h" +#include "ui_70-shared.h" + +static time_t read_agefile(char *path) +{ + time_t result; + size_t size; + char *buf = NULL; + struct strbuf date_buf = STRBUF_INIT; + + if (readfile(path, &buf, &size)) { + free(buf); + return -1; + } + + if (parse_date(buf, &date_buf) == 0) + result = strtoul(date_buf.buf, NULL, 10); + else + result = 0; + free(buf); + strbuf_release(&date_buf); + return result; +} + +static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) +{ + struct strbuf path = STRBUF_INIT; + struct stat s; + struct cgit_repo *r = (struct cgit_repo *)repo; + + if (repo->mtime != -1) { + *mtime = repo->mtime; + return 1; + } + strbuf_addf(&path, "%s/%s", repo->path, ctx.cfg.agefile); + if (stat(path.buf, &s) == 0) { + *mtime = read_agefile(path.buf); + if (*mtime) { + r->mtime = *mtime; + goto end; + } + } + + strbuf_reset(&path); + strbuf_addf(&path, "%s/refs/heads/%s", repo->path, + repo->defbranch ? repo->defbranch : "master"); + if (stat(path.buf, &s) == 0) { + *mtime = s.st_mtime; + r->mtime = *mtime; + goto end; + } + + strbuf_reset(&path); + strbuf_addf(&path, "%s/%s", repo->path, "packed-refs"); + if (stat(path.buf, &s) == 0) { + *mtime = s.st_mtime; + r->mtime = *mtime; + goto end; + } + + *mtime = 0; + r->mtime = *mtime; +end: + strbuf_release(&path); + return (r->mtime != 0); +} + +static void print_modtime(struct cgit_repo *repo) +{ + time_t t; + if (get_repo_modtime(repo, &t)) + cgit_print_age(t, 0, -1); + else + cgit_gopher_text_pad("----", GOPHER_SUMMARY_DATE_LEN); +} + +static int is_match(struct cgit_repo *repo) +{ + if (!ctx.qry.search) + return 1; + if (repo->url && strcasestr(repo->url, ctx.qry.search)) + return 1; + if (repo->name && strcasestr(repo->name, ctx.qry.search)) + return 1; + if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) + return 1; + if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) + return 1; + return 0; +} + +static int is_in_url(struct cgit_repo *repo) +{ + if (!ctx.qry.url) + return 1; + if (repo->url && starts_with(repo->url, ctx.qry.url)) + return 1; + return 0; +} + +static int is_visible(struct cgit_repo *repo) +{ + if (repo->hide || repo->ignore) + return 0; + if (!(is_match(repo) && is_in_url(repo))) + return 0; + return 1; +} + +static int any_repos_visible(void) +{ + int i; + + for (i = 0; i < cgit_repolist.count; i++) { + if (is_visible(&cgit_repolist.repos[i])) + return 1; + } + return 0; +} + + +static void print_header(void) +{ + cgit_gopher_info("Repositories"); + cgit_gopher_info("____________"); + cgit_gopher_start_selector(GOPHER_INFO); + cgit_gopher_text_pad("Name", GOPHER_SUMMARY_NAME_LEN); + cgit_gopher_text_pad("Decription", GOPHER_SUMMARY_DESC_LEN); + cgit_gopher_text_pad("Modified", GOPHER_SUMMARY_DATE_LEN); + cgit_gopher_text("\t"); + cgit_gopher_selector_link("Err"); + cgit_gopher_end_selector(); +} + + + +static int cmp(const char *s1, const char *s2) +{ + if (s1 && s2) { + if (ctx.cfg.case_sensitive_sort) + return strcmp(s1, s2); + else + return strcasecmp(s1, s2); + } + if (s1 && !s2) + return -1; + if (s2 && !s1) + return 1; + return 0; +} + +static int sort_name(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->name, r2->name); +} + +static int sort_desc(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->desc, r2->desc); +} + +static int sort_owner(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->owner, r2->owner); +} + +static int sort_idle(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + time_t t1, t2; + + t1 = t2 = 0; + get_repo_modtime(r1, &t1); + get_repo_modtime(r2, &t2); + return t2 - t1; +} + +static int sort_section(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + int result; + + result = cmp(r1->section, r2->section); + if (!result) { + if (!strcmp(ctx.cfg.repository_sort, "age")) + result = sort_idle(r1, r2); + if (!result) + result = cmp(r1->name, r2->name); + } + return result; +} + +struct sortcolumn { + const char *name; + int (*fn)(const void *a, const void *b); +}; + +static const struct sortcolumn sortcolumn[] = { + {"section", sort_section}, + {"name", sort_name}, + {"desc", sort_desc}, + {"owner", sort_owner}, + {"idle", sort_idle}, + {NULL, NULL} +}; + +static int sort_repolist(char *field) +{ + const struct sortcolumn *column; + + for (column = &sortcolumn[0]; column->name; column++) { + if (strcmp(field, column->name)) + continue; + qsort(cgit_repolist.repos, cgit_repolist.count, + sizeof(struct cgit_repo), column->fn); + return 1; + } + return 0; +} + + +void cgit_print_repolist(void) +{ + int i, columns = 3, hits = 0; + char *last_section = NULL; + char *section; + int sorted = 0; + + if (!any_repos_visible()) { + cgit_gopher_error("No repositories found"); + return; + } + + if (ctx.cfg.enable_index_links) + ++columns; + if (ctx.cfg.enable_index_owner) + ++columns; + + ctx.page.title = ctx.cfg.root_title; + + if (ctx.qry.sort) + sorted = sort_repolist(ctx.qry.sort); + else if (ctx.cfg.section_sort) + sort_repolist("section"); + print_header(); + + for (i = 0; i < cgit_repolist.count; i++) { + ctx.repo = &cgit_repolist.repos[i]; + if (!is_visible(ctx.repo)) + continue; + hits++; + if (hits <= ctx.qry.ofs) + continue; + if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) + continue; + section = ctx.repo->section; + if (section && !strcmp(section, "")) + section = NULL; + if (!sorted && + ((last_section == NULL && section != NULL) || + (last_section != NULL && section == NULL) || + (last_section != NULL && section != NULL && + strcmp(section, last_section)))) { + cgit_gopher_info(section); + last_section = section; + } + + cgit_gopher_start_selector(GOPHER_MENU); + cgit_gopher_text_pad(ctx.repo->name, GOPHER_SUMMARY_NAME_LEN); + cgit_gopher_text_pad(ctx.repo->desc, GOPHER_SUMMARY_DESC_LEN); + print_modtime(ctx.repo); + cgit_gopher_text("\t"); + cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); + cgit_gopher_end_selector(); + } +} + +void cgit_print_site_readme(void) +{ + cgit_print_layout_start(); + if (!ctx.cfg.root_readme) + goto done; + cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme); + html_include(ctx.cfg.root_readme); + cgit_close_filter(ctx.cfg.about_filter); +done: + cgit_print_layout_end(); +} diff --git a/ui_70-shared.c b/ui_70-shared.c new file mode 100644 index 0000000..990143c --- /dev/null +++ b/ui_70-shared.c @@ -0,0 +1,1315 @@ +/* +* +* Gopher-related functions +* +*/ + +#include <stdio.h> +#include "cgit.h" +#include "html.h" +#include "ui_70-shared.h" + + +void cgit_gopher_selector(char type, char *str, char *sel, const char *host, const char *port){ + + printf("%c%s\t%s\t%s\t%s\r\n", + type, str, sel, host, port); +} + + +void cgit_gopher_info(char *msg){ + cgit_gopher_selector('i', msg, "Err", ctx.env.server_name, ctx.env.server_port); +} + +void cgit_gopher_menu(char *descr, char *sel){ + + cgit_gopher_selector('1', descr, sel, ctx.env.server_name, ctx.env.server_port); +} + +void cgit_gopher_textfile(char *descr, char *sel){ + + cgit_gopher_selector('0', descr, sel, ctx.env.server_name, ctx.env.server_port); +} + +void cgit_gopher_error(char *msg){ + cgit_gopher_selector('3', msg, "Err", ctx.env.server_name, ctx.env.server_port); +} + + +void cgit_gopher_start_selector(char type){ + + printf("%c", type); +} + +void cgit_gopher_selector_descr(const char *descr){ + + printf("%s\t", descr); +} + +void cgit_gopher_selector_link(const char *sel){ + + printf("%s\t", sel); +} + +void cgit_gopher_text(const char *txt){ + + printf("%s", txt); +} + + +void gopher_vtxtf(const char *format, va_list ap) +{ + va_list cp; + struct strbuf buf = STRBUF_INIT; + + va_copy(cp, ap); + strbuf_vaddf(&buf, format, cp); + va_end(cp); + printf(buf.buf); + strbuf_release(&buf); +} + +void cgit_gopher_textf(const char *fmt, va_list ap){ + + va_list cp; + va_copy(cp, ap); + gopher_vtxtf(fmt, cp); + va_end(cp); +} + + +void cgit_gopher_text_pad(const char *txt, int len){ + + static char fmt[80]; + int sz; + + memset(fmt, 0, 80 * sizeof(char)); + sz = snprintf(fmt, len, "%s", txt); + while(sz < len ){ + fmt[sz++] = GOPHER_PAD_CHAR; + } + fmt[sz] = '\0'; + printf(fmt); +} + + +void cgit_gopher_end_selector(){ + + printf("%s\t%s\r\n", ctx.env.server_name, ctx.env.server_port); +} + + +void cgit_tree_link_gopher(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path) +{ + + + +} + + + + +/* print an error message with format */ +void cgit_vprint_error(const char *fmt, va_list ap) +{ + va_list cp; + va_copy(cp, ap); + gopher_vtxtf(fmt, cp); + va_end(cp); +} + + +void cgit_print_error_page(int code, const char *msg, const char *fmt, ...) +{ + va_list ap; + ctx.page.expires = ctx.cfg.cache_dynamic_ttl; + ctx.page.status = code; + ctx.page.statusmsg = msg; + va_start(ap, fmt); + cgit_vprint_error(fmt, ap); + va_end(ap); +} + + +/* ui-shared.c: common web output functions + * + * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-shared.h" +#include "cmd.h" +#include "html.h" +#include "version.h" + +static const char cgit_doctype[] = +"<!DOCTYPE html>\n"; + +static char *http_date(time_t t) +{ + static char day[][4] = + {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + static char month[][4] = + {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + struct tm *tm = gmtime(&t); + return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], + tm->tm_mday, month[tm->tm_mon], 1900 + tm->tm_year, + tm->tm_hour, tm->tm_min, tm->tm_sec); +} + +void cgit_print_error(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + cgit_vprint_error(fmt, ap); + va_end(ap); +} + +/* +void cgit_vprint_error(const char *fmt, va_list ap) +{ + va_list cp; + html("<div class='error'>"); + va_copy(cp, ap); + html_vtxtf(fmt, cp); + va_end(cp); + html("</div>\n"); +} +*/ + +const char *cgit_httpscheme(void) +{ + if (ctx.env.https && !strcmp(ctx.env.https, "on")) + return "https://"; + else + return "http://"; +} + +char *cgit_hosturl(void) +{ + if (ctx.env.http_host) + return xstrdup(ctx.env.http_host); + if (!ctx.env.server_name) + return NULL; + if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) + return xstrdup(ctx.env.server_name); + return fmtalloc("%s:%s", ctx.env.server_name, ctx.env.server_port); +} + +char *cgit_currenturl(void) +{ + const char *root = cgit_rooturl(); + size_t len = strlen(root); + + if (!ctx.qry.url) + return xstrdup(root); + if (len && root[len - 1] == '/') + return fmtalloc("%s%s", root, ctx.qry.url); + return fmtalloc("%s/%s", root, ctx.qry.url); +} + +const char *cgit_rooturl(void) +{ + if (ctx.cfg.virtual_root) + return ctx.cfg.virtual_root; + else + return ctx.cfg.script_name; +} + +const char *cgit_loginurl(void) +{ + static const char *login_url; + if (!login_url) + login_url = fmtalloc("%s?p=login", cgit_rooturl()); + return login_url; +} + +char *cgit_repourl(const char *reponame) +{ + if (ctx.cfg.virtual_root) + return fmtalloc("%s%s/", ctx.cfg.virtual_root, reponame); + else + return fmtalloc("?r=%s", reponame); +} + +char *cgit_fileurl(const char *reponame, const char *pagename, + const char *filename, const char *query) +{ + struct strbuf sb = STRBUF_INIT; + char *delim; + + if (ctx.cfg.virtual_root) { + strbuf_addf(&sb, "%s%s/%s/%s", ctx.cfg.virtual_root, reponame, + pagename, (filename ? filename:"")); + delim = "?"; + } else { + strbuf_addf(&sb, "?url=%s/%s/%s", reponame, pagename, + (filename ? filename : "")); + delim = "&"; + } + if (query) + strbuf_addf(&sb, "%s%s", delim, query); + return strbuf_detach(&sb, NULL); +} + +char *cgit_pageurl(const char *reponame, const char *pagename, + const char *query) +{ + return cgit_fileurl(reponame, pagename, NULL, query); +} + +const char *cgit_repobasename(const char *reponame) +{ + /* I assume we don't need to store more than one repo basename */ + static char rvbuf[1024]; + int p; + const char *rv; + size_t len; + + len = strlcpy(rvbuf, reponame, sizeof(rvbuf)); + if (len >= sizeof(rvbuf)) + die("cgit_repobasename: truncated repository name '%s'", reponame); + p = len - 1; + /* strip trailing slashes */ + while (p && rvbuf[p] == '/') + rvbuf[p--] = '\0'; + /* strip trailing .git */ + if (p >= 3 && starts_with(&rvbuf[p-3], ".git")) { + p -= 3; + rvbuf[p--] = '\0'; + } + /* strip more trailing slashes if any */ + while (p && rvbuf[p] == '/') + rvbuf[p--] = '\0'; + /* find last slash in the remaining string */ + rv = strrchr(rvbuf, '/'); + if (rv) + return ++rv; + return rvbuf; +} + +const char *cgit_snapshot_prefix(const struct cgit_repo *repo) +{ + if (repo->snapshot_prefix) + return repo->snapshot_prefix; + + return cgit_repobasename(repo->url); +} + +static void site_url(const char *page, const char *search, const char *sort, int ofs, int always_root) +{ + char *delim = "?"; + + if (always_root || page) + html_attr(cgit_rooturl()); + else { + char *currenturl = cgit_currenturl(); + html_attr(currenturl); + free(currenturl); + } + + if (page) { + htmlf("?p=%s", page); + delim = "&"; + } + if (search) { + html(delim); + html("q="); + html_attr(search); + delim = "&"; + } + if (sort) { + html(delim); + html("s="); + html_attr(sort); + delim = "&"; + } + if (ofs) { + html(delim); + htmlf("ofs=%d", ofs); + } +} + +static void site_link(const char *page, const char *name, const char *title, + const char *class, const char *search, const char *sort, int ofs, int always_root) +{ + html("<a"); + if (title) { + html(" title='"); + html_attr(title); + html("'"); + } + if (class) { + html(" class='"); + html_attr(class); + html("'"); + } + html(" href='"); + site_url(page, search, sort, ofs, always_root); + html("'>"); + html_txt(name); + html("</a>"); +} + +void cgit_index_link(const char *name, const char *title, const char *class, + const char *pattern, const char *sort, int ofs, int always_root) +{ + site_link(NULL, name, title, class, pattern, sort, ofs, always_root); +} + + +/* +static char *repolink(const char *title, const char *class, const char *page, + const char *head, const char *path) +{ + char *delim = "?"; + + html("<a"); + if (title) { + html(" title='"); + html_attr(title); + html("'"); + } + if (class) { + html(" class='"); + html_attr(class); + html("'"); + } + html(" href='"); + if (ctx.cfg.virtual_root) { + html_url_path(ctx.cfg.virtual_root); + html_url_path(ctx.repo->url); + if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') + html("/"); + if (page) { + html_url_path(page); + html("/"); + if (path) + html_url_path(path); + } + } else { + html_url_path(ctx.cfg.script_name); + html("?url="); + html_url_arg(ctx.repo->url); + if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') + html("/"); + if (page) { + html_url_arg(page); + html("/"); + if (path) + html_url_arg(path); + } + delim = "&"; + } + if (head && ctx.repo->defbranch && strcmp(head, ctx.repo->defbranch)) { + html(delim); + html("h="); + html_url_arg(head); + delim = "&"; + } + return fmt("%s", delim); +} + +*/ + + +static char *repolink(const char *title, const char *class, const char *page, + const char *head, const char *path) +{ + char *delim = "?"; + + cgit_gopher_text("/"); + cgit_gopher_text(ctx.cfg.script_name); + cgit_gopher_text("?url="); + cgit_gopher_text(ctx.repo->url); + if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') + cgit_gopher_text("/"); + if (page) { + cgit_gopher_text(page); + cgit_gopher_text("/"); + if (path) + cgit_gopher_text(path); + } + delim = "&"; + if (head && ctx.repo->defbranch && strcmp(head, ctx.repo->defbranch)) { + cgit_gopher_text(delim); + cgit_gopher_text("h="); + cgit_gopher_text(head); + delim = "&"; + } + return fmt("%s", delim); +} + +static void reporevlink(const char *page, const char *name, const char *title, + const char *class, const char *head, const char *rev, + const char *path) +{ + char *delim; + + + delim = repolink(title, class, page, head, path); + if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { + cgit_gopher_text(delim); + cgit_gopher_text("id="); + cgit_gopher_text(rev); + } + cgit_gopher_text("\t"); +} + +void cgit_summary_link(const char *name, const char *title, const char *class, + const char *head) +{ + reporevlink(NULL, name, title, class, head, NULL, NULL); +} + +void cgit_tag_link(const char *name, const char *title, const char *class, + const char *tag) +{ + reporevlink("tag", name, title, class, tag, NULL, NULL); +} + +void cgit_tree_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path) +{ + reporevlink("tree", name, title, class, head, rev, path); +} + +void cgit_plain_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path) +{ + reporevlink("plain", name, title, class, head, rev, path); +} + +void cgit_blame_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path) +{ + reporevlink("blame", name, title, class, head, rev, path); +} + +void cgit_log_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path, + int ofs, const char *grep, const char *pattern, int showmsg, + int follow) +{ + char *delim; + + delim = repolink(title, class, "log", head, path); + if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) { + cgit_gopher_text(delim); + cgit_gopher_text("id="); + html_url_arg(rev); + delim = "&"; + } + if (grep && pattern) { + cgit_gopher_text(delim); + cgit_gopher_text("qt="); + html_url_arg(grep); + delim = "&"; + cgit_gopher_text(delim); + cgit_gopher_text("q="); + html_url_arg(pattern); + } + if (ofs > 0) { + cgit_gopher_text(delim); + cgit_gopher_text("ofs="); + cgit_gopher_textf("%d", ofs); + delim = "&"; + } + if (showmsg) { + cgit_gopher_text(delim); + cgit_gopher_text("showmsg=1"); + delim = "&"; + } + if (follow) { + cgit_gopher_text(delim); + cgit_gopher_text("follow=1"); + } +} + +void cgit_commit_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path) +{ + char *delim; + + delim = repolink(title, class, "commit", head, path); + if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) { + html(delim); + html("id="); + html_url_arg(rev); + delim = "&"; + } + if (ctx.qry.difftype) { + html(delim); + htmlf("dt=%d", ctx.qry.difftype); + delim = "&"; + } + if (ctx.qry.context > 0 && ctx.qry.context != 3) { + html(delim); + html("context="); + htmlf("%d", ctx.qry.context); + delim = "&"; + } + if (ctx.qry.ignorews) { + html(delim); + html("ignorews=1"); + delim = "&"; + } + if (ctx.qry.follow) { + html(delim); + html("follow=1"); + } + html("'>"); + if (name[0] != '\0') { + if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { + html_ntxt(name, ctx.cfg.max_msg_len - 3); + html("..."); + } else + html_txt(name); + } else + html_txt("(no commit message)"); + html("</a>"); +} + +void cgit_refs_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path) +{ + reporevlink("refs", name, title, class, head, rev, path); +} + +void cgit_snapshot_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, + const char *archivename) +{ + reporevlink("snapshot", name, title, class, head, rev, archivename); +} + +void cgit_diff_link(const char *name, const char *title, const char *class, + const char *head, const char *new_rev, const char *old_rev, + const char *path) +{ + char *delim; + + delim = repolink(title, class, "diff", head, path); + if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { + html(delim); + html("id="); + html_url_arg(new_rev); + delim = "&"; + } + if (old_rev) { + html(delim); + html("id2="); + html_url_arg(old_rev); + delim = "&"; + } + if (ctx.qry.difftype) { + html(delim); + htmlf("dt=%d", ctx.qry.difftype); + delim = "&"; + } + if (ctx.qry.context > 0 && ctx.qry.context != 3) { + html(delim); + html("context="); + htmlf("%d", ctx.qry.context); + delim = "&"; + } + if (ctx.qry.ignorews) { + html(delim); + html("ignorews=1"); + delim = "&"; + } + if (ctx.qry.follow) { + html(delim); + html("follow=1"); + } + html("'>"); + html_txt(name); + html("</a>"); +} + +void cgit_patch_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path) +{ + reporevlink("patch", name, title, class, head, rev, path); +} + +void cgit_stats_link(const char *name, const char *title, const char *class, + const char *head, const char *path) +{ + reporevlink("stats", name, title, class, head, NULL, path); +} + +static void cgit_self_link(char *name, const char *title, const char *class) +{ + if (!strcmp(ctx.qry.page, "repolist")) + cgit_index_link(name, title, class, ctx.qry.search, ctx.qry.sort, + ctx.qry.ofs, 1); + else if (!strcmp(ctx.qry.page, "summary")) + cgit_summary_link(name, title, class, ctx.qry.head); + else if (!strcmp(ctx.qry.page, "tag")) + cgit_tag_link(name, title, class, ctx.qry.has_sha1 ? + ctx.qry.sha1 : ctx.qry.head); + else if (!strcmp(ctx.qry.page, "tree")) + cgit_tree_link(name, title, class, ctx.qry.head, + ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.path); + else if (!strcmp(ctx.qry.page, "plain")) + cgit_plain_link(name, title, class, ctx.qry.head, + ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.path); + else if (!strcmp(ctx.qry.page, "blame")) + cgit_blame_link(name, title, class, ctx.qry.head, + ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.path); + else if (!strcmp(ctx.qry.page, "log")) + cgit_log_link(name, title, class, ctx.qry.head, + ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.path, ctx.qry.ofs, + ctx.qry.grep, ctx.qry.search, + ctx.qry.showmsg, ctx.qry.follow); + else if (!strcmp(ctx.qry.page, "commit")) + cgit_commit_link(name, title, class, ctx.qry.head, + ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.path); + else if (!strcmp(ctx.qry.page, "patch")) + cgit_patch_link(name, title, class, ctx.qry.head, + ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.path); + else if (!strcmp(ctx.qry.page, "refs")) + cgit_refs_link(name, title, class, ctx.qry.head, + ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.path); + else if (!strcmp(ctx.qry.page, "snapshot")) + cgit_snapshot_link(name, title, class, ctx.qry.head, + ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.path); + else if (!strcmp(ctx.qry.page, "diff")) + cgit_diff_link(name, title, class, ctx.qry.head, + ctx.qry.sha1, ctx.qry.sha2, + ctx.qry.path); + else if (!strcmp(ctx.qry.page, "stats")) + cgit_stats_link(name, title, class, ctx.qry.head, + ctx.qry.path); + else { + /* Don't known how to make link for this page */ + repolink(title, class, ctx.qry.page, ctx.qry.head, ctx.qry.path); + html("><!-- cgit_self_link() doesn't know how to make link for page '"); + html_txt(ctx.qry.page); + html("' -->"); + html_txt(name); + html("</a>"); + } +} + +void cgit_object_link(struct object *obj) +{ + char *page, *shortrev, *fullrev, *name; + + fullrev = oid_to_hex(&obj->oid); + shortrev = xstrdup(fullrev); + shortrev[10] = '\0'; + if (obj->type == OBJ_COMMIT) { + cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, + ctx.qry.head, fullrev, NULL); + return; + } else if (obj->type == OBJ_TREE) + page = "tree"; + else if (obj->type == OBJ_TAG) + page = "tag"; + else + page = "blob"; + name = fmt("%s %s...", type_name(obj->type), shortrev); + reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); +} + +static struct string_list_item *lookup_path(struct string_list *list, + const char *path) +{ + struct string_list_item *item; + + while (path && path[0]) { + if ((item = string_list_lookup(list, path))) + return item; + if (!(path = strchr(path, '/'))) + break; + path++; + } + return NULL; +} + +void cgit_submodule_link(const char *class, char *path, const char *rev) +{ + struct string_list *list; + struct string_list_item *item; + char tail, *dir; + size_t len; + + len = 0; + tail = 0; + list = &ctx.repo->submodules; + item = lookup_path(list, path); + if (!item) { + len = strlen(path); + tail = path[len - 1]; + if (tail == '/') { + path[len - 1] = 0; + item = lookup_path(list, path); + } + } + if (item || ctx.repo->module_link) { + html("<a "); + if (class) + htmlf("class='%s' ", class); + html("href='"); + if (item) { + html_attrf(item->util, rev); + } else { + dir = strrchr(path, '/'); + if (dir) + dir++; + else + dir = path; + html_attrf(ctx.repo->module_link, dir, rev); + } + html("'>"); + html_txt(path); + html("</a>"); + } else { + html("<span"); + if (class) + htmlf(" class='%s'", class); + html(">"); + html_txt(path); + html("</span>"); + } + html_txtf(" @ %.7s", rev); + if (item && tail) + path[len - 1] = tail; +} + +const struct date_mode *cgit_date_mode(enum date_mode_type type) +{ + static struct date_mode mode; + mode.type = type; + mode.local = ctx.cfg.local_time; + return &mode; +} + + +void cgit_print_age(time_t t, int tz, time_t max_relative) +{ + + if (!t) + return; + cgit_gopher_text_pad(show_date(t, tz, cgit_date_mode(DATE_ISO8601)), GOPHER_SUMMARY_DATE_LEN); + return; +} + +void cgit_print_http_headers(void) +{ + if (ctx.env.no_http && !strcmp(ctx.env.no_http, "1")) + return; + + if (ctx.page.status) + htmlf("Status: %d %s\n", ctx.page.status, ctx.page.statusmsg); + if (ctx.page.mimetype && ctx.page.charset) + htmlf("Content-Type: %s; charset=%s\n", ctx.page.mimetype, + ctx.page.charset); + else if (ctx.page.mimetype) + htmlf("Content-Type: %s\n", ctx.page.mimetype); + if (ctx.page.size) + htmlf("Content-Length: %zd\n", ctx.page.size); + if (ctx.page.filename) { + html("Content-Disposition: inline; filename=\""); + html_header_arg_in_quotes(ctx.page.filename); + html("\"\n"); + } + if (!ctx.env.authenticated) + html("Cache-Control: no-cache, no-store\n"); + htmlf("Last-Modified: %s\n", http_date(ctx.page.modified)); + htmlf("Expires: %s\n", http_date(ctx.page.expires)); + if (ctx.page.etag) + htmlf("ETag: \"%s\"\n", ctx.page.etag); + html("\n"); + if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) + exit(0); +} + +void cgit_redirect(const char *url, bool permanent) +{ + htmlf("Status: %d %s\n", permanent ? 301 : 302, permanent ? "Moved" : "Found"); + html("Location: "); + html_url_path(url); + html("\n\n"); +} + +static void print_rel_vcs_link(const char *url) +{ + html("<link rel='vcs-git' href='"); + html_attr(url); + html("' title='"); + html_attr(ctx.repo->name); + html(" Git repository'/>\n"); +} + +void cgit_print_docstart(void) +{ + char *host = cgit_hosturl(); + + if (ctx.cfg.embedded) { + if (ctx.cfg.header) + html_include(ctx.cfg.header); + return; + } + + html(cgit_doctype); + html("<html lang='en'>\n"); + html("<head>\n"); + html("<title>"); + html_txt(ctx.page.title); + html("</title>\n"); + htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); + if (ctx.cfg.robots && *ctx.cfg.robots) + htmlf("<meta name='robots' content='%s'/>\n", ctx.cfg.robots); + html("<link rel='stylesheet' type='text/css' href='"); + html_attr(ctx.cfg.css); + html("'/>\n"); + if (ctx.cfg.favicon) { + html("<link rel='shortcut icon' href='"); + html_attr(ctx.cfg.favicon); + html("'/>\n"); + } + if (host && ctx.repo && ctx.qry.head) { + char *fileurl; + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "h=%s", ctx.qry.head); + + html("<link rel='alternate' title='Atom feed' href='"); + html(cgit_httpscheme()); + html_attr(host); + fileurl = cgit_fileurl(ctx.repo->url, "atom", ctx.qry.vpath, + sb.buf); + html_attr(fileurl); + html("' type='application/atom+xml'/>\n"); + strbuf_release(&sb); + free(fileurl); + } + if (ctx.repo) + cgit_add_clone_urls(print_rel_vcs_link); + if (ctx.cfg.head_include) + html_include(ctx.cfg.head_include); + if (ctx.repo && ctx.repo->extra_head_content) + html(ctx.repo->extra_head_content); + html("</head>\n"); + html("<body>\n"); + if (ctx.cfg.header) + html_include(ctx.cfg.header); + free(host); +} + +void cgit_print_docend(void) +{ + html("</div> <!-- class=content -->\n"); + if (ctx.cfg.embedded) { + html("</div> <!-- id=cgit -->\n"); + if (ctx.cfg.footer) + html_include(ctx.cfg.footer); + return; + } + if (ctx.cfg.footer) + html_include(ctx.cfg.footer); + else { + htmlf("<div class='footer'>generated by <a href='https://git.zx2c4.com/cgit/about/'>cgit %s</a> " + "(<a href='https://git-scm.com/'>git %s</a>) at ", cgit_version, git_version_string); + html_txt(show_date(time(NULL), 0, cgit_date_mode(DATE_ISO8601))); + html("</div>\n"); + } + html("</div> <!-- id=cgit -->\n"); + html("</body>\n</html>\n"); +} + +/* +void cgit_print_error_page(int code, const char *msg, const char *fmt, ...) +{ + va_list ap; + ctx.page.expires = ctx.cfg.cache_dynamic_ttl; + ctx.page.status = code; + ctx.page.statusmsg = msg; + cgit_print_layout_start(); + va_start(ap, fmt); + cgit_vprint_error(fmt, ap); + va_end(ap); + cgit_print_layout_end(); +} +*/ + +void cgit_print_layout_start(void) +{ + cgit_print_http_headers(); + cgit_print_docstart(); + cgit_print_pageheader(); +} + +void cgit_print_layout_end(void) +{ + cgit_print_docend(); +} + +static void add_clone_urls(void (*fn)(const char *), char *txt, char *suffix) +{ + struct strbuf **url_list = strbuf_split_str(txt, ' ', 0); + int i; + + for (i = 0; url_list[i]; i++) { + strbuf_rtrim(url_list[i]); + if (url_list[i]->len == 0) + continue; + if (suffix && *suffix) + strbuf_addf(url_list[i], "/%s", suffix); + fn(url_list[i]->buf); + } + + strbuf_list_free(url_list); +} + +void cgit_add_clone_urls(void (*fn)(const char *)) +{ + if (ctx.repo->clone_url) + add_clone_urls(fn, expand_macros(ctx.repo->clone_url), NULL); + else if (ctx.cfg.clone_prefix) + add_clone_urls(fn, ctx.cfg.clone_prefix, ctx.repo->url); +} + +static int print_branch_option(const char *refname, const struct object_id *oid, + int flags, void *cb_data) +{ + char *name = (char *)refname; + html_option(name, name, ctx.qry.head); + return 0; +} + +void cgit_add_hidden_formfields(int incl_head, int incl_search, + const char *page) +{ + if (!ctx.cfg.virtual_root) { + struct strbuf url = STRBUF_INIT; + + strbuf_addf(&url, "%s/%s", ctx.qry.repo, page); + if (ctx.qry.vpath) + strbuf_addf(&url, "/%s", ctx.qry.vpath); + html_hidden("url", url.buf); + strbuf_release(&url); + } + + if (incl_head && ctx.qry.head && ctx.repo->defbranch && + strcmp(ctx.qry.head, ctx.repo->defbranch)) + html_hidden("h", ctx.qry.head); + + if (ctx.qry.sha1) + html_hidden("id", ctx.qry.sha1); + if (ctx.qry.sha2) + html_hidden("id2", ctx.qry.sha2); + if (ctx.qry.showmsg) + html_hidden("showmsg", "1"); + + if (incl_search) { + if (ctx.qry.grep) + html_hidden("qt", ctx.qry.grep); + if (ctx.qry.search) + html_hidden("q", ctx.qry.search); + } +} + +static const char *hc(const char *page) +{ + if (!ctx.qry.page) + return NULL; + + return strcmp(ctx.qry.page, page) ? NULL : "active"; +} + +static void cgit_print_path_crumbs(char *path) +{ + char *old_path = ctx.qry.path; + char *p = path, *q, *end = path + strlen(path); + + ctx.qry.path = NULL; + cgit_self_link("root", NULL, NULL); + ctx.qry.path = p = path; + while (p < end) { + if (!(q = strchr(p, '/'))) + q = end; + *q = '\0'; + html_txt("/"); + cgit_self_link(p, NULL, NULL); + if (q < end) + *q = '/'; + p = q + 1; + } + ctx.qry.path = old_path; +} + +static void print_header(void) +{ + char *logo = NULL, *logo_link = NULL; + + html("<table id='header'>\n"); + html("<tr>\n"); + + if (ctx.repo && ctx.repo->logo && *ctx.repo->logo) + logo = ctx.repo->logo; + else + logo = ctx.cfg.logo; + if (ctx.repo && ctx.repo->logo_link && *ctx.repo->logo_link) + logo_link = ctx.repo->logo_link; + else + logo_link = ctx.cfg.logo_link; + if (logo && *logo) { + html("<td class='logo' rowspan='2'><a href='"); + if (logo_link && *logo_link) + html_attr(logo_link); + else + html_attr(cgit_rooturl()); + html("'><img src='"); + html_attr(logo); + html("' alt='cgit logo'/></a></td>\n"); + } + + html("<td class='main'>"); + if (ctx.repo) { + cgit_index_link("index", NULL, NULL, NULL, NULL, 0, 1); + html(" : "); + cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); + if (ctx.env.authenticated) { + html("</td><td class='form'>"); + html("<form method='get'>\n"); + cgit_add_hidden_formfields(0, 1, ctx.qry.page); + html("<select name='h' onchange='this.form.submit();'>\n"); + for_each_branch_ref(print_branch_option, ctx.qry.head); + if (ctx.repo->enable_remote_branches) + for_each_remote_ref(print_branch_option, ctx.qry.head); + html("</select> "); + html("<input type='submit' value='switch'/>"); + html("</form>"); + } + } else + html_txt(ctx.cfg.root_title); + html("</td></tr>\n"); + + html("<tr><td class='sub'>"); + if (ctx.repo) { + html_txt(ctx.repo->desc); + html("</td><td class='sub right'>"); + html_txt(ctx.repo->owner); + } else { + if (ctx.cfg.root_desc) + html_txt(ctx.cfg.root_desc); + } + html("</td></tr></table>\n"); +} + +void cgit_print_pageheader(void) +{ + html("<div id='cgit'>"); + if (!ctx.env.authenticated || !ctx.cfg.noheader) + print_header(); + + html("<table class='tabs'><tr><td>\n"); + if (ctx.env.authenticated && ctx.repo) { + if (ctx.repo->readme.nr) + reporevlink("about", "about", NULL, + hc("about"), ctx.qry.head, NULL, + NULL); + cgit_summary_link("summary", NULL, hc("summary"), + ctx.qry.head); + cgit_refs_link("refs", NULL, hc("refs"), ctx.qry.head, + ctx.qry.sha1, NULL); + cgit_log_link("log", NULL, hc("log"), ctx.qry.head, + NULL, ctx.qry.vpath, 0, NULL, NULL, + ctx.qry.showmsg, ctx.qry.follow); + if (ctx.qry.page && !strcmp(ctx.qry.page, "blame")) + cgit_blame_link("blame", NULL, hc("blame"), ctx.qry.head, + ctx.qry.sha1, ctx.qry.vpath); + else + cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head, + ctx.qry.sha1, ctx.qry.vpath); + cgit_commit_link("commit", NULL, hc("commit"), + ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath); + cgit_diff_link("diff", NULL, hc("diff"), ctx.qry.head, + ctx.qry.sha1, ctx.qry.sha2, ctx.qry.vpath); + if (ctx.repo->max_stats) + cgit_stats_link("stats", NULL, hc("stats"), + ctx.qry.head, ctx.qry.vpath); + if (ctx.repo->homepage) { + html("<a href='"); + html_attr(ctx.repo->homepage); + html("'>homepage</a>"); + } + html("</td><td class='form'>"); + html("<form class='right' method='get' action='"); + if (ctx.cfg.virtual_root) { + char *fileurl = cgit_fileurl(ctx.qry.repo, "log", + ctx.qry.vpath, NULL); + html_url_path(fileurl); + free(fileurl); + } + html("'>\n"); + cgit_add_hidden_formfields(1, 0, "log"); + html("<select name='qt'>\n"); + html_option("grep", "log msg", ctx.qry.grep); + html_option("author", "author", ctx.qry.grep); + html_option("committer", "committer", ctx.qry.grep); + html_option("range", "range", ctx.qry.grep); + html("</select>\n"); + html("<input class='txt' type='search' size='10' name='q' value='"); + html_attr(ctx.qry.search); + html("'/>\n"); + html("<input type='submit' value='search'/>\n"); + html("</form>\n"); + } else if (ctx.env.authenticated) { + char *currenturl = cgit_currenturl(); + site_link(NULL, "index", NULL, hc("repolist"), NULL, NULL, 0, 1); + if (ctx.cfg.root_readme) + site_link("about", "about", NULL, hc("about"), + NULL, NULL, 0, 1); + html("</td><td class='form'>"); + html("<form method='get' action='"); + html_attr(currenturl); + html("'>\n"); + html("<input type='search' name='q' size='10' value='"); + html_attr(ctx.qry.search); + html("'/>\n"); + html("<input type='submit' value='search'/>\n"); + html("</form>"); + free(currenturl); + } + html("</td></tr></table>\n"); + if (ctx.env.authenticated && ctx.repo && ctx.qry.vpath) { + html("<div class='path'>"); + html("path: "); + cgit_print_path_crumbs(ctx.qry.vpath); + if (ctx.cfg.enable_follow_links && !strcmp(ctx.qry.page, "log")) { + html(" ("); + ctx.qry.follow = !ctx.qry.follow; + cgit_self_link(ctx.qry.follow ? "follow" : "unfollow", + NULL, NULL); + ctx.qry.follow = !ctx.qry.follow; + html(")"); + } + html("</div>"); + } + html("<div class='content'>"); +} + +void cgit_print_filemode(unsigned short mode) +{ + if (S_ISDIR(mode)) + html("d"); + else if (S_ISLNK(mode)) + html("l"); + else if (S_ISGITLINK(mode)) + html("m"); + else + html("-"); + html_fileperm(mode >> 6); + html_fileperm(mode >> 3); + html_fileperm(mode); +} + +void cgit_compose_snapshot_prefix(struct strbuf *filename, const char *base, + const char *ref) +{ + struct object_id oid; + + /* + * Prettify snapshot names by stripping leading "v" or "V" if the tag + * name starts with {v,V}[0-9] and the prettify mapping is injective, + * i.e. each stripped tag can be inverted without ambiguities. + */ + if (get_oid(fmt("refs/tags/%s", ref), &oid) == 0 && + (ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]) && + ((get_oid(fmt("refs/tags/%s", ref + 1), &oid) == 0) + + (get_oid(fmt("refs/tags/v%s", ref + 1), &oid) == 0) + + (get_oid(fmt("refs/tags/V%s", ref + 1), &oid) == 0) == 1)) + ref++; + + strbuf_addf(filename, "%s-%s", base, ref); +} + +void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, + const char *separator) +{ + const struct cgit_snapshot_format *f; + struct strbuf filename = STRBUF_INIT; + const char *basename; + size_t prefixlen; + + basename = cgit_snapshot_prefix(repo); + if (starts_with(ref, basename)) + strbuf_addstr(&filename, ref); + else + cgit_compose_snapshot_prefix(&filename, basename, ref); + + prefixlen = filename.len; + for (f = cgit_snapshot_formats; f->suffix; f++) { + if (!(repo->snapshots & cgit_snapshot_format_bit(f))) + continue; + strbuf_setlen(&filename, prefixlen); + strbuf_addstr(&filename, f->suffix); + cgit_snapshot_link(filename.buf, NULL, NULL, NULL, NULL, + filename.buf); + if (cgit_snapshot_get_sig(ref, f)) { + strbuf_addstr(&filename, ".asc"); + html(" ("); + cgit_snapshot_link("sig", NULL, NULL, NULL, NULL, + filename.buf); + html(")"); + } else if (starts_with(f->suffix, ".tar") && cgit_snapshot_get_sig(ref, &cgit_snapshot_formats[0])) { + strbuf_setlen(&filename, strlen(filename.buf) - strlen(f->suffix)); + strbuf_addstr(&filename, ".tar.asc"); + html(" ("); + cgit_snapshot_link("sig", NULL, NULL, NULL, NULL, + filename.buf); + html(")"); + } + html(separator); + } + strbuf_release(&filename); +} + +void cgit_set_title_from_path(const char *path) +{ + size_t path_len, path_index, path_last_end; + char *new_title; + + if (!path) + return; + + path_len = strlen(path); + new_title = xmalloc(path_len + 3 + strlen(ctx.page.title) + 1); + new_title[0] = '\0'; + + for (path_index = path_len, path_last_end = path_len; path_index-- > 0;) { + if (path[path_index] == '/') { + if (path_index == path_len - 1) { + path_last_end = path_index - 1; + continue; + } + strncat(new_title, &path[path_index + 1], path_last_end - path_index - 1); + strcat(new_title, "\\"); + path_last_end = path_index; + } + } + if (path_last_end) + strncat(new_title, path, path_last_end); + + strcat(new_title, " - "); + strcat(new_title, ctx.page.title); + ctx.page.title = new_title; + +} diff --git a/ui_70-shared.h b/ui_70-shared.h new file mode 100644 index 0000000..7dd2d35 --- /dev/null +++ b/ui_70-shared.h @@ -0,0 +1,130 @@ +#ifndef UI_70_SHARED_H +#define UI_70_SHARED_H + +#define GOPHER_TXT '0' +#define GOPHER_MENU '1' +#define GOPHER_CCSO '2' +#define GOPHER_ERR '3' +#define GOPHER_BINHX '4' +#define GOPHER_DOS '5' +#define GOPHER_UUENC '6' +#define GOPHER_SRCH '7' +#define GOPHER_TELN '8' +#define GOPHER_BIN '9' +#define GOPHER_IMG 'I' +#define GOPHER_3270 'T' +#define GOPHER_GIF 'g' +#define GOPHER_HTML 'h' +#define GOPHER_INFO 'i' +#define GOPHER_SND 's' +#define GOPHER_MIRR '+' + +#define GOPHER_SUMMARY_NAME_LEN 22 +#define GOPHER_SUMMARY_DESC_LEN 45 +#define GOPHER_SUMMARY_DATE_LEN 20 +#define GOPHER_SUMMARY_AUTH_LEN 20 +#define GOPHER_SUMMARY_AGE_LEN 10 +#define GOPHER_PAD_CHAR ' ' + + + +void cgit_gopher_selector(char type, char *str, char *sel, const char *host, const char * port); +void cgit_gopher_info(char *msg); +void cgit_gopher_menu(char *descr, char *sel); +void cgit_gopher_textfile(char *descr, char *sel); +void cgit_gopher_error(char *descr); + + +void cgit_gopher_start_selector(char type); +void cgit_gopher_selector_descr(const char *descr); +void cgit_gopher_selector_link(const char *sel); +void cgit_gopher_text(const char *txt); +void cgit_gopher_text_pad(const char *txt, int len); +void cgit_gopher_end_selector(); + + + +extern const char *cgit_httpscheme(void); +extern char *cgit_hosturl(void); +extern const char *cgit_rooturl(void); +extern char *cgit_currenturl(void); +extern const char *cgit_loginurl(void); +extern char *cgit_repourl(const char *reponame); +extern char *cgit_fileurl(const char *reponame, const char *pagename, + const char *filename, const char *query); +extern char *cgit_pageurl(const char *reponame, const char *pagename, + const char *query); + +extern void cgit_add_clone_urls(void (*fn)(const char *)); + +extern void cgit_index_link(const char *name, const char *title, + const char *class, const char *pattern, const char *sort, int ofs, int always_root); +extern void cgit_summary_link(const char *name, const char *title, + const char *class, const char *head); +extern void cgit_tag_link(const char *name, const char *title, + const char *class, const char *tag); +extern void cgit_tree_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_plain_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_blame_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_log_link(const char *name, const char *title, + const char *class, const char *head, const char *rev, + const char *path, int ofs, const char *grep, + const char *pattern, int showmsg, int follow); +extern void cgit_commit_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_patch_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_refs_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_snapshot_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *archivename); +extern void cgit_diff_link(const char *name, const char *title, + const char *class, const char *head, + const char *new_rev, const char *old_rev, + const char *path); +extern void cgit_stats_link(const char *name, const char *title, + const char *class, const char *head, + const char *path); +extern void cgit_object_link(struct object *obj); + +extern void cgit_submodule_link(const char *class, char *path, + const char *rev); + +extern void cgit_print_layout_start(void); +extern void cgit_print_layout_end(void); + +__attribute__((format (printf,1,2))) +extern void cgit_print_error(const char *fmt, ...); +__attribute__((format (printf,1,0))) +extern void cgit_vprint_error(const char *fmt, va_list ap); +extern const struct date_mode *cgit_date_mode(enum date_mode_type type); +extern void cgit_print_age(time_t t, int tz, time_t max_relative); +extern void cgit_print_http_headers(void); +extern void cgit_redirect(const char *url, bool permanent); +extern void cgit_print_docstart(void); +extern void cgit_print_docend(void); +__attribute__((format (printf,3,4))) +extern void cgit_print_error_page(int code, const char *msg, const char *fmt, ...); +extern void cgit_print_pageheader(void); +extern void cgit_print_filemode(unsigned short mode); +extern void cgit_compose_snapshot_prefix(struct strbuf *filename, + const char *base, const char *ref); +extern void cgit_print_snapshot_links(const struct cgit_repo *repo, + const char *ref, const char *separator); +extern const char *cgit_snapshot_prefix(const struct cgit_repo *repo); +extern void cgit_add_hidden_formfields(int incl_head, int incl_search, + const char *page); + +extern void cgit_set_title_from_path(const char *path); +#endif /* UI_70_SHARED_H */ + diff --git a/ui_70-summary.c b/ui_70-summary.c new file mode 100644 index 0000000..4140446 --- /dev/null +++ b/ui_70-summary.c @@ -0,0 +1,146 @@ +/* ui-summary.c: functions for generating repo summary page + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-summary.h" +#include "html.h" +#include "ui-blob.h" +#include "ui-log.h" +#include "ui-plain.h" +#include "ui-refs.h" +#include "ui-shared.h" + +static int urls; + +static void print_url(const char *url) +{ + int columns = 3; + + if (ctx.repo->enable_log_filecount) + columns++; + if (ctx.repo->enable_log_linecount) + columns++; + + if (urls++ == 0) { + htmlf("<tr class='nohover'><td colspan='%d'> </td></tr>", columns); + htmlf("<tr class='nohover'><th class='left' colspan='%d'>Clone</th></tr>\n", columns); + } + + htmlf("<tr><td colspan='%d'><a rel='vcs-git' href='", columns); + html_url_path(url); + html("' title='"); + html_attr(ctx.repo->name); + html(" Git repository'>"); + html_txt(url); + html("</a></td></tr>\n"); +} + +void cgit_print_summary(void) +{ + int columns = 3; + + if (ctx.repo->enable_log_filecount) + columns++; + if (ctx.repo->enable_log_linecount) + columns++; + + cgit_print_branches(ctx.cfg.summary_branches); + + cgit_print_tags(ctx.cfg.summary_tags); + if (ctx.cfg.summary_log > 0) { + htmlf("<tr class='nohover'><td colspan='%d'> </td></tr>", columns); + cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, + NULL, NULL, 0, 0, 0); + } + urls = 0; + cgit_add_clone_urls(print_url); + html("</table>"); + cgit_print_layout_end(); +} + +/* The caller must free the return value. */ +static char* append_readme_path(const char *filename, const char *ref, const char *path) +{ + char *file, *base_dir, *full_path, *resolved_base = NULL, *resolved_full = NULL; + /* If a subpath is specified for the about page, make it relative + * to the directory containing the configured readme. */ + + file = xstrdup(filename); + base_dir = dirname(file); + if (!strcmp(base_dir, ".") || !strcmp(base_dir, "..")) { + if (!ref) { + free(file); + return NULL; + } + full_path = xstrdup(path); + } else + full_path = fmtalloc("%s/%s", base_dir, path); + + if (!ref) { + resolved_base = realpath(base_dir, NULL); + resolved_full = realpath(full_path, NULL); + if (!resolved_base || !resolved_full || !starts_with(resolved_full, resolved_base)) { + free(full_path); + full_path = NULL; + } + } + + free(file); + free(resolved_base); + free(resolved_full); + + return full_path; +} + +void cgit_print_repo_readme(char *path) +{ + char *filename, *ref, *mimetype; + int free_filename = 0; + + mimetype = get_mimetype_for_filename(path); + if (mimetype && (!strncmp(mimetype, "image/", 6) || !strncmp(mimetype, "video/", 6))) { + ctx.page.mimetype = mimetype; + ctx.page.charset = NULL; + cgit_print_plain(); + free(mimetype); + return; + } + free(mimetype); + + cgit_print_layout_start(); + if (ctx.repo->readme.nr == 0) + goto done; + + filename = ctx.repo->readme.items[0].string; + ref = ctx.repo->readme.items[0].util; + + if (path) { + free_filename = 1; + filename = append_readme_path(filename, ref, path); + if (!filename) + goto done; + } + + /* Print the calculated readme, either from the git repo or from the + * filesystem, while applying the about-filter. + */ + html("<div id='summary'>"); + cgit_open_filter(ctx.repo->about_filter, filename); + if (ref) + cgit_print_file(filename, ref, 1); + else + html_include(filename); + cgit_close_filter(ctx.repo->about_filter); + + html("</div>"); + if (free_filename) + free(filename); + +done: + cgit_print_layout_end(); +} diff --git a/ui_70_repolist.c b/ui_70_repolist.c new file mode 100644 index 0000000..41424c0 --- /dev/null +++ b/ui_70_repolist.c @@ -0,0 +1,379 @@ +/* ui-repolist.c: functions for generating the repolist page + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-repolist.h" +#include "html.h" +#include "ui-shared.h" + +static time_t read_agefile(char *path) +{ + time_t result; + size_t size; + char *buf = NULL; + struct strbuf date_buf = STRBUF_INIT; + + if (readfile(path, &buf, &size)) { + free(buf); + return -1; + } + + if (parse_date(buf, &date_buf) == 0) + result = strtoul(date_buf.buf, NULL, 10); + else + result = 0; + free(buf); + strbuf_release(&date_buf); + return result; +} + +static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) +{ + struct strbuf path = STRBUF_INIT; + struct stat s; + struct cgit_repo *r = (struct cgit_repo *)repo; + + if (repo->mtime != -1) { + *mtime = repo->mtime; + return 1; + } + strbuf_addf(&path, "%s/%s", repo->path, ctx.cfg.agefile); + if (stat(path.buf, &s) == 0) { + *mtime = read_agefile(path.buf); + if (*mtime) { + r->mtime = *mtime; + goto end; + } + } + + strbuf_reset(&path); + strbuf_addf(&path, "%s/refs/heads/%s", repo->path, + repo->defbranch ? repo->defbranch : "master"); + if (stat(path.buf, &s) == 0) { + *mtime = s.st_mtime; + r->mtime = *mtime; + goto end; + } + + strbuf_reset(&path); + strbuf_addf(&path, "%s/%s", repo->path, "packed-refs"); + if (stat(path.buf, &s) == 0) { + *mtime = s.st_mtime; + r->mtime = *mtime; + goto end; + } + + *mtime = 0; + r->mtime = *mtime; +end: + strbuf_release(&path); + return (r->mtime != 0); +} + +static void print_modtime(struct cgit_repo *repo) +{ + time_t t; + if (get_repo_modtime(repo, &t)) + cgit_print_age(t, 0, -1); +} + +static int is_match(struct cgit_repo *repo) +{ + if (!ctx.qry.search) + return 1; + if (repo->url && strcasestr(repo->url, ctx.qry.search)) + return 1; + if (repo->name && strcasestr(repo->name, ctx.qry.search)) + return 1; + if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) + return 1; + if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) + return 1; + return 0; +} + +static int is_in_url(struct cgit_repo *repo) +{ + if (!ctx.qry.url) + return 1; + if (repo->url && starts_with(repo->url, ctx.qry.url)) + return 1; + return 0; +} + +static int is_visible(struct cgit_repo *repo) +{ + if (repo->hide || repo->ignore) + return 0; + if (!(is_match(repo) && is_in_url(repo))) + return 0; + return 1; +} + +static int any_repos_visible(void) +{ + int i; + + for (i = 0; i < cgit_repolist.count; i++) { + if (is_visible(&cgit_repolist.repos[i])) + return 1; + } + return 0; +} + +static void print_sort_header(const char *title, const char *sort) +{ + char *currenturl = cgit_currenturl(); + html("<th class='left'><a href='"); + html_attr(currenturl); + htmlf("?s=%s", sort); + if (ctx.qry.search) { + html("&q="); + html_url_arg(ctx.qry.search); + } + htmlf("'>%s</a></th>", title); + free(currenturl); +} + +static void print_header(void) +{ + html("<tr class='nohover'>"); + print_sort_header("Name", "name"); + print_sort_header("Description", "desc"); + if (ctx.cfg.enable_index_owner) + print_sort_header("Owner", "owner"); + print_sort_header("Idle", "idle"); + if (ctx.cfg.enable_index_links) + html("<th class='left'>Links</th>"); + html("</tr>\n"); +} + + +static void print_pager(int items, int pagelen, char *search, char *sort) +{ + int i, ofs; + char *class = NULL; + html("<ul class='pager'>"); + for (i = 0, ofs = 0; ofs < items; i++, ofs = i * pagelen) { + class = (ctx.qry.ofs == ofs) ? "current" : NULL; + html("<li>"); + cgit_index_link(fmt("[%d]", i + 1), fmt("Page %d", i + 1), + class, search, sort, ofs, 0); + html("</li>"); + } + html("</ul>"); +} + +static int cmp(const char *s1, const char *s2) +{ + if (s1 && s2) { + if (ctx.cfg.case_sensitive_sort) + return strcmp(s1, s2); + else + return strcasecmp(s1, s2); + } + if (s1 && !s2) + return -1; + if (s2 && !s1) + return 1; + return 0; +} + +static int sort_name(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->name, r2->name); +} + +static int sort_desc(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->desc, r2->desc); +} + +static int sort_owner(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + + return cmp(r1->owner, r2->owner); +} + +static int sort_idle(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + time_t t1, t2; + + t1 = t2 = 0; + get_repo_modtime(r1, &t1); + get_repo_modtime(r2, &t2); + return t2 - t1; +} + +static int sort_section(const void *a, const void *b) +{ + const struct cgit_repo *r1 = a; + const struct cgit_repo *r2 = b; + int result; + + result = cmp(r1->section, r2->section); + if (!result) { + if (!strcmp(ctx.cfg.repository_sort, "age")) + result = sort_idle(r1, r2); + if (!result) + result = cmp(r1->name, r2->name); + } + return result; +} + +struct sortcolumn { + const char *name; + int (*fn)(const void *a, const void *b); +}; + +static const struct sortcolumn sortcolumn[] = { + {"section", sort_section}, + {"name", sort_name}, + {"desc", sort_desc}, + {"owner", sort_owner}, + {"idle", sort_idle}, + {NULL, NULL} +}; + +static int sort_repolist(char *field) +{ + const struct sortcolumn *column; + + for (column = &sortcolumn[0]; column->name; column++) { + if (strcmp(field, column->name)) + continue; + qsort(cgit_repolist.repos, cgit_repolist.count, + sizeof(struct cgit_repo), column->fn); + return 1; + } + return 0; +} + + +void cgit_print_repolist(void) +{ + int i, columns = 3, hits = 0, header = 0; + char *last_section = NULL; + char *section; + char *repourl; + int sorted = 0; + + if (!any_repos_visible()) { + cgit_print_error_page(404, "Not found", "No repositories found"); + return; + } + + if (ctx.cfg.enable_index_links) + ++columns; + if (ctx.cfg.enable_index_owner) + ++columns; + + ctx.page.title = ctx.cfg.root_title; + cgit_print_http_headers(); + cgit_print_docstart(); + cgit_print_pageheader(); + + if (ctx.qry.sort) + sorted = sort_repolist(ctx.qry.sort); + else if (ctx.cfg.section_sort) + sort_repolist("section"); + + html("<table summary='repository list' class='list nowrap'>"); + for (i = 0; i < cgit_repolist.count; i++) { + ctx.repo = &cgit_repolist.repos[i]; + if (!is_visible(ctx.repo)) + continue; + hits++; + if (hits <= ctx.qry.ofs) + continue; + if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) + continue; + if (!header++) + print_header(); + section = ctx.repo->section; + if (section && !strcmp(section, "")) + section = NULL; + if (!sorted && + ((last_section == NULL && section != NULL) || + (last_section != NULL && section == NULL) || + (last_section != NULL && section != NULL && + strcmp(section, last_section)))) { + htmlf("<tr class='nohover-highlight'><td colspan='%d' class='reposection'>", + columns); + html_txt(section); + html("</td></tr>"); + last_section = section; + } + htmlf("<tr><td class='%s'>", + !sorted && section ? "sublevel-repo" : "toplevel-repo"); + cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); + html("</td><td>"); + repourl = cgit_repourl(ctx.repo->url); + html_link_open(repourl, NULL, NULL); + free(repourl); + if (html_ntxt(ctx.repo->desc, ctx.cfg.max_repodesc_len) < 0) + html("..."); + html_link_close(); + html("</td><td>"); + if (ctx.cfg.enable_index_owner) { + if (ctx.repo->owner_filter) { + cgit_open_filter(ctx.repo->owner_filter); + html_txt(ctx.repo->owner); + cgit_close_filter(ctx.repo->owner_filter); + } else { + char *currenturl = cgit_currenturl(); + html("<a href='"); + html_attr(currenturl); + html("?q="); + html_url_arg(ctx.repo->owner); + html("'>"); + html_txt(ctx.repo->owner); + html("</a>"); + free(currenturl); + } + html("</td><td>"); + } + print_modtime(ctx.repo); + html("</td>"); + if (ctx.cfg.enable_index_links) { + html("<td>"); + cgit_summary_link("summary", NULL, "button", NULL); + cgit_log_link("log", NULL, "button", NULL, NULL, NULL, + 0, NULL, NULL, ctx.qry.showmsg, 0); + cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); + html("</td>"); + } + html("</tr>\n"); + } + html("</table>"); + if (hits > ctx.cfg.max_repo_count) + print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search, ctx.qry.sort); + cgit_print_docend(); +} + +void cgit_print_site_readme(void) +{ + cgit_print_layout_start(); + if (!ctx.cfg.root_readme) + goto done; + cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme); + html_include(ctx.cfg.root_readme); + cgit_close_filter(ctx.cfg.about_filter); +done: + cgit_print_layout_end(); +} |