+ if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
+ name[ctx.cfg.max_msg_len] = '\0';
+ name[ctx.cfg.max_msg_len - 1] = '.';
+ name[ctx.cfg.max_msg_len - 2] = '.';
+ name[ctx.cfg.max_msg_len - 3] = '.';
+ }
+
+ char *delim;
+
+ delim = repolink(title, class, "commit", head, path);
+ if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) {
+ html(delim);
+ html("id=");
+ html_url_arg(rev);
+ delim = "&";
+ }
+ if (ctx.qry.difftype) {
+ html(delim);
+ htmlf("dt=%d", ctx.qry.difftype);
+ delim = "&";
+ }
+ if (ctx.qry.context > 0 && ctx.qry.context != 3) {
+ html(delim);
+ html("context=");
+ htmlf("%d", ctx.qry.context);
+ delim = "&";
+ }
+ if (ctx.qry.ignorews) {
+ html(delim);
+ html("ignorews=1");
+ delim = "&";
+ }
+ if (ctx.qry.follow) {
+ html(delim);
+ html("follow=1");
+ }
+ html("'>");
+ if (name[0] != '\0')
+ html_txt(name);
+ else
+ html_txt("(no commit message)");
+ html("</a>");
+}
+
+void cgit_refs_link(const char *name, const char *title, const char *class,
+ const char *head, const char *rev, const char *path)
+{
+ reporevlink("refs", name, title, class, head, rev, path);
+}
+
+void cgit_snapshot_link(const char *name, const char *title, const char *class,
+ const char *head, const char *rev,
+ const char *archivename)
+{
+ reporevlink("snapshot", name, title, class, head, rev, archivename);
+}
+
+void cgit_diff_link(const char *name, const char *title, const char *class,
+ const char *head, const char *new_rev, const char *old_rev,
+ const char *path)
+{
+ char *delim;
+
+ delim = repolink(title, class, "diff", head, path);
+ if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
+ html(delim);
+ html("id=");
+ html_url_arg(new_rev);
+ delim = "&";
+ }
+ if (old_rev) {
+ html(delim);
+ html("id2=");
+ html_url_arg(old_rev);
+ delim = "&";
+ }
+ if (ctx.qry.difftype) {
+ html(delim);
+ htmlf("dt=%d", ctx.qry.difftype);
+ delim = "&";
+ }
+ if (ctx.qry.context > 0 && ctx.qry.context != 3) {
+ html(delim);
+ html("context=");
+ htmlf("%d", ctx.qry.context);
+ delim = "&";
+ }
+ if (ctx.qry.ignorews) {
+ html(delim);
+ html("ignorews=1");
+ delim = "&";
+ }
+ if (ctx.qry.follow) {
+ html(delim);
+ html("follow=1");
+ }
+ html("'>");
+ html_txt(name);
+ html("</a>");
+}
+
+void cgit_patch_link(const char *name, const char *title, const char *class,
+ const char *head, const char *rev, const char *path)
+{
+ reporevlink("patch", name, title, class, head, rev, path);
+}
+
+void cgit_stats_link(const char *name, const char *title, const char *class,
+ const char *head, const char *path)
+{
+ reporevlink("stats", name, title, class, head, NULL, path);
+}
+
+static void cgit_self_link(char *name, const char *title, const char *class)
+{
+ if (!strcmp(ctx.qry.page, "repolist"))
+ cgit_index_link(name, title, class, ctx.qry.search, ctx.qry.sort,
+ ctx.qry.ofs, 1);
+ else if (!strcmp(ctx.qry.page, "summary"))
+ cgit_summary_link(name, title, class, ctx.qry.head);
+ else if (!strcmp(ctx.qry.page, "tag"))
+ cgit_tag_link(name, title, class, ctx.qry.has_sha1 ?
+ ctx.qry.sha1 : ctx.qry.head);
+ else if (!strcmp(ctx.qry.page, "tree"))
+ cgit_tree_link(name, title, class, ctx.qry.head,
+ ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+ ctx.qry.path);
+ else if (!strcmp(ctx.qry.page, "plain"))
+ cgit_plain_link(name, title, class, ctx.qry.head,
+ ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL,
+ ctx.qry.path);
+ else if (!strcmp(ctx.qry.page, "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 = sha1_to_hex(obj->sha1);
+ 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...", typename(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;
+}
+
+static const char *fmt_date(time_t secs, const char *format, int local_time)
+{
+ static char buf[64];
+ struct tm *time;
+
+ if (!secs)
+ return "";
+ if (local_time)
+ time = localtime(&secs);
+ else
+ time = gmtime(&secs);
+ strftime(buf, sizeof(buf)-1, format, time);
+ return buf;
+}
+
+void cgit_print_date(time_t secs, const char *format, int local_time)
+{
+ html_txt(fmt_date(secs, format, local_time));
+}
+
+static void print_rel_date(time_t t, double value,
+ const char *class, const char *suffix)
+{
+ htmlf("<span class='%s' title='", class);
+ html_attr(fmt_date(t, FMT_LONGDATE, ctx.cfg.local_time));
+ htmlf("'>%.0f %s</span>", value, suffix);
+}
+
+void cgit_print_age(time_t t, time_t max_relative, const char *format)
+{
+ time_t now, secs;
+
+ if (!t)
+ return;
+ time(&now);
+ secs = now - t;
+ if (secs < 0)
+ secs = 0;
+
+ if (secs > max_relative && max_relative >= 0) {
+ html("<span title='");
+ html_attr(fmt_date(t, FMT_LONGDATE, ctx.cfg.local_time));
+ html("'>");
+ cgit_print_date(t, format, ctx.cfg.local_time);
+ html("</span>");
+ return;
+ }
+
+ if (secs < TM_HOUR * 2) {
+ print_rel_date(t, secs * 1.0 / TM_MIN, "age-mins", "min.");
+ return;
+ }
+ if (secs < TM_DAY * 2) {
+ print_rel_date(t, secs * 1.0 / TM_HOUR, "age-hours", "hours");
+ return;
+ }
+ if (secs < TM_WEEK * 2) {
+ print_rel_date(t, secs * 1.0 / TM_DAY, "age-days", "days");
+ return;
+ }
+ if (secs < TM_MONTH * 2) {
+ print_rel_date(t, secs * 1.0 / TM_WEEK, "age-weeks", "weeks");
+ return;
+ }
+ if (secs < TM_YEAR * 2) {
+ print_rel_date(t, secs * 1.0 / TM_MONTH, "age-months", "months");
+ return;
+ }
+ print_rel_date(t, secs * 1.0 / TM_YEAR, "age-years", "years");
+}
+
+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)
+ htmlf("Content-Disposition: inline; filename=\"%s\"\n",
+ ctx.page.filename);
+ 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);