/* * 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); 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); 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(""); if (ofs > 0) { int prev_ofs = ofs > pagesize ? ofs - pagesize : 0; url = cgit_pageurl(ctx.qry.repo, cgit_fmt("refs/%s", path), cgit_fmt("ofs=%d", prev_ofs)); html("[prev] "); free(url); } htmlf("%d - %d of %d", ofs + 1, ofs + pagesize < count ? ofs + pagesize : count, count); if (ofs + pagesize < count) { url = cgit_pageurl(ctx.qry.repo, cgit_fmt("refs/%s", path), cgit_fmt("ofs=%d", ofs + pagesize)); html(" [next]"); free(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(); }