+
+const char *cgit_httpscheme(void)
+{
+ if (ctx.env.https && !strcmp(ctx.env.https, "on"))
+ return "https://";
+ else
+ return "http://";
+}
+
+const char *cgit_hosturl(void)
+{
+ if (ctx.env.http_host)
+ return ctx.env.http_host;
+ if (!ctx.env.server_name)
+ return NULL;
+ if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
+ return ctx.env.server_name;
+ return fmtalloc("%s:%s", ctx.env.server_name, ctx.env.server_port);
+}
+
+const char *cgit_currenturl(void)
+{
+ if (!ctx.qry.url)
+ return cgit_rooturl();
+ const char *root = cgit_rooturl();
+ size_t len = strlen(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 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;
+ strncpy(rvbuf, reponame, sizeof(rvbuf));
+ if (rvbuf[sizeof(rvbuf)-1])
+ die("cgit_repobasename: truncated repository name '%s'", reponame);
+ p = strlen(rvbuf)-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;
+}
+
+static void site_url(const char *page, const char *search, const char *sort, int ofs, int always_root)
+{
+ char *delim = "?";
+
+ if (always_root || page)
+ html_attr(cgit_rooturl());
+ else
+ html_attr(cgit_currenturl());
+
+ if (page) {
+ htmlf("?p=%s", page);
+ delim = "&";
+ }
+ if (search) {
+ html(delim);
+ html("q=");
+ html_attr(search);
+ delim = "&";
+ }
+ if (sort) {
+ html(delim);
+ html("s=");
+ html_attr(sort);
+ delim = "&";
+ }
+ if (ofs) {
+ html(delim);
+ htmlf("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)
+{
+ html("<a");
+ if (title) {
+ html(" title='");
+ html_attr(title);
+ html("'");
+ }
+ if (class) {
+ html(" class='");
+ html_attr(class);
+ html("'");
+ }
+ html(" href='");
+ site_url(page, search, sort, ofs, always_root);
+ html("'>");
+ html_txt(name);
+ html("</a>");
+}
+
+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);
+}
+
+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 && strcmp(head, ctx.repo->defbranch)) {
+ html(delim);
+ html("h=");
+ html_url_arg(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)) {
+ html(delim);
+ html("id=");
+ html_url_arg(rev);
+ }
+ html("'>");
+ html_txt(name);
+ html("</a>");
+}
+
+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_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_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_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)) {
+ html(delim);
+ html("id=");
+ html_url_arg(rev);
+ delim = "&";
+ }
+ if (grep && pattern) {
+ html(delim);
+ html("qt=");
+ html_url_arg(grep);
+ delim = "&";
+ html(delim);
+ html("q=");
+ html_url_arg(pattern);
+ }
+ if (ofs > 0) {
+ html(delim);
+ html("ofs=");
+ htmlf("%d", ofs);
+ delim = "&";
+ }
+ if (showmsg) {
+ html(delim);
+ html("showmsg=1");
+ delim = "&";
+ }
+ if (follow) {
+ html(delim);
+ html("follow=1");
+ }
+ html("'>");
+ html_txt(name);
+ html("</a>");
+}
+
+void cgit_commit_link(char *name, const char *title, const char *class,
+ const char *head, const char *rev, const char *path)
+{
+ 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)