Diff options
Diffstat (limited to '')
| -rw-r--r-- | source/ui-stats.c | 186 |
1 file changed, 75 insertions, 111 deletions
diff --git a/source/ui-stats.c b/source/ui-stats.c index e4dcf8e..ee5b76b 100644 --- a/source/ui-stats.c +++ b/source/ui-stats.c @@ -1,9 +1,7 @@ /* - * The statistics page, which counts commits per author across a window of - * recent weeks, months, quarters or years. The window sizes live in one table - * here, which cgit.c also resolves the max-stats setting against so a - * repository can refuse the coarser windows. The page reads no diff and no - * blob contents, so the whole thing costs about one rev-list. + * The statistics page counts each author's commits in the most recent periods + * of the size asked for. It reads no diff and no blob contents, so the whole + * thing costs about one rev-list. */ #define USE_THE_REPOSITORY_VARIABLE @@ -11,12 +9,29 @@ #include "cgit.h" #include "html.h" #include "parsing.h" -#include "shared.h" #include "ui-shared.h" #include "ui-stats.h" #define DEFAULT_AUTHOR_ROWS 10 +// How many periods the page shows side by side. +#define PERIOD_COLUMNS 4 + +struct cgit_period { + const char code; + const char *name; + + // Move a tm back to the first day of the period it falls in. + void (*trunc)(struct tm *tm); + + // Step a truncated tm n whole periods, back when n is negative. + void (*step)(struct tm *tm, int n); + + // Label the period a tm falls in. The buffer is reused, so a caller + // that keeps a label copies it. + char *(*pretty)(struct tm *tm); +}; + // One author's share of the window. periods is keyed by the label of a period, // with the commit count stored in the util field itself rather than behind // another allocation. @@ -35,19 +50,11 @@ static void trunc_week(struct tm *tm) gmtime_r(&t, tm); } -static void dec_week(struct tm *tm) -{ - time_t t = timegm(tm); - - t -= SECONDS_PER_WEEK; - gmtime_r(&t, tm); -} - -static void inc_week(struct tm *tm) +static void step_week(struct tm *tm, int n) { time_t t = timegm(tm); - t += SECONDS_PER_WEEK; + t += n * SECONDS_PER_WEEK; gmtime_r(&t, tm); } @@ -68,21 +75,15 @@ static void trunc_month(struct tm *tm) tm->tm_mday = 1; } -static void dec_month(struct tm *tm) +static void step_month(struct tm *tm, int n) { - tm->tm_mon--; + tm->tm_mon += n; if (tm->tm_mon < 0) { tm->tm_year--; - tm->tm_mon = 11; - } -} - -static void inc_month(struct tm *tm) -{ - tm->tm_mon++; - if (tm->tm_mon > 11) { + tm->tm_mon += 12; + } else if (tm->tm_mon > 11) { tm->tm_year++; - tm->tm_mon = 0; + tm->tm_mon -= 12; } } @@ -98,22 +99,12 @@ static char *pretty_month(struct tm *tm) static void trunc_quarter(struct tm *tm) { trunc_month(tm); - while (tm->tm_mon % 3 != 0) - dec_month(tm); + tm->tm_mon -= tm->tm_mon % 3; } -static void dec_quarter(struct tm *tm) +static void step_quarter(struct tm *tm, int n) { - dec_month(tm); - dec_month(tm); - dec_month(tm); -} - -static void inc_quarter(struct tm *tm) -{ - inc_month(tm); - inc_month(tm); - inc_month(tm); + step_month(tm, 3 * n); } static char *pretty_quarter(struct tm *tm) @@ -127,14 +118,9 @@ static void trunc_year(struct tm *tm) tm->tm_mon = 0; } -static void dec_year(struct tm *tm) -{ - tm->tm_year--; -} - -static void inc_year(struct tm *tm) +static void step_year(struct tm *tm, int n) { - tm->tm_year++; + tm->tm_year += n; } static char *pretty_year(struct tm *tm) @@ -145,10 +131,10 @@ static char *pretty_year(struct tm *tm) // The order runs from the finest window to the coarsest, because a repository // caps the page by storing an index into this table as its max-stats. static const struct cgit_period periods[] = { - {'w', "week", 4, trunc_week, dec_week, inc_week, pretty_week}, - {'m', "month", 4, trunc_month, dec_month, inc_month, pretty_month}, - {'q', "quarter", 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter}, - {'y', "year", 4, trunc_year, dec_year, inc_year, pretty_year}, + {'w', "week", trunc_week, step_week, pretty_week}, + {'m', "month", trunc_month, step_month, pretty_month}, + {'q', "quarter", trunc_quarter, step_quarter, pretty_quarter}, + {'y', "year", trunc_year, step_year, pretty_year}, }; static void window_start(const struct cgit_period *period, struct tm *tm) @@ -162,8 +148,12 @@ static void window_start(const struct cgit_period *period, struct tm *tm) time(&now); gmtime_r(&now, tm); period->trunc(tm); - for (i = 1; i < period->count; i++) - period->dec(tm); + // The window opens at midnight, not at the time of day it is now. + tm->tm_hour = 0; + tm->tm_min = 0; + tm->tm_sec = 0; + for (i = 1; i < PERIOD_COLUMNS; i++) + period->step(tm, -1); } static void add_commit(struct string_list *authors, struct commitinfo *info, @@ -186,13 +176,12 @@ static void add_commit(struct string_list *authors, struct commitinfo *info, stats = author->util; when = info->committer_date; // A crafted commit can carry a date outside the range gmtime_r can - // represent, which would leave date uninitialized and later index the + // represent, which would leave date uninitialised and later index the // month table out of bounds. if (!gmtime_r(&when, &date)) return; period->trunc(&date); label = xstrdup(period->pretty(&date)); - // The pointer field holds the count itself. bucket = string_list_insert(&stats->periods, label); if (bucket->util) free(label); @@ -211,38 +200,25 @@ static int collect_stats(const struct cgit_period *period, struct string_list *a { struct rev_info rev; struct commit *commit; - // setup_revisions reads these the way main does, skipping the first - // entry, and reads past the count up to a NULL after the double dash, - // so the sentinel has to stay even when the path fills the slot before - // it. - const char *argv[] = { NULL, ctx.qry.head, NULL, NULL, NULL }; - int argc = 2; + struct strvec argv = STRVEC_INIT; time_t since; struct tm tm; window_start(period, &tm); since = timegm(&tm); - if (ctx.qry.path) { - argv[2] = "--"; - argv[3] = ctx.qry.path; - argc += 2; - } + strvec_pushl(&argv, "stats", ctx.qry.head, NULL); + if (ctx.qry.path) + strvec_pushl(&argv, "--", ctx.qry.path, NULL); repo_init_revisions(the_repository, &rev, NULL); - rev.abbrev = DEFAULT_ABBREV; - rev.commit_format = CMIT_FMT_DEFAULT; rev.max_parents = 1; - rev.verbose_header = 1; - rev.show_root_diff = 0; - setup_revisions(argc, argv, &rev, NULL); + setup_revisions_from_strvec(&argv, &rev, NULL); // Prune the walk to the displayed window instead of traversing the // whole history and discarding older commits. The check below still // bounds the period edge exactly. rev.max_age = since; - // A failed setup leaves the walk holding freed commits, so it must - // not be read from. - if (prepare_revision_walk(&rev)) { - cgit_print_error_page(500, "Internal Server Error", "Unable to read the history"); + if (cgit_prepare_walk(&rev)) { + strvec_clear(&argv); return -1; } while ((commit = get_revision(&rev))) { @@ -255,6 +231,8 @@ static int collect_stats(const struct cgit_period *period, struct string_list *a release_commit_memory(the_repository->parsed_objects, commit); commit->parents = NULL; } + release_revisions(&rev); + strvec_clear(&argv); return 0; } @@ -286,9 +264,9 @@ static struct string_list build_period_labels(const struct cgit_period *period) int i; window_start(period, &tm); - for (i = 0; i < period->count; i++) { + for (i = 0; i < PERIOD_COLUMNS; i++) { string_list_append(&labels, period->pretty(&tm)); - period->inc(&tm); + period->step(&tm, 1); } return labels; } @@ -297,9 +275,9 @@ static struct string_list build_period_labels(const struct cgit_period *period) * The run of authors to sum is given as a start and a count rather than as * two indices, so that an empty author list cannot describe a run that wraps. */ -static void print_summary_row(struct string_list *authors, size_t from, size_t count, - const char *label_format, const char *leftclass, const char *centerclass, - const char *rightclass, const struct string_list *labels) +static void print_row(struct string_list *authors, size_t from, size_t count, + const char *name, const char *nameclass, const char *cellclass, + const struct string_list *labels) { struct authorstat *stats; struct string_list_item *bucket; @@ -307,7 +285,9 @@ static void print_summary_row(struct string_list *authors, size_t from, size_t c long total, subtotal; total = 0; - htmlf("<tr><td class='%s'>%s</td>", leftclass, cgit_fmt(label_format, (long)count)); + htmlf("<tr><td class='%s'>", nameclass); + html_txt(name); + html("</td>"); for (column = 0; column < labels->nr; column++) { const char *label = labels->items[column].string; @@ -318,18 +298,18 @@ static void print_summary_row(struct string_list *authors, size_t from, size_t c if (bucket) subtotal += (uintptr_t)bucket->util; } - htmlf("<td class='%s'>%ld</td>", centerclass, subtotal); + if (cellclass) + htmlf("<td class='%s'>%ld</td>", cellclass, subtotal); + else + htmlf("<td>%ld</td>", subtotal); total += subtotal; } - htmlf("<td class='%s'>%ld</td></tr>\n", rightclass, total); + htmlf("<td class='sum'>%ld</td></tr>\n", total); } static void print_authors(struct string_list *authors, int max_rows, const struct string_list *labels) { - struct string_list_item *author, *bucket; - struct authorstat *stats; size_t i, column, rows; - long total; html("<table class='stats'>\n<tr><th scope='col'>Author</th>"); for (column = 0; column < labels->nr; column++) @@ -339,32 +319,14 @@ static void print_authors(struct string_list *authors, int max_rows, const struc // The row count arrives through ofs, which carries -1 for "all". rows = (max_rows <= 0 || (size_t)max_rows > authors->nr) ? authors->nr : (size_t)max_rows; - for (i = 0; i < rows; i++) { - author = &authors->items[i]; - html("<tr><td class='left'>"); - html_txt(author->string); - html("</td>"); - stats = author->util; - total = 0; - for (column = 0; column < labels->nr; column++) { - const char *label = labels->items[column].string; - - bucket = string_list_lookup(&stats->periods, label); - if (!bucket) - html("<td>0</td>"); - else { - htmlf("<td>%ju</td>", (uintmax_t)(uintptr_t)bucket->util); - total += (uintptr_t)bucket->util; - } - } - htmlf("<td class='sum'>%ld</td></tr>\n", total); - } + for (i = 0; i < rows; i++) + print_row(authors, i, 1, authors->items[i].string, "left", NULL, labels); if (rows < authors->nr) - print_summary_row(authors, rows, authors->nr - rows, "Others (%ld)", "left", "", "sum", - labels); + print_row(authors, rows, authors->nr - rows, + cgit_fmt("Others (%ld)", (long)(authors->nr - rows)), "left", NULL, labels); - print_summary_row(authors, 0, authors->nr, "Total", "total", "sum", "sum", labels); + print_row(authors, 0, authors->nr, "Total", "total", "sum", labels); html("</table>\n"); } @@ -375,7 +337,7 @@ static void print_options_form(const struct cgit_period *period, int top) html("<div class='cgit-panel'>\n"); html("<strong>Stat options</strong>"); html("<form method='get' aria-label='Stat options'>"); - cgit_add_hidden_formfields(1, 0, "stats"); + cgit_add_hidden_formfields(1, 0); html("<div class='fields'>"); if (ctx.repo->max_stats > 1) { choices = ctx.repo->max_stats; @@ -448,9 +410,11 @@ void cgit_show_stats(void) cgit_print_error_page(400, "Bad Request", "Statistics type disabled: %s", period->name); return; } + if (cgit_refuse_pathspec_magic(ctx.qry.path)) + return; if (collect_stats(period, &authors)) return; - cgit_qsort(authors.items, authors.nr, sizeof(struct string_list_item), cmp_total_commits); + QSORT(authors.items, authors.nr, cmp_total_commits); top = ctx.qry.ofs; if (!top) |
