/*
* The branch and tag listings, shown as sections of the summary page and as
* the whole of a repository's refs page. Every row is one ref beside
* the commit or tag object it points at, ordered newest first, with branches
* then reordered by name unless branch-sort asks for age. A section longer
* than max-ref-count is cut short and ends in a link to a dedicated heads or
* tags page, which walks the same list one offset at a time.
*/
#define USE_THE_REPOSITORY_VARIABLE
#include "cgit.h"
#include "filter.h"
#include "html.h"
#include "shared.h"
#include "ui-refs.h"
#include "ui-shared.h"
/*
* The slice of a sorted ref list that one page shows, with end one past the
* last row. size is what a full page holds, so it also decides whether the
* list needs a pager.
*/
struct ref_page {
int size;
int start;
int end;
};
/*
* The tagger date and the committer date live in a union in struct refinfo and
* only the member matching the object type is ever filled, so assuming a ref
* points at a commit reads past the end of the smaller struct.
*/
static timestamp_t ref_date(struct refinfo *ref)
{
if (!ref->object)
return 0;
switch (ref->object->type) {
case OBJ_TAG:
return ref->tag ? ref->tag->tagger_date : 0;
case OBJ_COMMIT:
return ref->commit ? ref->commit->committer_date : 0;
}
return 0;
}
static int cmp_date(const void *a, const void *b)
{
struct refinfo *ref1 = *(struct refinfo **)a;
struct refinfo *ref2 = *(struct refinfo **)b;
timestamp_t date1 = ref_date(ref1), date2 = ref_date(ref2);
// Compared rather than subtracted, at the width the dates are stored
// at. A commit may carry any timestamp, so a difference that does not
// fit an int would leave qsort with a contradictory ordering.
if (date1 < date2)
return 1;
if (date1 > date2)
return -1;
return 0;
}
static int cmp_name(const void *a, const void *b)
{
struct refinfo *ref1 = *(struct refinfo **)a;
struct refinfo *ref2 = *(struct refinfo **)b;
return strcmp(ref1->refname, ref2->refname);
}
static void collect_branches(struct reflist *list)
{
list->refs = NULL;
list->alloc = list->count = 0;
refs_for_each_branch_ref(get_main_ref_store(the_repository), cgit_refs_cb, list);
if (ctx.repo->enable_remote_branches)
refs_for_each_remote_ref(get_main_ref_store(the_repository), cgit_refs_cb, list);
}
static void print_branch_header(void)
{
html("
| Branch | "
"Commit message | "
"Author | "
"Age |
\n");
}
static int print_branch(struct refinfo *ref)
{
struct commitinfo *info = ref->commit;
const char *name = ref->refname;
if (!info)
return 1;
html("| ");
cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, ctx.qry.showmsg, 0);
html(" | ");
if (ref->object->type == OBJ_COMMIT) {
cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL);
html(" | ");
cgit_open_filter(ctx.repo->email_filter, info->author_email, "refs");
html_txt(info->author);
cgit_close_filter(ctx.repo->email_filter);
html(" | ");
cgit_print_age(info->committer_date, info->committer_tz, -1);
} else {
// The object link sits in the message column, and the empty
// tail cells keep the row as wide as the header.
cgit_object_link(ref->object, 0);
html(" | | ");
}
html(" |
\n");
return 0;
}
static void collect_tags(struct reflist *list)
{
list->refs = NULL;
list->alloc = list->count = 0;
refs_for_each_tag_ref(get_main_ref_store(the_repository), cgit_refs_cb, list);
}
static void print_tag_header(void)
{
html("| Tag | "
"Download | "
"Author | "
"Age |
\n");
}
static int print_tag(struct refinfo *ref)
{
struct taginfo *info = NULL;
const char *name = ref->refname;
struct object *obj = ref->object;
// A lightweight tag has no tag object, so the author and age columns
// below fall back to the commit.
if (obj->type == OBJ_TAG) {
struct tag *tag = (struct tag *)obj;
obj = tag->tagged;
info = ref->tag;
if (!info)
return 1;
}
html("| ");
cgit_tag_link(name, NULL, NULL, name);
html(" | ");
if (ctx.repo->snapshots && (obj->type == OBJ_COMMIT))
cgit_print_snapshot_links(ctx.repo, name, " ");
else
cgit_object_link(obj, 0);
html(" | ");
if (info) {
if (info->tagger) {
cgit_open_filter(ctx.repo->email_filter, info->tagger_email, "refs");
html_txt(info->tagger);
cgit_close_filter(ctx.repo->email_filter);
}
} else if (ref->object->type == OBJ_COMMIT) {
cgit_open_filter(ctx.repo->email_filter, ref->commit->author_email, "refs");
html_txt(ref->commit->author);
cgit_close_filter(ctx.repo->email_filter);
}
html(" | ");
if (info) {
if (info->tagger_date > 0)
cgit_print_age(info->tagger_date, info->tagger_tz, -1);
} else if (ref->object->type == OBJ_COMMIT) {
cgit_print_age(ref->commit->commit->date, 0, -1);
}
html(" |
\n");
return 0;
}
static void print_refs_link(const char *path)
{
html("| ");
cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
html(" |
\n");
}
static void print_ref_pager(int ofs, int pagesize, int count, const char *path)
{
char *url;
html("
\n");
}
static struct ref_page page_bounds(int pagesize, int count)
{
struct ref_page page;
page.size = (pagesize <= 0 || pagesize > count) ? count : pagesize;
page.start = ctx.qry.ofs > 0 ? ctx.qry.ofs : 0;
if (page.start > count)
page.start = count;
page.end = page.start + page.size < count ? page.start + page.size : count;
return page;
}
/*
* Unlike the capped sections, the whole list is sorted before a page is cut
* out of it, so a branch keeps its place no matter which page it lands on.
*/
static void print_branches_page(int pagesize)
{
struct reflist list;
struct ref_page page;
int i;
print_branch_header();
collect_branches(&list);
qsort(list.refs, list.count, sizeof(*list.refs), cmp_date);
if (ctx.repo->branch_sort == 0)
qsort(list.refs, list.count, sizeof(*list.refs), cmp_name);
page = page_bounds(pagesize, list.count);
for (i = page.start; i < page.end; i++)
print_branch(list.refs[i]);
if (page.size < list.count)
print_ref_pager(page.start, page.size, list.count, "heads");
cgit_free_reflist_inner(&list);
}
static void print_tags_page(int pagesize)
{
struct reflist list;
struct ref_page page;
int i;
collect_tags(&list);
if (list.count == 0)
return;
qsort(list.refs, list.count, sizeof(*list.refs), cmp_date);
page = page_bounds(pagesize, list.count);
print_tag_header();
for (i = page.start; i < page.end; i++)
print_tag(list.refs[i]);
if (page.size < list.count)
print_ref_pager(page.start, page.size, list.count, "tags");
cgit_free_reflist_inner(&list);
}
void cgit_print_branches(int maxcount)
{
struct reflist list;
int i;
print_branch_header();
collect_branches(&list);
if (maxcount == 0 || maxcount > list.count)
maxcount = list.count;
// The date sort covers the list, the name sort only the rows about to
// be shown, so the section holds the newest branches rather than the
// first ones by name.
qsort(list.refs, list.count, sizeof(*list.refs), cmp_date);
if (ctx.repo->branch_sort == 0)
qsort(list.refs, maxcount, sizeof(*list.refs), cmp_name);
for (i = 0; i < maxcount; i++)
print_branch(list.refs[i]);
if (maxcount < list.count)
print_refs_link("heads");
cgit_free_reflist_inner(&list);
}
void cgit_print_tags(int maxcount)
{
struct reflist list;
int i;
collect_tags(&list);
if (list.count == 0)
return;
qsort(list.refs, list.count, sizeof(*list.refs), cmp_date);
if (!maxcount)
maxcount = list.count;
else if (maxcount > list.count)
maxcount = list.count;
print_tag_header();
for (i = 0; i < maxcount; i++)
print_tag(list.refs[i]);
if (maxcount < list.count)
print_refs_link("tags");
cgit_free_reflist_inner(&list);
}
void cgit_print_refs(void)
{
cgit_print_layout_start();
html("\n");
if (ctx.qry.path && starts_with(ctx.qry.path, "heads"))
print_branches_page(ctx.cfg.max_ref_count);
else if (ctx.qry.path && starts_with(ctx.qry.path, "tags"))
print_tags_page(ctx.cfg.max_ref_count);
else {
cgit_print_branches(ctx.cfg.max_ref_count);
html(" |
\n");
cgit_print_tags(ctx.cfg.max_ref_count);
}
html("
\n");
cgit_print_layout_end();
}