List for cgit developers and users
 help / color / mirror / Atom feed
From: Cameron Katri <me@cameronkatri.com>
To: cgit@lists.zx2c4.com
Cc: Cameron Katri <me@cameronkatri.com>
Subject: [PATCH] Add a license tab
Date: Tue, 15 Jun 2021 11:57:24 -0400	[thread overview]
Message-ID: <20210615155724.97097-1-me@cameronkatri.com> (raw)

This tab is nearly identical to the about tab, use the license field in
cgitrc to set a list of files to check for or use repo.license or
cgit.license as a git-config key.
---
This is useful in large repos where you gotta scroll and scroll to find
the license text, you can just click the license tab and get it right
there.

 cgit.c       | 59 ++++++++++++++++++++++++++++++++++------------------
 cgit.h       |  3 +++
 cgitrc.5.txt | 24 ++++++++++++++++++++-
 cmd.c        | 27 +++++++++++++++++++++++-
 shared.c     |  1 +
 ui-shared.c  |  4 ++++
 ui-summary.c | 17 +++++++--------
 ui-summary.h |  4 +++-
 8 files changed, 107 insertions(+), 32 deletions(-)

diff --git a/cgit.c b/cgit.c
index 08d81a1..c88dd5b 100644
--- a/cgit.c
+++ b/cgit.c
@@ -99,6 +99,10 @@ static void repo_config(struct cgit_repo *repo, const char *name, const char *va
 		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, "license") && value != NULL) {
+		if (repo->license.items == ctx.cfg.license.items)
+			memset(&repo->license, 0, sizeof(repo->license));
+		string_list_append(&repo->license, xstrdup(value));
 	} else if (!strcmp(name, "logo") && value != NULL)
 		repo->logo = xstrdup(value);
 	else if (!strcmp(name, "logo-link") && value != NULL)
@@ -135,6 +139,8 @@ static void config_cb(const char *name, const char *value)
 		repo_config(ctx.repo, arg, value);
 	else if (!strcmp(name, "readme"))
 		string_list_append(&ctx.cfg.readme, xstrdup(value));
+	else if (!strcmp(name, "license"))
+		string_list_append(&ctx.cfg.license, xstrdup(value));
 	else if (!strcmp(name, "root-title"))
 		ctx.cfg.root_title = xstrdup(value);
 	else if (!strcmp(name, "root-desc"))
@@ -213,6 +219,8 @@ static void config_cb(const char *name, const char *value)
 		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-license-ttl"))
+		ctx.cfg.cache_license_ttl = atoi(value);
 	else if (!strcmp(name, "cache-snapshot-ttl"))
 		ctx.cfg.cache_snapshot_ttl = atoi(value);
 	else if (!strcmp(name, "case-sensitive-sort"))
@@ -367,6 +375,7 @@ static void prepare_context(void)
 	ctx.cfg.cache_max_create_time = 5;
 	ctx.cfg.cache_root = CGIT_CACHE_ROOT;
 	ctx.cfg.cache_about_ttl = 15;
+	ctx.cfg.cache_license_ttl = 15;
 	ctx.cfg.cache_snapshot_ttl = 5;
 	ctx.cfg.cache_repo_ttl = 5;
 	ctx.cfg.cache_root_ttl = 5;
@@ -494,46 +503,46 @@ static char *guess_defbranch(void)
 }
 
 /* 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)
+static inline void parse_info_file(const char *file, char **filename, char **ref, struct cgit_repo *repo)
 {
 	const char *colon;
 
 	*filename = NULL;
 	*ref = NULL;
 
-	if (!readme || !readme[0])
+	if (!file || !file[0])
 		return;
 
-	/* Check if the readme is tracked in the git repo. */
-	colon = strchr(readme, ':');
+	/* Check if the file is tracked in the git repo. */
+	colon = strchr(file, ':');
 	if (colon && strlen(colon) > 1) {
 		/* If it starts with a colon, we want to use
 		 * the default branch */
-		if (colon == readme && repo->defbranch)
+		if (colon == file && repo->defbranch)
 			*ref = xstrdup(repo->defbranch);
 		else
-			*ref = xstrndup(readme, colon - readme);
-		readme = colon + 1;
+			*ref = xstrndup(file, colon - file);
+		file = colon + 1;
 	}
 
-	/* Prepend repo path to relative readme path unless tracked. */
-	if (!(*ref) && readme[0] != '/')
-		*filename = fmtalloc("%s/%s", repo->path, readme);
+	/* Prepend repo path to relative file path unless tracked. */
+	if (!(*ref) && file[0] != '/')
+		*filename = fmtalloc("%s/%s", repo->path, file);
 	else
-		*filename = xstrdup(readme);
+		*filename = xstrdup(file);
 }
-static void choose_readme(struct cgit_repo *repo)
+static void choose_string_list_item(struct cgit_repo *repo, struct string_list *file)
 {
 	int found;
 	char *filename, *ref;
 	struct string_list_item *entry;
 
-	if (!repo->readme.nr)
+	if (!file->nr)
 		return;
 
 	found = 0;
-	for_each_string_list_item(entry, &repo->readme) {
-		parse_readme(entry->string, &filename, &ref, repo);
+	for_each_string_list_item(entry, file) {
+		parse_info_file(entry->string, &filename, &ref, repo);
 		if (!filename) {
 			free(filename);
 			free(ref);
@@ -552,11 +561,11 @@ static void choose_readme(struct cgit_repo *repo)
 		free(filename);
 		free(ref);
 	}
-	repo->readme.strdup_strings = 1;
-	string_list_clear(&repo->readme, 0);
-	repo->readme.strdup_strings = 0;
+	file->strdup_strings = 1;
+	string_list_clear(file, 0);
+	file->strdup_strings = 0;
 	if (found)
-		string_list_append(&repo->readme, filename)->util = ref;
+		string_list_append(file, filename)->util = ref;
 }
 
 static void print_no_repo_clone_urls(const char *url)
@@ -636,7 +645,8 @@ static int prepare_repo_cmd(int nongit)
 	}
 	string_list_sort(&ctx.repo->submodules);
 	cgit_prepare_repo_env(ctx.repo);
-	choose_readme(ctx.repo);
+	choose_string_list_item(ctx.repo, &ctx.repo->readme);
+	choose_string_list_item(ctx.repo, &ctx.repo->license);
 	return 0;
 }
 
@@ -804,6 +814,12 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
 		else
 			fprintf(f, "repo.readme=%s\n", item->string);
 	}
+	for_each_string_list_item(item, &repo->license) {
+		if (item->util)
+			fprintf(f, "repo.license=%s:%s\n", (char *)item->util, item->string);
+		else
+			fprintf(f, "repo.license=%s\n", item->string);
+	}
 	if (repo->defbranch)
 		fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
 	if (repo->extra_head_content)
@@ -1034,6 +1050,9 @@ static int calc_ttl(void)
 	if (!strcmp(ctx.qry.page, "about"))
 		return ctx.cfg.cache_about_ttl;
 
+	if (!strcmp(ctx.qry.page, "license"))
+		return ctx.cfg.cache_license_ttl;
+
 	if (!strcmp(ctx.qry.page, "snapshot"))
 		return ctx.cfg.cache_snapshot_ttl;
 
diff --git a/cgit.h b/cgit.h
index 69b5c13..f0ad595 100644
--- a/cgit.h
+++ b/cgit.h
@@ -88,6 +88,7 @@ struct cgit_repo {
 	char *defbranch;
 	char *module_link;
 	struct string_list readme;
+	struct string_list license;
 	char *section;
 	char *clone_url;
 	char *logo;
@@ -206,6 +207,7 @@ struct cgit_config {
 	char *module_link;
 	char *project_list;
 	struct string_list readme;
+	struct string_list license;
 	char *robots;
 	char *root_title;
 	char *root_desc;
@@ -223,6 +225,7 @@ struct cgit_config {
 	int cache_scanrc_ttl;
 	int cache_static_ttl;
 	int cache_about_ttl;
+	int cache_license_ttl;
 	int cache_snapshot_ttl;
 	int case_sensitive_sort;
 	int embedded;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 33a6a8c..5a0e422 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -331,6 +331,12 @@ readme::
 	in this list. This is useful in conjunction with scan-path. Default
 	value: none. See also: scan-path, repo.readme.
 
+license::
+	Text which will be used as default value for "repo.license". Multiple
+	config keys may be specified, and cgit will use the first found file
+	in this list. This is useful in conjunction with scan-path. Default
+	value: none. See also: scan-path, repo.license.
+
 remove-suffix::
 	If set to "1" and scan-path is enabled, if any repositories are found
 	with a suffix of ".git", this suffix will be removed for the url and
@@ -585,6 +591,17 @@ repo.readme::
 	are no non-public files located in the same directory as the readme
 	file. Default value: <readme>.
 
+repo.license::
+	A path (relative to <repo.path>) which specifies a file to include
+	verbatim as the "license" page for this repo. You may also specify a
+	git refspec by head or by hash by prepending the refspec followed by
+	a colon. For example, "master:LICENSE". If the value begins
+	with a colon, i.e. ":COPYING", the default branch of the
+	repository will be used. Sharing any file will expose that entire
+	directory tree to the "/license/PATH" endpoints, so be sure that there
+	are no non-public files located in the same directory as the license 
+	file. Default value: <license>.
+
 repo.section::
 	Override the current section name for this repository. Default value:
 	none.
@@ -669,7 +686,7 @@ Parameters are provided to filters as follows.
 about filter::
 	This filter is given a single parameter: the filename of the source
 	file to filter. The filter can use the filename to determine (for
-	example) the type of syntax to follow when formatting the readme file.
+	example) the type of syntax to follow when formatting the readme/license file.
 	The about text that is to be filtered is available on standard input
 	and the filtered text is expected on standard output.
 
@@ -930,6 +947,10 @@ readme=:install.txt
 readme=:INSTALL
 readme=:install
 
+license=:LICENSE.md
+license=:LICENSE
+license=:COPYING.md
+license=:COPYING
 
 ##
 ## List of repositories.
@@ -946,6 +967,7 @@ repo.path=/pub/git/foo.git
 repo.desc=the master foo repository
 repo.owner=fooman@example.com
 repo.readme=info/web/about.html
+repo.license=GPLv3.html
 
 
 repo.url=bar
diff --git a/cmd.c b/cmd.c
index 0eb75b1..fb1ccc0 100644
--- a/cmd.c
+++ b/cmd.c
@@ -50,7 +50,7 @@ static void about_fn(void)
 			free(currenturl);
 			free(redirect);
 		} else if (ctx.repo->readme.nr)
-			cgit_print_repo_readme(ctx.qry.path);
+			cgit_print_repo_info_file(ctx.repo->readme, ctx.qry.path);
 		else if (ctx.repo->homepage)
 			cgit_redirect(ctx.repo->homepage, false);
 		else {
@@ -97,6 +97,30 @@ static void info_fn(void)
 	cgit_clone_info();
 }
 
+static void license_fn(void)
+{
+	if (ctx.repo) {
+		size_t path_info_len = ctx.env.path_info ? strlen(ctx.env.path_info) : 0;
+		if (!ctx.qry.path &&
+		    ctx.qry.url[strlen(ctx.qry.url) - 1] != '/' &&
+		    (!path_info_len || ctx.env.path_info[path_info_len - 1] != '/')) {
+			char *currenturl = cgit_currenturl();
+			char *redirect = fmtalloc("%s/", currenturl);
+			cgit_redirect(redirect, true);
+			free(currenturl);
+			free(redirect);
+		} else if (ctx.repo->license.nr)
+			cgit_print_repo_info_file(ctx.repo->license, ctx.qry.path);
+		else {
+			char *currenturl = cgit_currenturl();
+			char *redirect = fmtalloc("%s../", currenturl);
+			cgit_redirect(redirect, false);
+			free(currenturl);
+			free(redirect);
+		}
+	}
+}
+
 static void log_fn(void)
 {
 	cgit_print_log(ctx.qry.oid, ctx.qry.ofs, ctx.cfg.max_commit_count,
@@ -178,6 +202,7 @@ struct cgit_cmd *cgit_get_cmd(void)
 		def_cmd(commit, 1, 1, 0),
 		def_cmd(diff, 1, 1, 0),
 		def_cmd(info, 1, 0, 1),
+		def_cmd(license, 1, 1, 0),
 		def_cmd(log, 1, 1, 0),
 		def_cmd(ls_cache, 0, 0, 0),
 		def_cmd(objects, 1, 0, 1),
diff --git a/shared.c b/shared.c
index 8115469..c7b98cc 100644
--- a/shared.c
+++ b/shared.c
@@ -70,6 +70,7 @@ struct cgit_repo *cgit_add_repo(const char *url)
 	ret->commit_sort = ctx.cfg.commit_sort;
 	ret->module_link = ctx.cfg.module_link;
 	ret->readme = ctx.cfg.readme;
+	ret->license = ctx.cfg.license;
 	ret->mtime = -1;
 	ret->about_filter = ctx.cfg.about_filter;
 	ret->commit_filter = ctx.cfg.commit_filter;
diff --git a/ui-shared.c b/ui-shared.c
index acd8ab5..207bf69 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1056,6 +1056,10 @@ void cgit_print_pageheader(void)
 		if (ctx.repo->max_stats)
 			cgit_stats_link("stats", NULL, hc("stats"),
 					ctx.qry.head, ctx.qry.vpath);
+		if (ctx.repo->license.nr)
+			reporevlink("license", "license", NULL,
+				    hc("license"), ctx.qry.head, NULL,
+				    NULL);
 		if (ctx.repo->homepage) {
 			html("<a href='");
 			html_attr(ctx.repo->homepage);
diff --git a/ui-summary.c b/ui-summary.c
index 947812a..dc4d0a6 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -66,11 +66,11 @@ void cgit_print_summary(void)
 }
 
 /* The caller must free the return value. */
-static char* append_readme_path(const char *filename, const char *ref, const char *path)
+static char* append_file_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. */
+	 * to the directory containing the configured file. */
 
 	file = xstrdup(filename);
 	base_dir = dirname(file);
@@ -98,8 +98,7 @@ static char* append_readme_path(const char *filename, const char *ref, const cha
 
 	return full_path;
 }
-
-void cgit_print_repo_readme(const char *path)
+void cgit_print_repo_info_file(struct string_list file, const char *path)
 {
 	char *filename, *ref, *mimetype;
 	int free_filename = 0;
@@ -115,20 +114,20 @@ void cgit_print_repo_readme(const char *path)
 	free(mimetype);
 
 	cgit_print_layout_start();
-	if (ctx.repo->readme.nr == 0)
+	if (file.nr == 0)
 		goto done;
 
-	filename = ctx.repo->readme.items[0].string;
-	ref = ctx.repo->readme.items[0].util;
+	filename = file.items[0].string;
+	ref = file.items[0].util;
 
 	if (path) {
 		free_filename = 1;
-		filename = append_readme_path(filename, ref, path);
+		filename = append_file_path(filename, ref, path);
 		if (!filename)
 			goto done;
 	}
 
-	/* Print the calculated readme, either from the git repo or from the
+	/* Print the calculated file, either from the git repo or from the
 	 * filesystem, while applying the about-filter.
 	 */
 	html("<div id='summary'>");
diff --git a/ui-summary.h b/ui-summary.h
index cba696a..9592802 100644
--- a/ui-summary.h
+++ b/ui-summary.h
@@ -1,7 +1,9 @@
 #ifndef UI_SUMMARY_H
 #define UI_SUMMARY_H
 
+#include <string-list.h>
+
 extern void cgit_print_summary(void);
-extern void cgit_print_repo_readme(const char *path);
+extern void cgit_print_repo_info_file(struct string_list file, const char *path);
 
 #endif /* UI_SUMMARY_H */
-- 
2.31.1


                 reply	other threads:[~2021-06-15 16:07 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210615155724.97097-1-me@cameronkatri.com \
    --to=me@cameronkatri.com \
    --cc=cgit@lists.zx2c4.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).