summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2008-12-07 12:17:21 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2008-12-07 12:17:21 (UTC)
commitfb2f3f6c29bad733723152893c5246a756e4cada (patch) (unidiff)
tree5b2953c0c116f276ca48beee676a6662b6329d95
parentc6a6aa2186daf39814baa0e71378c2e9e1041002 (diff)
downloadcgit-fb2f3f6c29bad733723152893c5246a756e4cada.zip
cgit-fb2f3f6c29bad733723152893c5246a756e4cada.tar.gz
cgit-fb2f3f6c29bad733723152893c5246a756e4cada.tar.bz2
ui-stats: replace 'enable-stats' setting with 'max-stats'
The new 'max-stats' and 'repo.max-stats' settings makes it possible to define the maximum statistics period, both globally and per repo. Hence, it is now feasible to allow statistics on repositories with a high commit frequency, like linux-2.6, by setting repo.max-stats to e.g. 'month'. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c10
-rw-r--r--cgit.h4
-rw-r--r--cgitrc.5.txt23
-rw-r--r--cmd.c5
-rw-r--r--shared.c2
-rw-r--r--ui-shared.c2
-rw-r--r--ui-stats.c97
-rw-r--r--ui-stats.h19
8 files changed, 104 insertions, 58 deletions
diff --git a/cgit.c b/cgit.c
index 22b6d7c..57e11cd 100644
--- a/cgit.c
+++ b/cgit.c
@@ -3,24 +3,25 @@
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h"
15#include "scan-tree.h" 16#include "scan-tree.h"
16 17
17const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
18 19
19void config_cb(const char *name, const char *value) 20void config_cb(const char *name, const char *value)
20{ 21{
21 if (!strcmp(name, "root-title")) 22 if (!strcmp(name, "root-title"))
22 ctx.cfg.root_title = xstrdup(value); 23 ctx.cfg.root_title = xstrdup(value);
23 else if (!strcmp(name, "root-desc")) 24 else if (!strcmp(name, "root-desc"))
24 ctx.cfg.root_desc = xstrdup(value); 25 ctx.cfg.root_desc = xstrdup(value);
25 else if (!strcmp(name, "root-readme")) 26 else if (!strcmp(name, "root-readme"))
26 ctx.cfg.root_readme = xstrdup(value); 27 ctx.cfg.root_readme = xstrdup(value);
@@ -45,26 +46,26 @@ void config_cb(const char *name, const char *value)
45 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 46 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
46 ctx.cfg.virtual_root = ""; 47 ctx.cfg.virtual_root = "";
47 } else if (!strcmp(name, "nocache")) 48 } else if (!strcmp(name, "nocache"))
48 ctx.cfg.nocache = atoi(value); 49 ctx.cfg.nocache = atoi(value);
49 else if (!strcmp(name, "snapshots")) 50 else if (!strcmp(name, "snapshots"))
50 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 51 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
51 else if (!strcmp(name, "enable-index-links")) 52 else if (!strcmp(name, "enable-index-links"))
52 ctx.cfg.enable_index_links = atoi(value); 53 ctx.cfg.enable_index_links = atoi(value);
53 else if (!strcmp(name, "enable-log-filecount")) 54 else if (!strcmp(name, "enable-log-filecount"))
54 ctx.cfg.enable_log_filecount = atoi(value); 55 ctx.cfg.enable_log_filecount = atoi(value);
55 else if (!strcmp(name, "enable-log-linecount")) 56 else if (!strcmp(name, "enable-log-linecount"))
56 ctx.cfg.enable_log_linecount = atoi(value); 57 ctx.cfg.enable_log_linecount = atoi(value);
57 else if (!strcmp(name, "enable-stats")) 58 else if (!strcmp(name, "max-stats"))
58 ctx.cfg.enable_stats = atoi(value); 59 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
59 else if (!strcmp(name, "cache-size")) 60 else if (!strcmp(name, "cache-size"))
60 ctx.cfg.cache_size = atoi(value); 61 ctx.cfg.cache_size = atoi(value);
61 else if (!strcmp(name, "cache-root")) 62 else if (!strcmp(name, "cache-root"))
62 ctx.cfg.cache_root = xstrdup(value); 63 ctx.cfg.cache_root = xstrdup(value);
63 else if (!strcmp(name, "cache-root-ttl")) 64 else if (!strcmp(name, "cache-root-ttl"))
64 ctx.cfg.cache_root_ttl = atoi(value); 65 ctx.cfg.cache_root_ttl = atoi(value);
65 else if (!strcmp(name, "cache-repo-ttl")) 66 else if (!strcmp(name, "cache-repo-ttl"))
66 ctx.cfg.cache_repo_ttl = atoi(value); 67 ctx.cfg.cache_repo_ttl = atoi(value);
67 else if (!strcmp(name, "cache-static-ttl")) 68 else if (!strcmp(name, "cache-static-ttl"))
68 ctx.cfg.cache_static_ttl = atoi(value); 69 ctx.cfg.cache_static_ttl = atoi(value);
69 else if (!strcmp(name, "cache-dynamic-ttl")) 70 else if (!strcmp(name, "cache-dynamic-ttl"))
70 ctx.cfg.cache_dynamic_ttl = atoi(value); 71 ctx.cfg.cache_dynamic_ttl = atoi(value);
@@ -105,26 +106,26 @@ void config_cb(const char *name, const char *value)
105 else if (ctx.repo && !strcmp(name, "repo.desc")) 106 else if (ctx.repo && !strcmp(name, "repo.desc"))
106 ctx.repo->desc = xstrdup(value); 107 ctx.repo->desc = xstrdup(value);
107 else if (ctx.repo && !strcmp(name, "repo.owner")) 108 else if (ctx.repo && !strcmp(name, "repo.owner"))
108 ctx.repo->owner = xstrdup(value); 109 ctx.repo->owner = xstrdup(value);
109 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 110 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
110 ctx.repo->defbranch = xstrdup(value); 111 ctx.repo->defbranch = xstrdup(value);
111 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 112 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
112 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 113 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
113 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 114 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
114 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 115 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
115 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 116 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
116 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 117 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
117 else if (ctx.repo && !strcmp(name, "repo.enable-stats")) 118 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
118 ctx.repo->enable_stats = ctx.cfg.enable_stats && atoi(value); 119 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
119 else if (ctx.repo && !strcmp(name, "repo.module-link")) 120 else if (ctx.repo && !strcmp(name, "repo.module-link"))
120 ctx.repo->module_link= xstrdup(value); 121 ctx.repo->module_link= xstrdup(value);
121 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 122 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
122 if (*value == '/') 123 if (*value == '/')
123 ctx.repo->readme = xstrdup(value); 124 ctx.repo->readme = xstrdup(value);
124 else 125 else
125 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 126 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
126 } else if (!strcmp(name, "include")) 127 } else if (!strcmp(name, "include"))
127 parse_configfile(value, config_cb); 128 parse_configfile(value, config_cb);
128} 129}
129 130
130static void querystring_cb(const char *name, const char *value) 131static void querystring_cb(const char *name, const char *value)
@@ -174,24 +175,25 @@ static void prepare_context(struct cgit_context *ctx)
174 ctx->cfg.cache_repo_ttl = 5; 175 ctx->cfg.cache_repo_ttl = 5;
175 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 176 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
176 ctx->cfg.cache_root_ttl = 5; 177 ctx->cfg.cache_root_ttl = 5;
177 ctx->cfg.cache_static_ttl = -1; 178 ctx->cfg.cache_static_ttl = -1;
178 ctx->cfg.css = "/cgit.css"; 179 ctx->cfg.css = "/cgit.css";
179 ctx->cfg.logo = "/git-logo.png"; 180 ctx->cfg.logo = "/git-logo.png";
180 ctx->cfg.local_time = 0; 181 ctx->cfg.local_time = 0;
181 ctx->cfg.max_repo_count = 50; 182 ctx->cfg.max_repo_count = 50;
182 ctx->cfg.max_commit_count = 50; 183 ctx->cfg.max_commit_count = 50;
183 ctx->cfg.max_lock_attempts = 5; 184 ctx->cfg.max_lock_attempts = 5;
184 ctx->cfg.max_msg_len = 80; 185 ctx->cfg.max_msg_len = 80;
185 ctx->cfg.max_repodesc_len = 80; 186 ctx->cfg.max_repodesc_len = 80;
187 ctx->cfg.max_stats = 0;
186 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 188 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
187 ctx->cfg.renamelimit = -1; 189 ctx->cfg.renamelimit = -1;
188 ctx->cfg.robots = "index, nofollow"; 190 ctx->cfg.robots = "index, nofollow";
189 ctx->cfg.root_title = "Git repository browser"; 191 ctx->cfg.root_title = "Git repository browser";
190 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 192 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
191 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 193 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
192 ctx->cfg.summary_branches = 10; 194 ctx->cfg.summary_branches = 10;
193 ctx->cfg.summary_log = 10; 195 ctx->cfg.summary_log = 10;
194 ctx->cfg.summary_tags = 10; 196 ctx->cfg.summary_tags = 10;
195 ctx->page.mimetype = "text/html"; 197 ctx->page.mimetype = "text/html";
196 ctx->page.charset = PAGE_ENCODING; 198 ctx->page.charset = PAGE_ENCODING;
197 ctx->page.filename = NULL; 199 ctx->page.filename = NULL;
diff --git a/cgit.h b/cgit.h
index 85045c4..f2cb671 100644
--- a/cgit.h
+++ b/cgit.h
@@ -52,25 +52,25 @@ struct cgit_repo {
52 char *name; 52 char *name;
53 char *path; 53 char *path;
54 char *desc; 54 char *desc;
55 char *owner; 55 char *owner;
56 char *defbranch; 56 char *defbranch;
57 char *group; 57 char *group;
58 char *module_link; 58 char *module_link;
59 char *readme; 59 char *readme;
60 char *clone_url; 60 char *clone_url;
61 int snapshots; 61 int snapshots;
62 int enable_log_filecount; 62 int enable_log_filecount;
63 int enable_log_linecount; 63 int enable_log_linecount;
64 int enable_stats; 64 int max_stats;
65}; 65};
66 66
67struct cgit_repolist { 67struct cgit_repolist {
68 int length; 68 int length;
69 int count; 69 int count;
70 struct cgit_repo *repos; 70 struct cgit_repo *repos;
71}; 71};
72 72
73struct commitinfo { 73struct commitinfo {
74 struct commit *commit; 74 struct commit *commit;
75 char *author; 75 char *author;
76 char *author_email; 76 char *author_email;
@@ -144,31 +144,31 @@ struct cgit_config {
144 char *root_readme; 144 char *root_readme;
145 char *script_name; 145 char *script_name;
146 char *virtual_root; 146 char *virtual_root;
147 int cache_size; 147 int cache_size;
148 int cache_dynamic_ttl; 148 int cache_dynamic_ttl;
149 int cache_max_create_time; 149 int cache_max_create_time;
150 int cache_repo_ttl; 150 int cache_repo_ttl;
151 int cache_root_ttl; 151 int cache_root_ttl;
152 int cache_static_ttl; 152 int cache_static_ttl;
153 int enable_index_links; 153 int enable_index_links;
154 int enable_log_filecount; 154 int enable_log_filecount;
155 int enable_log_linecount; 155 int enable_log_linecount;
156 int enable_stats;
157 int local_time; 156 int local_time;
158 int max_repo_count; 157 int max_repo_count;
159 int max_commit_count; 158 int max_commit_count;
160 int max_lock_attempts; 159 int max_lock_attempts;
161 int max_msg_len; 160 int max_msg_len;
162 int max_repodesc_len; 161 int max_repodesc_len;
162 int max_stats;
163 int nocache; 163 int nocache;
164 int renamelimit; 164 int renamelimit;
165 int snapshots; 165 int snapshots;
166 int summary_branches; 166 int summary_branches;
167 int summary_log; 167 int summary_log;
168 int summary_tags; 168 int summary_tags;
169}; 169};
170 170
171struct cgit_page { 171struct cgit_page {
172 time_t modified; 172 time_t modified;
173 time_t expires; 173 time_t expires;
174 size_t size; 174 size_t size;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 60d3ea4..0bbbea3 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -65,28 +65,24 @@ enable-index-links
65 "commit" and "tree" pages). Default value: "0". 65 "commit" and "tree" pages). Default value: "0".
66 66
67enable-log-filecount 67enable-log-filecount
68 Flag which, when set to "1", will make cgit print the number of 68 Flag which, when set to "1", will make cgit print the number of
69 modified files for each commit on the repository log page. Default 69 modified files for each commit on the repository log page. Default
70 value: "0". 70 value: "0".
71 71
72enable-log-linecount 72enable-log-linecount
73 Flag which, when set to "1", will make cgit print the number of added 73 Flag which, when set to "1", will make cgit print the number of added
74 and removed lines for each commit on the repository log page. Default 74 and removed lines for each commit on the repository log page. Default
75 value: "0". 75 value: "0".
76 76
77enable-stats
78 Globally enable/disable statistics for each repository. Default
79 value: "0".
80
81favicon 77favicon
82 Url used as link to a shortcut icon for cgit. If specified, it is 78 Url used as link to a shortcut icon for cgit. If specified, it is
83 suggested to use the value "/favicon.ico" since certain browsers will 79 suggested to use the value "/favicon.ico" since certain browsers will
84 ignore other values. Default value: none. 80 ignore other values. Default value: none.
85 81
86footer 82footer
87 The content of the file specified with this option will be included 83 The content of the file specified with this option will be included
88 verbatim at the bottom of all pages (i.e. it replaces the standard 84 verbatim at the bottom of all pages (i.e. it replaces the standard
89 "generated by..." message. Default value: none. 85 "generated by..." message. Default value: none.
90 86
91include 87include
92 Name of a configfile to include before the rest of the current config- 88 Name of a configfile to include before the rest of the current config-
@@ -124,24 +120,29 @@ max-commit-count
124max-message-length 120max-message-length
125 Specifies the maximum number of commit message characters to display in 121 Specifies the maximum number of commit message characters to display in
126 "log" view. Default value: "80". 122 "log" view. Default value: "80".
127 123
128max-repo-count 124max-repo-count
129 Specifies the number of entries to list per page on therepository 125 Specifies the number of entries to list per page on therepository
130 index page. Default value: "50". 126 index page. Default value: "50".
131 127
132max-repodesc-length 128max-repodesc-length
133 Specifies the maximum number of repo description characters to display 129 Specifies the maximum number of repo description characters to display
134 on the repository index page. Default value: "80". 130 on the repository index page. Default value: "80".
135 131
132max-stats
133 Set the default maximum statistics period. Valid values are "week",
134 "month", "quarter" and "year". If unspecified, statistics are
135 disabled. Default value: none. See also: "repo.max-stats".
136
136module-link 137module-link
137 Text which will be used as the formatstring for a hyperlink when a 138 Text which will be used as the formatstring for a hyperlink when a
138 submodule is printed in a directory listing. The arguments for the 139 submodule is printed in a directory listing. The arguments for the
139 formatstring are the path and SHA1 of the submodule commit. Default 140 formatstring are the path and SHA1 of the submodule commit. Default
140 value: "./?repo=%s&page=commit&id=%s" 141 value: "./?repo=%s&page=commit&id=%s"
141 142
142nocache 143nocache
143 If set to the value "1" caching will be disabled. This settings is 144 If set to the value "1" caching will be disabled. This settings is
144 deprecated, and will not be honored starting with cgit-1.0. Default 145 deprecated, and will not be honored starting with cgit-1.0. Default
145 value: "0". 146 value: "0".
146 147
147renamelimit 148renamelimit
@@ -213,27 +214,28 @@ repo.defbranch
213 214
214repo.desc 215repo.desc
215 The value to show as repository description. Default value: none. 216 The value to show as repository description. Default value: none.
216 217
217repo.enable-log-filecount 218repo.enable-log-filecount
218 A flag which can be used to disable the global setting 219 A flag which can be used to disable the global setting
219 `enable-log-filecount'. Default value: none. 220 `enable-log-filecount'. Default value: none.
220 221
221repo.enable-log-linecount 222repo.enable-log-linecount
222 A flag which can be used to disable the global setting 223 A flag which can be used to disable the global setting
223 `enable-log-linecount'. Default value: none. 224 `enable-log-linecount'. Default value: none.
224 225
225repo.enable-stats 226repo.max-stats
226 A flag which can be used to disable the global setting 227 Override the default maximum statistics period. Valid values are equal
227 `enable-stats'. Default value: none. 228 to the values specified for the global "max-stats" setting. Default
229 value: none.
228 230
229repo.name 231repo.name
230 The value to show as repository name. Default value: <repo.url>. 232 The value to show as repository name. Default value: <repo.url>.
231 233
232repo.owner 234repo.owner
233 A value used to identify the owner of the repository. Default value: 235 A value used to identify the owner of the repository. Default value:
234 none. 236 none.
235 237
236repo.path 238repo.path
237 An absolute path to the repository directory. For non-bare repositories 239 An absolute path to the repository directory. For non-bare repositories
238 this is the .git-directory. Default value: none. 240 this is the .git-directory. Default value: none.
239 241
@@ -275,24 +277,28 @@ enable-log-filecount=1
275# Show number of added/removed lines per commit on the log pages 277# Show number of added/removed lines per commit on the log pages
276enable-log-linecount=1 278enable-log-linecount=1
277 279
278 280
279# Add a cgit favicon 281# Add a cgit favicon
280favicon=/favicon.ico 282favicon=/favicon.ico
281 283
282 284
283# Use a custom logo 285# Use a custom logo
284logo=/img/mylogo.png 286logo=/img/mylogo.png
285 287
286 288
289# Enable statistics per week, month and quarter
290max-stats=quarter
291
292
287# Set the title and heading of the repository index page 293# Set the title and heading of the repository index page
288root-title=foobar.com git repositories 294root-title=foobar.com git repositories
289 295
290 296
291# Set a subheading for the repository index page 297# Set a subheading for the repository index page
292root-desc=tracking the foobar development 298root-desc=tracking the foobar development
293 299
294 300
295# Include some more info about foobar.com on the index page 301# Include some more info about foobar.com on the index page
296root-readme=/var/www/htdocs/about.html 302root-readme=/var/www/htdocs/about.html
297 303
298 304
@@ -347,24 +353,27 @@ repo.desc=the dscm
347 353
348 354
349repo.url=linux 355repo.url=linux
350repo.path=/pub/git/linux.git 356repo.path=/pub/git/linux.git
351repo.desc=the kernel 357repo.desc=the kernel
352 358
353# Disable adhoc downloads of this repo 359# Disable adhoc downloads of this repo
354repo.snapshots=0 360repo.snapshots=0
355 361
356# Disable line-counts for this repo 362# Disable line-counts for this repo
357repo.enable-log-linecount=0 363repo.enable-log-linecount=0
358 364
365# Restrict the max statistics period for this repo
366repo.max-stats=month
367
359 368
360BUGS 369BUGS
361---- 370----
362Comments currently cannot appear on the same line as a setting; the comment 371Comments currently cannot appear on the same line as a setting; the comment
363will be included as part of the value. E.g. this line: 372will be included as part of the value. E.g. this line:
364 373
365 robots=index # allow indexing 374 robots=index # allow indexing
366 375
367will generate the following html element: 376will generate the following html element:
368 377
369 <meta name='robots' content='index # allow indexing'/> 378 <meta name='robots' content='index # allow indexing'/>
370 379
diff --git a/cmd.c b/cmd.c
index 744bf84..763a558 100644
--- a/cmd.c
+++ b/cmd.c
@@ -103,28 +103,25 @@ static void refs_fn(struct cgit_context *ctx)
103 cgit_print_refs(); 103 cgit_print_refs();
104} 104}
105 105
106static void snapshot_fn(struct cgit_context *ctx) 106static void snapshot_fn(struct cgit_context *ctx)
107{ 107{
108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, 108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1,
109 cgit_repobasename(ctx->repo->url), ctx->qry.path, 109 cgit_repobasename(ctx->repo->url), ctx->qry.path,
110 ctx->repo->snapshots, ctx->qry.nohead); 110 ctx->repo->snapshots, ctx->qry.nohead);
111} 111}
112 112
113static void stats_fn(struct cgit_context *ctx) 113static void stats_fn(struct cgit_context *ctx)
114{ 114{
115 if (ctx->repo->enable_stats) 115 cgit_show_stats(ctx);
116 cgit_show_stats(ctx);
117 else
118 cgit_print_error("Stats disabled for this repo");
119} 116}
120 117
121static void summary_fn(struct cgit_context *ctx) 118static void summary_fn(struct cgit_context *ctx)
122{ 119{
123 cgit_print_summary(); 120 cgit_print_summary();
124} 121}
125 122
126static void tag_fn(struct cgit_context *ctx) 123static void tag_fn(struct cgit_context *ctx)
127{ 124{
128 cgit_print_tag(ctx->qry.sha1); 125 cgit_print_tag(ctx->qry.sha1);
129} 126}
130 127
diff --git a/shared.c b/shared.c
index 37333f0..7382609 100644
--- a/shared.c
+++ b/shared.c
@@ -49,25 +49,25 @@ struct cgit_repo *cgit_add_repo(const char *url)
49 49
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 52 ret->name = ret->url;
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->group = ctx.cfg.repo_group; 56 ret->group = ctx.cfg.repo_group;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_stats = ctx.cfg.enable_stats; 61 ret->max_stats = ctx.cfg.max_stats;
62 ret->module_link = ctx.cfg.module_link; 62 ret->module_link = ctx.cfg.module_link;
63 ret->readme = NULL; 63 ret->readme = NULL;
64 return ret; 64 return ret;
65} 65}
66 66
67struct cgit_repo *cgit_get_repoinfo(const char *url) 67struct cgit_repo *cgit_get_repoinfo(const char *url)
68{ 68{
69 int i; 69 int i;
70 struct cgit_repo *repo; 70 struct cgit_repo *repo;
71 71
72 for (i=0; i<cgit_repolist.count; i++) { 72 for (i=0; i<cgit_repolist.count; i++) {
73 repo = &cgit_repolist.repos[i]; 73 repo = &cgit_repolist.repos[i];
diff --git a/ui-shared.c b/ui-shared.c
index 0e688a0..97b9d46 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -632,25 +632,25 @@ void cgit_print_pageheader(struct cgit_context *ctx)
632 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 632 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
633 ctx->qry.head); 633 ctx->qry.head);
634 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 634 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
635 ctx->qry.sha1, NULL); 635 ctx->qry.sha1, NULL);
636 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 636 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
637 NULL, NULL, 0, NULL, NULL); 637 NULL, NULL, 0, NULL, NULL);
638 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 638 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
639 ctx->qry.sha1, NULL); 639 ctx->qry.sha1, NULL);
640 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 640 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
641 ctx->qry.head, ctx->qry.sha1); 641 ctx->qry.head, ctx->qry.sha1);
642 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 642 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
643 ctx->qry.sha1, ctx->qry.sha2, NULL); 643 ctx->qry.sha1, ctx->qry.sha2, NULL);
644 if (ctx->repo->enable_stats) 644 if (ctx->repo->max_stats)
645 reporevlink("stats", "stats", NULL, hc(cmd, "stats"), 645 reporevlink("stats", "stats", NULL, hc(cmd, "stats"),
646 ctx->qry.head, NULL, NULL); 646 ctx->qry.head, NULL, NULL);
647 if (ctx->repo->readme) 647 if (ctx->repo->readme)
648 reporevlink("about", "about", NULL, 648 reporevlink("about", "about", NULL,
649 hc(cmd, "about"), ctx->qry.head, NULL, 649 hc(cmd, "about"), ctx->qry.head, NULL,
650 NULL); 650 NULL);
651 html("</td><td class='form'>"); 651 html("</td><td class='form'>");
652 html("<form class='right' method='get' action='"); 652 html("<form class='right' method='get' action='");
653 if (ctx->cfg.virtual_root) 653 if (ctx->cfg.virtual_root)
654 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 654 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
655 ctx->qry.path, NULL)); 655 ctx->qry.path, NULL));
656 html("'>\n"); 656 html("'>\n");
diff --git a/ui-stats.c b/ui-stats.c
index 3cc8d70..1104485 100644
--- a/ui-stats.c
+++ b/ui-stats.c
@@ -1,35 +1,21 @@
1#include <string-list.h>
2
1#include "cgit.h" 3#include "cgit.h"
2#include "html.h" 4#include "html.h"
3#include <string-list.h> 5#include "ui-shared.h"
6#include "ui-stats.h"
4 7
5#define MONTHS 6 8#define MONTHS 6
6 9
7struct Period {
8 const char code;
9 const char *name;
10 int max_periods;
11 int count;
12
13 /* Convert a tm value to the first day in the period */
14 void (*trunc)(struct tm *tm);
15
16 /* Update tm value to start of next/previous period */
17 void (*dec)(struct tm *tm);
18 void (*inc)(struct tm *tm);
19
20 /* Pretty-print a tm value */
21 char *(*pretty)(struct tm *tm);
22};
23
24struct authorstat { 10struct authorstat {
25 long total; 11 long total;
26 struct string_list list; 12 struct string_list list;
27}; 13};
28 14
29#define DAY_SECS (60 * 60 * 24) 15#define DAY_SECS (60 * 60 * 24)
30#define WEEK_SECS (DAY_SECS * 7) 16#define WEEK_SECS (DAY_SECS * 7)
31 17
32static void trunc_week(struct tm *tm) 18static void trunc_week(struct tm *tm)
33{ 19{
34 time_t t = timegm(tm); 20 time_t t = timegm(tm);
35 t -= ((tm->tm_wday + 6) % 7) * DAY_SECS; 21 t -= ((tm->tm_wday + 6) % 7) * DAY_SECS;
@@ -128,33 +114,57 @@ static void dec_year(struct tm *tm)
128} 114}
129 115
130static void inc_year(struct tm *tm) 116static void inc_year(struct tm *tm)
131{ 117{
132 tm->tm_year++; 118 tm->tm_year++;
133} 119}
134 120
135static char *pretty_year(struct tm *tm) 121static char *pretty_year(struct tm *tm)
136{ 122{
137 return fmt("%d", tm->tm_year + 1900); 123 return fmt("%d", tm->tm_year + 1900);
138} 124}
139 125
140struct Period periods[] = { 126struct cgit_period periods[] = {
141 {'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week}, 127 {'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week},
142 {'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month}, 128 {'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month},
143 {'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter}, 129 {'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter},
144 {'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year}, 130 {'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year},
145}; 131};
146 132
133/* Given a period code or name, return a period index (1, 2, 3 or 4)
134 * and update the period pointer to the correcsponding struct.
135 * If no matching code is found, return 0.
136 */
137int cgit_find_stats_period(const char *expr, struct cgit_period **period)
138{
139 int i;
140 char code = '\0';
141
142 if (!expr)
143 return 0;
144
145 if (strlen(expr) == 1)
146 code = expr[0];
147
148 for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++)
149 if (periods[i].code == code || !strcmp(periods[i].name, expr)) {
150 if (period)
151 *period = &periods[i];
152 return i+1;
153 }
154 return 0;
155}
156
147static void add_commit(struct string_list *authors, struct commit *commit, 157static void add_commit(struct string_list *authors, struct commit *commit,
148 struct Period *period) 158 struct cgit_period *period)
149{ 159{
150 struct commitinfo *info; 160 struct commitinfo *info;
151 struct string_list_item *author, *item; 161 struct string_list_item *author, *item;
152 struct authorstat *authorstat; 162 struct authorstat *authorstat;
153 struct string_list *items; 163 struct string_list *items;
154 char *tmp; 164 char *tmp;
155 struct tm *date; 165 struct tm *date;
156 time_t t; 166 time_t t;
157 167
158 info = cgit_parse_commit(commit); 168 info = cgit_parse_commit(commit);
159 tmp = xstrdup(info->author); 169 tmp = xstrdup(info->author);
160 author = string_list_insert(tmp, authors); 170 author = string_list_insert(tmp, authors);
@@ -181,25 +191,25 @@ static int cmp_total_commits(const void *a1, const void *a2)
181 const struct string_list_item *i1 = a1; 191 const struct string_list_item *i1 = a1;
182 const struct string_list_item *i2 = a2; 192 const struct string_list_item *i2 = a2;
183 const struct authorstat *auth1 = i1->util; 193 const struct authorstat *auth1 = i1->util;
184 const struct authorstat *auth2 = i2->util; 194 const struct authorstat *auth2 = i2->util;
185 195
186 return auth2->total - auth1->total; 196 return auth2->total - auth1->total;
187} 197}
188 198
189/* Walk the commit DAG and collect number of commits per author per 199/* Walk the commit DAG and collect number of commits per author per
190 * timeperiod into a nested string_list collection. 200 * timeperiod into a nested string_list collection.
191 */ 201 */
192struct string_list collect_stats(struct cgit_context *ctx, 202struct string_list collect_stats(struct cgit_context *ctx,
193 struct Period *period) 203 struct cgit_period *period)
194{ 204{
195 struct string_list authors; 205 struct string_list authors;
196 struct rev_info rev; 206 struct rev_info rev;
197 struct commit *commit; 207 struct commit *commit;
198 const char *argv[] = {NULL, ctx->qry.head, NULL, NULL, NULL, NULL}; 208 const char *argv[] = {NULL, ctx->qry.head, NULL, NULL, NULL, NULL};
199 int argc = 3; 209 int argc = 3;
200 time_t now; 210 time_t now;
201 long i; 211 long i;
202 struct tm *tm; 212 struct tm *tm;
203 char tmp[11]; 213 char tmp[11];
204 214
205 time(&now); 215 time(&now);
@@ -224,25 +234,25 @@ struct string_list collect_stats(struct cgit_context *ctx,
224 prepare_revision_walk(&rev); 234 prepare_revision_walk(&rev);
225 memset(&authors, 0, sizeof(authors)); 235 memset(&authors, 0, sizeof(authors));
226 while ((commit = get_revision(&rev)) != NULL) { 236 while ((commit = get_revision(&rev)) != NULL) {
227 add_commit(&authors, commit, period); 237 add_commit(&authors, commit, period);
228 free(commit->buffer); 238 free(commit->buffer);
229 free_commit_list(commit->parents); 239 free_commit_list(commit->parents);
230 } 240 }
231 return authors; 241 return authors;
232} 242}
233 243
234void print_combined_authorrow(struct string_list *authors, int from, int to, 244void print_combined_authorrow(struct string_list *authors, int from, int to,
235 const char *name, const char *leftclass, const char *centerclass, 245 const char *name, const char *leftclass, const char *centerclass,
236 const char *rightclass, struct Period *period) 246 const char *rightclass, struct cgit_period *period)
237{ 247{
238 struct string_list_item *author; 248 struct string_list_item *author;
239 struct authorstat *authorstat; 249 struct authorstat *authorstat;
240 struct string_list *items; 250 struct string_list *items;
241 struct string_list_item *date; 251 struct string_list_item *date;
242 time_t now; 252 time_t now;
243 long i, j, total, subtotal; 253 long i, j, total, subtotal;
244 struct tm *tm; 254 struct tm *tm;
245 char *tmp; 255 char *tmp;
246 256
247 time(&now); 257 time(&now);
248 tm = gmtime(&now); 258 tm = gmtime(&now);
@@ -262,25 +272,26 @@ void print_combined_authorrow(struct string_list *authors, int from, int to,
262 authorstat = author->util; 272 authorstat = author->util;
263 items = &authorstat->list; 273 items = &authorstat->list;
264 date = string_list_lookup(tmp, items); 274 date = string_list_lookup(tmp, items);
265 if (date) 275 if (date)
266 subtotal += (size_t)date->util; 276 subtotal += (size_t)date->util;
267 } 277 }
268 htmlf("<td class='%s'>%d</td>", centerclass, subtotal); 278 htmlf("<td class='%s'>%d</td>", centerclass, subtotal);
269 total += subtotal; 279 total += subtotal;
270 } 280 }
271 htmlf("<td class='%s'>%d</td></tr>", rightclass, total); 281 htmlf("<td class='%s'>%d</td></tr>", rightclass, total);
272} 282}
273 283
274void print_authors(struct string_list *authors, int top, struct Period *period) 284void print_authors(struct string_list *authors, int top,
285 struct cgit_period *period)
275{ 286{
276 struct string_list_item *author; 287 struct string_list_item *author;
277 struct authorstat *authorstat; 288 struct authorstat *authorstat;
278 struct string_list *items; 289 struct string_list *items;
279 struct string_list_item *date; 290 struct string_list_item *date;
280 time_t now; 291 time_t now;
281 long i, j, total; 292 long i, j, total;
282 struct tm *tm; 293 struct tm *tm;
283 char *tmp; 294 char *tmp;
284 295
285 time(&now); 296 time(&now);
286 tm = gmtime(&now); 297 tm = gmtime(&now);
@@ -330,61 +341,69 @@ void print_authors(struct string_list *authors, int top, struct Period *period)
330 print_combined_authorrow(authors, 0, authors->nr - 1, "Total", 341 print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
331 "total", "sum", "sum", period); 342 "total", "sum", "sum", period);
332 html("</table>"); 343 html("</table>");
333} 344}
334 345
335/* Create a sorted string_list with one entry per author. The util-field 346/* Create a sorted string_list with one entry per author. The util-field
336 * for each author is another string_list which is used to calculate the 347 * for each author is another string_list which is used to calculate the
337 * number of commits per time-interval. 348 * number of commits per time-interval.
338 */ 349 */
339void cgit_show_stats(struct cgit_context *ctx) 350void cgit_show_stats(struct cgit_context *ctx)
340{ 351{
341 struct string_list authors; 352 struct string_list authors;
342 struct Period *period; 353 struct cgit_period *period;
343 int top, i; 354 int top, i;
355 const char *code = "w";
344 356
345 period = &periods[0]; 357 if (ctx->qry.period)
346 if (ctx->qry.period) { 358 code = ctx->qry.period;
347 for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++) 359
348 if (periods[i].code == ctx->qry.period[0]) { 360 i = cgit_find_stats_period(code, &period);
349 period = &periods[i]; 361 if (!i) {
350 break; 362 cgit_print_error(fmt("Unknown statistics type: %c", code));
351 } 363 return;
364 }
365 if (i > ctx->repo->max_stats) {
366 cgit_print_error(fmt("Statistics type disabled: %s",
367 period->name));
368 return;
352 } 369 }
353 authors = collect_stats(ctx, period); 370 authors = collect_stats(ctx, period);
354 qsort(authors.items, authors.nr, sizeof(struct string_list_item), 371 qsort(authors.items, authors.nr, sizeof(struct string_list_item),
355 cmp_total_commits); 372 cmp_total_commits);
356 373
357 top = ctx->qry.ofs; 374 top = ctx->qry.ofs;
358 if (!top) 375 if (!top)
359 top = 10; 376 top = 10;
360 htmlf("<h2>Commits per author per %s", period->name); 377 htmlf("<h2>Commits per author per %s", period->name);
361 if (ctx->qry.path) { 378 if (ctx->qry.path) {
362 html(" (path '"); 379 html(" (path '");
363 html_txt(ctx->qry.path); 380 html_txt(ctx->qry.path);
364 html("')"); 381 html("')");
365 } 382 }
366 html("</h2>"); 383 html("</h2>");
367 384
368 html("<form method='get' action='.' style='float: right; text-align: right;'>"); 385 html("<form method='get' action='.' style='float: right; text-align: right;'>");
369 if (strcmp(ctx->qry.head, ctx->repo->defbranch)) 386 if (strcmp(ctx->qry.head, ctx->repo->defbranch))
370 htmlf("<input type='hidden' name='h' value='%s'/>", ctx->qry.head); 387 htmlf("<input type='hidden' name='h' value='%s'/>", ctx->qry.head);
371 html("Period: "); 388 if (ctx->repo->max_stats > 1) {
372 html("<select name='period' onchange='this.form.submit();'>"); 389 html("Period: ");
373 for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++) 390 html("<select name='period' onchange='this.form.submit();'>");
374 htmlf("<option value='%c'%s>%s</option>", 391 for (i = 0; i < ctx->repo->max_stats; i++)
375 periods[i].code, 392 htmlf("<option value='%c'%s>%s</option>",
376 period == &periods[i] ? " selected" : "", 393 periods[i].code,
377 periods[i].name); 394 period == &periods[i] ? " selected" : "",
378 html("</select><br/><br/>"); 395 periods[i].name);
396 html("</select><br/><br/>");
397 }
379 html("Authors: "); 398 html("Authors: ");
380 html(""); 399 html("");
381 html("<select name='ofs' onchange='this.form.submit();'>"); 400 html("<select name='ofs' onchange='this.form.submit();'>");
382 htmlf("<option value='10'%s>10</option>", top == 10 ? " selected" : ""); 401 htmlf("<option value='10'%s>10</option>", top == 10 ? " selected" : "");
383 htmlf("<option value='25'%s>25</option>", top == 25 ? " selected" : ""); 402 htmlf("<option value='25'%s>25</option>", top == 25 ? " selected" : "");
384 htmlf("<option value='50'%s>50</option>", top == 50 ? " selected" : ""); 403 htmlf("<option value='50'%s>50</option>", top == 50 ? " selected" : "");
385 htmlf("<option value='100'%s>100</option>", top == 100 ? " selected" : ""); 404 htmlf("<option value='100'%s>100</option>", top == 100 ? " selected" : "");
386 htmlf("<option value='-1'%s>All</option>", top == -1 ? " selected" : ""); 405 htmlf("<option value='-1'%s>All</option>", top == -1 ? " selected" : "");
387 html("</select>"); 406 html("</select>");
388 html("<noscript>&nbsp;&nbsp;<input type='submit' value='Reload'/></noscript>"); 407 html("<noscript>&nbsp;&nbsp;<input type='submit' value='Reload'/></noscript>");
389 html("</form>"); 408 html("</form>");
390 print_authors(&authors, top, period); 409 print_authors(&authors, top, period);
diff --git a/ui-stats.h b/ui-stats.h
index f1d744c..4f13dba 100644
--- a/ui-stats.h
+++ b/ui-stats.h
@@ -1,8 +1,27 @@
1#ifndef UI_STATS_H 1#ifndef UI_STATS_H
2#define UI_STATS_H 2#define UI_STATS_H
3 3
4#include "cgit.h" 4#include "cgit.h"
5 5
6struct cgit_period {
7 const char code;
8 const char *name;
9 int max_periods;
10 int count;
11
12 /* Convert a tm value to the first day in the period */
13 void (*trunc)(struct tm *tm);
14
15 /* Update tm value to start of next/previous period */
16 void (*dec)(struct tm *tm);
17 void (*inc)(struct tm *tm);
18
19 /* Pretty-print a tm value */
20 char *(*pretty)(struct tm *tm);
21};
22
23extern int cgit_find_stats_period(const char *expr, struct cgit_period **period);
24
6extern void cgit_show_stats(struct cgit_context *ctx); 25extern void cgit_show_stats(struct cgit_context *ctx);
7 26
8#endif /* UI_STATS_H */ 27#endif /* UI_STATS_H */