summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2011-02-19 13:01:59 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2011-02-19 13:01:59 (UTC)
commit31e1f9af1d46bd7dfdb3b2ac580c0d0cc8dbaa63 (patch) (unidiff)
tree26200ad3c376ea1208d2a870ebb3b2bc6d7abee6
parente66a16cebcdac53b63e77876acef1ca9e4877038 (diff)
parentc2bfd40f8aaaa69a66c6eb729c202e42a43ec166 (diff)
downloadcgit-31e1f9af1d46bd7dfdb3b2ac580c0d0cc8dbaa63.zip
cgit-31e1f9af1d46bd7dfdb3b2ac580c0d0cc8dbaa63.tar.gz
cgit-31e1f9af1d46bd7dfdb3b2ac580c0d0cc8dbaa63.tar.bz2
Merge branch 'jh/graph'
* jh/graph: ui-log: Move 'Age' column when commit graph is present ui-log: Line-wrap long commit subjects when showmsg is enabled ui-log: Colorize commit graph ui-log: Implement support for commit graphs ui-log: Change display of full commit messages (and notes) Conflicts: cgit.css
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--cgit.c6
-rw-r--r--cgit.css40
-rw-r--r--cgit.h3
-rw-r--r--cgitrc.5.txt13
-rw-r--r--cmd.c3
-rw-r--r--shared.c1
-rw-r--r--ui-log.c183
-rw-r--r--ui-log.h3
-rw-r--r--ui-summary.c2
9 files changed, 212 insertions, 42 deletions
diff --git a/cgit.c b/cgit.c
index 412fbf0..53ab68d 100644
--- a/cgit.c
+++ b/cgit.c
@@ -48,24 +48,26 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
48 if (!strcmp(name, "name")) 48 if (!strcmp(name, "name"))
49 repo->name = xstrdup(value); 49 repo->name = xstrdup(value);
50 else if (!strcmp(name, "clone-url")) 50 else if (!strcmp(name, "clone-url"))
51 repo->clone_url = xstrdup(value); 51 repo->clone_url = xstrdup(value);
52 else if (!strcmp(name, "desc")) 52 else if (!strcmp(name, "desc"))
53 repo->desc = xstrdup(value); 53 repo->desc = xstrdup(value);
54 else if (!strcmp(name, "owner")) 54 else if (!strcmp(name, "owner"))
55 repo->owner = xstrdup(value); 55 repo->owner = xstrdup(value);
56 else if (!strcmp(name, "defbranch")) 56 else if (!strcmp(name, "defbranch"))
57 repo->defbranch = xstrdup(value); 57 repo->defbranch = xstrdup(value);
58 else if (!strcmp(name, "snapshots")) 58 else if (!strcmp(name, "snapshots"))
59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
60 else if (!strcmp(name, "enable-commit-graph"))
61 repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value);
60 else if (!strcmp(name, "enable-log-filecount")) 62 else if (!strcmp(name, "enable-log-filecount"))
61 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 63 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
62 else if (!strcmp(name, "enable-log-linecount")) 64 else if (!strcmp(name, "enable-log-linecount"))
63 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 65 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
64 else if (!strcmp(name, "enable-remote-branches")) 66 else if (!strcmp(name, "enable-remote-branches"))
65 repo->enable_remote_branches = atoi(value); 67 repo->enable_remote_branches = atoi(value);
66 else if (!strcmp(name, "enable-subject-links")) 68 else if (!strcmp(name, "enable-subject-links"))
67 repo->enable_subject_links = atoi(value); 69 repo->enable_subject_links = atoi(value);
68 else if (!strcmp(name, "max-stats")) 70 else if (!strcmp(name, "max-stats"))
69 repo->max_stats = cgit_find_stats_period(value, NULL); 71 repo->max_stats = cgit_find_stats_period(value, NULL);
70 else if (!strcmp(name, "module-link")) 72 else if (!strcmp(name, "module-link"))
71 repo->module_link= xstrdup(value); 73 repo->module_link= xstrdup(value);
@@ -132,24 +134,26 @@ void config_cb(const char *name, const char *value)
132 else if (!strcmp(name, "noplainemail")) 134 else if (!strcmp(name, "noplainemail"))
133 ctx.cfg.noplainemail = atoi(value); 135 ctx.cfg.noplainemail = atoi(value);
134 else if (!strcmp(name, "noheader")) 136 else if (!strcmp(name, "noheader"))
135 ctx.cfg.noheader = atoi(value); 137 ctx.cfg.noheader = atoi(value);
136 else if (!strcmp(name, "snapshots")) 138 else if (!strcmp(name, "snapshots"))
137 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 139 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
138 else if (!strcmp(name, "enable-filter-overrides")) 140 else if (!strcmp(name, "enable-filter-overrides"))
139 ctx.cfg.enable_filter_overrides = atoi(value); 141 ctx.cfg.enable_filter_overrides = atoi(value);
140 else if (!strcmp(name, "enable-gitweb-owner")) 142 else if (!strcmp(name, "enable-gitweb-owner"))
141 ctx.cfg.enable_gitweb_owner = atoi(value); 143 ctx.cfg.enable_gitweb_owner = atoi(value);
142 else if (!strcmp(name, "enable-index-links")) 144 else if (!strcmp(name, "enable-index-links"))
143 ctx.cfg.enable_index_links = atoi(value); 145 ctx.cfg.enable_index_links = atoi(value);
146 else if (!strcmp(name, "enable-commit-graph"))
147 ctx.cfg.enable_commit_graph = atoi(value);
144 else if (!strcmp(name, "enable-log-filecount")) 148 else if (!strcmp(name, "enable-log-filecount"))
145 ctx.cfg.enable_log_filecount = atoi(value); 149 ctx.cfg.enable_log_filecount = atoi(value);
146 else if (!strcmp(name, "enable-log-linecount")) 150 else if (!strcmp(name, "enable-log-linecount"))
147 ctx.cfg.enable_log_linecount = atoi(value); 151 ctx.cfg.enable_log_linecount = atoi(value);
148 else if (!strcmp(name, "enable-remote-branches")) 152 else if (!strcmp(name, "enable-remote-branches"))
149 ctx.cfg.enable_remote_branches = atoi(value); 153 ctx.cfg.enable_remote_branches = atoi(value);
150 else if (!strcmp(name, "enable-subject-links")) 154 else if (!strcmp(name, "enable-subject-links"))
151 ctx.cfg.enable_subject_links = atoi(value); 155 ctx.cfg.enable_subject_links = atoi(value);
152 else if (!strcmp(name, "enable-tree-linenumbers")) 156 else if (!strcmp(name, "enable-tree-linenumbers"))
153 ctx.cfg.enable_tree_linenumbers = atoi(value); 157 ctx.cfg.enable_tree_linenumbers = atoi(value);
154 else if (!strcmp(name, "max-stats")) 158 else if (!strcmp(name, "max-stats"))
155 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 159 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
@@ -531,24 +535,26 @@ void print_repo(FILE *f, struct cgit_repo *repo)
531 free(tmp); 535 free(tmp);
532 } 536 }
533 if (repo->readme) 537 if (repo->readme)
534 fprintf(f, "repo.readme=%s\n", repo->readme); 538 fprintf(f, "repo.readme=%s\n", repo->readme);
535 if (repo->defbranch) 539 if (repo->defbranch)
536 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 540 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
537 if (repo->module_link) 541 if (repo->module_link)
538 fprintf(f, "repo.module-link=%s\n", repo->module_link); 542 fprintf(f, "repo.module-link=%s\n", repo->module_link);
539 if (repo->section) 543 if (repo->section)
540 fprintf(f, "repo.section=%s\n", repo->section); 544 fprintf(f, "repo.section=%s\n", repo->section);
541 if (repo->clone_url) 545 if (repo->clone_url)
542 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 546 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
547 fprintf(f, "repo.enable-commit-graph=%d\n",
548 repo->enable_commit_graph);
543 fprintf(f, "repo.enable-log-filecount=%d\n", 549 fprintf(f, "repo.enable-log-filecount=%d\n",
544 repo->enable_log_filecount); 550 repo->enable_log_filecount);
545 fprintf(f, "repo.enable-log-linecount=%d\n", 551 fprintf(f, "repo.enable-log-linecount=%d\n",
546 repo->enable_log_linecount); 552 repo->enable_log_linecount);
547 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 553 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
548 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 554 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
549 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 555 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
550 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 556 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
551 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 557 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
552 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 558 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
553 if (repo->snapshots != ctx.cfg.snapshots) { 559 if (repo->snapshots != ctx.cfg.snapshots) {
554 char *tmp = build_snapshot_setting(repo->snapshots); 560 char *tmp = build_snapshot_setting(repo->snapshots);
diff --git a/cgit.css b/cgit.css
index a2a685b..008cff8 100644
--- a/cgit.css
+++ b/cgit.css
@@ -144,44 +144,62 @@ table.list th {
144 border-top: dashed 1px #888; 144 border-top: dashed 1px #888;
145 border-bottom: dashed 1px #888; 145 border-bottom: dashed 1px #888;
146 */ 146 */
147 padding: 0.1em 0.5em 0.05em 0.5em; 147 padding: 0.1em 0.5em 0.05em 0.5em;
148 vertical-align: baseline; 148 vertical-align: baseline;
149} 149}
150 150
151table.list td { 151table.list td {
152 border: none; 152 border: none;
153 padding: 0.1em 0.5em 0.1em 0.5em; 153 padding: 0.1em 0.5em 0.1em 0.5em;
154} 154}
155 155
156table.list td.logsubject { 156table.list td.commitgraph {
157 font-family: monospace; 157 font-family: monospace;
158 font-weight: bold; 158 white-space: pre;
159} 159}
160 160
161table.list td.logmsg { 161table.list td.commitgraph .column1 {
162 font-family: monospace; 162 color: #a00;
163 white-space: pre;
164 padding: 1em 0.5em 2em 0.5em;
165} 163}
166 164
167table.list td.lognotes-label { 165table.list td.commitgraph .column2 {
168 text-align:right; 166 color: #0a0;
169 vertical-align:top; 167}
168
169table.list td.commitgraph .column3 {
170 color: #aa0;
171}
172
173table.list td.commitgraph .column4 {
174 color: #00a;
175}
176
177table.list td.commitgraph .column5 {
178 color: #a0a;
170} 179}
171 180
172table.list td.lognotes { 181table.list td.commitgraph .column6 {
182 color: #0aa;
183}
184
185table.list td.logsubject {
186 font-family: monospace;
187 font-weight: bold;
188}
189
190table.list td.logmsg {
173 font-family: monospace; 191 font-family: monospace;
174 white-space: pre; 192 white-space: pre;
175 padding: 0em 0.5em 2em 0.5em; 193 padding: 0 0.5em;
176} 194}
177 195
178table.list td a { 196table.list td a {
179 color: black; 197 color: black;
180} 198}
181 199
182table.list td a.ls-dir { 200table.list td a.ls-dir {
183 font-weight: bold; 201 font-weight: bold;
184 color: #00f; 202 color: #00f;
185} 203}
186 204
187table.list td a:hover { 205table.list td a:hover {
diff --git a/cgit.h b/cgit.h
index f5f68ac..bed770b 100644
--- a/cgit.h
+++ b/cgit.h
@@ -11,24 +11,25 @@
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22#include <notes.h> 22#include <notes.h>
23#include <graph.h>
23 24
24 25
25/* 26/*
26 * Dateformats used on misc. pages 27 * Dateformats used on misc. pages
27 */ 28 */
28#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 29#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
29#define FMT_SHORTDATE "%Y-%m-%d" 30#define FMT_SHORTDATE "%Y-%m-%d"
30#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 31#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
31 32
32 33
33/* 34/*
34 * Limits used for relative dates 35 * Limits used for relative dates
@@ -62,24 +63,25 @@ struct cgit_filter {
62struct cgit_repo { 63struct cgit_repo {
63 char *url; 64 char *url;
64 char *name; 65 char *name;
65 char *path; 66 char *path;
66 char *desc; 67 char *desc;
67 char *owner; 68 char *owner;
68 char *defbranch; 69 char *defbranch;
69 char *module_link; 70 char *module_link;
70 char *readme; 71 char *readme;
71 char *section; 72 char *section;
72 char *clone_url; 73 char *clone_url;
73 int snapshots; 74 int snapshots;
75 int enable_commit_graph;
74 int enable_log_filecount; 76 int enable_log_filecount;
75 int enable_log_linecount; 77 int enable_log_linecount;
76 int enable_remote_branches; 78 int enable_remote_branches;
77 int enable_subject_links; 79 int enable_subject_links;
78 int max_stats; 80 int max_stats;
79 time_t mtime; 81 time_t mtime;
80 struct cgit_filter *about_filter; 82 struct cgit_filter *about_filter;
81 struct cgit_filter *commit_filter; 83 struct cgit_filter *commit_filter;
82 struct cgit_filter *source_filter; 84 struct cgit_filter *source_filter;
83}; 85};
84 86
85typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 87typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
@@ -179,24 +181,25 @@ struct cgit_config {
179 char *strict_export; 181 char *strict_export;
180 int cache_size; 182 int cache_size;
181 int cache_dynamic_ttl; 183 int cache_dynamic_ttl;
182 int cache_max_create_time; 184 int cache_max_create_time;
183 int cache_repo_ttl; 185 int cache_repo_ttl;
184 int cache_root_ttl; 186 int cache_root_ttl;
185 int cache_scanrc_ttl; 187 int cache_scanrc_ttl;
186 int cache_static_ttl; 188 int cache_static_ttl;
187 int embedded; 189 int embedded;
188 int enable_filter_overrides; 190 int enable_filter_overrides;
189 int enable_gitweb_owner; 191 int enable_gitweb_owner;
190 int enable_index_links; 192 int enable_index_links;
193 int enable_commit_graph;
191 int enable_log_filecount; 194 int enable_log_filecount;
192 int enable_log_linecount; 195 int enable_log_linecount;
193 int enable_remote_branches; 196 int enable_remote_branches;
194 int enable_subject_links; 197 int enable_subject_links;
195 int enable_tree_linenumbers; 198 int enable_tree_linenumbers;
196 int local_time; 199 int local_time;
197 int max_atom_items; 200 int max_atom_items;
198 int max_repo_count; 201 int max_repo_count;
199 int max_commit_count; 202 int max_commit_count;
200 int max_lock_attempts; 203 int max_lock_attempts;
201 int max_msg_len; 204 int max_msg_len;
202 int max_repodesc_len; 205 int max_repodesc_len;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 8e51ca5..3c20fe1 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -82,24 +82,29 @@ commit-filter::
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
94enable-commit-graph::
95 Flag which, when set to "1", will make cgit print an ASCII-art commit
96 history graph to the left of the commit messages in the repository
97 log page. Default value: "0".
98
94enable-filter-overrides:: 99enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 100 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 101 overridden in repository-specific cgitrc files. Default value: none.
97 102
98enable-gitweb-owner:: 103enable-gitweb-owner::
99 If set to "1" and scan-path is enabled, we first check each repository 104 If set to "1" and scan-path is enabled, we first check each repository
100 for the git config value "gitweb.owner" to determine the owner. 105 for the git config value "gitweb.owner" to determine the owner.
101 Default value: "1". See also: scan-path. 106 Default value: "1". See also: scan-path.
102 107
103enable-index-links:: 108enable-index-links::
104 Flag which, when set to "1", will make cgit generate extra links for 109 Flag which, when set to "1", will make cgit generate extra links for
105 each repo in the repository index (specifically, to the "summary", 110 each repo in the repository index (specifically, to the "summary",
@@ -345,24 +350,28 @@ repo.clone-url::
345repo.commit-filter:: 350repo.commit-filter::
346 Override the default commit-filter. Default value: none. See also: 351 Override the default commit-filter. Default value: none. See also:
347 "enable-filter-overrides". 352 "enable-filter-overrides".
348 353
349repo.defbranch:: 354repo.defbranch::
350 The name of the default branch for this repository. If no such branch 355 The name of the default branch for this repository. If no such branch
351 exists in the repository, the first branch name (when sorted) is used 356 exists in the repository, the first branch name (when sorted) is used
352 as default instead. Default value: "master". 357 as default instead. Default value: "master".
353 358
354repo.desc:: 359repo.desc::
355 The value to show as repository description. Default value: none. 360 The value to show as repository description. Default value: none.
356 361
362repo.enable-commit-graph::
363 A flag which can be used to disable the global setting
364 `enable-commit-graph'. Default value: none.
365
357repo.enable-log-filecount:: 366repo.enable-log-filecount::
358 A flag which can be used to disable the global setting 367 A flag which can be used to disable the global setting
359 `enable-log-filecount'. Default value: none. 368 `enable-log-filecount'. Default value: none.
360 369
361repo.enable-log-linecount:: 370repo.enable-log-linecount::
362 A flag which can be used to disable the global setting 371 A flag which can be used to disable the global setting
363 `enable-log-linecount'. Default value: none. 372 `enable-log-linecount'. Default value: none.
364 373
365repo.enable-remote-branches:: 374repo.enable-remote-branches::
366 Flag which, when set to "1", will make cgit display remote branches 375 Flag which, when set to "1", will make cgit display remote branches
367 in the summary and refs views. Default value: <enable-remote-branches>. 376 in the summary and refs views. Default value: <enable-remote-branches>.
368 377
@@ -432,24 +441,28 @@ cache-size=1000
432 441
433# Specify some default clone prefixes 442# Specify some default clone prefixes
434clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git 443clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git
435 444
436# Specify the css url 445# Specify the css url
437css=/css/cgit.css 446css=/css/cgit.css
438 447
439 448
440# Show extra links for each repository on the index page 449# Show extra links for each repository on the index page
441enable-index-links=1 450enable-index-links=1
442 451
443 452
453# Enable ASCII art commit history graph on the log pages
454enable-commit-graph=1
455
456
444# Show number of affected files per commit on the log pages 457# Show number of affected files per commit on the log pages
445enable-log-filecount=1 458enable-log-filecount=1
446 459
447 460
448# Show number of added/removed lines per commit on the log pages 461# Show number of added/removed lines per commit on the log pages
449enable-log-linecount=1 462enable-log-linecount=1
450 463
451 464
452# Add a cgit favicon 465# Add a cgit favicon
453favicon=/favicon.ico 466favicon=/favicon.ico
454 467
455 468
diff --git a/cmd.c b/cmd.c
index 6dc9f5e..536515b 100644
--- a/cmd.c
+++ b/cmd.c
@@ -58,25 +58,26 @@ static void diff_fn(struct cgit_context *ctx)
58{ 58{
59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
60} 60}
61 61
62static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
63{ 63{
64 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
65} 65}
66 66
67static void log_fn(struct cgit_context *ctx) 67static void log_fn(struct cgit_context *ctx)
68{ 68{
69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, 69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1,
71 ctx->repo->enable_commit_graph);
71} 72}
72 73
73static void ls_cache_fn(struct cgit_context *ctx) 74static void ls_cache_fn(struct cgit_context *ctx)
74{ 75{
75 ctx->page.mimetype = "text/plain"; 76 ctx->page.mimetype = "text/plain";
76 ctx->page.filename = "ls-cache.txt"; 77 ctx->page.filename = "ls-cache.txt";
77 cgit_print_http_headers(ctx); 78 cgit_print_http_headers(ctx);
78 cache_ls(ctx->cfg.cache_root); 79 cache_ls(ctx->cfg.cache_root);
79} 80}
80 81
81static void objects_fn(struct cgit_context *ctx) 82static void objects_fn(struct cgit_context *ctx)
82{ 83{
diff --git a/shared.c b/shared.c
index 765cd27..7ec2e19 100644
--- a/shared.c
+++ b/shared.c
@@ -47,24 +47,25 @@ struct cgit_repo *cgit_add_repo(const char *url)
47 } 47 }
48 48
49 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 49 ret = &cgit_repolist.repos[cgit_repolist.count-1];
50 memset(ret, 0, sizeof(struct cgit_repo)); 50 memset(ret, 0, sizeof(struct cgit_repo));
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->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches; 62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->enable_subject_links = ctx.cfg.enable_subject_links; 63 ret->enable_subject_links = ctx.cfg.enable_subject_links;
63 ret->max_stats = ctx.cfg.max_stats; 64 ret->max_stats = ctx.cfg.max_stats;
64 ret->module_link = ctx.cfg.module_link; 65 ret->module_link = ctx.cfg.module_link;
65 ret->readme = ctx.cfg.readme; 66 ret->readme = ctx.cfg.readme;
66 ret->mtime = -1; 67 ret->mtime = -1;
67 ret->about_filter = ctx.cfg.about_filter; 68 ret->about_filter = ctx.cfg.about_filter;
68 ret->commit_filter = ctx.cfg.commit_filter; 69 ret->commit_filter = ctx.cfg.commit_filter;
69 ret->source_filter = ctx.cfg.source_filter; 70 ret->source_filter = ctx.cfg.source_filter;
70 return ret; 71 return ret;
diff --git a/ui-log.c b/ui-log.c
index 27f5a1a..8add66a 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -4,24 +4,39 @@
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 "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "vector.h" 12#include "vector.h"
13 13
14int files, add_lines, rem_lines; 14int files, add_lines, rem_lines;
15 15
16/*
17 * The list of available column colors in the commit graph.
18 */
19static const char *column_colors_html[] = {
20 "<span class='column1'>",
21 "<span class='column2'>",
22 "<span class='column3'>",
23 "<span class='column4'>",
24 "<span class='column5'>",
25 "<span class='column6'>",
26 "</span>",
27};
28
29#define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1)
30
16void count_lines(char *line, int size) 31void count_lines(char *line, int size)
17{ 32{
18 if (size <= 0) 33 if (size <= 0)
19 return; 34 return;
20 35
21 if (line[0] == '+') 36 if (line[0] == '+')
22 add_lines++; 37 add_lines++;
23 38
24 else if (line[0] == '-') 39 else if (line[0] == '-')
25 rem_lines++; 40 rem_lines++;
26} 41}
27 42
@@ -68,81 +83,179 @@ void show_commit_decorations(struct commit *commit)
68 ctx.qry.showmsg); 83 ctx.qry.showmsg);
69 } 84 }
70 else { 85 else {
71 strncpy(buf, deco->name, sizeof(buf) - 1); 86 strncpy(buf, deco->name, sizeof(buf) - 1);
72 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 87 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
73 sha1_to_hex(commit->object.sha1), 88 sha1_to_hex(commit->object.sha1),
74 ctx.qry.vpath, 0); 89 ctx.qry.vpath, 0);
75 } 90 }
76 deco = deco->next; 91 deco = deco->next;
77 } 92 }
78} 93}
79 94
80void print_commit(struct commit *commit) 95void print_commit(struct commit *commit, struct rev_info *revs)
81{ 96{
82 struct commitinfo *info; 97 struct commitinfo *info;
83 char *tmp; 98 char *tmp;
84 int cols = 2; 99 int cols = revs->graph ? 3 : 2;
100 struct strbuf graphbuf = STRBUF_INIT;
101 struct strbuf msgbuf = STRBUF_INIT;
102
103 if (ctx.repo->enable_log_filecount) {
104 cols++;
105 if (ctx.repo->enable_log_linecount)
106 cols++;
107 }
108
109 if (revs->graph) {
110 /* Advance graph until current commit */
111 while (!graph_next_line(revs->graph, &graphbuf)) {
112 /* Print graph segment in otherwise empty table row */
113 html("<tr class='nohover'><td class='commitgraph'>");
114 html(graphbuf.buf);
115 htmlf("</td><td colspan='%d' /></tr>\n", cols);
116 strbuf_setlen(&graphbuf, 0);
117 }
118 /* Current commit's graph segment is now ready in graphbuf */
119 }
85 120
86 info = cgit_parse_commit(commit); 121 info = cgit_parse_commit(commit);
87 htmlf("<tr%s><td>", 122 htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
88 ctx.qry.showmsg ? " class='logheader'" : ""); 123
124 if (revs->graph) {
125 /* Print graph segment for current commit */
126 html("<td class='commitgraph'>");
127 html(graphbuf.buf);
128 html("</td>");
129 strbuf_setlen(&graphbuf, 0);
130 }
131 else {
132 html("<td>");
89 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 133 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
90 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); 134 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
91 html_link_open(tmp, NULL, NULL); 135 html_link_open(tmp, NULL, NULL);
92 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 136 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
93 html_link_close(); 137 html_link_close();
94 htmlf("</td><td%s>", 138 html("</td>");
95 ctx.qry.showmsg ? " class='logsubject'" : ""); 139 }
140
141 htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
142 if (ctx.qry.showmsg) {
143 /* line-wrap long commit subjects instead of truncating them */
144 size_t subject_len = strlen(info->subject);
145
146 if (subject_len > ctx.cfg.max_msg_len &&
147 ctx.cfg.max_msg_len >= 15) {
148 /* symbol for signaling line-wrap (in PAGE_ENCODING) */
149 const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 };
150 int i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
151
152 /* Rewind i to preceding space character */
153 while (i > 0 && !isspace(info->subject[i]))
154 --i;
155 if (!i) /* Oops, zero spaces. Reset i */
156 i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
157
158 /* add remainder starting at i to msgbuf */
159 strbuf_add(&msgbuf, info->subject + i, subject_len - i);
160 strbuf_trim(&msgbuf);
161 strbuf_add(&msgbuf, "\n\n", 2);
162
163 /* Place wrap_symbol at position i in info->subject */
164 strcpy(info->subject + i, wrap_symbol);
165 }
166 }
96 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 167 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
97 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); 168 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
98 show_commit_decorations(commit); 169 show_commit_decorations(commit);
99 html("</td><td>"); 170 html("</td><td>");
100 html_txt(info->author); 171 html_txt(info->author);
172
173 if (revs->graph) {
174 html("</td><td>");
175 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
176 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
177 html_link_open(tmp, NULL, NULL);
178 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
179 html_link_close();
180 }
181
101 if (ctx.repo->enable_log_filecount) { 182 if (ctx.repo->enable_log_filecount) {
102 files = 0; 183 files = 0;
103 add_lines = 0; 184 add_lines = 0;
104 rem_lines = 0; 185 rem_lines = 0;
105 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath); 186 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
106 html("</td><td>"); 187 html("</td><td>");
107 htmlf("%d", files); 188 htmlf("%d", files);
108 if (ctx.repo->enable_log_linecount) { 189 if (ctx.repo->enable_log_linecount) {
109 html("</td><td>"); 190 html("</td><td>");
110 htmlf("-%d/+%d", rem_lines, add_lines); 191 htmlf("-%d/+%d", rem_lines, add_lines);
111 } 192 }
112 } 193 }
113 html("</td></tr>\n"); 194 html("</td></tr>\n");
195
196 if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */
197 html("<tr class='nohover'>");
198
114 if (ctx.qry.showmsg) { 199 if (ctx.qry.showmsg) {
115 struct strbuf notes = STRBUF_INIT; 200 /* Concatenate commit message + notes in msgbuf */
116 format_note(NULL, commit->object.sha1, &notes, PAGE_ENCODING, 0); 201 if (info->msg && *(info->msg)) {
202 strbuf_addstr(&msgbuf, info->msg);
203 strbuf_addch(&msgbuf, '\n');
204 }
205 format_note(NULL, commit->object.sha1, &msgbuf,
206 PAGE_ENCODING,
207 NOTES_SHOW_HEADER | NOTES_INDENT);
208 strbuf_addch(&msgbuf, '\n');
209 strbuf_ltrim(&msgbuf);
210 }
117 211
118 if (ctx.repo->enable_log_filecount) { 212 if (revs->graph) {
119 cols++; 213 int lines = 0;
120 if (ctx.repo->enable_log_linecount) 214
121 cols++; 215 /* Calculate graph padding */
216 if (ctx.qry.showmsg) {
217 /* Count #lines in commit message + notes */
218 const char *p = msgbuf.buf;
219 lines = 1;
220 while ((p = strchr(p, '\n'))) {
221 p++;
222 lines++;
122 } 223 }
123 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
124 cols);
125 html_txt(info->msg);
126 html("</td></tr>\n");
127 if (notes.len != 0) {
128 html("<tr class='nohover'>");
129 html("<td class='lognotes-label'>Notes:</td>");
130 htmlf("<td colspan='%d' class='lognotes'>",
131 cols);
132 html_txt(notes.buf);
133 html("</td></tr>\n");
134 } 224 }
135 strbuf_release(&notes); 225
226 /* Print graph padding */
227 html("<td class='commitgraph'>");
228 while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
229 if (graphbuf.len)
230 html("\n");
231 strbuf_setlen(&graphbuf, 0);
232 graph_next_line(revs->graph, &graphbuf);
233 html(graphbuf.buf);
234 lines--;
235 }
236 html("</td>\n");
237 }
238 else
239 html("<td/>"); /* Empty 'Age' column */
240
241 /* Print msgbuf into remainder of table row */
242 htmlf("<td colspan='%d'%s>\n", cols,
243 ctx.qry.showmsg ? " class='logmsg'" : "");
244 html_txt(msgbuf.buf);
245 html("</td></tr>\n");
136 } 246 }
247
248 strbuf_release(&msgbuf);
249 strbuf_release(&graphbuf);
137 cgit_free_commitinfo(info); 250 cgit_free_commitinfo(info);
138} 251}
139 252
140static const char *disambiguate_ref(const char *ref) 253static const char *disambiguate_ref(const char *ref)
141{ 254{
142 unsigned char sha1[20]; 255 unsigned char sha1[20];
143 const char *longref; 256 const char *longref;
144 257
145 longref = fmt("refs/heads/%s", ref); 258 longref = fmt("refs/heads/%s", ref);
146 if (get_sha1(longref, sha1) == 0) 259 if (get_sha1(longref, sha1) == 0)
147 return longref; 260 return longref;
148 261
@@ -163,25 +276,25 @@ static char *next_token(char **src)
163 while (**src) { 276 while (**src) {
164 if (isspace(**src)) { 277 if (isspace(**src)) {
165 **src = '\0'; 278 **src = '\0';
166 (*src)++; 279 (*src)++;
167 break; 280 break;
168 } 281 }
169 (*src)++; 282 (*src)++;
170 } 283 }
171 return result; 284 return result;
172} 285}
173 286
174void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 287void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
175 char *path, int pager) 288 char *path, int pager, int commit_graph)
176{ 289{
177 struct rev_info rev; 290 struct rev_info rev;
178 struct commit *commit; 291 struct commit *commit;
179 struct vector vec = VECTOR_INIT(char *); 292 struct vector vec = VECTOR_INIT(char *);
180 int i, columns = 3; 293 int i, columns = 3;
181 char *arg; 294 char *arg;
182 295
183 /* First argv is NULL */ 296 /* First argv is NULL */
184 vector_push(&vec, NULL, 0); 297 vector_push(&vec, NULL, 0);
185 298
186 if (!tip) 299 if (!tip)
187 tip = ctx.qry.head; 300 tip = ctx.qry.head;
@@ -203,24 +316,32 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
203 */ 316 */
204 vec.count--; 317 vec.count--;
205 while ((arg = next_token(&pattern))) { 318 while ((arg = next_token(&pattern))) {
206 if (*arg == '-') { 319 if (*arg == '-') {
207 fprintf(stderr, "Bad range expr: %s\n", 320 fprintf(stderr, "Bad range expr: %s\n",
208 arg); 321 arg);
209 break; 322 break;
210 } 323 }
211 vector_push(&vec, &arg, 0); 324 vector_push(&vec, &arg, 0);
212 } 325 }
213 } 326 }
214 } 327 }
328 if (commit_graph) {
329 static const char *graph_arg = "--graph";
330 static const char *color_arg = "--color";
331 vector_push(&vec, &graph_arg, 0);
332 vector_push(&vec, &color_arg, 0);
333 graph_set_column_colors(column_colors_html,
334 COLUMN_COLORS_HTML_MAX);
335 }
215 336
216 if (path) { 337 if (path) {
217 arg = "--"; 338 arg = "--";
218 vector_push(&vec, &arg, 0); 339 vector_push(&vec, &arg, 0);
219 vector_push(&vec, &path, 0); 340 vector_push(&vec, &path, 0);
220 } 341 }
221 342
222 /* Make sure the vector is NULL-terminated */ 343 /* Make sure the vector is NULL-terminated */
223 vector_push(&vec, NULL, 0); 344 vector_push(&vec, NULL, 0);
224 vec.count--; 345 vec.count--;
225 346
226 init_revisions(&rev, NULL); 347 init_revisions(&rev, NULL);
@@ -229,57 +350,63 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
229 rev.verbose_header = 1; 350 rev.verbose_header = 1;
230 rev.show_root_diff = 0; 351 rev.show_root_diff = 0;
231 setup_revisions(vec.count, vec.data, &rev, NULL); 352 setup_revisions(vec.count, vec.data, &rev, NULL);
232 load_ref_decorations(DECORATE_FULL_REFS); 353 load_ref_decorations(DECORATE_FULL_REFS);
233 rev.show_decorations = 1; 354 rev.show_decorations = 1;
234 rev.grep_filter.regflags |= REG_ICASE; 355 rev.grep_filter.regflags |= REG_ICASE;
235 compile_grep_patterns(&rev.grep_filter); 356 compile_grep_patterns(&rev.grep_filter);
236 prepare_revision_walk(&rev); 357 prepare_revision_walk(&rev);
237 358
238 if (pager) 359 if (pager)
239 html("<table class='list nowrap'>"); 360 html("<table class='list nowrap'>");
240 361
241 html("<tr class='nohover'><th class='left'>Age</th>" 362 html("<tr class='nohover'>");
242 "<th class='left'>Commit message"); 363 if (commit_graph)
364 html("<th></th>");
365 else
366 html("<th class='left'>Age</th>");
367 html("<th class='left'>Commit message");
243 if (pager) { 368 if (pager) {
244 html(" ("); 369 html(" (");
245 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 370 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
246 NULL, ctx.qry.head, ctx.qry.sha1, 371 NULL, ctx.qry.head, ctx.qry.sha1,
247 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, 372 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
248 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 373 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
249 html(")"); 374 html(")");
250 } 375 }
251 html("</th><th class='left'>Author</th>"); 376 html("</th><th class='left'>Author</th>");
377 if (commit_graph)
378 html("<th class='left'>Age</th>");
252 if (ctx.repo->enable_log_filecount) { 379 if (ctx.repo->enable_log_filecount) {
253 html("<th class='left'>Files</th>"); 380 html("<th class='left'>Files</th>");
254 columns++; 381 columns++;
255 if (ctx.repo->enable_log_linecount) { 382 if (ctx.repo->enable_log_linecount) {
256 html("<th class='left'>Lines</th>"); 383 html("<th class='left'>Lines</th>");
257 columns++; 384 columns++;
258 } 385 }
259 } 386 }
260 html("</tr>\n"); 387 html("</tr>\n");
261 388
262 if (ofs<0) 389 if (ofs<0)
263 ofs = 0; 390 ofs = 0;
264 391
265 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 392 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
266 free(commit->buffer); 393 free(commit->buffer);
267 commit->buffer = NULL; 394 commit->buffer = NULL;
268 free_commit_list(commit->parents); 395 free_commit_list(commit->parents);
269 commit->parents = NULL; 396 commit->parents = NULL;
270 } 397 }
271 398
272 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 399 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
273 print_commit(commit); 400 print_commit(commit, &rev);
274 free(commit->buffer); 401 free(commit->buffer);
275 commit->buffer = NULL; 402 commit->buffer = NULL;
276 free_commit_list(commit->parents); 403 free_commit_list(commit->parents);
277 commit->parents = NULL; 404 commit->parents = NULL;
278 } 405 }
279 if (pager) { 406 if (pager) {
280 html("</table><div class='pager'>"); 407 html("</table><div class='pager'>");
281 if (ofs > 0) { 408 if (ofs > 0) {
282 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 409 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
283 ctx.qry.sha1, ctx.qry.vpath, 410 ctx.qry.sha1, ctx.qry.vpath,
284 ofs - cnt, ctx.qry.grep, 411 ofs - cnt, ctx.qry.grep,
285 ctx.qry.search, ctx.qry.showmsg); 412 ctx.qry.search, ctx.qry.showmsg);
diff --git a/ui-log.h b/ui-log.h
index 6034055..d0cb779 100644
--- a/ui-log.h
+++ b/ui-log.h
@@ -1,8 +1,9 @@
1#ifndef UI_LOG_H 1#ifndef UI_LOG_H
2#define UI_LOG_H 2#define UI_LOG_H
3 3
4extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, 4extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep,
5 char *pattern, char *path, int pager); 5 char *pattern, char *path, int pager,
6 int commit_graph);
6extern void show_commit_decorations(struct commit *commit); 7extern void show_commit_decorations(struct commit *commit);
7 8
8#endif /* UI_LOG_H */ 9#endif /* UI_LOG_H */
diff --git a/ui-summary.c b/ui-summary.c
index b203bcc..5be2545 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -50,25 +50,25 @@ static void print_urls(char *txt, char *suffix)
50 } 50 }
51} 51}
52 52
53void cgit_print_summary() 53void cgit_print_summary()
54{ 54{
55 html("<table summary='repository info' class='list nowrap'>"); 55 html("<table summary='repository info' class='list nowrap'>");
56 cgit_print_branches(ctx.cfg.summary_branches); 56 cgit_print_branches(ctx.cfg.summary_branches);
57 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 57 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
58 cgit_print_tags(ctx.cfg.summary_tags); 58 cgit_print_tags(ctx.cfg.summary_tags);
59 if (ctx.cfg.summary_log > 0) { 59 if (ctx.cfg.summary_log > 0) {
60 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 60 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
61 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, 61 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
62 NULL, NULL, 0); 62 NULL, NULL, 0, 0);
63 } 63 }
64 if (ctx.repo->clone_url) 64 if (ctx.repo->clone_url)
65 print_urls(ctx.repo->clone_url, NULL); 65 print_urls(ctx.repo->clone_url, NULL);
66 else if (ctx.cfg.clone_prefix) 66 else if (ctx.cfg.clone_prefix)
67 print_urls(ctx.cfg.clone_prefix, ctx.repo->url); 67 print_urls(ctx.cfg.clone_prefix, ctx.repo->url);
68 html("</table>"); 68 html("</table>");
69} 69}
70 70
71void cgit_print_repo_readme(char *path) 71void cgit_print_repo_readme(char *path)
72{ 72{
73 char *slash, *tmp, *colon, *ref; 73 char *slash, *tmp, *colon, *ref;
74 74