diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | AUTHORS | 13 | ||||
| -rw-r--r-- | AUTHORS_70 | 5 | ||||
| -rw-r--r-- | Makefile | 20 | ||||
| -rw-r--r-- | NOTES_70 | 41 | ||||
| -rw-r--r-- | README | 6 | ||||
| -rw-r--r-- | README_70 | 47 | ||||
| -rw-r--r-- | cgit-70.c | 1079 | ||||
| -rw-r--r-- | cgit.h | 2 | ||||
| -rw-r--r-- | cgit_70.mk | 155 | ||||
| -rw-r--r-- | cmd_70.c | 234 | ||||
| -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-commit.c | 171 | ||||
| -rw-r--r-- | ui_70-diff.c | 518 | ||||
| -rw-r--r-- | ui_70-log.c | 544 | ||||
| -rw-r--r-- | ui_70-patch.c | 92 | ||||
| -rw-r--r-- | ui_70-refs.c | 218 | ||||
| -rw-r--r-- | ui_70-repolist.c | 331 | ||||
| -rw-r--r-- | ui_70-shared.c | 1345 | ||||
| -rw-r--r-- | ui_70-shared.h | 137 | ||||
| -rw-r--r-- | ui_70-summary.c | 144 | ||||
| -rw-r--r-- | ui_70-tag.c | 131 | ||||
| -rw-r--r-- | ui_70-tree.c | 337 | ||||
| -rw-r--r-- | ui_70_repolist.c | 379 | 
26 files changed, 6571 insertions, 10 deletions
| @@ -1,5 +1,6 @@  # Files I don't care to see in git-status/commit  /cgit +/cgit-70  cgit.conf  CGIT-CFLAGS  VERSION @@ -1,7 +1,14 @@ -Maintainer: +cgit-70 is developed and maintained by  +        Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + +cgit-70 is a mod/fork of cgit, based on cgit source code. Details about +the maintainers and developers of cgit (to whom I am thankful) are +reported below:  + +cgit Maintainer:  	Jason A. Donenfeld <Jason@zx2c4.com> -Contributors: +cgit Contributors:  	Jason A. Donenfeld <Jason@zx2c4.com>  	Lukas Fleischer <cgit@cryptocrack.de>  	Johan Herland <johan@herland.net> @@ -9,5 +16,5 @@ Contributors:  	Ferry Huberts <ferry.huberts@pelagic.nl>  	John Keeping <john@keeping.me.uk> -Previous Maintainer: +cgit Previous Maintainer:  	Lars Hjemli <hjemli@gmail.com> diff --git a/AUTHORS_70 b/AUTHORS_70 new file mode 100644 index 0000000..75ad29b --- /dev/null +++ b/AUTHORS_70 @@ -0,0 +1,5 @@ +cgit-70 is a mod/fork of cgit developed and maintained by  +       Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + +See the file AUTHORS for more information about cgit developers and +maintainers. @@ -1,10 +1,10 @@  all::  CGIT_VERSION = v1.2.1 -CGIT_SCRIPT_NAME = cgit.cgi -CGIT_SCRIPT_PATH = /var/www/htdocs/cgit +CGIT_SCRIPT_NAME = cgit-70.cgi +CGIT_SCRIPT_PATH = /var/www/htdocs/cgit-70  CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) -CGIT_CONFIG = /etc/cgitrc +CGIT_CONFIG = /etc/cgit-70rc  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 @@ -147,7 +152,7 @@ $(DOC_PDF): %.pdf : %.txt  	a2x -f pdf cgitrc.5.txt  clean: clean-doc -	$(RM) cgit VERSION CGIT-CFLAGS *.o tags +	$(RM) cgit-70 VERSION CGIT-CFLAGS *.o tags  	$(RM) -r .deps  cleanall: clean @@ -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..8202bbc --- /dev/null +++ b/NOTES_70 @@ -0,0 +1,41 @@ +## 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. + +(20180729 - 6:20) + +  The PoC is complete. The cgit-70 cgi script implements all the basic  +  functionalities of cgit. I am sure more is to be done, but the result +  is not bad at all. @@ -1,3 +1,9 @@ +This is the original README for cgit. It is still mostly relevant for +cgit-70. More specific information about cgit-70 are available in  +README_70. + + +  cgit - CGI for Git  ================== diff --git a/README_70 b/README_70 new file mode 100644 index 0000000..3cf84dc --- /dev/null +++ b/README_70 @@ -0,0 +1,47 @@ +                               _ _   ____ ___ +                      ___ __ _(_) |_|___ / _ \ +                     / __/ _` | | __| / / | | | +                    | (_| (_| | | |_ / /| |_| | +                     \___\__, |_|\__|_/  \___/ +                         |___/ +                    --------------------------- + +cgit-70 is a mod/fork of cgit (https://git.zx2c4.com/cgit/), the popular +web-front-end to git. cgit-70 provides a dynamic interface to git +repositories over Gopher. I started working on a proof-of-concept of a +simple gopher interface to git, modifying a few functions in cgit, but +after the first bits fell nicely into place, I decided to keep going and +replace as much as possible of the web interface with corresponding +gopher-friendly stuff. The result is cgit-70. + +You can see cgit-70 in action at: + +  gopher://cgit.mine.nu/1/cgit-70.cgi + +and you can clone the repo at: + +  http://cgit.mine.nu/cgit-70 + +All you need to run cgit-70 is a gopher server that supports the same +CGI interface defined by geomyidae (http://git.r-36.net/geomyidae/). If +you like shell-stuff, you could also give a try to gosher +(http://cgit.mine.nu/gosher), which implements the same interface. + +                    **** DISCLAIMER **** + +cgit-70 is based on source code from cgit and is distributed under the +terms of the GNU General Public License v2. Please read the COPYING file +shipped with the sources. + +cgit is a rock-solid well-tested piece of software, but cgit-70 must be +considered alpha software. Use it at your own risk, and feel free to +send patches over if you find a bug (which is quite probable to happen, +at this stage). + +cgit-70 is based on cgit. The cgit-70 files modified from the cgit +originals are: +        (C) 2006-2018 cgit Development Team <cgit@lists.zx2c4.com> +        (C) 2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + +cgit is (C) 2006-2018 cgit Development Team <cgit@lists.zx2c4.com> + diff --git a/cgit-70.c b/cgit-70.c new file mode 100644 index 0000000..714ac1c --- /dev/null +++ b/cgit-70.c @@ -0,0 +1,1079 @@ +/* cgit.c: cgi for the git scm + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * 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_70-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); +		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 = "cgit-70 -- Git repository browser over Gopher"; +	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_gopher_error("Invalid request"); +		return; +	} + +	if (!ctx.cfg.enable_http_clone && cmd->is_clone) { +		ctx.page.title = "cgit error"; +		cgit_gopher_error("Invalid request"); +		return; +	} + +	if (cmd->want_repo && !ctx.repo) { +		cgit_gopher_error("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); +	switch (++argc){ +		case 5: +			ctx.env.server_port = xstrdup(argv[4]); +#ifdef DEBUG_GOPHER +			fprintf(stdout, "i  -- port: %s\n", ctx.env.server_port); +#endif +		case 4: +			ctx.env.server_name = xstrdup(argv[3]); +#ifdef DEBUG_GOPHER +			fprintf(stdout, "i  -- hostname: %s\n", ctx.env.server_name); +#endif +		case 3: +			if (strlen(argv[2])){ +				ctx.env.query_string = xstrdup(argv[2]); +				ctx.qry.raw = xstrdup(argv[2]); +			} +#ifdef DEBUG_GOPHER +			fprintf(stdout, "i  -- query_string: '%s'\n", ctx.env.query_string); +#endif +		case 2: +			if (strlen(argv[1])){ +				ctx.env.gopher_search = xstrdup(argv[1]); +			} +#ifdef DEBUG_GOPHER +			fprintf(stdout, "i  -- gopher_search: %s\n", ctx.env.gopher_search); +#endif +		case 1: +			ctx.env.script_name = xstrdup(argv[0]); +#ifdef DEBUG_GOPHER +			fprintf(stdout, "i  -- script_name: %s\n", ctx.env.script_name); +#endif +	}	 +	 +} + +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; +#ifdef DEBUG_GOPHER	 +	fprintf(stdout, "i -- cmd_main -- ctx.qry.raw: %s\n", ctx.qry.raw); +#endif +	http_parse_querystring(ctx.qry.raw, querystring_cb); +	 +#ifdef DEBUG_GOPHER	 +	fprintf(stdout, "i -- cmd_main -- got url: %s\n", ctx.qry.url);	 +#endif + +	/* 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; +} @@ -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.mk b/cgit_70.mk new file mode 100644 index 0000000..0d05ec3 --- /dev/null +++ b/cgit_70.mk @@ -0,0 +1,155 @@ +# 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_70-commit.o +##CGIT_OBJ_NAMES += ui-diff.o +CGIT_OBJ_NAMES += ui_70-diff.o +##CGIT_OBJ_NAMES += ui-log.o +CGIT_OBJ_NAMES += ui_70-log.o +##CGIT_OBJ_NAMES += ui-patch.o +CGIT_OBJ_NAMES += ui_70-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_70-tag.o +##CGIT_OBJ_NAMES += ui-tree.o +CGIT_OBJ_NAMES += ui_70-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..7e33c0b --- /dev/null +++ b/cmd_70.c @@ -0,0 +1,234 @@ +/* cmd.c: the cgit command dispatcher + * + * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * Licensed under GNU General Public License v2 + *   (see COPYING for full license text) + */ + +#include "cgit.h" +#include "cmd.h" +#include "cache.h" +#include "ui_70-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_gopher_error("Not implemented"); +	return; +/*	cgit_clone_head();*/ +} + +static void atom_fn(void) +{ +	cgit_gopher_error("Not implemented"); +	return; +/*	cgit_print_atom(ctx.qry.head, ctx.qry.path, ctx.cfg.max_atom_items);*/ +} + +static void about_fn(void) +{ +	cgit_gopher_error("Not implemented"); +	return; +/* +	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) +{ +	cgit_gopher_error("Not implemented"); +	return; +/* +	if (ctx.cfg.enable_blame) +		cgit_print_blame(); +	else +		cgit_print_error_page(403, "Forbidden", "Blame is disabled"); +*/ +} + +static void blob_fn(void) +{ +	cgit_gopher_error("Not implemented"); +	return; +/*	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_gopher_error("Not implemented"); +	return; +/*	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_gopher_error("Not implemented"); +	return; +/*	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_gopher_error("Not implemented"); +	return; +/*	cgit_print_snapshot(ctx.qry.head, ctx.qry.sha1, ctx.qry.path, ctx.qry.nohead);*/ +} + +static void stats_fn(void) +{ +	cgit_gopher_error("Not implemented"); +	return; +/*	cgit_show_stats();*/ +} + +static void summary_fn(void) +{ +#ifdef DEBUG_GOPHER +	fprintf(stderr, "   ---- selected function: cgit_print_summary\n"); +#endif +	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; +} 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-commit.c b/ui_70-commit.c new file mode 100644 index 0000000..d7b5f9d --- /dev/null +++ b/ui_70-commit.c @@ -0,0 +1,171 @@ +/* ui-commit.c: generate commit view + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * Licensed under GNU General Public License v2 + *   (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-commit.h" +#include "html.h" +#include "ui_70-shared.h" +#include "ui-diff.h" +#include "ui-log.h" + +void cgit_print_commit(char *hex, const char *prefix) +{ +	struct commit *commit, *parent; +	struct commitinfo *info, *parent_info; +	struct commit_list *p; +	struct strbuf notes = STRBUF_INIT; +	struct object_id oid; +	char *tmp, *tmp2; +	int parents = 0; + +	if (!hex) +		hex = ctx.qry.head; + +	if (get_oid(hex, &oid)) { +		cgit_gopher_error("Bad object id"); +		return; +	} +	commit = lookup_commit_reference(&oid); +	if (!commit) { +		cgit_gopher_error("Bad commit reference"); +		return; +	} +	info = cgit_parse_commit(commit); + +	/*format_display_notes(&oid, ¬es, PAGE_ENCODING, 0);*/ + +	load_ref_decorations(NULL, DECORATE_FULL_REFS); + +	cgit_print_layout_start(); +	/*cgit_print_diff_ctrls();*/ + +	cgit_gopher_start_selector(GOPHER_INFO); +	cgit_gopher_text("author: "); +	cgit_gopher_text(info->author); +	if (!ctx.cfg.noplainemail) { +		cgit_gopher_text(" "); +		cgit_gopher_text(info->author_email); +		cgit_gopher_text(" "); +	} +	cgit_gopher_text(show_date(info->author_date, info->author_tz, +				cgit_date_mode(DATE_ISO8601))); +	cgit_gopher_tab(); +	cgit_gopher_selector_link("Err"); +	cgit_gopher_end_selector(); + +	cgit_gopher_start_selector(GOPHER_INFO); +	cgit_gopher_text("committer: "); +	cgit_gopher_text(info->committer); +	if (!ctx.cfg.noplainemail) { +		cgit_gopher_text(" "); +		cgit_gopher_text(info->committer_email); +		cgit_gopher_text(" "); +	} +	cgit_gopher_text(show_date(info->committer_date, info->committer_tz, +				cgit_date_mode(DATE_ISO8601))); +	cgit_gopher_tab(); +	cgit_gopher_selector_link("Err"); +	cgit_gopher_end_selector(); +	 +	cgit_gopher_start_selector(GOPHER_MENU); +	cgit_gopher_text("commit "); +	tmp = oid_to_hex(&commit->object.oid); +	cgit_gopher_text(tmp); +	cgit_gopher_tab(); +	cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix); +	cgit_gopher_end_selector(); + +	cgit_gopher_start_selector(GOPHER_TXT); +	cgit_gopher_text("patch "); +	cgit_gopher_tab(); +	cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix); +	cgit_gopher_end_selector(); +	 +	cgit_gopher_start_selector(GOPHER_MENU); +	cgit_gopher_text("tree "); +	cgit_gopher_tab(); +	tmp = xstrdup(hex); +	cgit_tree_link(oid_to_hex(&commit->maybe_tree->object.oid), NULL, NULL, +		       ctx.qry.head, tmp, NULL); +	cgit_gopher_end_selector(); + +/*	if (prefix) { +		html(" /"); +		cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix); +	} +*/	free(tmp); +	 +	for (p = commit->parents; p; p = p->next) { +		parent = lookup_commit_reference(&p->item->object.oid); +		if (!parent) { +			cgit_gopher_info("Error reading parent commit"); +			continue; +		} +		 +		cgit_gopher_start_selector(GOPHER_MENU); +		cgit_gopher_text("parent "); +		cgit_gopher_tab(); +		tmp = tmp2 = oid_to_hex(&p->item->object.oid); +		if (ctx.repo->enable_subject_links) { +			parent_info = cgit_parse_commit(parent); +			tmp2 = parent_info->subject; +		} +		cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix); +		cgit_gopher_end_selector(); +		/*cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, +			       oid_to_hex(&p->item->object.oid), prefix); +		html(")</td></tr>"); +		*/ +		parents++; +	} +	/* +	if (ctx.repo->snapshots) { +		html("<tr><th>download</th><td colspan='2' class='sha1'>"); +		cgit_print_snapshot_links(ctx.repo, hex, "<br/>"); +		html("</td></tr>"); +	} +	*/ +	cgit_gopher_start_selector(GOPHER_INFO); +	cgit_gopher_text("subject: "); +	cgit_gopher_text(info->subject); +	cgit_gopher_tab(); +	cgit_gopher_selector_link("Err"); +	cgit_gopher_end_selector(); + +	if (strlen(info->msg)){	 +		cgit_gopher_start_selector(GOPHER_INFO); +		cgit_gopher_text("message: "); +		cgit_gopher_text(info->msg); +		cgit_gopher_tab(); +		cgit_gopher_selector_link("Err"); +		cgit_gopher_end_selector(); +	} +	 +	/* FIXME: NOTES HAVE BEEN DISABLED*/	 +/*	if (notes.len != 0) { +		html("<div class='notes-header'>Notes</div>"); +		html("<div class='notes'>"); +		cgit_open_filter(ctx.repo->commit_filter); +		html_txt(notes.buf); +		cgit_close_filter(ctx.repo->commit_filter); +		html("</div>"); +		html("<div class='notes-footer'></div>"); +	}*/ +/*	if (parents < 3) { +		if (parents) +			tmp = oid_to_hex(&commit->parents->item->object.oid); +		else +			tmp = NULL; +		cgit_print_diff(ctx.qry.sha1, tmp, prefix, 0, 0); +	} +*/ +	strbuf_release(¬es); +	cgit_free_commitinfo(info); +	cgit_print_layout_end(); +} diff --git a/ui_70-diff.c b/ui_70-diff.c new file mode 100644 index 0000000..2fe52b9 --- /dev/null +++ b/ui_70-diff.c @@ -0,0 +1,518 @@ +/* ui-diff.c: show diff between two blobs + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * Licensed under GNU General Public License v2 + *   (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-diff.h" +#include "html.h" +#include "ui_70-shared.h" +#include "ui-ssdiff.h" + +struct object_id old_rev_oid[1]; +struct object_id new_rev_oid[1]; + +static int files, slots; +static int total_adds, total_rems, max_changes; +static int lines_added, lines_removed; + +static struct fileinfo { +	char status; +	struct object_id old_oid[1]; +	struct object_id new_oid[1]; +	unsigned short old_mode; +	unsigned short new_mode; +	char *old_path; +	char *new_path; +	unsigned int added; +	unsigned int removed; +	unsigned long old_size; +	unsigned long new_size; +	unsigned int binary:1; +} *items; + +static int use_ssdiff = 0; +static struct diff_filepair *current_filepair; +static const char *current_prefix; + +struct diff_filespec *cgit_get_current_old_file(void) +{ +	return current_filepair->one; +} + +struct diff_filespec *cgit_get_current_new_file(void) +{ +	return current_filepair->two; +} + +static void print_fileinfo(struct fileinfo *info) +{ +	char *class; + +	switch (info->status) { +	case DIFF_STATUS_ADDED: +		class = "add"; +		break; +	case DIFF_STATUS_COPIED: +		class = "cpy"; +		break; +	case DIFF_STATUS_DELETED: +		class = "del"; +		break; +	case DIFF_STATUS_MODIFIED: +		class = "upd"; +		break; +	case DIFF_STATUS_RENAMED: +		class = "mov"; +		break; +	case DIFF_STATUS_TYPE_CHANGED: +		class = "typ"; +		break; +	case DIFF_STATUS_UNKNOWN: +		class = "unk"; +		break; +	case DIFF_STATUS_UNMERGED: +		class = "stg"; +		break; +	default: +		die("bug: unhandled diff status %c", info->status); +	} + +	html("<tr>"); +	htmlf("<td class='mode'>"); +	if (is_null_oid(info->new_oid)) { +		cgit_print_filemode(info->old_mode); +	} else { +		cgit_print_filemode(info->new_mode); +	} + +	if (info->old_mode != info->new_mode && +	    !is_null_oid(info->old_oid) && +	    !is_null_oid(info->new_oid)) { +		html("<span class='modechange'>["); +		cgit_print_filemode(info->old_mode); +		html("]</span>"); +	} +	htmlf("</td><td class='%s'>", class); +	cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, +		       ctx.qry.sha2, info->new_path); +	if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) { +		htmlf(" (%s from ", +		      info->status == DIFF_STATUS_COPIED ? "copied" : "renamed"); +		html_txt(info->old_path); +		html(")"); +	} +	html("</td><td class='right'>"); +	if (info->binary) { +		htmlf("bin</td><td class='graph'>%ld -> %ld bytes", +		      info->old_size, info->new_size); +		return; +	} +	htmlf("%d", info->added + info->removed); +	html("</td><td class='graph'>"); +	htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); +	htmlf("<td class='add' style='width: %.1f%%;'/>", +	      info->added * 100.0 / max_changes); +	htmlf("<td class='rem' style='width: %.1f%%;'/>", +	      info->removed * 100.0 / max_changes); +	htmlf("<td class='none' style='width: %.1f%%;'/>", +	      (max_changes - info->removed - info->added) * 100.0 / max_changes); +	html("</tr></table></td></tr>\n"); +} + +static void count_diff_lines(char *line, int len) +{ +	if (line && (len > 0)) { +		if (line[0] == '+') +			lines_added++; +		else if (line[0] == '-') +			lines_removed++; +	} +} + +static int show_filepair(struct diff_filepair *pair) +{ +	/* Always show if we have no limiting prefix. */ +	if (!current_prefix) +		return 1; + +	/* Show if either path in the pair begins with the prefix. */ +	if (starts_with(pair->one->path, current_prefix) || +	    starts_with(pair->two->path, current_prefix)) +		return 1; + +	/* Otherwise we don't want to show this filepair. */ +	return 0; +} + +static void inspect_filepair(struct diff_filepair *pair) +{ +	int binary = 0; +	unsigned long old_size = 0; +	unsigned long new_size = 0; + +	if (!show_filepair(pair)) +		return; + +	files++; +	lines_added = 0; +	lines_removed = 0; +	cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size, &new_size, +			&binary, 0, ctx.qry.ignorews, count_diff_lines); +	if (files >= slots) { +		if (slots == 0) +			slots = 4; +		else +			slots = slots * 2; +		items = xrealloc(items, slots * sizeof(struct fileinfo)); +	} +	items[files-1].status = pair->status; +	oidcpy(items[files-1].old_oid, &pair->one->oid); +	oidcpy(items[files-1].new_oid, &pair->two->oid); +	items[files-1].old_mode = pair->one->mode; +	items[files-1].new_mode = pair->two->mode; +	items[files-1].old_path = xstrdup(pair->one->path); +	items[files-1].new_path = xstrdup(pair->two->path); +	items[files-1].added = lines_added; +	items[files-1].removed = lines_removed; +	items[files-1].old_size = old_size; +	items[files-1].new_size = new_size; +	items[files-1].binary = binary; +	if (lines_added + lines_removed > max_changes) +		max_changes = lines_added + lines_removed; +	total_adds += lines_added; +	total_rems += lines_removed; +} + +static void cgit_print_diffstat(const struct object_id *old_oid, +				const struct object_id *new_oid, +				const char *prefix) +{ +	int i; + +	html("<div class='diffstat-header'>"); +	cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, +		       ctx.qry.sha2, NULL); +	if (prefix) { +		html(" (limited to '"); +		html_txt(prefix); +		html("')"); +	} +	html("</div>"); +	html("<table summary='diffstat' class='diffstat'>"); +	max_changes = 0; +	cgit_diff_tree(old_oid, new_oid, inspect_filepair, prefix, +		       ctx.qry.ignorews); +	for (i = 0; i<files; i++) +		print_fileinfo(&items[i]); +	html("</table>"); +	html("<div class='diffstat-summary'>"); +	htmlf("%d files changed, %d insertions, %d deletions", +	      files, total_adds, total_rems); +	html("</div>"); +} + + +/* + * print a single line returned from xdiff + */ +static void print_line(char *line, int len) +{ +	char *class = "ctx"; +	char c = line[len-1]; + +	if (line[0] == '+') +		class = "add"; +	else if (line[0] == '-') +		class = "del"; +	else if (line[0] == '@') +		class = "hunk"; + +	htmlf("<div class='%s'>", class); +	line[len-1] = '\0'; +	html_txt(line); +	html("</div>"); +	line[len-1] = c; +} + +static void header(const struct object_id *oid1, char *path1, int mode1, +		   const struct object_id *oid2, char *path2, int mode2) +{ +	char *abbrev1, *abbrev2; +	int subproject; + +	subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); +	html("<div class='head'>"); +	html("diff --git a/"); +	html_txt(path1); +	html(" b/"); +	html_txt(path2); + +	if (mode1 == 0) +		htmlf("<br/>new file mode %.6o", mode2); + +	if (mode2 == 0) +		htmlf("<br/>deleted file mode %.6o", mode1); + +	if (!subproject) { +		abbrev1 = xstrdup(find_unique_abbrev(oid1, DEFAULT_ABBREV)); +		abbrev2 = xstrdup(find_unique_abbrev(oid2, DEFAULT_ABBREV)); +		htmlf("<br/>index %s..%s", abbrev1, abbrev2); +		free(abbrev1); +		free(abbrev2); +		if (mode1 != 0 && mode2 != 0) { +			htmlf(" %.6o", mode1); +			if (mode2 != mode1) +				htmlf("..%.6o", mode2); +		} +		if (is_null_oid(oid1)) { +			path1 = "dev/null"; +			html("<br/>--- /"); +		} else +			html("<br/>--- a/"); +		if (mode1 != 0) +			cgit_tree_link(path1, NULL, NULL, ctx.qry.head, +				       oid_to_hex(old_rev_oid), path1); +		else +			html_txt(path1); +		if (is_null_oid(oid2)) { +			path2 = "dev/null"; +			html("<br/>+++ /"); +		} else +			html("<br/>+++ b/"); +		if (mode2 != 0) +			cgit_tree_link(path2, NULL, NULL, ctx.qry.head, +				       oid_to_hex(new_rev_oid), path2); +		else +			html_txt(path2); +	} +	html("</div>"); +} + +static void filepair_cb(struct diff_filepair *pair) +{ +	unsigned long old_size = 0; +	unsigned long new_size = 0; +	int binary = 0; +	linediff_fn print_line_fn = print_line; + +	if (!show_filepair(pair)) +		return; + +	current_filepair = pair; +	if (use_ssdiff) { +		cgit_ssdiff_header_begin(); +		print_line_fn = cgit_ssdiff_line_cb; +	} +	header(&pair->one->oid, pair->one->path, pair->one->mode, +	       &pair->two->oid, pair->two->path, pair->two->mode); +	if (use_ssdiff) +		cgit_ssdiff_header_end(); +	if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { +		if (S_ISGITLINK(pair->one->mode)) +			print_line_fn(fmt("-Subproject %s", oid_to_hex(&pair->one->oid)), 52); +		if (S_ISGITLINK(pair->two->mode)) +			print_line_fn(fmt("+Subproject %s", oid_to_hex(&pair->two->oid)), 52); +		if (use_ssdiff) +			cgit_ssdiff_footer(); +		return; +	} +	if (cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size, +			    &new_size, &binary, ctx.qry.context, +			    ctx.qry.ignorews, print_line_fn)) +		cgit_print_error("Error running diff"); +	if (binary) { +		if (use_ssdiff) +			html("<tr><td colspan='4'>Binary files differ</td></tr>"); +		else +			html("Binary files differ"); +	} +	if (use_ssdiff) +		cgit_ssdiff_footer(); +} + +void cgit_print_diff_ctrls(void) +{ +	int i, curr; + +	html("<div class='cgit-panel'>"); +	html("<b>diff options</b>"); +	html("<form method='get'>"); +	cgit_add_hidden_formfields(1, 0, ctx.qry.page); +	html("<table>"); +	html("<tr><td colspan='2'/></tr>"); +	html("<tr>"); +	html("<td class='label'>context:</td>"); +	html("<td class='ctrl'>"); +	html("<select name='context' onchange='this.form.submit();'>"); +	curr = ctx.qry.context; +	if (!curr) +		curr = 3; +	for (i = 1; i <= 10; i++) +		html_intoption(i, fmt("%d", i), curr); +	for (i = 15; i <= 40; i += 5) +		html_intoption(i, fmt("%d", i), curr); +	html("</select>"); +	html("</td>"); +	html("</tr><tr>"); +	html("<td class='label'>space:</td>"); +	html("<td class='ctrl'>"); +	html("<select name='ignorews' onchange='this.form.submit();'>"); +	html_intoption(0, "include", ctx.qry.ignorews); +	html_intoption(1, "ignore", ctx.qry.ignorews); +	html("</select>"); +	html("</td>"); +	html("</tr><tr>"); +	html("<td class='label'>mode:</td>"); +	html("<td class='ctrl'>"); +	html("<select name='dt' onchange='this.form.submit();'>"); +	curr = ctx.qry.has_difftype ? ctx.qry.difftype : ctx.cfg.difftype; +	html_intoption(0, "unified", curr); +	html_intoption(1, "ssdiff", curr); +	html_intoption(2, "stat only", curr); +	html("</select></td></tr>"); +	html("<tr><td/><td class='ctrl'>"); +	html("<noscript><input type='submit' value='reload'/></noscript>"); +	html("</td></tr></table>"); +	html("</form>"); +	html("</div>"); +} + +struct strbuf* cgit_gopher_add_info_tag(struct diff_options *opt, void *data){ +	 +	struct strbuf* buff; +	char *str; + + +	buff = malloc(sizeof(struct strbuf));	 +	str = malloc(2 * sizeof(char)); +	str[0]= 'i'; +	str[1]= '\0'; +	 +	strbuf_init(buff, 2); +	strbuf_attach(buff, str, 2, 2); + +	return buff;	 +	 +}  + +void cgit_print_diff(const char *new_rev, const char *old_rev, +		     const char *prefix, int show_ctrls, int raw) +{ +	struct commit *commit, *commit2; +	const struct object_id *old_tree_oid, *new_tree_oid; +	diff_type difftype; + +	/* +	 * If "follow" is set then the diff machinery needs to examine the +	 * entire commit to detect renames so we must limit the paths in our +	 * own callbacks and not pass the prefix to the diff machinery. +	 */ +	if (ctx.qry.follow && ctx.cfg.enable_follow_links) { +		current_prefix = prefix; +		prefix = ""; +	} else { +		current_prefix = NULL; +	} + +	if (!new_rev) +		new_rev = ctx.qry.head; +	if (get_oid(new_rev, new_rev_oid)) { +		cgit_gopher_error("Bad object name"); +		return; +	} +	commit = lookup_commit_reference(new_rev_oid); +	if (!commit || parse_commit(commit)) { +		cgit_gopher_error("Bad commit"); +		return; +	} +	new_tree_oid = &commit->maybe_tree->object.oid; + +	if (old_rev) { +		if (get_oid(old_rev, old_rev_oid)) { +			cgit_gopher_error("Bad object name"); +			return; +		} +	} else if (commit->parents && commit->parents->item) { +		oidcpy(old_rev_oid, &commit->parents->item->object.oid); +	} else { +		oidclr(old_rev_oid); +	} + +	if (!is_null_oid(old_rev_oid)) { +		commit2 = lookup_commit_reference(old_rev_oid); +		if (!commit2 || parse_commit(commit2)) { +			cgit_gopher_error("Bad commit"); +			return; +		} +		old_tree_oid = &commit2->maybe_tree->object.oid; +	} else { +		old_tree_oid = NULL; +	} + +	/* FIXME: we are currently forcing raw diffs */ +   	raw = 1;	 +	if (raw) { +		struct diff_options diffopt; + +		diff_setup(&diffopt); +		diffopt.output_format = DIFF_FORMAT_PATCH; +		/*diffopt.output_format = DIFF_FORMAT_RAW;*/ +		diffopt.flags.recursive = 1; +		diffopt.output_prefix=cgit_gopher_add_info_tag; +		diff_setup_done(&diffopt); + +		ctx.page.mimetype = "text/plain"; +		if (old_tree_oid) { +			diff_tree_oid(old_tree_oid, new_tree_oid, "", +				       &diffopt); +		} else { +			diff_root_tree_oid(new_tree_oid, "", &diffopt); +		} +		diffcore_std(&diffopt); +		diff_flush(&diffopt); + +		return; +	} + +	difftype = ctx.qry.has_difftype ? ctx.qry.difftype : ctx.cfg.difftype; +	use_ssdiff = difftype == DIFF_SSDIFF; + +	if (show_ctrls) { +		cgit_print_layout_start(); +		/* cgit_print_diff_ctrls();*/ +	} + +	/* +	 * Clicking on a link to a file in the diff stat should show a diff +	 * of the file, showing the diff stat limited to a single file is +	 * pretty useless.  All links from this point on will be to +	 * individual files, so we simply reset the difftype in the query +	 * here to avoid propagating DIFF_STATONLY to the individual files. +	 */ +	if (difftype == DIFF_STATONLY) +		ctx.qry.difftype = ctx.cfg.difftype; + +	cgit_print_diffstat(old_rev_oid, new_rev_oid, prefix); + +	if (difftype == DIFF_STATONLY) +		return; + +	if (use_ssdiff) { +		html("<table summary='ssdiff' class='ssdiff'>"); +	} else { +		html("<table summary='diff' class='diff'>"); +		html("<tr><td>"); +	} +	cgit_diff_tree(old_rev_oid, new_rev_oid, filepair_cb, prefix, +		       ctx.qry.ignorews); +	if (!use_ssdiff) +		html("</td></tr>"); + +	if (show_ctrls) +		cgit_print_layout_end(); +} diff --git a/ui_70-log.c b/ui_70-log.c new file mode 100644 index 0000000..b45468e --- /dev/null +++ b/ui_70-log.c @@ -0,0 +1,544 @@ +/* ui-log.c: functions for log output + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * Licensed under GNU General Public License v2 + *   (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-log.h" +#include "html.h" +#include "ui_70-shared.h" +#include "argv-array.h" + +static int files, add_lines, rem_lines, lines_counted; + +/* + * The list of available column colors in the commit graph. + */ +static const char *column_colors_html[] = { +	"<span class='column1'>", +	"<span class='column2'>", +	"<span class='column3'>", +	"<span class='column4'>", +	"<span class='column5'>", +	"<span class='column6'>", +	"</span>", +}; + +#define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1) + +static void count_lines(char *line, int size) +{ +	if (size <= 0) +		return; + +	if (line[0] == '+') +		add_lines++; + +	else if (line[0] == '-') +		rem_lines++; +} + +static void inspect_files(struct diff_filepair *pair) +{ +	unsigned long old_size = 0; +	unsigned long new_size = 0; +	int binary = 0; + +	files++; +	if (ctx.repo->enable_log_linecount) +		cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size, +				&new_size, &binary, 0, ctx.qry.ignorews, +				count_lines); +} + +void show_commit_decorations(struct commit *commit) +{ +	const struct name_decoration *deco; +	static char buf[1024]; + +	buf[sizeof(buf) - 1] = 0; +	deco = get_name_decoration(&commit->object); +	if (!deco) +		return; +	html("<span class='decoration'>"); +	while (deco) { +		struct object_id peeled; +		int is_annotated = 0; +		strncpy(buf, prettify_refname(deco->name), sizeof(buf) - 1); +		switch(deco->type) { +		case DECORATION_NONE: +			/* If the git-core doesn't recognize it, +			 * don't display anything. */ +			break; +		case DECORATION_REF_LOCAL: +			cgit_log_link(buf, NULL, "branch-deco", buf, NULL, +				ctx.qry.vpath, 0, NULL, NULL, +				ctx.qry.showmsg, 0); +			break; +		case DECORATION_REF_TAG: +			if (!peel_ref(deco->name, &peeled)) +				is_annotated = !oidcmp(&commit->object.oid, &peeled); +			cgit_tag_link(buf, NULL, is_annotated ? "tag-annotated-deco" : "tag-deco", buf); +			break; +		case DECORATION_REF_REMOTE: +			if (!ctx.repo->enable_remote_branches) +				break; +			cgit_log_link(buf, NULL, "remote-deco", NULL, +				oid_to_hex(&commit->object.oid), +				ctx.qry.vpath, 0, NULL, NULL, +				ctx.qry.showmsg, 0); +			break; +		default: +			cgit_commit_link(buf, NULL, "deco", ctx.qry.head, +					oid_to_hex(&commit->object.oid), +					ctx.qry.vpath); +			break; +		} +		deco = deco->next; +	} +	html("</span>"); +} + +static void handle_rename(struct diff_filepair *pair) +{ +	/* +	 * After we have seen a rename, we generate links to the previous +	 * name of the file so that commit & diff views get fed the path +	 * that is correct for the commit they are showing, avoiding the +	 * need to walk the entire history leading back to every commit we +	 * show in order detect renames. +	 */ +	if (0 != strcmp(ctx.qry.vpath, pair->two->path)) { +		free(ctx.qry.vpath); +		ctx.qry.vpath = xstrdup(pair->two->path); +	} +	inspect_files(pair); +} + +static int show_commit(struct commit *commit, struct rev_info *revs) +{ +	struct commit_list *parents = commit->parents; +	struct commit *parent; +	int found = 0, saved_fmt; +	struct diff_flags saved_flags = revs->diffopt.flags; + +	/* Always show if we're not in "follow" mode with a single file. */ +	if (!ctx.qry.follow) +		return 1; + +	/* +	 * In "follow" mode, we don't show merges.  This is consistent with +	 * "git log --follow -- <file>". +	 */ +	if (parents && parents->next) +		return 0; + +	/* +	 * If this is the root commit, do what rev_info tells us. +	 */ +	if (!parents) +		return revs->show_root_diff; + +	/* When we get here we have precisely one parent. */ +	parent = parents->item; +	/* If we can't parse the commit, let print_commit() report an error. */ +	if (parse_commit(parent)) +		return 1; + +	files = 0; +	add_lines = 0; +	rem_lines = 0; + +	revs->diffopt.flags.recursive = 1; +	diff_tree_oid(&parent->maybe_tree->object.oid, +		      &commit->maybe_tree->object.oid, +		      "", &revs->diffopt); +	diffcore_std(&revs->diffopt); + +	found = !diff_queue_is_empty(); +	saved_fmt = revs->diffopt.output_format; +	revs->diffopt.output_format = DIFF_FORMAT_CALLBACK; +	revs->diffopt.format_callback = cgit_diff_tree_cb; +	revs->diffopt.format_callback_data = handle_rename; +	diff_flush(&revs->diffopt); +	revs->diffopt.output_format = saved_fmt; +	revs->diffopt.flags = saved_flags; + +	lines_counted = 1; +	return found; +} + +static void print_commit(struct commit *commit, struct rev_info *revs) +{ +	struct commitinfo *info; +	int columns = revs->graph ? 4 : 3; +	struct strbuf graphbuf = STRBUF_INIT; +	struct strbuf msgbuf = STRBUF_INIT; + +	if (ctx.repo->enable_log_filecount) +		columns++; +	if (ctx.repo->enable_log_linecount) +		columns++; + +	if (revs->graph) { +		/* Advance graph until current commit */ +		while (!graph_next_line(revs->graph, &graphbuf)) { +			/* Print graph segment in otherwise empty table row */ +			html("<tr class='nohover'><td class='commitgraph'>"); +			html(graphbuf.buf); +			htmlf("</td><td colspan='%d' /></tr>\n", columns); +			strbuf_setlen(&graphbuf, 0); +		} +		/* Current commit's graph segment is now ready in graphbuf */ +	} + +	info = cgit_parse_commit(commit); + +	cgit_gopher_start_selector(GOPHER_MENU); + +	if (revs->graph) { +		/* Print graph segment for current commit */ +		cgit_gopher_text(graphbuf.buf); +		strbuf_setlen(&graphbuf, 0); +	} +	else { +		cgit_print_age(info->committer_date, info->committer_tz, TM_WEEK * 2); +	} + +	if (ctx.qry.showmsg) { +		/* line-wrap long commit subjects instead of truncating them */ +		size_t subject_len = strlen(info->subject); + +		if (subject_len > ctx.cfg.max_msg_len && +		    ctx.cfg.max_msg_len >= 15) { +			/* symbol for signaling line-wrap (in PAGE_ENCODING) */ +			const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 }; +			int i = ctx.cfg.max_msg_len - strlen(wrap_symbol); + +			/* Rewind i to preceding space character */ +			while (i > 0 && !isspace(info->subject[i])) +				--i; +			if (!i) /* Oops, zero spaces. Reset i */ +				i = ctx.cfg.max_msg_len - strlen(wrap_symbol); + +			/* add remainder starting at i to msgbuf */ +			strbuf_add(&msgbuf, info->subject + i, subject_len - i); +			strbuf_trim(&msgbuf); +			strbuf_add(&msgbuf, "\n\n", 2); + +			/* Place wrap_symbol at position i in info->subject */ +			strcpy(info->subject + i, wrap_symbol); +		} +	} +	cgit_gopher_text_pad(info->subject, GOPHER_SUMMARY_DESC_LEN + 1); +	cgit_gopher_text_pad(info->author, GOPHER_SUMMARY_NAME_LEN); +	cgit_gopher_tab(); +	cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, +			 oid_to_hex(&commit->object.oid), ctx.qry.vpath); + +	cgit_gopher_end_selector(); + +	if (revs->graph) { +		html("</td><td>"); +		cgit_print_age(info->committer_date, info->committer_tz, TM_WEEK * 2); +	} + +	if (!lines_counted && (ctx.repo->enable_log_filecount || +			       ctx.repo->enable_log_linecount)) { +		files = 0; +		add_lines = 0; +		rem_lines = 0; +		cgit_diff_commit(commit, inspect_files, ctx.qry.vpath); +	} + +/*	if (ctx.repo->enable_log_filecount) +		htmlf("</td><td>%d", files); +	if (ctx.repo->enable_log_linecount) +		htmlf("</td><td><span class='deletions'>-%d</span>/" +			"<span class='insertions'>+%d</span>", rem_lines, add_lines); +*/ + +	if ((revs->graph && !graph_is_commit_finished(revs->graph)) +			|| ctx.qry.showmsg) { /* Print a second table row */ +		html("<tr class='nohover-highlight'>"); + +		if (ctx.qry.showmsg) { +			/* Concatenate commit message + notes in msgbuf */ +			if (info->msg && *(info->msg)) { +				strbuf_addstr(&msgbuf, info->msg); +				strbuf_addch(&msgbuf, '\n'); +			} +			format_display_notes(&commit->object.oid, +					     &msgbuf, PAGE_ENCODING, 0); +			strbuf_addch(&msgbuf, '\n'); +			strbuf_ltrim(&msgbuf); +		} + +		if (revs->graph) { +			int lines = 0; + +			/* Calculate graph padding */ +			if (ctx.qry.showmsg) { +				/* Count #lines in commit message + notes */ +				const char *p = msgbuf.buf; +				lines = 1; +				while ((p = strchr(p, '\n'))) { +					p++; +					lines++; +				} +			} + +			/* Print graph padding */ +			html("<td class='commitgraph'>"); +			while (lines > 0 || !graph_is_commit_finished(revs->graph)) { +				if (graphbuf.len) +					html("\n"); +				strbuf_setlen(&graphbuf, 0); +				graph_next_line(revs->graph, &graphbuf); +				html(graphbuf.buf); +				lines--; +			} +			html("</td>\n"); +		} +		else +			html("<td/>"); /* Empty 'Age' column */ + +		/* Print msgbuf into remainder of table row */ +		htmlf("<td colspan='%d'%s>\n", columns - (revs->graph ? 1 : 0), +			ctx.qry.showmsg ? " class='logmsg'" : ""); +		html_txt(msgbuf.buf); +		html("</td></tr>\n"); +	} + +	strbuf_release(&msgbuf); +	strbuf_release(&graphbuf); +	cgit_free_commitinfo(info); +} + +static const char *disambiguate_ref(const char *ref, int *must_free_result) +{ +	struct object_id oid; +	struct strbuf longref = STRBUF_INIT; + +	strbuf_addf(&longref, "refs/heads/%s", ref); +	if (get_oid(longref.buf, &oid) == 0) { +		*must_free_result = 1; +		return strbuf_detach(&longref, NULL); +	} + +	*must_free_result = 0; +	strbuf_release(&longref); +	return ref; +} + +static char *next_token(char **src) +{ +	char *result; + +	if (!src || !*src) +		return NULL; +	while (isspace(**src)) +		(*src)++; +	if (!**src) +		return NULL; +	result = *src; +	while (**src) { +		if (isspace(**src)) { +			**src = '\0'; +			(*src)++; +			break; +		} +		(*src)++; +	} +	return result; +} + +void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, +		    char *path, int pager, int commit_graph, int commit_sort) +{ +	struct rev_info rev; +	struct commit *commit; +	struct argv_array rev_argv = ARGV_ARRAY_INIT; +	int i, columns = commit_graph ? 4 : 3; +	int must_free_tip = 0; + +	/* rev_argv.argv[0] will be ignored by setup_revisions */ +	argv_array_push(&rev_argv, "log_rev_setup"); + +	if (!tip) +		tip = ctx.qry.head; +	tip = disambiguate_ref(tip, &must_free_tip); +	argv_array_push(&rev_argv, tip); + +	if (grep && pattern && *pattern) { +		pattern = xstrdup(pattern); +		if (!strcmp(grep, "grep") || !strcmp(grep, "author") || +		    !strcmp(grep, "committer")) { +			argv_array_pushf(&rev_argv, "--%s=%s", grep, pattern); +		} else if (!strcmp(grep, "range")) { +			char *arg; +			/* Split the pattern at whitespace and add each token +			 * as a revision expression. Do not accept other +			 * rev-list options. Also, replace the previously +			 * pushed tip (it's no longer relevant). +			 */ +			argv_array_pop(&rev_argv); +			while ((arg = next_token(&pattern))) { +				if (*arg == '-') { +					fprintf(stderr, "Bad range expr: %s\n", +						arg); +					break; +				} +				argv_array_push(&rev_argv, arg); +			} +		} +	} + +	if (!path || !ctx.cfg.enable_follow_links) { +		/* +		 * If we don't have a path, "follow" is a no-op so make sure +		 * the variable is set to false to avoid needing to check +		 * both this and whether we have a path everywhere. +		 */ +		ctx.qry.follow = 0; +	} + +	if (commit_graph && !ctx.qry.follow) { +		argv_array_push(&rev_argv, "--graph"); +		argv_array_push(&rev_argv, "--color"); +		graph_set_column_colors(column_colors_html, +					COLUMN_COLORS_HTML_MAX); +	} + +	if (commit_sort == 1) +		argv_array_push(&rev_argv, "--date-order"); +	else if (commit_sort == 2) +		argv_array_push(&rev_argv, "--topo-order"); + +	if (path && ctx.qry.follow) +		argv_array_push(&rev_argv, "--follow"); +	argv_array_push(&rev_argv, "--"); +	if (path) +		argv_array_push(&rev_argv, path); + +	init_revisions(&rev, NULL); +	rev.abbrev = DEFAULT_ABBREV; +	rev.commit_format = CMIT_FMT_DEFAULT; +	rev.verbose_header = 1; +	rev.show_root_diff = 0; +	rev.ignore_missing = 1; +	rev.simplify_history = 1; +	setup_revisions(rev_argv.argc, rev_argv.argv, &rev, NULL); +	load_ref_decorations(NULL, DECORATE_FULL_REFS); +	rev.show_decorations = 1; +	rev.grep_filter.ignore_case = 1; + +	rev.diffopt.detect_rename = 1; +	rev.diffopt.rename_limit = ctx.cfg.renamelimit; +	if (ctx.qry.ignorews) +		DIFF_XDL_SET(&rev.diffopt, IGNORE_WHITESPACE); + +	compile_grep_patterns(&rev.grep_filter); +	prepare_revision_walk(&rev); + +	if (pager) { +		cgit_print_layout_start(); +		/*html("<table class='list nowrap'>");*/ +	} + +	cgit_gopher_start_selector(GOPHER_INFO); +	if (commit_graph) +		cgit_gopher_text_pad("", GOPHER_SUMMARY_DATE_LEN);	 +	else +		cgit_gopher_text_pad("Age", GOPHER_SUMMARY_DATE_LEN); +	cgit_gopher_text_pad("Commit message", GOPHER_SUMMARY_DESC_LEN); +	/*if (pager) { +		html(" ("); +		cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, +			      NULL, ctx.qry.head, ctx.qry.sha1, +			      ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, +			      ctx.qry.search, ctx.qry.showmsg ? 0 : 1, +			      ctx.qry.follow); +		html(")"); +	}*/ +	cgit_gopher_text_pad("Author", GOPHER_SUMMARY_NAME_LEN); +	if (rev.graph) +		cgit_gopher_text_pad("Age", GOPHER_SUMMARY_AGE_LEN); +	if (ctx.repo->enable_log_filecount) { +		cgit_gopher_text_pad("Files", GOPHER_SUMMARY_MODE_LEN); +		columns++; +	} +	if (ctx.repo->enable_log_linecount) { +		cgit_gopher_text_pad("Lines", GOPHER_SUMMARY_MODE_LEN); +		columns++; +	} +	cgit_gopher_tab(); +	cgit_gopher_selector_link("Err"); +	cgit_gopher_end_selector(); + +	if (ofs<0) +		ofs = 0; + +	for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; /* nop */) { +		if (show_commit(commit, &rev)) +			i++; +		free_commit_buffer(commit); +		free_commit_list(commit->parents); +		commit->parents = NULL; +	} + +	for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; /* nop */) { +		/* +		 * In "follow" mode, we must count the files and lines the +		 * first time we invoke diff on a given commit, and we need +		 * to do that to see if the commit touches the path we care +		 * about, so we do it in show_commit.  Hence we must clear +		 * lines_counted here. +		 * +		 * This has the side effect of avoiding running diff twice +		 * when we are both following renames and showing file +		 * and/or line counts. +		 */ +		lines_counted = 0; +		if (show_commit(commit, &rev)) { +			i++; +			print_commit(commit, &rev); +		} +		free_commit_buffer(commit); +		free_commit_list(commit->parents); +		commit->parents = NULL; +	} +	if (pager) { +		if (ofs > 0) { +			html("<li>"); +			cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, +				      ctx.qry.sha1, ctx.qry.vpath, +				      ofs - cnt, ctx.qry.grep, +				      ctx.qry.search, ctx.qry.showmsg, +				      ctx.qry.follow); +			html("</li>"); +		} +		if ((commit = get_revision(&rev)) != NULL) { +			html("<li>"); +			cgit_log_link("[next]", NULL, NULL, ctx.qry.head, +				      ctx.qry.sha1, ctx.qry.vpath, +				      ofs + cnt, ctx.qry.grep, +				      ctx.qry.search, ctx.qry.showmsg, +				      ctx.qry.follow); +			html("</li>"); +		} +		cgit_print_layout_end(); +	} else if ((commit = get_revision(&rev)) != NULL) { +		cgit_gopher_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, +			      ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, +			      ctx.qry.follow); +	} + +	/* If we allocated tip then it is safe to cast away const. */ +	if (must_free_tip) +		free((char*) tip); +} diff --git a/ui_70-patch.c b/ui_70-patch.c new file mode 100644 index 0000000..70f159e --- /dev/null +++ b/ui_70-patch.c @@ -0,0 +1,92 @@ +/* ui-patch.c: generate patch view + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * Licensed under GNU General Public License v2 + *   (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-patch.h" +#include "html.h" +#include "ui_70-shared.h" + +void cgit_print_patch(const char *new_rev, const char *old_rev, +		      const char *prefix) +{ +	struct rev_info rev; +	struct commit *commit; +	struct object_id new_rev_oid, old_rev_oid; +	char rev_range[2 * 40 + 3]; +	const char *rev_argv[] = { NULL, "--reverse", "--format=email", rev_range, "--", prefix, NULL }; +	int rev_argc = ARRAY_SIZE(rev_argv) - 1; +	char *patchname; + +	if (!prefix) +		rev_argc--; + +	if (!new_rev) +		new_rev = ctx.qry.head; + +	if (get_oid(new_rev, &new_rev_oid)) { +		cgit_gopher_error("Bad object id"); +		return; +	} +	commit = lookup_commit_reference(&new_rev_oid); +	if (!commit) { +		cgit_gopher_error("Bad commit reference"); +		return; +	} + +	if (old_rev) { +		if (get_oid(old_rev, &old_rev_oid)) { +			cgit_gopher_error("Bad object id"); +			return; +		} +		if (!lookup_commit_reference(&old_rev_oid)) { +			cgit_gopher_error("Bad commit reference"); +			return; +		} +	} else if (commit->parents && commit->parents->item) { +		oidcpy(&old_rev_oid, &commit->parents->item->object.oid); +	} else { +		oidclr(&old_rev_oid); +	} + +	if (is_null_oid(&old_rev_oid)) { +		memcpy(rev_range, oid_to_hex(&new_rev_oid), GIT_SHA1_HEXSZ + 1); +	} else { +		sprintf(rev_range, "%s..%s", oid_to_hex(&old_rev_oid), +			oid_to_hex(&new_rev_oid)); +	} + +	patchname = fmt("%s.patch", rev_range); +	ctx.page.mimetype = "text/plain"; +	ctx.page.filename = patchname; +	/*cgit_print_http_headers();*/ + +	if (ctx.cfg.noplainemail) { +		rev_argv[2] = "--format=format:From %H Mon Sep 17 00:00:00 " +			      "2001%nFrom: %an%nDate: %aD%n%w(78,0,1)Subject: " +			      "%s%n%n%w(0)%b"; +	} + +	init_revisions(&rev, NULL); +	rev.abbrev = DEFAULT_ABBREV; +	rev.verbose_header = 1; +	rev.diff = 1; +	rev.show_root_diff = 1; +	rev.max_parents = 1; +	rev.diffopt.output_format |= DIFF_FORMAT_DIFFSTAT | +			DIFF_FORMAT_PATCH | DIFF_FORMAT_SUMMARY; +	if (prefix) +		rev.diffopt.stat_sep = fmt("(limited to '%s')\n\n", prefix); +	setup_revisions(rev_argc, rev_argv, &rev, NULL); +	prepare_revision_walk(&rev); + +	while ((commit = get_revision(&rev)) != NULL) { +		log_tree_commit(&rev, commit); +		/*printf("-- \ncgit %s\n\n", cgit_version);*/ +	} +} diff --git a/ui_70-refs.c b/ui_70-refs.c new file mode 100644 index 0000000..457e69f --- /dev/null +++ b/ui_70-refs.c @@ -0,0 +1,218 @@ +/* ui-refs.c: browse symbolic refs + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * 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 { +		cgit_object_link(ref->object); +	} +	cgit_gopher_tab();	 +	cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, +		      ctx.qry.showmsg, 0); +	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(oid_to_hex(&obj->oid), 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) +{ +	cgit_gopher_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); +} + +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(); + +	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); +		cgit_print_tags(0); +	} +	cgit_print_layout_end(); +} diff --git a/ui_70-repolist.c b/ui_70-repolist.c new file mode 100644 index 0000000..40d9619 --- /dev/null +++ b/ui_70-repolist.c @@ -0,0 +1,331 @@ +/* ui-repolist.c: functions for generating the repolist page + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * 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){ +#ifdef DEBUG_GOPHER +		fprintf(stdout, "i -- ctx.qry.url is NULL\n"); +#endif +		return 1; +	} +	if (repo->url && starts_with(repo->url, ctx.qry.url)){ +#ifdef DEBUG_GOPHER +		fprintf(stdout, "i -- repo URL not in qry.url\n"); +#endif +		return 1; +	} +	return 0; +} + +static int is_visible(struct cgit_repo *repo) +{ +	if (repo->hide || repo->ignore){ +#ifdef DEBUG_GOPHER +		fprintf(stdout, "i -- repo: '%s' is invisible or ignored", repo->name);  +#endif +		return 0; +	} +	if (!(is_match(repo) && is_in_url(repo))){ +#ifdef DEBUG_GOPHER +		fprintf(stdout, "i -- !(is_match(repo) && is_in_url(repo))\n"); +#endif +		return 0; +	} +	return 1; +} + +static int any_repos_visible(void) +{ +	int i; + +#ifdef DEBUG_GOPHER +	fprintf(stdout, "i -- ctx.qry.search: %s\n", ctx.qry.search); +#endif	 +	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 visible 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"); + +	cgit_print_layout_start(); +	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..9abf94c --- /dev/null +++ b/ui_70-shared.c @@ -0,0 +1,1345 @@ +/* ui-shared.c: common web output functions + * + * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * Licensed under GNU General Public License v2 + *   (see COPYING for full license text) + */ + +#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 cgit_gopher_tab(){ +	printf("\t"); +} + + +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 gopherf(const char *format, ...) +{ +	va_list args; +	struct strbuf buf = STRBUF_INIT; + +	va_start(args, format); +	strbuf_vaddf(&buf, format, args); +	va_end(args); +	cgit_gopher_text(buf.buf); +	strbuf_release(&buf); +} + + + +void gopher_fileperm(unsigned short mode) +{ +	gopherf("%c%c%c", (mode & 4 ? 'r' : '-'), +	      (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-')); +} + + +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 fmtalloc("/%s", 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 fmtalloc("/%s", 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){ +		 +		cgit_gopher_text(cgit_rooturl()); +		} +	else { +		char *currenturl = cgit_currenturl(); +		cgit_gopher_text(currenturl); +		free(currenturl); +	} + +	if (page) { +		gopherf("?p=%s", page); +		delim = "&"; +	} +	if (search) { +		cgit_gopher_text(delim); +		cgit_gopher_text("q="); +		cgit_gopher_text(search); +		delim = "&"; +	} +	if (sort) { +		cgit_gopher_text(delim); +		cgit_gopher_text("s="); +		cgit_gopher_text(sort); +		delim = "&"; +	} +	if (ofs) { +		cgit_gopher_text(delim); +		gopherf("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) +{ +	cgit_gopher_start_selector(GOPHER_MENU); +	cgit_gopher_text(name); +	cgit_gopher_tab(); +	site_url(page, search, sort, ofs, always_root); +	cgit_gopher_tab(); +	cgit_gopher_end_selector(); +} + +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); +} + +void cgit_gopher_index_link(const char *name, const char *title, const char *class, +		     const char *pattern, const char *sort, int ofs, int always_root) +{ + +	cgit_index_link(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_tab(); +} + +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_gopher_summary_link(const char *name, const char *title, const char *class, +		       const char *head) +{ + +	cgit_gopher_start_selector(GOPHER_MENU); +	cgit_gopher_text(name); +	cgit_gopher_tab(); +	cgit_summary_link(name, title, class, head); +	cgit_gopher_end_selector(); +} + +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_gopher_tree_link(const char *name, const char *title, const char *class, +		    const char *head, const char *rev, const char *path) +{ +	cgit_gopher_start_selector(GOPHER_MENU); +	cgit_gopher_text(name); +	cgit_gopher_tab(); +	cgit_tree_link(name, title, class, head, rev, path); +	cgit_gopher_end_selector(); +}	 + +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="); +		gopherf("%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"); +	} +	cgit_gopher_tab(); +} + + +void cgit_gopher_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) +{ +	cgit_gopher_start_selector(GOPHER_MENU); +	cgit_gopher_text(name); +	cgit_gopher_tab(); +	cgit_log_link(name, title, class, head, rev, path, ofs, grep, pattern, showmsg, follow);	 +	cgit_gopher_end_selector(); + +} + +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)) { +		cgit_gopher_text(delim); +		cgit_gopher_text("id="); +		cgit_gopher_text(rev); +		delim = "&"; +	} +	if (ctx.qry.difftype) { +		cgit_gopher_text(delim); +		gopherf("dt=%d", ctx.qry.difftype); +		delim = "&"; +	} +	if (ctx.qry.context > 0 && ctx.qry.context != 3) { +		cgit_gopher_text(delim); +		cgit_gopher_text("context="); +		gopherf("%d", ctx.qry.context); +		delim = "&"; +	} +	if (ctx.qry.ignorews) { +		cgit_gopher_text(delim); +		cgit_gopher_text("ignorews=1"); +		delim = "&"; +	} +	if (ctx.qry.follow) { +		cgit_gopher_text(delim); +		cgit_gopher_text("follow=1"); +	} +/*	if (name[0] != '\0') { +		cgit_gopher_text(name); +	} else +		cgit_gopher_text("(no commit message)"); +*/	cgit_gopher_tab(); +} +	 + +void cgit_gopher_commit_link(const char *name, const char *title, const char *class, +		      const char *head, const char *rev, const char *path) +{ +	cgit_gopher_start_selector(GOPHER_MENU); +	cgit_gopher_text(name); +	cgit_gopher_tab(); +	cgit_commit_link(name, title, class, head, rev, path);	 +	cgit_gopher_end_selector(); +} + + +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_gopher_refs_link(const char *name, const char *title, const char *class, +		    const char *head, const char *rev, const char *path) +{ +	cgit_gopher_start_selector(GOPHER_MENU); +	cgit_gopher_text(name); +	cgit_gopher_tab(); +	cgit_refs_link(name, title, class, head, rev, path); +	cgit_gopher_end_selector(); +} + +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)) { +		cgit_gopher_text(delim); +		cgit_gopher_text("id="); +		cgit_gopher_text(new_rev); +		delim = "&"; +	} +	if (old_rev) { +		cgit_gopher_text(delim); +		cgit_gopher_text("id2="); +		cgit_gopher_text(old_rev); +		delim = "&"; +	} +	if (ctx.qry.difftype) { +		cgit_gopher_text(delim); +		gopherf("dt=%d", ctx.qry.difftype); +		delim = "&"; +	} +	if (ctx.qry.context > 0 && ctx.qry.context != 3) { +		cgit_gopher_text(delim); +		cgit_gopher_text("context="); +		gopherf("%d", ctx.qry.context); +		delim = "&"; +	} +	if (ctx.qry.ignorews) { +		cgit_gopher_text(delim); +		cgit_gopher_text("ignorews=1"); +		delim = "&"; +	} +	if (ctx.qry.follow) { +		cgit_gopher_text(delim); +		cgit_gopher_text("follow=1"); +	} +	cgit_gopher_tab(); +} +	 + +void cgit_gopher_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) +{ + +	cgit_gopher_start_selector(GOPHER_MENU); +	cgit_gopher_text(name); +	cgit_gopher_tab(); +	cgit_diff_link(name, title, class, head, new_rev, old_rev, path);	 +	cgit_gopher_end_selector(); + +} + +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 + 1); +	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) +{ +	if (ctx.repo) { +		cgit_gopher_index_link("repo list", NULL, NULL, NULL, NULL, 0, 1); +		cgit_gopher_start_selector(GOPHER_MENU); +		cgit_gopher_text(ctx.repo->name); +		cgit_gopher_text(" (owned by "); +		cgit_gopher_text(ctx.repo->owner); +		cgit_gopher_text(")"); +		cgit_gopher_tab(); +		cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); +		cgit_gopher_end_selector(); +	} else +		cgit_gopher_info(ctx.cfg.root_title); +} + +void cgit_print_pageheader(void) +{ +	if (!ctx.env.authenticated || !ctx.cfg.noheader) +		print_header(); + +	if (ctx.repo) { +		if (ctx.repo->readme.nr) +			reporevlink("about", "about", NULL, +				    hc("about"), ctx.qry.head, NULL, +				    NULL); +		cgit_gopher_summary_link("summary", NULL, hc("summary"), +				  ctx.qry.head); +		cgit_gopher_refs_link("refs", NULL, hc("refs"), ctx.qry.head, +			       ctx.qry.sha1, NULL); +		cgit_gopher_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_gopher_tree_link("tree", NULL, hc("tree"), ctx.qry.head, +				       ctx.qry.sha1, ctx.qry.vpath); +		cgit_gopher_commit_link("commit", NULL, hc("commit"), +				 ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath); +		cgit_gopher_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) { +			/* cgit_gopher_menu("homepage", * ctx.repo->homepage);*/ +		} +	} /*else { +		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); +	} +	if (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>"); +	}*/ +} + +void cgit_print_filemode(unsigned short mode) +{ +	if (S_ISDIR(mode)) +		cgit_gopher_text("d"); +	else if (S_ISLNK(mode)) +		cgit_gopher_text("l"); +	else if (S_ISGITLINK(mode)) +		cgit_gopher_text("m"); +	else +		cgit_gopher_text("-"); +	gopher_fileperm(mode >> 6); +	gopher_fileperm(mode >> 3); +	gopher_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..c793950 --- /dev/null +++ b/ui_70-shared.h @@ -0,0 +1,137 @@ +#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_SUMMARY_MODE_LEN 12 +#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_tab(); +void cgit_gopher_text_pad(const char *txt, int len); +void cgit_gopher_end_selector(); +void gopherf(const char *format, ...); + + + +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_gopher_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..aa680f6 --- /dev/null +++ b/ui_70-summary.c @@ -0,0 +1,144 @@ +/* ui-summary.c: functions for generating repo summary page + * + * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * 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><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; + +	cgit_print_layout_start(); +	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) { +		cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, +			       NULL, NULL, 0, 0, 0); +	} +	 +	urls = 0; +} + +/* 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-tag.c b/ui_70-tag.c new file mode 100644 index 0000000..0a1b386 --- /dev/null +++ b/ui_70-tag.c @@ -0,0 +1,131 @@ +/* ui-tag.c: display a tag + * + * 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-tag.h" +#include "html.h" +#include "ui_70-shared.h" + +static void print_tag_content(char *buf) +{ +	char *p; + +	if (!buf) +		return; + +	p = strchr(buf, '\n'); +	if (p) +		*p = '\0'; +	cgit_gopher_start_selector(GOPHER_INFO); +	cgit_gopher_text(buf); +	cgit_gopher_tab(); +	cgit_gopher_selector_link("Err"); +	cgit_gopher_end_selector(); +	if (p) { +		cgit_gopher_start_selector(GOPHER_INFO); +		cgit_gopher_text(++p); +		cgit_gopher_tab(); +		cgit_gopher_selector_link("Err"); +		cgit_gopher_end_selector(); +	} +} + +static void print_download_links(char *revname) +{ +	html("<tr><th>download</th><td class='sha1'>"); +	cgit_print_snapshot_links(ctx.repo, revname, "<br/>"); +	html("</td></tr>"); +} + +void cgit_print_tag(char *revname) +{ +	struct strbuf fullref = STRBUF_INIT; +	struct object_id oid; +	struct object *obj; + +	if (!revname) +		revname = ctx.qry.head; + +	strbuf_addf(&fullref, "refs/tags/%s", revname); +	if (get_oid(fullref.buf, &oid)) { +		cgit_gopher_error("Bad tag reference"); +		goto cleanup; +	} +	obj = parse_object(&oid); +	if (!obj) { +		cgit_gopher_error("Bad object id"); +		goto cleanup; +	} +	if (obj->type == OBJ_TAG) { +		struct tag *tag; +		struct taginfo *info; + +		tag = lookup_tag(&oid); +		if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) { +			cgit_gopher_error("Bad tag object"); +			goto cleanup; +		} +		cgit_print_layout_start(); +		cgit_gopher_start_selector(GOPHER_INFO); +		cgit_gopher_text_pad("tag name", GOPHER_SUMMARY_NAME_LEN); +		cgit_gopher_text(revname); +		gopherf(" (%s)", oid_to_hex(&oid)); +		cgit_gopher_tab(); +		cgit_gopher_selector_link("Err"); +		cgit_gopher_end_selector(); +		if (info->tagger_date > 0) { +			cgit_gopher_start_selector(GOPHER_INFO); +			cgit_gopher_text_pad("tag date", GOPHER_SUMMARY_NAME_LEN); +			cgit_gopher_text(show_date(info->tagger_date, info->tagger_tz, +						cgit_date_mode(DATE_ISO8601))); +			cgit_gopher_tab(); +			cgit_gopher_selector_link("Err"); +			cgit_gopher_end_selector(); +		} +		if (info->tagger) { +			cgit_gopher_start_selector(GOPHER_INFO); +			cgit_gopher_text_pad("tagged by", GOPHER_SUMMARY_NAME_LEN); +			cgit_gopher_text(info->tagger); +			if (info->tagger_email && !ctx.cfg.noplainemail) { +				cgit_gopher_text(" "); +				cgit_gopher_text(info->tagger_email); +			} +			cgit_gopher_tab(); +			cgit_gopher_selector_link("Err"); +			cgit_gopher_end_selector(); +		} +		cgit_gopher_start_selector(GOPHER_MENU); +		cgit_gopher_text_pad("tagged object", GOPHER_SUMMARY_NAME_LEN); +		cgit_gopher_text(oid_to_hex(&tag->tagged->oid)); +		cgit_gopher_tab(); +		cgit_object_link(tag->tagged); +		cgit_gopher_end_selector();	 +		print_tag_content(info->msg); +		cgit_print_layout_end(); +		cgit_free_taginfo(info); +	} else { +		cgit_print_layout_start(); +		cgit_gopher_start_selector(GOPHER_INFO); +		cgit_gopher_text_pad("tag name", GOPHER_SUMMARY_NAME_LEN); +		cgit_gopher_text(revname); +		cgit_gopher_tab(); +		cgit_gopher_selector_link("Err"); +		cgit_gopher_end_selector(); +		 +		cgit_gopher_start_selector(GOPHER_MENU); +		cgit_gopher_text_pad("tagged object", GOPHER_SUMMARY_NAME_LEN); +		cgit_gopher_text(oid_to_hex(&obj->oid)); +		cgit_gopher_tab(); +		cgit_object_link(obj); +		cgit_gopher_end_selector();	 +		cgit_print_layout_end(); +	} + +cleanup: +	strbuf_release(&fullref); +} diff --git a/ui_70-tree.c b/ui_70-tree.c new file mode 100644 index 0000000..32fd585 --- /dev/null +++ b/ui_70-tree.c @@ -0,0 +1,337 @@ +/* ui-tree.c: functions for tree output + * + * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com> + *                    2018 Vincenzo 'KatolaZ' Nicosia <katolaz@freaknet.org> + * + * Licensed under GNU General Public License v2 + *   (see COPYING for full license text) + */ + +#include "cgit.h" +#include "ui-tree.h" +#include "html.h" +#include "ui_70-shared.h" + +struct walk_tree_context { +	char *curr_rev; +	char *match_path; +	int state; +}; + +static void print_text_buffer(const char *name, char *buf, unsigned long size) +{ +	cgit_gopher_text(buf); +} + +#define ROWLEN 32 + +static void print_binary_buffer(char *buf, unsigned long size) +{ +	unsigned long ofs, idx; +	static char ascii[ROWLEN + 1]; + +	html("<table summary='blob content' class='bin-blob'>\n"); +	html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); +	for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { +		htmlf("<tr><td class='right'>%04lx</td><td class='hex'>", ofs); +		for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) +			htmlf("%*s%02x", +			      idx == 16 ? 4 : 1, "", +			      buf[idx] & 0xff); +		html(" </td><td class='hex'>"); +		for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) +			ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; +		ascii[idx] = '\0'; +		html_txt(ascii); +		html("</td></tr>\n"); +	} +	html("</table>\n"); +} + +static void print_object(const struct object_id *oid, char *path, const char *basename, const char *rev) +{ +	enum object_type type; +	char *buf; +	unsigned long size; + +	type = oid_object_info(the_repository, oid, &size); +	if (type == OBJ_BAD) { +		cgit_gopher_error("Bad object name"); +		return; +	} + +	buf = read_object_file(oid, &type, &size); +	if (!buf) { +		cgit_gopher_error("Error reading object"); +		return; +	} + +	cgit_set_title_from_path(path); + +	/*cgit_print_layout_start();*/ + +	if (buffer_is_binary(buf, size)) +		print_binary_buffer(buf, size); +	else +		print_text_buffer(basename, buf, size); + +	free(buf); +} + +struct single_tree_ctx { +	struct strbuf *path; +	struct object_id oid; +	char *name; +	size_t count; +}; + +static int single_tree_cb(const struct object_id *oid, struct strbuf *base, +			  const char *pathname, unsigned mode, int stage, +			  void *cbdata) +{ +	struct single_tree_ctx *ctx = cbdata; + +	if (++ctx->count > 1) +		return -1; + +	if (!S_ISDIR(mode)) { +		ctx->count = 2; +		return -1; +	} + +	ctx->name = xstrdup(pathname); +	oidcpy(&ctx->oid, oid); +	strbuf_addf(ctx->path, "/%s", pathname); +	return 0; +} + +static void write_tree_link(const struct object_id *oid, char *name, +			    char *rev, struct strbuf *fullpath) +{ +	size_t initial_length = fullpath->len; +	struct tree *tree; +	struct single_tree_ctx tree_ctx = { +		.path = fullpath, +		.count = 1, +	}; +	struct pathspec paths = { +		.nr = 0 +	}; + +	oidcpy(&tree_ctx.oid, oid); + +/*	while (tree_ctx.count == 1) {*/ +		cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev, +			       fullpath->buf); +/* +		tree = lookup_tree(&tree_ctx.oid); +		if (!tree) +			return; + +		free(tree_ctx.name); +		tree_ctx.name = NULL; +		tree_ctx.count = 0; + +		read_tree_recursive(tree, "", 0, 1, &paths, single_tree_cb, +				    &tree_ctx); + +		if (tree_ctx.count != 1) +			break; + +		html(" / "); +		name = tree_ctx.name; +	} +*/ + +	strbuf_setlen(fullpath, initial_length); +} + +static int ls_item(const struct object_id *oid, struct strbuf *base, +		const char *pathname, unsigned mode, int stage, void *cbdata) +{ +	struct walk_tree_context *walk_tree_ctx = cbdata; +	char *name; +	struct strbuf fullpath = STRBUF_INIT; +	struct strbuf class = STRBUF_INIT; +	enum object_type type; +	unsigned long size = 0; + +	name = xstrdup(pathname); +	strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "", +		    ctx.qry.path ? "/" : "", name); + +	if (!S_ISGITLINK(mode)) { +		type = oid_object_info(the_repository, oid, &size); +		if (type == OBJ_BAD) { +			cgit_gopher_error("Bad object"); +			free(name); +			return 0; +		} +	} + +/* +	if (S_ISGITLINK(mode)) { +		cgit_submodule_link("ls-mod", fullpath.buf, oid_to_hex(oid)); +	} else  +*/ +	if (S_ISDIR(mode)) { +		cgit_gopher_start_selector(GOPHER_MENU); +		cgit_print_filemode(mode); +		cgit_gopher_text(" "); +		cgit_gopher_text_pad(name, GOPHER_SUMMARY_NAME_LEN); +		gopherf("%10ld", size); +		cgit_gopher_tab(); +		write_tree_link(oid, name, walk_tree_ctx->curr_rev, +				&fullpath); +	} else { +		char *ext = strrchr(name, '.'); +		strbuf_addstr(&class, "ls-blob"); +		if (ext) +			strbuf_addf(&class, " %s", ext + 1); +		cgit_gopher_start_selector(GOPHER_TXT); +		cgit_print_filemode(mode); +		cgit_gopher_text(" "); +		cgit_gopher_text_pad(name, GOPHER_SUMMARY_NAME_LEN); +		gopherf("%10ld", size); +		cgit_gopher_tab(); +		cgit_tree_link(name, NULL, class.buf, ctx.qry.head, +			       walk_tree_ctx->curr_rev, fullpath.buf); +	} +/*	if (ctx.repo->max_stats) +		cgit_stats_link("stats", NULL, "button", ctx.qry.head, +				fullpath.buf); +	if (!S_ISGITLINK(mode)) +		cgit_plain_link("plain", NULL, "button", ctx.qry.head, +				walk_tree_ctx->curr_rev, fullpath.buf); +	if (!S_ISDIR(mode) && ctx.cfg.enable_blame) +		cgit_blame_link("blame", NULL, "button", ctx.qry.head, +				walk_tree_ctx->curr_rev, fullpath.buf); +*/ +	cgit_gopher_end_selector(); +	free(name); +	strbuf_release(&fullpath); +	strbuf_release(&class); +	return 0; +} + +static void ls_head(void) +{ +	cgit_print_layout_start(); +	cgit_gopher_start_selector(GOPHER_INFO); +	cgit_gopher_text_pad("Mode", GOPHER_SUMMARY_MODE_LEN); +	cgit_gopher_text_pad("Name", GOPHER_SUMMARY_NAME_LEN); +	cgit_gopher_text_pad("Size", GOPHER_SUMMARY_AGE_LEN); +	cgit_gopher_tab(); +	cgit_gopher_selector_link("Err"); +	cgit_gopher_end_selector(); +} + +static void ls_tail(void) +{ +/*	html("</table>\n"); +	cgit_print_layout_end(); +*/ +} + +static void ls_tree(const struct object_id *oid, char *path, struct walk_tree_context *walk_tree_ctx) +{ +	struct tree *tree; +	struct pathspec paths = { +		.nr = 0 +	}; + +	tree = parse_tree_indirect(oid); +	if (!tree) { +		cgit_gopher_error("Not a tree object"); +		return; +	} + +	ls_head(); +	read_tree_recursive(tree, "", 0, 1, &paths, ls_item, walk_tree_ctx); +	ls_tail(); +} + + +static int walk_tree(const struct object_id *oid, struct strbuf *base, +		const char *pathname, unsigned mode, int stage, void *cbdata) +{ +	struct walk_tree_context *walk_tree_ctx = cbdata; + +	if (walk_tree_ctx->state == 0) { +		struct strbuf buffer = STRBUF_INIT; + +		strbuf_addbuf(&buffer, base); +		strbuf_addstr(&buffer, pathname); +		if (strcmp(walk_tree_ctx->match_path, buffer.buf)) +			return READ_TREE_RECURSIVE; + +		if (S_ISDIR(mode)) { +			walk_tree_ctx->state = 1; +			cgit_set_title_from_path(buffer.buf); +			strbuf_release(&buffer); +			ls_head(); +			return READ_TREE_RECURSIVE; +		} else { +			walk_tree_ctx->state = 2; +			print_object(oid, buffer.buf, pathname, walk_tree_ctx->curr_rev); +			strbuf_release(&buffer); +			return 0; +		} +	} +	ls_item(oid, base, pathname, mode, stage, walk_tree_ctx); +	return 0; +} + +/* + * Show a tree or a blob + *   rev:  the commit pointing at the root tree object + *   path: path to tree or blob + */ +void cgit_print_tree(const char *rev, char *path) +{ +	struct object_id oid; +	struct commit *commit; +	struct pathspec_item path_items = { +		.match = path, +		.len = path ? strlen(path) : 0 +	}; +	struct pathspec paths = { +		.nr = path ? 1 : 0, +		.items = &path_items +	}; +	struct walk_tree_context walk_tree_ctx = { +		.match_path = path, +		.state = 0 +	}; + +	if (!rev) +		rev = ctx.qry.head; + +	if (get_oid(rev, &oid)) { +		cgit_gopher_error("Invalid revision name"); +		return; +	} +	commit = lookup_commit_reference(&oid); +	if (!commit || parse_commit(commit)) { +		cgit_gopher_error("Invalid commit reference"); +		return; +	} + +	walk_tree_ctx.curr_rev = xstrdup(rev); + +	if (path == NULL) { +		ls_tree(&commit->maybe_tree->object.oid, NULL, &walk_tree_ctx); +		goto cleanup; +	} + +	read_tree_recursive(commit->maybe_tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx); +	if (walk_tree_ctx.state == 1) +		ls_tail(); +	else if (walk_tree_ctx.state == 2) +		; +	else +		cgit_print_error_page(404, "Not found", "Path not found"); + +cleanup: +	free(walk_tree_ctx.curr_rev); +} 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(); +} | 
