Diff options
Diffstat (limited to '')
-rw-r--r--source/ui-shared.c689
1 file changed, 308 insertions, 381 deletions
diff --git a/source/ui-shared.c b/source/ui-shared.c
index 936a6a4..81db404 100644
--- a/source/ui-shared.c
+++ b/source/ui-shared.c
@@ -1,17 +1,17 @@
/*
* The output every cgit page has in common. A link from one page to another is
- * built here because only this file knows whether a site addresses a request
- * as a path below a virtual root or as a query string, and the two are spelled
- * differently down to the separator between arguments. The headers, the
- * document head, the header block, the footer, the error page and the age
- * column are here so that a page handler is left with only its own body to
- * produce.
+ * built here, as a path below the virtual root followed by the arguments the
+ * target page needs, so that every page spells its urls the same way. The
+ * headers, the document head, the header block, the footer, the error page and
+ * the age column are here so that a page handler is left with only its own body
+ * to produce.
*/
#define USE_THE_REPOSITORY_VARIABLE
#include "cache.h"
#include "cgit.h"
+#include "filter.h"
#include "html.h"
#include "shared.h"
#include "submodule-config.h"
@@ -35,39 +35,17 @@
// into a tab title while it still leaves room to spare in that buffer.
#define MAX_TITLE_PATH_LEN 256
-static const char *repo_basename(const char *reponame)
+/*
+ * Write one argument of a query string after delim, and return the delimiter
+ * for the next one.
+ */
+static const char *url_arg(const char *delim, const char *name, const char *value)
{
- // The result lives in a static buffer, so it is only good until the
- // next call.
- static char buf[1024];
- const char *slash;
- size_t len;
- int last;
-
- len = strlcpy(buf, reponame, sizeof(buf));
- if (len >= sizeof(buf))
- die("Repository name too long: %s", reponame);
- last = len - 1;
- while (last && buf[last] == '/')
- buf[last--] = '\0';
- if (last >= 3 && starts_with(&buf[last - 3], ".git")) {
- last -= 3;
- buf[last--] = '\0';
- }
- while (last && buf[last] == '/')
- buf[last--] = '\0';
- slash = strrchr(buf, '/');
- if (slash)
- return ++slash;
- return buf;
-}
-
-static const char *root_url(void)
-{
- if (ctx.cfg.virtual_root)
- return ctx.cfg.virtual_root;
- else
- return ctx.cfg.script_name;
+ html(delim);
+ html(name);
+ html("=");
+ html_url_arg(value);
+ return "&";
}
static void site_url(const char *page, const char *search, const char *sort, int ofs, int always_root)
@@ -75,7 +53,7 @@ static void site_url(const char *page, const char *search, const char *sort, int
const char *delim = "?";
if (always_root || page)
- html_attr(root_url());
+ html_attr(ctx.cfg.virtual_root);
else {
char *currenturl = cgit_currenturl();
html_attr(currenturl);
@@ -86,42 +64,20 @@ static void site_url(const char *page, const char *search, const char *sort, int
htmlf("?p=%s", page);
delim = "&";
}
- if (search) {
- html(delim);
- html("q=");
- html_url_arg(search);
- delim = "&";
- }
- if (sort) {
- html(delim);
- html("s=");
- html_url_arg(sort);
- delim = "&";
- }
- if (ofs) {
- html(delim);
- htmlf("ofs=%d", ofs);
- }
+ if (search)
+ delim = url_arg(delim, "q", search);
+ if (sort)
+ delim = url_arg(delim, "s", sort);
+ if (ofs)
+ htmlf("%sofs=%d", delim, ofs);
}
/*
- * Write the class of a link, and mark the link as the current one for
- * assistive technology when the class says it is the active tab or the
+ * Open an anchor up to the start of its href, marking the link as the current
+ * one for assistive technology when the class says it is the active tab or the
* current page of a pager.
*/
-static void link_class(const char *class)
-{
- if (!class)
- return;
- html(" class='");
- html_attr(class);
- html("'");
- if (!strcmp(class, "active") || !strcmp(class, "current"))
- html(" aria-current='page'");
-}
-
-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)
+static void open_link(const char *title, const char *class)
{
html("<a");
if (title) {
@@ -129,8 +85,20 @@ static void site_link(const char *page, const char *name, const char *title,
html_attr(title);
html("'");
}
- link_class(class);
+ if (class) {
+ html(" class='");
+ html_attr(class);
+ html("'");
+ if (!strcmp(class, "active") || !strcmp(class, "current"))
+ html(" aria-current='page'");
+ }
html(" href='");
+}
+
+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)
+{
+ open_link(title, class);
site_url(page, search, sort, ofs, always_root);
html("'>");
html_txt(name);
@@ -138,36 +106,20 @@ static void site_link(const char *page, const char *name, const char *title,
}
/*
- * Write the url naming the repository, the page and the path, and return
- * whether the url has opened its query string already.
+ * Write the url naming the repository, the page and the path.
*/
-static int write_page_url(const char *page, const char *path)
-{
- 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);
- }
- return 0;
- }
- html_url_path(ctx.cfg.script_name);
- html("?url=");
- html_url_arg(ctx.repo->url);
+static void write_page_url(const char *page, const char *path)
+{
+ 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_arg(page);
+ html_url_path(page);
html("/");
if (path)
- html_url_arg(path);
+ html_url_path(path);
}
- return 1;
}
/*
@@ -187,34 +139,19 @@ static int head_is_named(const char *head)
static const char *repolink(const char *title, const char *class, const char *page,
const char *head, const char *path)
{
- const char *delim;
+ const char *delim = "?";
- html("<a");
- if (title) {
- html(" title='");
- html_attr(title);
- html("'");
- }
- link_class(class);
- html(" href='");
- delim = write_page_url(page, path) ? "&amp;" : "?";
- if (head_is_named(head)) {
- html(delim);
- html("h=");
- html_url_arg(head);
- delim = "&amp;";
- }
- return cgit_fmt("%s", delim);
+ open_link(title, class);
+ write_page_url(page, path);
+ if (head_is_named(head))
+ delim = url_arg(delim, "h", head);
+ return delim;
}
static const char *emit_rev_arg(const char *delim, const char *rev)
{
- if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) {
- html(delim);
- html("id=");
- html_url_arg(rev);
- return "&amp;";
- }
+ if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head))
+ return url_arg(delim, "id", rev);
return delim;
}
@@ -233,14 +170,11 @@ static void reporevlink(const char *page, const char *name, const char *title,
static void emit_diff_args(const char *delim)
{
if (ctx.qry.difftype) {
- html(delim);
- htmlf("dt=%d", (int)ctx.qry.difftype);
+ htmlf("%sdt=%d", delim, (int)ctx.qry.difftype);
delim = "&amp;";
}
if (ctx.qry.context > 0 && ctx.qry.context != DEFAULT_DIFF_CONTEXT) {
- html(delim);
- html("context=");
- htmlf("%d", ctx.qry.context);
+ htmlf("%scontext=%d", delim, ctx.qry.context);
delim = "&amp;";
}
if (ctx.qry.ignorews) {
@@ -254,51 +188,6 @@ static void emit_diff_args(const char *delim)
}
}
-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;
-}
-
-/*
- * A repository can supply the module-link template through a repo-local
- * cgitrc, so the %s placeholders are expanded here instead of the template
- * being handed to printf, where a surplus conversion would read past the
- * argument list.
- */
-static void emit_module_link(const char *tmpl, const char **args, int nargs)
-{
- struct strbuf sb = STRBUF_INIT;
- int used = 0;
-
- while (*tmpl) {
- if (*tmpl != '%') {
- strbuf_addch(&sb, *tmpl++);
- continue;
- }
- tmpl++;
- if (*tmpl == '%') {
- strbuf_addch(&sb, '%');
- tmpl++;
- } else if (*tmpl == 's' && used < nargs) {
- strbuf_addstr(&sb, args[used++]);
- tmpl++;
- } else {
- strbuf_addch(&sb, '%');
- }
- }
- html_attr(sb.buf);
- strbuf_release(&sb);
-}
-
static struct date_mode configured_date_mode(void)
{
struct date_mode mode = ctx.cfg.date_mode;
@@ -382,23 +271,6 @@ static void emit_js_link(const char *src)
html("' defer></script>\n");
}
-static void add_clone_urls(void (*fn)(const char *), char *urls, char *suffix)
-{
- struct strbuf **url_list = strbuf_split_str(urls, ' ', 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);
-}
-
/*
* The id parameter pins a page to one commit while the rest of the chrome
* keeps naming the branch. Answer that commit, or null when the request
@@ -519,7 +391,7 @@ static void print_header(void)
if (logo_link && *logo_link)
html_attr(logo_link);
else
- html_attr(root_url());
+ html_attr(ctx.cfg.virtual_root);
html("'><img src='");
html_attr(logo);
html("' alt='cgit logo'></a>\n");
@@ -551,7 +423,7 @@ static void print_header(void)
// Switching branch means leaving the pinned commit behind, so
// the id fields stay out of the form.
ctx.qry.oid = ctx.qry.oid2 = NULL;
- cgit_add_hidden_formfields(0, 1, ctx.qry.page);
+ cgit_add_hidden_formfields(0, 1);
ctx.qry.oid = oid;
ctx.qry.oid2 = oid2;
html("<select name='h' aria-label='Branch'>\n");
@@ -655,10 +527,9 @@ static void print_repo_tabs(void)
static void print_repo_search(void)
{
html("<form class='search' role='search' method='get' action='");
- if (ctx.cfg.virtual_root)
- write_page_url("log", ctx.qry.vpath);
+ write_page_url("log", ctx.qry.vpath);
html("'>\n");
- cgit_add_hidden_formfields(1, 0, "log");
+ cgit_add_hidden_formfields(1, 0);
html("<select name='qt' aria-label='Search type'>\n");
html_option("grep", "log msg", ctx.qry.grep);
html_option("author", "author", ctx.qry.grep);
@@ -743,14 +614,8 @@ static void self_link(const char *name, const char *title, const char *class)
cgit_diff_link(name, title, class, ctx.qry.head, ctx.qry.oid, ctx.qry.oid2, ctx.qry.path);
else if (!strcmp(ctx.qry.page, "stats"))
cgit_stats_link(name, title, class, ctx.qry.head, ctx.qry.path);
- else {
- // A page name this switch does not know still gets a plain
- // repolink.
- repolink(title, class, ctx.qry.page, ctx.qry.head, ctx.qry.path);
- html("'>");
- html_txt(name);
- html("</a>");
- }
+ else
+ reporevlink(ctx.qry.page, name, title, class, ctx.qry.head, NULL, ctx.qry.path);
}
static void print_path_crumbs(char *path)
@@ -778,26 +643,10 @@ static void print_path_crumbs(char *path)
ctx.qry.path = old_path;
}
-/*
- * A reverse memchr, which glibc has as memrchr but macOS and the BSDs do not.
- */
-static const char *find_last_char(const char *start, const char *end, int c)
-{
- while (end > start) {
- if (*--end == c)
- return end;
- }
- return NULL;
-}
-
static void 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_vtxtf(fmt, ap);
html("</div>\n");
}
@@ -826,18 +675,18 @@ char *cgit_hosturl(void)
return NULL;
if (!ctx.env.server_port || atoi(ctx.env.server_port) == DEFAULT_HTTP_PORT)
return xstrdup(ctx.env.server_name);
- return cgit_fmtalloc("%s:%s", ctx.env.server_name, ctx.env.server_port);
+ return xstrfmt("%s:%s", ctx.env.server_name, ctx.env.server_port);
}
char *cgit_currenturl(void)
{
- const char *root = root_url();
+ const char *root = ctx.cfg.virtual_root;
if (!ctx.qry.url)
return xstrdup(root);
if (root[0] && root[strlen(root) - 1] == '/')
- return cgit_fmtalloc("%s%s", root, ctx.qry.url);
- return cgit_fmtalloc("%s/%s", root, ctx.qry.url);
+ return xstrfmt("%s%s", root, ctx.qry.url);
+ return xstrfmt("%s/%s", root, ctx.qry.url);
}
char *cgit_currentquery(void)
@@ -872,16 +721,11 @@ char *cgit_currentquery(void)
char *cgit_currentfullurl(void)
{
- const char *root = root_url();
+ char *url = cgit_currenturl();
char *query = cgit_currentquery();
- char *ret;
+ char *ret = xstrfmt("%s%s", url, query);
- if (!ctx.qry.url)
- ret = cgit_fmtalloc("%s%s", root, query);
- else if (root[0] && root[strlen(root) - 1] == '/')
- ret = cgit_fmtalloc("%s%s%s", root, ctx.qry.url, query);
- else
- ret = cgit_fmtalloc("%s/%s%s", root, ctx.qry.url, query);
+ free(url);
free(query);
return ret;
}
@@ -891,7 +735,7 @@ const char *cgit_loginurl(void)
static const char *login_url;
if (!login_url)
- login_url = cgit_fmtalloc("%s?p=login", root_url());
+ login_url = xstrfmt("%s?p=login", ctx.cfg.virtual_root);
return login_url;
}
@@ -899,14 +743,9 @@ char *cgit_repourl(const char *reponame)
{
struct strbuf sb = STRBUF_INIT;
- if (ctx.cfg.virtual_root) {
- strbuf_addstr(&sb, ctx.cfg.virtual_root);
- cgit_add_url_path(&sb, reponame);
- strbuf_addch(&sb, '/');
- } else {
- strbuf_addstr(&sb, "?r=");
- cgit_add_url_path(&sb, reponame);
- }
+ strbuf_addstr(&sb, ctx.cfg.virtual_root);
+ cgit_add_url_path(&sb, reponame);
+ strbuf_addch(&sb, '/');
return strbuf_detach(&sb, NULL);
}
@@ -914,21 +753,12 @@ char *cgit_fileurl(const char *reponame, const char *pagename, const char *filen
const char *query)
{
struct strbuf sb = STRBUF_INIT;
- const char *delim;
- // The query joiner is a bare ampersand, because each sink escapes the
- // whole string for wherever it lands.
- if (ctx.cfg.virtual_root) {
- strbuf_addstr(&sb, ctx.cfg.virtual_root);
- delim = "?";
- } else {
- strbuf_addstr(&sb, "?url=");
- delim = "&";
- }
+ strbuf_addstr(&sb, ctx.cfg.virtual_root);
cgit_add_url_path(&sb, reponame);
strbuf_addf(&sb, "/%s/%s", pagename, filename ? filename : "");
if (query)
- strbuf_addf(&sb, "%s%s", delim, query);
+ strbuf_addf(&sb, "?%s", query);
return strbuf_detach(&sb, NULL);
}
@@ -939,26 +769,32 @@ char *cgit_pageurl(const char *reponame, const char *pagename, const char *query
void cgit_tree_url(const char *head, struct strbuf *base, struct strbuf *query)
{
- int query_open;
-
html_capture_begin(base);
- query_open = write_page_url("tree", NULL);
+ write_page_url("tree", NULL);
html_capture_end();
if (!head_is_named(head))
return;
html_capture_begin(query);
- html(query_open ? "&" : "?");
- html("h=");
+ html("?h=");
html_url_arg(head);
html_capture_end();
}
const char *cgit_snapshot_prefix(const struct cgit_repo *repo)
{
+ static struct strbuf name = STRBUF_INIT;
+ const char *slash;
+
if (repo->snapshot_prefix)
return repo->snapshot_prefix;
- return repo_basename(repo->url);
+ strbuf_reset(&name);
+ strbuf_addstr(&name, repo->url);
+ strbuf_trim_trailing_dir_sep(&name);
+ strbuf_strip_suffix(&name, ".git");
+ strbuf_trim_trailing_dir_sep(&name);
+ slash = strrchr(name.buf, '/');
+ return slash ? slash + 1 : name.buf;
}
void cgit_index_link(const char *name, const char *title, const char *class,
@@ -1004,18 +840,11 @@ void cgit_log_link(const char *name, const char *title, const char *class,
delim = repolink(title, class, "log", head, path);
delim = emit_rev_arg(delim, rev);
if (grep && pattern) {
- html(delim);
- html("qt=");
- html_url_arg(grep);
- delim = "&amp;";
- html(delim);
- html("q=");
- html_url_arg(pattern);
+ delim = url_arg(delim, "qt", grep);
+ delim = url_arg(delim, "q", pattern);
}
if (ofs > 0) {
- html(delim);
- html("ofs=");
- htmlf("%d", ofs);
+ htmlf("%sofs=%d", delim, ofs);
delim = "&amp;";
}
if (showmsg) {
@@ -1067,12 +896,8 @@ void cgit_diff_link(const char *name, const char *title, const char *class,
delim = repolink(title, class, "diff", head, path);
delim = emit_rev_arg(delim, new_rev);
- if (old_rev) {
- html(delim);
- html("id2=");
- html_url_arg(old_rev);
- delim = "&amp;";
- }
+ if (old_rev)
+ delim = url_arg(delim, "id2", old_rev);
emit_diff_args(delim);
html("'>");
html_txt(name);
@@ -1094,16 +919,13 @@ void cgit_stats_link(const char *name, const char *title, const char *class,
void cgit_object_link(struct object *obj, int full)
{
const char *page;
- char *shortrev, *fullrev, *name;
+ char *fullrev, *name;
fullrev = oid_to_hex(&obj->oid);
- if (full) {
+ if (full)
name = cgit_fmt("%s %s", type_name(obj->type), fullrev);
- } else {
- shortrev = xstrdup(fullrev);
- shortrev[SHORT_HASH_LEN] = '\0';
- name = cgit_fmt("%s %s...", type_name(obj->type), shortrev);
- }
+ else
+ name = cgit_fmt("%s %.*s...", type_name(obj->type), SHORT_HASH_LEN, fullrev);
if (obj->type == OBJ_COMMIT) {
cgit_commit_link(name, NULL, NULL, ctx.qry.head, fullrev, NULL);
return;
@@ -1133,13 +955,6 @@ static const struct object_id *page_treeish(void)
return state > 0 ? &oid : NULL;
}
-static void strip_git_suffix(struct strbuf *sb)
-{
- while (sb->len && sb->buf[sb->len - 1] == '/')
- strbuf_setlen(sb, sb->len - 1);
- strbuf_strip_suffix(sb, ".git");
-}
-
/*
* Reduces a submodule url to the path it names on its host, so the tail can
* be compared against the urls this instance serves. A relative url points
@@ -1176,7 +991,8 @@ static char *submodule_url_path(const char *url)
strbuf_addstr(&sb, url);
}
- strip_git_suffix(&sb);
+ strbuf_trim_trailing_dir_sep(&sb);
+ strbuf_strip_suffix(&sb, ".git");
while (sb.len && sb.buf[0] == '/')
strbuf_remove(&sb, 0, 1);
if (!sb.len) {
@@ -1274,7 +1090,7 @@ static void gitmodules_link(const char *path, const char *rev, char **module,
target = repo_serving_url(url);
if (target) {
- char *query = cgit_fmtalloc("id=%s", rev);
+ char *query = xstrfmt("id=%s", rev);
*module = cgit_pageurl(target->url, "tree", query);
*commit = cgit_pageurl(target->url, "commit", query);
@@ -1287,8 +1103,7 @@ static void gitmodules_link(const char *path, const char *rev, char **module,
forge = forge_for_host(rest, strcspn(rest, "/"));
if (forge) {
strbuf_addstr(&sb, url);
- while (sb.len && sb.buf[sb.len - 1] == '/')
- strbuf_setlen(&sb, sb.len - 1);
+ strbuf_trim_trailing_dir_sep(&sb);
if (!forge->keep_dot_git)
strbuf_strip_suffix(&sb, ".git");
strbuf_addstr(&sb, forge->commit_seg);
@@ -1310,8 +1125,7 @@ static void gitmodules_link(const char *path, const char *rev, char **module,
while (*rest == '/')
rest++;
strbuf_addf(&sb, "https://%.*s/%s", (int)(colon - host), host, rest);
- while (sb.len && sb.buf[sb.len - 1] == '/')
- strbuf_setlen(&sb, sb.len - 1);
+ strbuf_trim_trailing_dir_sep(&sb);
if (!forge->keep_dot_git)
strbuf_strip_suffix(&sb, ".git");
*module = xstrdup(sb.buf);
@@ -1325,6 +1139,51 @@ static void gitmodules_link(const char *path, const char *rev, char **module,
*tooltip = url;
}
+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;
+}
+
+/*
+ * A repository can supply the module-link template through a repo-local
+ * cgitrc, so the %s placeholders are expanded here instead of the template
+ * being handed to printf, where a surplus conversion would read past the
+ * argument list.
+ */
+static void emit_module_link(const char *tmpl, const char **args, int nargs)
+{
+ struct strbuf sb = STRBUF_INIT;
+ int used = 0;
+
+ while (*tmpl) {
+ if (*tmpl != '%') {
+ strbuf_addch(&sb, *tmpl++);
+ continue;
+ }
+ tmpl++;
+ if (*tmpl == '%') {
+ strbuf_addch(&sb, '%');
+ tmpl++;
+ } else if (*tmpl == 's' && used < nargs) {
+ strbuf_addstr(&sb, args[used++]);
+ tmpl++;
+ } else {
+ strbuf_addch(&sb, '%');
+ }
+ }
+ html_attr(sb.buf);
+ strbuf_release(&sb);
+}
+
void cgit_submodule_link(const char *class, char *path, const char *rev)
{
struct string_list *list;
@@ -1355,10 +1214,7 @@ void cgit_submodule_link(const char *class, char *path, const char *rev)
name = name ? name + 1 : path;
if (item || ctx.repo->module_link || module) {
- html("<a ");
- if (class)
- htmlf("class='%s' ", class);
- html("href='");
+ open_link(NULL, class);
if (item) {
const char *args[] = { rev };
emit_module_link(item->util, args, 1);
@@ -1373,8 +1229,11 @@ void cgit_submodule_link(const char *class, char *path, const char *rev)
html("</a>");
} else {
html("<span");
- if (class)
- htmlf(" class='%s'", class);
+ if (class) {
+ html(" class='");
+ html_attr(class);
+ html("'");
+ }
if (tooltip) {
html(" title='");
html_attr(tooltip);
@@ -1404,9 +1263,8 @@ void cgit_submodule_link(const char *class, char *path, const char *rev)
struct date_mode cgit_date_mode(enum date_mode_type type)
{
- static struct date_mode mode;
+ struct date_mode mode = date_mode_from_type(type);
- mode.type = type;
mode.local = ctx.cfg.local_time;
return mode;
}
@@ -1450,6 +1308,42 @@ void cgit_print_age(time_t t, int tz, time_t max_relative)
print_rel_date(t, tz, secs * 1.0 / SECONDS_PER_YEAR, "age-years", "years");
}
+void cgit_print_date(timestamp_t date, int tz)
+{
+ html("<time datetime='");
+ html_attr(show_date(date, tz, cgit_date_mode(DATE_ISO8601_STRICT)));
+ html("'>");
+ html_txt(show_date(date, tz, cgit_date_mode(DATE_ISO8601)));
+ html("</time>");
+}
+
+void cgit_print_line_anchors(size_t first, size_t last)
+{
+ struct strbuf numbers = STRBUF_INIT;
+ size_t lineno;
+
+ for (lineno = first; lineno <= last; lineno++) {
+ strbuf_addf(&numbers, "<a id='n%zu' href='#n%zu'>%zu</a>\n", lineno, lineno, lineno);
+ if (numbers.len >= HTML_BATCH) {
+ html_raw(numbers.buf, numbers.len);
+ strbuf_reset(&numbers);
+ }
+ }
+ html_raw(numbers.buf, numbers.len);
+ strbuf_release(&numbers);
+}
+
+void cgit_print_person(const char *name, const char *email, const char *origin, int show_email)
+{
+ cgit_open_filter(ctx.repo->email_filter, email ? email : "", origin);
+ html_txt(name);
+ if (show_email && email && ctx.cfg.enable_plain_email) {
+ html(" ");
+ html_txt(email);
+ }
+ cgit_close_filter(ctx.repo->email_filter);
+}
+
/*
* Whether a content type carries text a charset applies to, which is every
* text type and the XML types of RFC 7303, the atom feed among them.
@@ -1518,11 +1412,12 @@ void cgit_redirect(const char *url, const char *query, bool permanent)
html("\n\n");
}
-void cgit_print_docstart(void)
+static void print_docstart(void)
{
const struct object_id *pinned;
struct string_list_item *item;
- char *host = cgit_hosturl();
+ const char *desc;
+ char *host;
if (ctx.cfg.embedded) {
if (ctx.cfg.header)
@@ -1549,13 +1444,10 @@ void cgit_print_docstart(void)
html("</title>\n");
// The placeholder a scanned repository carries instead of a description
// is left out, since it describes nothing.
- if (ctx.repo && ctx.repo->desc && *ctx.repo->desc && ctx.repo->desc != cgit_default_repo_desc) {
- html("<meta name='description' content='");
- html_attr(ctx.repo->desc);
- html("'>\n");
- } else if (!ctx.repo && ctx.cfg.root_desc && *ctx.cfg.root_desc) {
+ desc = ctx.repo ? ctx.repo->desc : ctx.cfg.root_desc;
+ if (desc && *desc && desc != cgit_default_repo_desc) {
html("<meta name='description' content='");
- html_attr(ctx.cfg.root_desc);
+ html_attr(desc);
html("'>\n");
}
if (ctx.cfg.enable_version_info)
@@ -1585,14 +1477,15 @@ void cgit_print_docstart(void)
html_attr(ctx.cfg.favicon);
html("'>\n");
}
- if (host && ctx.repo && ctx.qry.head) {
+ if (ctx.repo && ctx.qry.head && (host = cgit_hosturl())) {
html("<link rel='alternate' title='Atom feed' href='");
html(cgit_httpscheme());
html_attr(host);
- html(write_page_url("atom", ctx.qry.vpath) ? "&amp;" : "?");
- html("h=");
+ write_page_url("atom", ctx.qry.vpath);
+ html("?h=");
html_url_arg(ctx.qry.head);
html("' type='application/atom+xml'>\n");
+ free(host);
}
if (ctx.repo)
cgit_add_clone_urls(print_rel_vcs_link);
@@ -1604,52 +1497,24 @@ void cgit_print_docstart(void)
html("<body>\n");
if (ctx.cfg.header)
html_include(ctx.cfg.header);
- free(host);
-}
-
-void cgit_print_docend(void)
-{
- html("</main>\n");
- if (ctx.cfg.embedded) {
- html("</div>\n");
- if (ctx.cfg.footer)
- html_include(ctx.cfg.footer);
- return;
- }
- if (ctx.cfg.footer)
- html_include(ctx.cfg.footer);
- else if (ctx.cfg.enable_version_info)
- htmlf("<footer class='footer'>generated by "
- "<a href='https://github.com/brycekwon/cgit'>cgit %s</a> "
- "(<a href='https://git-scm.com/'>git %s</a>)</footer>\n",
- cgit_version, git_version_string);
- else
- html("<footer class='footer'>generated by <a href='https://github.com/brycekwon/cgit'>cgit</a></footer>\n");
- html("</div>\n");
- html("</body>\n</html>\n");
}
void cgit_print_error_page(int code, const char *msg, const char *fmt, ...)
{
va_list ap;
- va_start(ap, fmt);
- cgit_vprint_error_page(code, msg, fmt, ap);
- va_end(ap);
-}
-
-void cgit_vprint_error_page(int code, const char *msg, const char *fmt, va_list ap)
-{
// An error page describes a condition that may not outlast the slot
// holding it. A request naming a commit not yet pushed would cache its
// 404 under the never-expiring static ttl and keep serving it after
// the push, so the error is rendered straight to the visitor instead.
html_flush();
cache_abandon_fill();
+ va_start(ap, fmt);
// Once the status line is out the error can only join the body under
// way, and the page that hit it carries on to its own end.
if (ctx.page.headers_sent) {
vprint_error(fmt, ap);
+ va_end(ap);
return;
}
ctx.page.status = code;
@@ -1661,6 +1526,7 @@ void cgit_vprint_error_page(int code, const char *msg, const char *fmt, va_list
ctx.page.filename = NULL;
cgit_print_layout_start();
vprint_error(fmt, ap);
+ va_end(ap);
cgit_print_layout_end();
}
@@ -1670,16 +1536,64 @@ void cgit_print_blob_too_large(size_t size)
size / 1024, ctx.cfg.max_blob_size);
}
-void cgit_print_layout_start(void)
+struct commit *cgit_get_commit(const char *rev, struct object_id *oid)
{
- cgit_print_http_headers();
- cgit_print_docstart();
- cgit_print_pageheader();
+ struct commit *commit;
+
+ if (repo_get_oid(the_repository, rev, oid)) {
+ cgit_print_error_page(404, "Not Found", "Bad object id: %s", rev);
+ return NULL;
+ }
+ // lookup_commit_reference parses the commit, and fails on one git
+ // cannot parse.
+ commit = lookup_commit_reference(the_repository, oid);
+ if (!commit)
+ cgit_print_error_page(404, "Not Found", "Not a commit: %s", rev);
+ return commit;
}
-void cgit_print_layout_end(void)
+int cgit_get_old_oid(const char *old_rev, const struct commit *commit, struct object_id *oid)
+{
+ if (old_rev)
+ return cgit_get_commit(old_rev, oid) ? 0 : -1;
+ if (commit->parents && commit->parents->item)
+ oidcpy(oid, &commit->parents->item->object.oid);
+ else
+ oidclr(oid, the_repository->hash_algo);
+ return 0;
+}
+
+int cgit_prepare_walk(struct rev_info *rev)
{
- cgit_print_docend();
+ if (!prepare_revision_walk(rev))
+ return 0;
+ cgit_print_error_page(500, "Internal Server Error", "Unable to read the history");
+ return -1;
+}
+
+int cgit_refuse_pathspec_magic(const char *path)
+{
+ if (!path || path[0] != ':')
+ return 0;
+ cgit_print_error_page(400, "Bad Request", "Invalid path");
+ return -1;
+}
+
+static void add_clone_urls(void (*fn)(const char *), char *urls, char *suffix)
+{
+ struct strbuf **url_list = strbuf_split_str(urls, ' ', 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 *))
@@ -1707,12 +1621,6 @@ static void capture_http_clone_url(const char *url)
http_clone_url = xstrdup(url);
}
-/*
- * One row of the clone table. Every row is a link, but a browser cannot
- * follow the ssh, scp or git forms, so those rows point at the first http
- * url in the clone list, or at the repository's own page when the list has
- * none. A colspan of zero leaves the attribute out.
- */
void cgit_print_clone_row(const char *url, int colspan)
{
const char *href = url;
@@ -1742,19 +1650,9 @@ void cgit_print_clone_row(const char *url, int colspan)
free(fallback);
}
-void cgit_add_hidden_formfields(int incl_head, int incl_search, const char *page)
+void cgit_add_hidden_formfields(int incl_head, int incl_search)
{
- 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))
+ if (incl_head && head_is_named(ctx.qry.head))
html_hidden("h", ctx.qry.head);
if (ctx.qry.oid)
@@ -1772,7 +1670,7 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, const char *page
}
}
-void cgit_print_pageheader(void)
+static void print_pageheader(void)
{
const struct object_id *pinned;
@@ -1859,6 +1757,40 @@ void cgit_print_pageheader(void)
html("<main class='content'>\n");
}
+void cgit_print_layout_start(void)
+{
+ cgit_print_http_headers();
+ print_docstart();
+ print_pageheader();
+}
+
+void cgit_print_layout_end(void)
+{
+ html("</main>\n");
+ if (ctx.cfg.embedded) {
+ html("</div>\n");
+ if (ctx.cfg.footer)
+ html_include(ctx.cfg.footer);
+ return;
+ }
+ if (ctx.cfg.footer)
+ html_include(ctx.cfg.footer);
+ else if (ctx.cfg.enable_version_info)
+ htmlf("<footer class='footer'>generated by "
+ "<a href='https://github.com/brycekwon/cgit'>cgit %s</a> "
+ "(<a href='https://git-scm.com/'>git %s</a>)</footer>\n",
+ cgit_version, git_version_string);
+ else
+ html("<footer class='footer'>generated by <a href='https://github.com/brycekwon/cgit'>cgit</a></footer>\n");
+ html("</div>\n");
+ html("</body>\n</html>\n");
+}
+
+static void print_fileperm(unsigned short mode)
+{
+ htmlf("%c%c%c", (mode & 4 ? 'r' : '-'), (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-'));
+}
+
void cgit_print_filemode(unsigned short mode)
{
if (S_ISDIR(mode))
@@ -1869,9 +1801,9 @@ void cgit_print_filemode(unsigned short mode)
html("m");
else
html("-");
- html_fileperm(mode >> 6);
- html_fileperm(mode >> 3);
- html_fileperm(mode);
+ print_fileperm(mode >> 6);
+ print_fileperm(mode >> 3);
+ print_fileperm(mode);
}
// The tag names and their stems, a stem being the name with any leading v or V
@@ -1956,7 +1888,7 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co
html("<span class='snapshot'>");
snapshot_link(filename.buf, NULL, NULL, NULL, NULL, filename.buf);
if (cgit_snapshot_get_sig(ref, f)) {
- strbuf_addstr(&filename, ".asc");
+ strbuf_addstr(&filename, SIG_SUFFIX);
html(" (");
snapshot_link("sig", NULL, NULL, NULL, NULL, filename.buf);
html(")");
@@ -1968,7 +1900,7 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co
// the plain tar it expands to, which is the first
// format in the table.
strbuf_setlen(&filename, filename.len - strlen(f->suffix));
- strbuf_addstr(&filename, ".tar.asc");
+ strbuf_addstr(&filename, ".tar" SIG_SUFFIX);
html(" (");
snapshot_link("sig", NULL, NULL, NULL, NULL, filename.buf);
html(")");
@@ -1982,30 +1914,25 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref, co
void cgit_set_title_from_path(const char *path)
{
struct strbuf sb = STRBUF_INIT;
- const char *slash, *last_slash;
+ char *rest, *slash;
if (!path)
return;
// The path is read from the end so that the title names the file first
// and the directories it sits in after it.
- last_slash = path + strlen(path);
- while ((slash = find_last_char(path, last_slash, '/'))) {
- strbuf_add(&sb, slash + 1, last_slash - slash - 1);
- strbuf_addstr(&sb, " < ");
- last_slash = slash;
- }
- strbuf_add(&sb, path, last_slash - path);
- strbuf_addf(&sb, " - %s", ctx.page.title);
+ rest = xstrdup(path);
+ while ((slash = strrchr(rest, '/'))) {
+ strbuf_addf(&sb, "%s < ", slash + 1);
+ *slash = '\0';
+ }
+ strbuf_addf(&sb, "%s - %s", rest, ctx.page.title);
+ free(rest);
ctx.page.title = strbuf_detach(&sb, NULL);
}
-/*
- * Names the page ahead of the repository and the site, so that the pages of one
- * repository can be told apart in tabs, history and search results.
- */
void cgit_prefix_title(const char *name)
{
- ctx.page.title = cgit_fmtalloc("%s - %s", name,
+ ctx.page.title = xstrfmt("%s - %s", name,
ctx.page.title ? ctx.page.title : ctx.cfg.root_title);
}