cgit

commit cd4c77d989983778432363061e99219f034c3717

Author: Jason A. Donenfeld <Jason@zx2c4.com>

readme: Accept multiple candidates and test them.

The readme variable may now contain multiple space deliminated entries,
which per usual are either a filepath or a git ref filepath. If multiple
are specified, cgit will now select the first one in the list that
exists. This is to make it easier to specify multiple default readme
types in the main cgitrc file and have them automatically get applied to
each repo based on what exists.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>

 cgit.c | 35 ++++++++++++++++++++++++++++++
 ui-blob.c | 28 +++++++++++++++++++++++
 ui-blob.h | 1 
 ui-summary.c | 61 +++++++++++++++++++++++++++--------------------------
 ui-summary.h | 1 


diff --git a/cgit.c b/cgit.c
index 04682be06d8dd60dcecfbe5c89c4beeab1312880..f738b83acd4d442327cc3d721ac367b4d5666f29 100644
--- a/cgit.c
+++ b/cgit.c
@@ -14,6 +14,8 @@ #include "configfile.h"
 #include "html.h"
 #include "ui-shared.h"
 #include "ui-stats.h"
+#include "ui-blob.h"
+#include "ui-summary.h"
 #include "scan-tree.h"
 
 const char *cgit_version = CGIT_VERSION;
@@ -469,6 +471,38 @@ 		return "master";
 	return xstrdup(ref + 11);
 }
 
+static void choose_readme(struct cgit_repo *repo)
+{
+	char *entry, *filename, *ref;
+
+	/* If there's no space, we skip the possibly expensive
+	 * selection process. */
+	if (!repo->readme || !strchr(repo->readme, ' '))
+		return;
+
+	for (entry = strtok(repo->readme, " "); entry; entry = strtok(NULL, " ")) {
+		cgit_parse_readme(entry, NULL, &filename, &ref, repo);
+		if (!(*filename)) {
+			free(filename);
+			free(ref);
+			continue;
+		}
+		if (*ref && cgit_ref_path_exists(filename, ref)) {
+			free(filename);
+			free(ref);
+			break;
+		}
+		if (!access(filename, R_OK)) {
+			free(filename);
+			free(ref);
+			break;
+		}
+		free(filename);
+		free(ref);
+	}
+	repo->readme = entry;
+}
+
 static int prepare_repo_cmd(struct cgit_context *ctx)
 {
 	unsigned char sha1[20];
@@ -537,6 +571,7 @@ 		return 1;
 	}
 	sort_string_list(&ctx->repo->submodules);
 	cgit_prepare_repo_env(ctx->repo);
+	choose_readme(ctx->repo);
 	return 0;
 }
 




diff --git a/ui-blob.c b/ui-blob.c
index 8f6989f320b2317857b7161865aba55db915567b..b4be139043519d590b4891963e35662e40ffdd6d 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -13,7 +13,7 @@ #include "html.h"
 #include "ui-shared.h"
 
 struct walk_tree_context {
-	char *match_path;
+	const char *match_path;
 	unsigned char *matched_sha1;
 	int found_path;
 };
@@ -29,6 +29,32 @@ 		return READ_TREE_RECURSIVE;
 	memmove(walk_tree_ctx->matched_sha1, sha1, 20);
 	walk_tree_ctx->found_path = 1;
 	return 0;
+}
+
+int cgit_ref_path_exists(const char *path, const char *ref)
+{
+        unsigned char sha1[20];
+        unsigned long size;
+        struct pathspec_item path_items = {
+                .match = path,
+                .len = strlen(path)
+        };
+        struct pathspec paths = {
+                .nr = 1,
+                .items = &path_items
+        };
+        struct walk_tree_context walk_tree_ctx = {
+                .match_path = path,
+                .matched_sha1 = sha1,
+                .found_path = 0
+        };
+
+        if (get_sha1(ref, sha1))
+                return 0;
+        if (sha1_object_info(sha1, &size) != OBJ_COMMIT) 
+                return 0;
+        read_tree_recursive(lookup_commit_reference(sha1)->tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx);
+        return walk_tree_ctx.found_path;
 }
 
 int cgit_print_file(char *path, const char *head)




diff --git a/ui-blob.h b/ui-blob.h
index d7e7d4547626b200616f574806323f297ec3d72d..ce3649fea8762f5cff2c3168f59c9dd3da3d1328 100644
--- a/ui-blob.h
+++ b/ui-blob.h
@@ -1,6 +1,7 @@
 #ifndef UI_BLOB_H
 #define UI_BLOB_H
 
+extern int cgit_ref_path_exists(const char *path, const char *ref);
 extern int cgit_print_file(char *path, const char *head);
 extern void cgit_print_blob(const char *hex, char *path, const char *head);
 




diff --git a/ui-summary.c b/ui-summary.c
index ffad4f29e03e1e9d01bd8c0670b1ca74da83646c..2f8a822ad5c1b5414b99ffeec2573669b2ba9a46 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -95,70 +95,71 @@ 		print_urls(ctx.cfg.clone_prefix, ctx.repo->url);
 	html("</table>");
 }
 
-void cgit_print_repo_readme(char *path)
+/* The caller must free filename and ref after calling this. */
+void cgit_parse_readme(const char *readme, const char *path, char **filename, char **ref, struct cgit_repo *repo)
 {
-	char *slash, *tmp, *colon, *ref;
-	int free_filename = 0;
+	const char *slash, *colon;
 
-	if (!ctx.repo->readme || !(*ctx.repo->readme))
-		return;
+	*filename = NULL;
+	*ref = NULL;
 
-	ref = NULL;
+	if (!readme || !(*readme))
+		return;
 
 	/* Check if the readme is tracked in the git repo. */
-	colon = strchr(ctx.repo->readme, ':');
+	colon = strchr(readme, ':');
 	if (colon && strlen(colon) > 1) {
-		*colon = '\0';
 		/* If it starts with a colon, we want to use
 		 * the default branch */
-		if (colon == ctx.repo->readme && ctx.repo->defbranch)
-			ref = ctx.repo->defbranch;
+		if (colon == readme && repo->defbranch)
+			*ref = xstrdup(repo->defbranch);
 		else
-			ref = ctx.repo->readme;
-		ctx.repo->readme = colon + 1;
-		if (!(*ctx.repo->readme))
-			return;
+			*ref = xstrndup(readme, colon - readme);
+		readme = colon + 1;
 	}
 
 	/* Prepend repo path to relative readme path unless tracked. */
-	if (!ref && *ctx.repo->readme != '/')
-		ctx.repo->readme = fmtalloc("%s/%s", ctx.repo->path,
-						ctx.repo->readme);
+	if (!(*ref) && *readme != '/')
+		readme = fmtalloc("%s/%s", repo->path, readme);
 
 	/* 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 readme. */
 	if (path) {
-		slash = strrchr(ctx.repo->readme, '/');
+		slash = strrchr(readme, '/');
 		if (!slash) {
 			if (!colon)
 				return;
 			slash = colon;
 		}
-		free_filename = 1;
-		tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1);
-		strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1);
-		strcpy(tmp + (slash - ctx.repo->readme + 1), path);
+		*filename = xmalloc(slash - readme + 1 + strlen(path) + 1);
+		strncpy(*filename, readme, slash - readme + 1);
+		strcpy(*filename + (slash - readme + 1), path);
 	} else
-		tmp = ctx.repo->readme;
+		*filename = xstrdup(readme);
+}
+
+void cgit_print_repo_readme(char *path)
+{
+	char *filename, *ref;
+	cgit_parse_readme(ctx.repo->readme, path, &filename, &ref, ctx.repo);
 
 	/* Print the calculated readme, either from the git repo or from the
 	 * filesystem, while applying the about-filter.
 	 */
 	html("<div id='summary'>");
 	if (ctx.repo->about_filter) {
-		ctx.repo->about_filter->argv[1] = tmp;
+		ctx.repo->about_filter->argv[1] = filename;
 		cgit_open_filter(ctx.repo->about_filter);
 	}
 	if (ref)
-		cgit_print_file(tmp, ref);
+		cgit_print_file(filename, ref);
 	else
-		html_include(tmp);
+		html_include(filename);
 	if (ctx.repo->about_filter) {
 		cgit_close_filter(ctx.repo->about_filter);
 		ctx.repo->about_filter->argv[1] = NULL;
 	}
 	html("</div>");
-	if (free_filename)
-		free(tmp);
+	free(filename);
+	free(ref);
 }




diff --git a/ui-summary.h b/ui-summary.h
index c01f56012dcdcd64f39f6935c20eb5d18efb1b33..d6dc5bac67bf1eabf90cc0de3382aad9a080e97a 100644
--- a/ui-summary.h
+++ b/ui-summary.h
@@ -1,6 +1,7 @@
 #ifndef UI_SUMMARY_H
 #define UI_SUMMARY_H
 
+extern void cgit_parse_readme(const char *readme, const char *path, char **filename, char **ref, struct cgit_repo *repo);
 extern void cgit_print_summary();
 extern void cgit_print_repo_readme(char *path);