diff options
| author | KatolaZ <katolaz@freaknet.org> | 2018-07-26 15:02:48 +0100 | 
|---|---|---|
| committer | KatolaZ <katolaz@freaknet.org> | 2018-07-26 15:02:48 +0100 | 
| commit | bc2d48b8cd1c613fbad0feea71dd7739ae09513c (patch) | |
| tree | 8bafdd46c4a7d1d7ff1ab0a9907712a668ff4666 | |
| parent | 5897d950ec1fa084091b06b11a7dca96dc3253a4 (diff) | |
first commit on cgit_70 -- repolist, summary
| -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(); +} | 
