X-Git-Url: https://gitweb.ps.run/ps-cgit/blobdiff_plain/f86a23ff537258d36bf8f1876fa7a4bede6673d8..HEAD:/ui-stats.c?ds=sidebyside diff --git a/ui-stats.c b/ui-stats.c index 9150840..02c60ef 100644 --- a/ui-stats.c +++ b/ui-stats.c @@ -1,25 +1,17 @@ -#include "cgit.h" -#include "html.h" -#include - -#define MONTHS 6 - -struct Period { - const char code; - const char *name; - int max_periods; - int count; - - /* Convert a tm value to the first day in the period */ - void (*trunc)(struct tm *tm); +/* ui-stats.c: generate stats view + * + * Copyright (C) 2006-2014 cgit Development Team + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ - /* Update tm value to start of next/previous period */ - void (*dec)(struct tm *tm); - void (*inc)(struct tm *tm); +#define USE_THE_REPOSITORY_VARIABLE - /* Pretty-print a tm value */ - char *(*pretty)(struct tm *tm); -}; +#include "cgit.h" +#include "ui-stats.h" +#include "html.h" +#include "ui-shared.h" struct authorstat { long total; @@ -33,21 +25,21 @@ static void trunc_week(struct tm *tm) { time_t t = timegm(tm); t -= ((tm->tm_wday + 6) % 7) * DAY_SECS; - gmtime_r(&t, tm); + gmtime_r(&t, tm); } static void dec_week(struct tm *tm) { time_t t = timegm(tm); t -= WEEK_SECS; - gmtime_r(&t, tm); + gmtime_r(&t, tm); } static void inc_week(struct tm *tm) { time_t t = timegm(tm); t += WEEK_SECS; - gmtime_r(&t, tm); + gmtime_r(&t, tm); } static char *pretty_week(struct tm *tm) @@ -93,7 +85,7 @@ static char *pretty_month(struct tm *tm) static void trunc_quarter(struct tm *tm) { trunc_month(tm); - while(tm->tm_mon % 3 != 0) + while (tm->tm_mon % 3 != 0) dec_month(tm); } @@ -137,27 +129,60 @@ static char *pretty_year(struct tm *tm) return fmt("%d", tm->tm_year + 1900); } -struct Period periods[] = { +static const struct cgit_period periods[] = { {'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week}, {'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month}, {'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter}, {'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year}, }; +/* Given a period code or name, return a period index (1, 2, 3 or 4) + * and update the period pointer to the correcsponding struct. + * If no matching code is found, return 0. + */ +int cgit_find_stats_period(const char *expr, const struct cgit_period **period) +{ + int i; + char code = '\0'; + + if (!expr) + return 0; + + if (strlen(expr) == 1) + code = expr[0]; + + for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++) + if (periods[i].code == code || !strcmp(periods[i].name, expr)) { + if (period) + *period = &periods[i]; + return i + 1; + } + return 0; +} + +const char *cgit_find_stats_periodname(int idx) +{ + if (idx > 0 && idx < 4) + return periods[idx - 1].name; + else + return ""; +} + static void add_commit(struct string_list *authors, struct commit *commit, - struct Period *period) + const struct cgit_period *period) { struct commitinfo *info; struct string_list_item *author, *item; struct authorstat *authorstat; struct string_list *items; char *tmp; - struct tm *date; + struct tm date; time_t t; + uintptr_t *counter; info = cgit_parse_commit(commit); tmp = xstrdup(info->author); - author = string_list_insert(tmp, authors); + author = string_list_insert(authors, tmp); if (!author->util) author->util = xcalloc(1, sizeof(struct authorstat)); else @@ -165,13 +190,15 @@ static void add_commit(struct string_list *authors, struct commit *commit, authorstat = author->util; items = &authorstat->list; t = info->committer_date; - date = gmtime(&t); - period->trunc(date); - tmp = xstrdup(period->pretty(date)); - item = string_list_insert(tmp, items); - if (item->util) + gmtime_r(&t, &date); + period->trunc(&date); + tmp = xstrdup(period->pretty(&date)); + item = string_list_insert(items, tmp); + counter = (uintptr_t *)&item->util; + if (*counter) free(tmp); - item->util++; + (*counter)++; + authorstat->total++; cgit_free_commitinfo(info); } @@ -189,45 +216,53 @@ static int cmp_total_commits(const void *a1, const void *a2) /* Walk the commit DAG and collect number of commits per author per * timeperiod into a nested string_list collection. */ -struct string_list collect_stats(struct cgit_context *ctx, - struct Period *period) +static struct string_list collect_stats(const struct cgit_period *period) { struct string_list authors; struct rev_info rev; struct commit *commit; - const char *argv[] = {NULL, ctx->qry.head, NULL, NULL}; + const char *argv[] = {NULL, ctx.qry.head, NULL, NULL, NULL, NULL}; + int argc = 3; time_t now; long i; - struct tm *tm; + struct tm tm; char tmp[11]; time(&now); - tm = gmtime(&now); - period->trunc(tm); + gmtime_r(&now, &tm); + period->trunc(&tm); for (i = 1; i < period->count; i++) - period->dec(tm); - strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm); + period->dec(&tm); + strftime(tmp, sizeof(tmp), "%Y-%m-%d", &tm); argv[2] = xstrdup(fmt("--since=%s", tmp)); - init_revisions(&rev, NULL); + if (ctx.qry.path) { + argv[3] = "--"; + argv[4] = ctx.qry.path; + argc += 2; + } + repo_init_revisions(the_repository, &rev, NULL); rev.abbrev = DEFAULT_ABBREV; rev.commit_format = CMIT_FMT_DEFAULT; - rev.no_merges = 1; + rev.max_parents = 1; rev.verbose_header = 1; rev.show_root_diff = 0; - setup_revisions(3, argv, &rev, NULL); + setup_revisions(argc, argv, &rev, NULL); prepare_revision_walk(&rev); memset(&authors, 0, sizeof(authors)); while ((commit = get_revision(&rev)) != NULL) { add_commit(&authors, commit, period); - free(commit->buffer); - free_commit_list(commit->parents); + release_commit_memory(the_repository->parsed_objects, commit); + commit->parents = NULL; } return authors; } -void print_combined_authorrow(struct string_list *authors, int from, int to, - const char *name, const char *leftclass, const char *centerclass, - const char *rightclass, struct Period *period) +static void print_combined_authorrow(struct string_list *authors, int from, + int to, const char *name, + const char *leftclass, + const char *centerclass, + const char *rightclass, + const struct cgit_period *period) { struct string_list_item *author; struct authorstat *authorstat; @@ -235,37 +270,38 @@ void print_combined_authorrow(struct string_list *authors, int from, int to, struct string_list_item *date; time_t now; long i, j, total, subtotal; - struct tm *tm; + struct tm tm; char *tmp; time(&now); - tm = gmtime(&now); - period->trunc(tm); + gmtime_r(&now, &tm); + period->trunc(&tm); for (i = 1; i < period->count; i++) - period->dec(tm); + period->dec(&tm); total = 0; htmlf("%s", leftclass, fmt(name, to - from + 1)); for (j = 0; j < period->count; j++) { - tmp = period->pretty(tm); - period->inc(tm); + tmp = period->pretty(&tm); + period->inc(&tm); subtotal = 0; for (i = from; i <= to; i++) { author = &authors->items[i]; authorstat = author->util; items = &authorstat->list; - date = string_list_lookup(tmp, items); + date = string_list_lookup(items, tmp); if (date) - subtotal += (size_t)date->util; + subtotal += (uintptr_t)date->util; } - htmlf("%d", centerclass, subtotal); + htmlf("%ld", centerclass, subtotal); total += subtotal; } - htmlf("%d", rightclass, total); + htmlf("%ld", rightclass, total); } -void print_authors(struct string_list *authors, int top, struct Period *period) +static void print_authors(struct string_list *authors, int top, + const struct cgit_period *period) { struct string_list_item *author; struct authorstat *authorstat; @@ -273,20 +309,20 @@ void print_authors(struct string_list *authors, int top, struct Period *period) struct string_list_item *date; time_t now; long i, j, total; - struct tm *tm; + struct tm tm; char *tmp; time(&now); - tm = gmtime(&now); - period->trunc(tm); + gmtime_r(&now, &tm); + period->trunc(&tm); for (i = 1; i < period->count; i++) - period->dec(tm); + period->dec(&tm); html(""); for (j = 0; j < period->count; j++) { - tmp = period->pretty(tm); + tmp = period->pretty(&tm); htmlf("", tmp); - period->inc(tm); + period->inc(&tm); } html("\n"); @@ -302,24 +338,24 @@ void print_authors(struct string_list *authors, int top, struct Period *period) items = &authorstat->list; total = 0; for (j = 0; j < period->count; j++) - period->dec(tm); + period->dec(&tm); for (j = 0; j < period->count; j++) { - tmp = period->pretty(tm); - period->inc(tm); - date = string_list_lookup(tmp, items); + tmp = period->pretty(&tm); + period->inc(&tm); + date = string_list_lookup(items, tmp); if (!date) html(""); else { - htmlf("", date->util); - total += (size_t)date->util; + htmlf("", (uintptr_t)date->util); + total += (uintptr_t)date->util; } } - htmlf("", total); + htmlf("", total); } if (top < authors->nr) print_combined_authorrow(authors, top, authors->nr - 1, - "Others (%d)", "left", "", "sum", period); + "Others (%ld)", "left", "", "sum", period); print_combined_authorrow(authors, 0, authors->nr - 1, "Total", "total", "sum", "sum", period); @@ -330,51 +366,70 @@ void print_authors(struct string_list *authors, int top, struct Period *period) * for each author is another string_list which is used to calculate the * number of commits per time-interval. */ -void cgit_show_stats(struct cgit_context *ctx) +void cgit_show_stats(void) { struct string_list authors; - struct Period *period; + const struct cgit_period *period; int top, i; + const char *code = "w"; - period = &periods[0]; - if (ctx->qry.period) { - for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++) - if (periods[i].code == ctx->qry.period[0]) { - period = &periods[i]; - break; - } + if (ctx.qry.period) + code = ctx.qry.period; + + i = cgit_find_stats_period(code, &period); + if (!i) { + cgit_print_error_page(404, "Not found", + "Unknown statistics type: %c", code[0]); + return; + } + if (i > ctx.repo->max_stats) { + cgit_print_error_page(400, "Bad request", + "Statistics type disabled: %s", period->name); + return; } - authors = collect_stats(ctx, period); + authors = collect_stats(period); qsort(authors.items, authors.nr, sizeof(struct string_list_item), cmp_total_commits); - top = ctx->qry.ofs; + top = ctx.qry.ofs; if (!top) top = 10; - htmlf("

Commits per author per %s

", period->name); - html(""); - if (strcmp(ctx->qry.head, ctx->repo->defbranch)) - htmlf("", ctx->qry.head); - html("Period: "); - html("

"); - html("Authors: "); - html(""); - html(""); - html(""); + cgit_print_layout_start(); + html("
"); + html("stat options"); + html(""); + cgit_add_hidden_formfields(1, 0, "stats"); + html("
Author%sTotal
0%d%lu%d
%ld
"); + if (ctx.repo->max_stats > 1) { + html(""); + html(""); + } + html(""); + html(""); + html("
Period:
Authors:
"); + html(""); + html("
"); html(""); + html(""); + htmlf("

Commits per author per %s", period->name); + if (ctx.qry.path) { + html(" (path '"); + html_txt(ctx.qry.path); + html("')"); + } + html("

"); print_authors(&authors, top, period); + cgit_print_layout_end(); }