aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile7
-rw-r--r--README9
-rw-r--r--cgit.c28
-rw-r--r--cgit.css42
-rw-r--r--cgit.h6
-rw-r--r--cgit.pngbin1840 -> 1488 bytes
-rw-r--r--cgitrc.5.txt40
-rw-r--r--cmd.c3
m---------git0
-rw-r--r--html.c6
-rw-r--r--parsing.c24
-rw-r--r--scan-tree.c21
-rw-r--r--shared.c1
-rw-r--r--ui-diff.c19
-rw-r--r--ui-diff.h6
-rw-r--r--ui-log.c287
-rw-r--r--ui-log.h3
-rw-r--r--ui-shared.c20
-rw-r--r--ui-ssdiff.c34
-rw-r--r--ui-summary.c2
-rw-r--r--ui-tree.c2
-rw-r--r--vector.c38
-rw-r--r--vector.h17
23 files changed, 485 insertions, 130 deletions
diff --git a/Makefile b/Makefile
index fe4b10e..af2879e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
1CGIT_VERSION = v0.8.3.4 1CGIT_VERSION = v0.9
2CGIT_SCRIPT_NAME = cgit.cgi 2CGIT_SCRIPT_NAME = cgit.cgi
3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit 3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) 4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
@@ -12,7 +12,7 @@ htmldir = $(docdir)
12pdfdir = $(docdir) 12pdfdir = $(docdir)
13mandir = $(prefix)/share/man 13mandir = $(prefix)/share/man
14SHA1_HEADER = <openssl/sha.h> 14SHA1_HEADER = <openssl/sha.h>
15GIT_VER = 1.7.3 15GIT_VER = 1.7.4
16GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 16GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
17INSTALL = install 17INSTALL = install
18MAN5_TXT = $(wildcard *.5.txt) 18MAN5_TXT = $(wildcard *.5.txt)
@@ -115,6 +115,7 @@ OBJECTS += ui-stats.o
115OBJECTS += ui-summary.o 115OBJECTS += ui-summary.o
116OBJECTS += ui-tag.o 116OBJECTS += ui-tag.o
117OBJECTS += ui-tree.o 117OBJECTS += ui-tree.o
118OBJECTS += vector.o
118 119
119ifdef NEEDS_LIBICONV 120ifdef NEEDS_LIBICONV
120 EXTLIBS += -liconv 121 EXTLIBS += -liconv
@@ -240,4 +241,4 @@ clean-doc:
240 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo 241 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
241 242
242get-git: 243get-git:
243 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 244 curl $(GIT_URL) | tar -xjf - && rm -rf git && mv git-$(GIT_VER) git
diff --git a/README b/README
index 050e21e..1692ad6 100644
--- a/README
+++ b/README
@@ -36,7 +36,7 @@ file (see the Makefile for details).
36 36
37 37
38Dependencies: 38Dependencies:
39 -git 1.5.3 39 -git 1.7.4
40 -zip lib 40 -zip lib
41 -crypto lib 41 -crypto lib
42 -openssl lib 42 -openssl lib
@@ -86,13 +86,6 @@ The missing features
86 repository. This should probably be extended to a generic map between 86 repository. This should probably be extended to a generic map between
87 submodule path and url. 87 submodule path and url.
88 88
89* Branch- and tag-lists in the summary page can get very long, they should
90 probably only show something like the ten "latest modified" branches and
91 a similar number of "most recent" tags.
92
93* There should be a new page for browsing refs/heads and refs/tags, with links
94 from the summary page whenever the branch/tag lists overflow.
95
96* The log-page should have more/better search options (author, committer, 89* The log-page should have more/better search options (author, committer,
97 pickaxe, paths) and possibly support arbitrary revision specifiers. 90 pickaxe, paths) and possibly support arbitrary revision specifiers.
98 91
diff --git a/cgit.c b/cgit.c
index e0c2d9f..349d6e0 100644
--- a/cgit.c
+++ b/cgit.c
@@ -29,15 +29,17 @@ void add_mimetype(const char *name, const char *value)
29struct cgit_filter *new_filter(const char *cmd, int extra_args) 29struct cgit_filter *new_filter(const char *cmd, int extra_args)
30{ 30{
31 struct cgit_filter *f; 31 struct cgit_filter *f;
32 int args_size = 0;
32 33
33 if (!cmd || !cmd[0]) 34 if (!cmd || !cmd[0])
34 return NULL; 35 return NULL;
35 36
36 f = xmalloc(sizeof(struct cgit_filter)); 37 f = xmalloc(sizeof(struct cgit_filter));
37 f->cmd = xstrdup(cmd); 38 f->cmd = xstrdup(cmd);
38 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 39 args_size = (2 + extra_args) * sizeof(char *);
40 f->argv = xmalloc(args_size);
41 memset(f->argv, 0, args_size);
39 f->argv[0] = f->cmd; 42 f->argv[0] = f->cmd;
40 f->argv[1] = NULL;
41 return f; 43 return f;
42} 44}
43 45
@@ -57,6 +59,8 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
57 repo->defbranch = xstrdup(value); 59 repo->defbranch = xstrdup(value);
58 else if (!strcmp(name, "snapshots")) 60 else if (!strcmp(name, "snapshots"))
59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 61 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
62 else if (!strcmp(name, "enable-commit-graph"))
63 repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value);
60 else if (!strcmp(name, "enable-log-filecount")) 64 else if (!strcmp(name, "enable-log-filecount"))
61 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 65 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
62 else if (!strcmp(name, "enable-log-linecount")) 66 else if (!strcmp(name, "enable-log-linecount"))
@@ -71,9 +75,13 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
71 repo->module_link= xstrdup(value); 75 repo->module_link= xstrdup(value);
72 else if (!strcmp(name, "section")) 76 else if (!strcmp(name, "section"))
73 repo->section = xstrdup(value); 77 repo->section = xstrdup(value);
74 else if (!strcmp(name, "readme") && value != NULL) { 78 else if (!strcmp(name, "readme") && value != NULL)
75 repo->readme = xstrdup(value); 79 repo->readme = xstrdup(value);
76 } else if (ctx.cfg.enable_filter_overrides) { 80 else if (!strcmp(name, "logo") && value != NULL)
81 repo->logo = xstrdup(value);
82 else if (!strcmp(name, "logo-link") && value != NULL)
83 repo->logo_link = xstrdup(value);
84 else if (ctx.cfg.enable_filter_overrides) {
77 if (!strcmp(name, "about-filter")) 85 if (!strcmp(name, "about-filter"))
78 repo->about_filter = new_filter(value, 0); 86 repo->about_filter = new_filter(value, 0);
79 else if (!strcmp(name, "commit-filter")) 87 else if (!strcmp(name, "commit-filter"))
@@ -143,6 +151,8 @@ void config_cb(const char *name, const char *value)
143 ctx.cfg.enable_http_clone = atoi(value); 151 ctx.cfg.enable_http_clone = atoi(value);
144 else if (!strcmp(name, "enable-index-links")) 152 else if (!strcmp(name, "enable-index-links"))
145 ctx.cfg.enable_index_links = atoi(value); 153 ctx.cfg.enable_index_links = atoi(value);
154 else if (!strcmp(name, "enable-commit-graph"))
155 ctx.cfg.enable_commit_graph = atoi(value);
146 else if (!strcmp(name, "enable-log-filecount")) 156 else if (!strcmp(name, "enable-log-filecount"))
147 ctx.cfg.enable_log_filecount = atoi(value); 157 ctx.cfg.enable_log_filecount = atoi(value);
148 else if (!strcmp(name, "enable-log-linecount")) 158 else if (!strcmp(name, "enable-log-linecount"))
@@ -197,6 +207,8 @@ void config_cb(const char *name, const char *value)
197 ctx.cfg.project_list, repo_config); 207 ctx.cfg.project_list, repo_config);
198 else 208 else
199 scan_tree(expand_macros(value), repo_config); 209 scan_tree(expand_macros(value), repo_config);
210 else if (!strcmp(name, "scan-hidden-path"))
211 ctx.cfg.scan_hidden_path = atoi(value);
200 else if (!strcmp(name, "section-from-path")) 212 else if (!strcmp(name, "section-from-path"))
201 ctx.cfg.section_from_path = atoi(value); 213 ctx.cfg.section_from_path = atoi(value);
202 else if (!strcmp(name, "source-filter")) 214 else if (!strcmp(name, "source-filter"))
@@ -318,6 +330,7 @@ static void prepare_context(struct cgit_context *ctx)
318 ctx->cfg.robots = "index, nofollow"; 330 ctx->cfg.robots = "index, nofollow";
319 ctx->cfg.root_title = "Git repository browser"; 331 ctx->cfg.root_title = "Git repository browser";
320 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 332 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
333 ctx->cfg.scan_hidden_path = 0;
321 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 334 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
322 ctx->cfg.section = ""; 335 ctx->cfg.section = "";
323 ctx->cfg.summary_branches = 10; 336 ctx->cfg.summary_branches = 10;
@@ -550,6 +563,8 @@ void print_repo(FILE *f, struct cgit_repo *repo)
550 fprintf(f, "repo.section=%s\n", repo->section); 563 fprintf(f, "repo.section=%s\n", repo->section);
551 if (repo->clone_url) 564 if (repo->clone_url)
552 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 565 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
566 fprintf(f, "repo.enable-commit-graph=%d\n",
567 repo->enable_commit_graph);
553 fprintf(f, "repo.enable-log-filecount=%d\n", 568 fprintf(f, "repo.enable-log-filecount=%d\n",
554 repo->enable_log_filecount); 569 repo->enable_log_filecount);
555 fprintf(f, "repo.enable-log-linecount=%d\n", 570 fprintf(f, "repo.enable-log-linecount=%d\n",
@@ -749,10 +764,11 @@ int main(int argc, const char **argv)
749 http_parse_querystring(ctx.qry.raw, querystring_cb); 764 http_parse_querystring(ctx.qry.raw, querystring_cb);
750 765
751 /* If virtual-root isn't specified in cgitrc, lets pretend 766 /* If virtual-root isn't specified in cgitrc, lets pretend
752 * that virtual-root equals SCRIPT_NAME. 767 * that virtual-root equals SCRIPT_NAME, minus any possibly
768 * trailing slashes.
753 */ 769 */
754 if (!ctx.cfg.virtual_root) 770 if (!ctx.cfg.virtual_root)
755 ctx.cfg.virtual_root = ctx.cfg.script_name; 771 ctx.cfg.virtual_root = trim_end(ctx.cfg.script_name, '/');
756 772
757 /* If no url parameter is specified on the querystring, lets 773 /* If no url parameter is specified on the querystring, lets
758 * use PATH_INFO as url. This allows cgit to work with virtual 774 * use PATH_INFO as url. This allows cgit to work with virtual
diff --git a/cgit.css b/cgit.css
index a2a685b..1d90057 100644
--- a/cgit.css
+++ b/cgit.css
@@ -153,26 +153,44 @@ table.list td {
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; 163}
164 padding: 1em 0.5em 2em 0.5em; 164
165table.list td.commitgraph .column2 {
166 color: #0a0;
167}
168
169table.list td.commitgraph .column3 {
170 color: #aa0;
165} 171}
166 172
167table.list td.lognotes-label { 173table.list td.commitgraph .column4 {
168 text-align:right; 174 color: #00a;
169 vertical-align:top;
170} 175}
171 176
172table.list td.lognotes { 177table.list td.commitgraph .column5 {
178 color: #a0a;
179}
180
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 {
@@ -275,7 +293,7 @@ table.blob pre {
275 padding: 0; margin: 0; 293 padding: 0; margin: 0;
276} 294}
277 295
278table.blob a.no { 296table.blob a.no, table.ssdiff a.no {
279 color: gray; 297 color: gray;
280 text-align: right; 298 text-align: right;
281 text-decoration: none; 299 text-decoration: none;
diff --git a/cgit.h b/cgit.h
index c119712..ecae453 100644
--- a/cgit.h
+++ b/cgit.h
@@ -20,6 +20,7 @@
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/*
@@ -70,7 +71,10 @@ struct cgit_repo {
70 char *readme; 71 char *readme;
71 char *section; 72 char *section;
72 char *clone_url; 73 char *clone_url;
74 char *logo;
75 char *logo_link;
73 int snapshots; 76 int snapshots;
77 int enable_commit_graph;
74 int enable_log_filecount; 78 int enable_log_filecount;
75 int enable_log_linecount; 79 int enable_log_linecount;
76 int enable_remote_branches; 80 int enable_remote_branches;
@@ -189,6 +193,7 @@ struct cgit_config {
189 int enable_gitweb_owner; 193 int enable_gitweb_owner;
190 int enable_http_clone; 194 int enable_http_clone;
191 int enable_index_links; 195 int enable_index_links;
196 int enable_commit_graph;
192 int enable_log_filecount; 197 int enable_log_filecount;
193 int enable_log_linecount; 198 int enable_log_linecount;
194 int enable_remote_branches; 199 int enable_remote_branches;
@@ -208,6 +213,7 @@ struct cgit_config {
208 int noheader; 213 int noheader;
209 int renamelimit; 214 int renamelimit;
210 int remove_suffix; 215 int remove_suffix;
216 int scan_hidden_path;
211 int section_from_path; 217 int section_from_path;
212 int snapshots; 218 int snapshots;
213 int summary_branches; 219 int summary_branches;
diff --git a/cgit.png b/cgit.png
index d7f70bc..0bdf5a7 100644
--- a/cgit.png
+++ b/cgit.png
Binary files differ
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index b8c69b8..875d51f 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -90,7 +90,12 @@ embedded::
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.
@@ -274,13 +279,22 @@ root-title::
274 Text printed as heading on the repository index page. Default value: 279 Text printed as heading on the repository index page. Default value:
275 "Git Repository Browser". 280 "Git Repository Browser".
276 281
282scan-hidden-path::
283 If set to "1" and scan-path is enabled, scan-path will recurse into
284 directories whose name starts with a period ('.'). Otherwise,
285 scan-path will stay away from such directories (considered as
286 "hidden"). Note that this does not apply to the ".git" directory in
287 non-bare repos. This must be defined prior to scan-path.
288 Default value: 0. See also: scan-path.
289
277scan-path:: 290scan-path::
278 A path which will be scanned for repositories. If caching is enabled, 291 A path which will be scanned for repositories. If caching is enabled,
279 the result will be cached as a cgitrc include-file in the cache 292 the result will be cached as a cgitrc include-file in the cache
280 directory. If project-list has been defined prior to scan-path, 293 directory. If project-list has been defined prior to scan-path,
281 scan-path loads only the directories listed in the file pointed to by 294 scan-path loads only the directories listed in the file pointed to by
282 project-list. Default value: none. See also: cache-scanrc-ttl, 295 project-list. Be advised that only the global settings taken
283 project-list. 296 before the scan-path directive will be applied to each repository.
297 Default value: none. See also: cache-scanrc-ttl, project-list.
284 298
285section:: 299section::
286 The name of the current repository section - all repositories defined 300 The name of the current repository section - all repositories defined
@@ -300,7 +314,8 @@ side-by-side-diffs::
300snapshots:: 314snapshots::
301 Text which specifies the default set of snapshot formats generated by 315 Text which specifies the default set of snapshot formats generated by
302 cgit. The value is a space-separated list of zero or more of the 316 cgit. The value is a space-separated list of zero or more of the
303 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 317 values "tar", "tar.gz", "tar.bz2", "tar.xz" and "zip". Default value:
318 none.
304 319
305source-filter:: 320source-filter::
306 Specifies a command which will be invoked to format plaintext blobs 321 Specifies a command which will be invoked to format plaintext blobs
@@ -359,6 +374,10 @@ repo.defbranch::
359repo.desc:: 374repo.desc::
360 The value to show as repository description. Default value: none. 375 The value to show as repository description. Default value: none.
361 376
377repo.enable-commit-graph::
378 A flag which can be used to disable the global setting
379 `enable-commit-graph'. Default value: none.
380
362repo.enable-log-filecount:: 381repo.enable-log-filecount::
363 A flag which can be used to disable the global setting 382 A flag which can be used to disable the global setting
364 `enable-log-filecount'. Default value: none. 383 `enable-log-filecount'. Default value: none.
@@ -375,6 +394,15 @@ repo.enable-subject-links::
375 A flag which can be used to override the global setting 394 A flag which can be used to override the global setting
376 `enable-subject-links'. Default value: none. 395 `enable-subject-links'. Default value: none.
377 396
397repo.logo::
398 Url which specifies the source of an image which will be used as a logo
399 on this repo's pages. Default value: global logo.
400
401repo.logo-link::
402 Url loaded when clicking on the cgit logo image. If unspecified the
403 calculated url of the repository index page will be used. Default
404 value: global logo-link.
405
378repo.max-stats:: 406repo.max-stats::
379 Override the default maximum statistics period. Valid values are equal 407 Override the default maximum statistics period. Valid values are equal
380 to the values specified for the global "max-stats" setting. Default 408 to the values specified for the global "max-stats" setting. Default
@@ -446,6 +474,10 @@ css=/css/cgit.css
446enable-index-links=1 474enable-index-links=1
447 475
448 476
477# Enable ASCII art commit history graph on the log pages
478enable-commit-graph=1
479
480
449# Show number of affected files per commit on the log pages 481# Show number of affected files per commit on the log pages
450enable-log-filecount=1 482enable-log-filecount=1
451 483
diff --git a/cmd.c b/cmd.c
index 0224ee9..d114eb3 100644
--- a/cmd.c
+++ b/cmd.c
@@ -67,7 +67,8 @@ static void info_fn(struct cgit_context *ctx)
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)
diff --git a/git b/git
Subproject 87b50542a08ac6caa083ddc376e674424e37940 Subproject 7ed863a85a6ce2c4ac4476848310b8f917ab41f
diff --git a/html.c b/html.c
index 1305910..a60bc13 100644
--- a/html.c
+++ b/html.c
@@ -18,7 +18,7 @@ static const char* url_escape_table[256] = {
18 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", 18 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09",
19 "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13", 19 "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13",
20 "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d", 20 "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d",
21 "%1e", "%1f", "%20", 0, "%22", "%23", 0, "%25", "%26", "%27", 0, 0, 0, 21 "%1e", "%1f", "+", 0, "%22", "%23", 0, "%25", "%26", "%27", 0, 0, 0,
22 "%2b", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%3c", "%3d", 22 "%2b", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%3c", "%3d",
23 "%3e", "%3f", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 23 "%3e", "%3f", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24 0, 0, 0, 0, 0, 0, 0, 0, 0, "%5c", 0, "%5e", 0, "%60", 0, 0, 0, 0, 0, 24 0, 0, 0, 0, 0, 0, 0, 0, 0, "%5c", 0, "%5e", 0, "%60", 0, 0, 0, 0, 0,
@@ -181,7 +181,7 @@ void html_url_arg(const char *txt)
181 const char *e = url_escape_table[c]; 181 const char *e = url_escape_table[c];
182 if (e) { 182 if (e) {
183 html_raw(txt, t - txt); 183 html_raw(txt, t - txt);
184 html_raw(e, 3); 184 html_raw(e, strlen(e));
185 txt = t+1; 185 txt = t+1;
186 } 186 }
187 t++; 187 t++;
@@ -277,7 +277,7 @@ char *convert_query_hexchar(char *txt)
277 d1 = hextoint(*(txt+1)); 277 d1 = hextoint(*(txt+1));
278 d2 = hextoint(*(txt+2)); 278 d2 = hextoint(*(txt+2));
279 if (d1<0 || d2<0) { 279 if (d1<0 || d2<0) {
280 memmove(txt, txt+3, n-3); 280 memmove(txt, txt+3, n-2);
281 return txt-1; 281 return txt-1;
282 } else { 282 } else {
283 *txt = d1 * 16 + d2; 283 *txt = d1 * 16 + d2;
diff --git a/parsing.c b/parsing.c
index f37c49d..c9e4350 100644
--- a/parsing.c
+++ b/parsing.c
@@ -106,7 +106,11 @@ const char *reencode(char **txt, const char *src_enc, const char *dst_enc)
106 if (!txt || !*txt || !src_enc || !dst_enc) 106 if (!txt || !*txt || !src_enc || !dst_enc)
107 return *txt; 107 return *txt;
108 108
109 tmp = reencode_string(*txt, src_enc, dst_enc); 109 /* no encoding needed if src_enc equals dst_enc */
110 if(!strcasecmp(src_enc, dst_enc))
111 return *txt;
112
113 tmp = reencode_string(*txt, dst_enc, src_enc);
110 if (tmp) { 114 if (tmp) {
111 free(*txt); 115 free(*txt);
112 *txt = tmp; 116 *txt = tmp;
@@ -160,6 +164,10 @@ struct commitinfo *cgit_parse_commit(struct commit *commit)
160 } 164 }
161 } 165 }
162 166
167 /* if no special encoding is found, assume UTF-8 */
168 if(!ret->msg_encoding)
169 ret->msg_encoding = xstrdup("UTF-8");
170
163 // skip unknown header fields 171 // skip unknown header fields
164 while (p && *p && (*p != '\n')) { 172 while (p && *p && (*p != '\n')) {
165 p = strchr(p, '\n'); 173 p = strchr(p, '\n');
@@ -189,14 +197,12 @@ struct commitinfo *cgit_parse_commit(struct commit *commit)
189 } else 197 } else
190 ret->subject = xstrdup(p); 198 ret->subject = xstrdup(p);
191 199
192 if (ret->msg_encoding) { 200 reencode(&ret->author, ret->msg_encoding, PAGE_ENCODING);
193 reencode(&ret->author, PAGE_ENCODING, ret->msg_encoding); 201 reencode(&ret->author_email, ret->msg_encoding, PAGE_ENCODING);
194 reencode(&ret->author_email, PAGE_ENCODING, ret->msg_encoding); 202 reencode(&ret->committer, ret->msg_encoding, PAGE_ENCODING);
195 reencode(&ret->committer, PAGE_ENCODING, ret->msg_encoding); 203 reencode(&ret->committer_email, ret->msg_encoding, PAGE_ENCODING);
196 reencode(&ret->committer_email, PAGE_ENCODING, ret->msg_encoding); 204 reencode(&ret->subject, ret->msg_encoding, PAGE_ENCODING);
197 reencode(&ret->subject, PAGE_ENCODING, ret->msg_encoding); 205 reencode(&ret->msg, ret->msg_encoding, PAGE_ENCODING);
198 reencode(&ret->msg, PAGE_ENCODING, ret->msg_encoding);
199 }
200 206
201 return ret; 207 return ret;
202} 208}
diff --git a/scan-tree.c b/scan-tree.c
index a0e09ce..e5a4baf 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -159,24 +159,23 @@ static void add_repo(const char *base, const char *path, repo_config_fn fn)
159 159
160static void scan_path(const char *base, const char *path, repo_config_fn fn) 160static void scan_path(const char *base, const char *path, repo_config_fn fn)
161{ 161{
162 DIR *dir; 162 DIR *dir = opendir(path);
163 struct dirent *ent; 163 struct dirent *ent;
164 char *buf; 164 char *buf;
165 struct stat st; 165 struct stat st;
166 166
167 if (!dir) {
168 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
169 path, strerror(errno), errno);
170 return;
171 }
167 if (is_git_dir(path)) { 172 if (is_git_dir(path)) {
168 add_repo(base, path, fn); 173 add_repo(base, path, fn);
169 return; 174 goto end;
170 } 175 }
171 if (is_git_dir(fmt("%s/.git", path))) { 176 if (is_git_dir(fmt("%s/.git", path))) {
172 add_repo(base, fmt("%s/.git", path), fn); 177 add_repo(base, fmt("%s/.git", path), fn);
173 return; 178 goto end;
174 }
175 dir = opendir(path);
176 if (!dir) {
177 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
178 path, strerror(errno), errno);
179 return;
180 } 179 }
181 while((ent = readdir(dir)) != NULL) { 180 while((ent = readdir(dir)) != NULL) {
182 if (ent->d_name[0] == '.') { 181 if (ent->d_name[0] == '.') {
@@ -184,6 +183,8 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
184 continue; 183 continue;
185 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') 184 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
186 continue; 185 continue;
186 if (!ctx.cfg.scan_hidden_path)
187 continue;
187 } 188 }
188 buf = malloc(strlen(path) + strlen(ent->d_name) + 2); 189 buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
189 if (!buf) { 190 if (!buf) {
@@ -202,6 +203,7 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
202 scan_path(base, buf, fn); 203 scan_path(base, buf, fn);
203 free(buf); 204 free(buf);
204 } 205 }
206end:
205 closedir(dir); 207 closedir(dir);
206} 208}
207 209
@@ -217,6 +219,7 @@ void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn
217 if (!projects) { 219 if (!projects) {
218 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n", 220 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
219 projectsfile, strerror(errno), errno); 221 projectsfile, strerror(errno), errno);
222 return;
220 } 223 }
221 while (fgets(line, sizeof(line), projects) != NULL) { 224 while (fgets(line, sizeof(line), projects) != NULL) {
222 for (z = &lastc(line); 225 for (z = &lastc(line);
diff --git a/shared.c b/shared.c
index 765cd27..7ec2e19 100644
--- a/shared.c
+++ b/shared.c
@@ -56,6 +56,7 @@ struct cgit_repo *cgit_add_repo(const char *url)
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;
diff --git a/ui-diff.c b/ui-diff.c
index 7ff7e46..a7bc667 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -34,6 +34,17 @@ static struct fileinfo {
34} *items; 34} *items;
35 35
36static int use_ssdiff = 0; 36static int use_ssdiff = 0;
37static struct diff_filepair *current_filepair;
38
39struct diff_filespec *cgit_get_current_old_file(void)
40{
41 return current_filepair->one;
42}
43
44struct diff_filespec *cgit_get_current_new_file(void)
45{
46 return current_filepair->two;
47}
37 48
38static void print_fileinfo(struct fileinfo *info) 49static void print_fileinfo(struct fileinfo *info)
39{ 50{
@@ -161,8 +172,11 @@ void cgit_print_diffstat(const unsigned char *old_sha1,
161 html("<div class='diffstat-header'>"); 172 html("<div class='diffstat-header'>");
162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 173 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
163 ctx.qry.sha2, NULL, 0); 174 ctx.qry.sha2, NULL, 0);
164 if (prefix) 175 if (prefix) {
165 htmlf(" (limited to '%s')", prefix); 176 html(" (limited to '");
177 html_txt(prefix);
178 html("')");
179 }
166 html(" ("); 180 html(" (");
167 ctx.qry.context = (save_context > 0 ? save_context : 3) << 1; 181 ctx.qry.context = (save_context > 0 ? save_context : 3) << 1;
168 cgit_self_link("more", NULL, NULL, &ctx); 182 cgit_self_link("more", NULL, NULL, &ctx);
@@ -284,6 +298,7 @@ static void filepair_cb(struct diff_filepair *pair)
284 int binary = 0; 298 int binary = 0;
285 linediff_fn print_line_fn = print_line; 299 linediff_fn print_line_fn = print_line;
286 300
301 current_filepair = pair;
287 if (use_ssdiff) { 302 if (use_ssdiff) {
288 cgit_ssdiff_header_begin(); 303 cgit_ssdiff_header_begin();
289 print_line_fn = cgit_ssdiff_line_cb; 304 print_line_fn = cgit_ssdiff_line_cb;
diff --git a/ui-diff.h b/ui-diff.h
index 70b2926..12d0c62 100644
--- a/ui-diff.h
+++ b/ui-diff.h
@@ -7,4 +7,10 @@ extern void cgit_print_diffstat(const unsigned char *old_sha1,
7extern void cgit_print_diff(const char *new_hex, const char *old_hex, 7extern void cgit_print_diff(const char *new_hex, const char *old_hex,
8 const char *prefix); 8 const char *prefix);
9 9
10extern struct diff_filespec *cgit_get_current_old_file(void);
11extern struct diff_filespec *cgit_get_current_new_file(void);
12
13extern unsigned char old_rev_sha1[20];
14extern unsigned char new_rev_sha1[20];
15
10#endif /* UI_DIFF_H */ 16#endif /* UI_DIFF_H */
diff --git a/ui-log.c b/ui-log.c
index b9771fa..2e6e9d6 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -9,9 +9,25 @@
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 13
13int files, add_lines, rem_lines; 14int files, add_lines, rem_lines;
14 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
15void count_lines(char *line, int size) 31void count_lines(char *line, int size)
16{ 32{
17 if (size <= 0) 33 if (size <= 0)
@@ -76,63 +92,160 @@ void show_commit_decorations(struct commit *commit)
76 } 92 }
77} 93}
78 94
79void print_commit(struct commit *commit) 95void print_commit(struct commit *commit, struct rev_info *revs)
80{ 96{
81 struct commitinfo *info; 97 struct commitinfo *info;
82 char *tmp; 98 char *tmp;
83 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 if (revs->graph) {
109 /* Advance graph until current commit */
110 while (!graph_next_line(revs->graph, &graphbuf)) {
111 /* Print graph segment in otherwise empty table row */
112 html("<tr class='nohover'><td class='commitgraph'>");
113 html(graphbuf.buf);
114 htmlf("</td><td colspan='%d' /></tr>\n", cols);
115 strbuf_setlen(&graphbuf, 0);
116 }
117 /* Current commit's graph segment is now ready in graphbuf */
118 }
84 119
85 info = cgit_parse_commit(commit); 120 info = cgit_parse_commit(commit);
86 htmlf("<tr%s><td>", 121 htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
87 ctx.qry.showmsg ? " class='logheader'" : ""); 122
88 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 123 if (revs->graph) {
89 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); 124 /* Print graph segment for current commit */
90 html_link_open(tmp, NULL, NULL); 125 html("<td class='commitgraph'>");
91 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 126 html(graphbuf.buf);
92 html_link_close(); 127 html("</td>");
93 htmlf("</td><td%s>", 128 strbuf_setlen(&graphbuf, 0);
94 ctx.qry.showmsg ? " class='logsubject'" : ""); 129 }
130 else {
131 html("<td>");
132 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
133 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
134 html_link_open(tmp, NULL, NULL);
135 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
136 html_link_close();
137 html("</td>");
138 }
139
140 htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
141 if (ctx.qry.showmsg) {
142 /* line-wrap long commit subjects instead of truncating them */
143 size_t subject_len = strlen(info->subject);
144
145 if (subject_len > ctx.cfg.max_msg_len &&
146 ctx.cfg.max_msg_len >= 15) {
147 /* symbol for signaling line-wrap (in PAGE_ENCODING) */
148 const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 };
149 int i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
150
151 /* Rewind i to preceding space character */
152 while (i > 0 && !isspace(info->subject[i]))
153 --i;
154 if (!i) /* Oops, zero spaces. Reset i */
155 i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
156
157 /* add remainder starting at i to msgbuf */
158 strbuf_add(&msgbuf, info->subject + i, subject_len - i);
159 strbuf_trim(&msgbuf);
160 strbuf_add(&msgbuf, "\n\n", 2);
161
162 /* Place wrap_symbol at position i in info->subject */
163 strcpy(info->subject + i, wrap_symbol);
164 }
165 }
95 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 166 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
96 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); 167 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
97 show_commit_decorations(commit); 168 show_commit_decorations(commit);
98 html("</td><td>"); 169 html("</td><td>");
99 html_txt(info->author); 170 html_txt(info->author);
100 if (ctx.repo->enable_log_filecount) { 171
172 if (revs->graph) {
173 html("</td><td>");
174 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
175 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
176 html_link_open(tmp, NULL, NULL);
177 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
178 html_link_close();
179 }
180
181 if (ctx.repo->enable_log_filecount || ctx.repo->enable_log_linecount) {
101 files = 0; 182 files = 0;
102 add_lines = 0; 183 add_lines = 0;
103 rem_lines = 0; 184 rem_lines = 0;
104 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath); 185 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
105 html("</td><td>");
106 htmlf("%d", files);
107 if (ctx.repo->enable_log_linecount) {
108 html("</td><td>");
109 htmlf("-%d/+%d", rem_lines, add_lines);
110 }
111 } 186 }
187
188 if (ctx.repo->enable_log_filecount)
189 htmlf("</td><td>%d", files);
190 if (ctx.repo->enable_log_linecount)
191 htmlf("</td><td>-%d/+%d", rem_lines, add_lines);
192
112 html("</td></tr>\n"); 193 html("</td></tr>\n");
113 if (ctx.qry.showmsg) {
114 struct strbuf notes = STRBUF_INIT;
115 format_note(NULL, commit->object.sha1, &notes, PAGE_ENCODING, 0);
116 194
117 if (ctx.repo->enable_log_filecount) { 195 if (revs->graph || ctx.qry.showmsg) { /* Print a second table row */
118 cols++; 196 html("<tr class='nohover'>");
119 if (ctx.repo->enable_log_linecount) 197
120 cols++; 198 if (ctx.qry.showmsg) {
199 /* Concatenate commit message + notes in msgbuf */
200 if (info->msg && *(info->msg)) {
201 strbuf_addstr(&msgbuf, info->msg);
202 strbuf_addch(&msgbuf, '\n');
203 }
204 format_note(NULL, commit->object.sha1, &msgbuf,
205 PAGE_ENCODING,
206 NOTES_SHOW_HEADER | NOTES_INDENT);
207 strbuf_addch(&msgbuf, '\n');
208 strbuf_ltrim(&msgbuf);
121 } 209 }
122 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 210
123 cols); 211 if (revs->graph) {
124 html_txt(info->msg); 212 int lines = 0;
125 html("</td></tr>\n"); 213
126 if (notes.len != 0) { 214 /* Calculate graph padding */
127 html("<tr class='nohover'>"); 215 if (ctx.qry.showmsg) {
128 html("<td class='lognotes-label'>Notes:</td>"); 216 /* Count #lines in commit message + notes */
129 htmlf("<td colspan='%d' class='lognotes'>", 217 const char *p = msgbuf.buf;
130 cols); 218 lines = 1;
131 html_txt(notes.buf); 219 while ((p = strchr(p, '\n'))) {
132 html("</td></tr>\n"); 220 p++;
221 lines++;
222 }
223 }
224
225 /* Print graph padding */
226 html("<td class='commitgraph'>");
227 while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
228 if (graphbuf.len)
229 html("\n");
230 strbuf_setlen(&graphbuf, 0);
231 graph_next_line(revs->graph, &graphbuf);
232 html(graphbuf.buf);
233 lines--;
234 }
235 html("</td>\n");
133 } 236 }
134 strbuf_release(&notes); 237 else
238 html("<td/>"); /* Empty 'Age' column */
239
240 /* Print msgbuf into remainder of table row */
241 htmlf("<td colspan='%d'%s>\n", cols,
242 ctx.qry.showmsg ? " class='logmsg'" : "");
243 html_txt(msgbuf.buf);
244 html("</td></tr>\n");
135 } 245 }
246
247 strbuf_release(&msgbuf);
248 strbuf_release(&graphbuf);
136 cgit_free_commitinfo(info); 249 cgit_free_commitinfo(info);
137} 250}
138 251
@@ -148,38 +261,94 @@ static const char *disambiguate_ref(const char *ref)
148 return ref; 261 return ref;
149} 262}
150 263
264static char *next_token(char **src)
265{
266 char *result;
267
268 if (!src || !*src)
269 return NULL;
270 while (isspace(**src))
271 (*src)++;
272 if (!**src)
273 return NULL;
274 result = *src;
275 while (**src) {
276 if (isspace(**src)) {
277 **src = '\0';
278 (*src)++;
279 break;
280 }
281 (*src)++;
282 }
283 return result;
284}
285
151void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 286void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
152 char *path, int pager) 287 char *path, int pager, int commit_graph)
153{ 288{
154 struct rev_info rev; 289 struct rev_info rev;
155 struct commit *commit; 290 struct commit *commit;
156 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 291 struct vector vec = VECTOR_INIT(char *);
157 int argc = 2;
158 int i, columns = 3; 292 int i, columns = 3;
293 char *arg;
294
295 /* First argv is NULL */
296 vector_push(&vec, NULL, 0);
159 297
160 if (!tip) 298 if (!tip)
161 tip = ctx.qry.head; 299 tip = ctx.qry.head;
162 300 tip = disambiguate_ref(tip);
163 argv[1] = disambiguate_ref(tip); 301 vector_push(&vec, &tip, 0);
164 302
165 if (grep && pattern && *pattern) { 303 if (grep && pattern && *pattern) {
304 pattern = xstrdup(pattern);
166 if (!strcmp(grep, "grep") || !strcmp(grep, "author") || 305 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
167 !strcmp(grep, "committer")) 306 !strcmp(grep, "committer")) {
168 argv[argc++] = fmt("--%s=%s", grep, pattern); 307 arg = fmt("--%s=%s", grep, pattern);
169 if (!strcmp(grep, "range")) 308 vector_push(&vec, &arg, 0);
170 argv[1] = pattern; 309 }
310 if (!strcmp(grep, "range")) {
311 /* Split the pattern at whitespace and add each token
312 * as a revision expression. Do not accept other
313 * rev-list options. Also, replace the previously
314 * pushed tip (it's no longer relevant).
315 */
316 vec.count--;
317 while ((arg = next_token(&pattern))) {
318 if (*arg == '-') {
319 fprintf(stderr, "Bad range expr: %s\n",
320 arg);
321 break;
322 }
323 vector_push(&vec, &arg, 0);
324 }
325 }
326 }
327 if (commit_graph) {
328 static const char *graph_arg = "--graph";
329 static const char *color_arg = "--color";
330 vector_push(&vec, &graph_arg, 0);
331 vector_push(&vec, &color_arg, 0);
332 graph_set_column_colors(column_colors_html,
333 COLUMN_COLORS_HTML_MAX);
171 } 334 }
172 335
173 if (path) { 336 if (path) {
174 argv[argc++] = "--"; 337 arg = "--";
175 argv[argc++] = path; 338 vector_push(&vec, &arg, 0);
339 vector_push(&vec, &path, 0);
176 } 340 }
341
342 /* Make sure the vector is NULL-terminated */
343 vector_push(&vec, NULL, 0);
344 vec.count--;
345
177 init_revisions(&rev, NULL); 346 init_revisions(&rev, NULL);
178 rev.abbrev = DEFAULT_ABBREV; 347 rev.abbrev = DEFAULT_ABBREV;
179 rev.commit_format = CMIT_FMT_DEFAULT; 348 rev.commit_format = CMIT_FMT_DEFAULT;
180 rev.verbose_header = 1; 349 rev.verbose_header = 1;
181 rev.show_root_diff = 0; 350 rev.show_root_diff = 0;
182 setup_revisions(argc, argv, &rev, NULL); 351 setup_revisions(vec.count, vec.data, &rev, NULL);
183 load_ref_decorations(DECORATE_FULL_REFS); 352 load_ref_decorations(DECORATE_FULL_REFS);
184 rev.show_decorations = 1; 353 rev.show_decorations = 1;
185 rev.grep_filter.regflags |= REG_ICASE; 354 rev.grep_filter.regflags |= REG_ICASE;
@@ -189,8 +358,12 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
189 if (pager) 358 if (pager)
190 html("<table class='list nowrap'>"); 359 html("<table class='list nowrap'>");
191 360
192 html("<tr class='nohover'><th class='left'>Age</th>" 361 html("<tr class='nohover'>");
193 "<th class='left'>Commit message"); 362 if (commit_graph)
363 html("<th></th>");
364 else
365 html("<th class='left'>Age</th>");
366 html("<th class='left'>Commit message");
194 if (pager) { 367 if (pager) {
195 html(" ("); 368 html(" (");
196 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 369 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
@@ -200,13 +373,15 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
200 html(")"); 373 html(")");
201 } 374 }
202 html("</th><th class='left'>Author</th>"); 375 html("</th><th class='left'>Author</th>");
376 if (commit_graph)
377 html("<th class='left'>Age</th>");
203 if (ctx.repo->enable_log_filecount) { 378 if (ctx.repo->enable_log_filecount) {
204 html("<th class='left'>Files</th>"); 379 html("<th class='left'>Files</th>");
205 columns++; 380 columns++;
206 if (ctx.repo->enable_log_linecount) { 381 }
207 html("<th class='left'>Lines</th>"); 382 if (ctx.repo->enable_log_linecount) {
208 columns++; 383 html("<th class='left'>Lines</th>");
209 } 384 columns++;
210 } 385 }
211 html("</tr>\n"); 386 html("</tr>\n");
212 387
@@ -221,7 +396,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
221 } 396 }
222 397
223 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 398 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
224 print_commit(commit); 399 print_commit(commit, &rev);
225 free(commit->buffer); 400 free(commit->buffer);
226 commit->buffer = NULL; 401 commit->buffer = NULL;
227 free_commit_list(commit->parents); 402 free_commit_list(commit->parents);
diff --git a/ui-log.h b/ui-log.h
index 6034055..d0cb779 100644
--- a/ui-log.h
+++ b/ui-log.h
@@ -2,7 +2,8 @@
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-shared.c b/ui-shared.c
index ae29615..5aa9119 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -574,7 +574,7 @@ void cgit_print_http_headers(struct cgit_context *ctx)
574 else if (ctx->page.mimetype) 574 else if (ctx->page.mimetype)
575 htmlf("Content-Type: %s\n", ctx->page.mimetype); 575 htmlf("Content-Type: %s\n", ctx->page.mimetype);
576 if (ctx->page.size) 576 if (ctx->page.size)
577 htmlf("Content-Length: %ld\n", ctx->page.size); 577 htmlf("Content-Length: %zd\n", ctx->page.size);
578 if (ctx->page.filename) 578 if (ctx->page.filename)
579 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 579 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
580 ctx->page.filename); 580 ctx->page.filename);
@@ -756,17 +756,27 @@ static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
756 756
757static void print_header(struct cgit_context *ctx) 757static void print_header(struct cgit_context *ctx)
758{ 758{
759 char *logo = NULL, *logo_link = NULL;
760
759 html("<table id='header'>\n"); 761 html("<table id='header'>\n");
760 html("<tr>\n"); 762 html("<tr>\n");
761 763
762 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 764 if (ctx->repo && ctx->repo->logo && *ctx->repo->logo)
765 logo = ctx->repo->logo;
766 else
767 logo = ctx->cfg.logo;
768 if (ctx->repo && ctx->repo->logo_link && *ctx->repo->logo_link)
769 logo_link = ctx->repo->logo_link;
770 else
771 logo_link = ctx->cfg.logo_link;
772 if (logo && *logo) {
763 html("<td class='logo' rowspan='2'><a href='"); 773 html("<td class='logo' rowspan='2'><a href='");
764 if (ctx->cfg.logo_link) 774 if (logo_link && *logo_link)
765 html_attr(ctx->cfg.logo_link); 775 html_attr(logo_link);
766 else 776 else
767 html_attr(cgit_rooturl()); 777 html_attr(cgit_rooturl());
768 html("'><img src='"); 778 html("'><img src='");
769 html_attr(ctx->cfg.logo); 779 html_attr(logo);
770 html("' alt='cgit logo'/></a></td>\n"); 780 html("' alt='cgit logo'/></a></td>\n");
771 } 781 }
772 782
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
index 408e620..2481585 100644
--- a/ui-ssdiff.c
+++ b/ui-ssdiff.c
@@ -1,6 +1,7 @@
1#include "cgit.h" 1#include "cgit.h"
2#include "html.h" 2#include "html.h"
3#include "ui-shared.h" 3#include "ui-shared.h"
4#include "ui-diff.h"
4 5
5extern int use_ssdiff; 6extern int use_ssdiff;
6 7
@@ -191,17 +192,24 @@ static void print_ssdiff_line(char *class,
191 char *new_line, int individual_chars) 192 char *new_line, int individual_chars)
192{ 193{
193 char *lcs = NULL; 194 char *lcs = NULL;
195
194 if (old_line) 196 if (old_line)
195 old_line = replace_tabs(old_line + 1); 197 old_line = replace_tabs(old_line + 1);
196 if (new_line) 198 if (new_line)
197 new_line = replace_tabs(new_line + 1); 199 new_line = replace_tabs(new_line + 1);
198 if (individual_chars && old_line && new_line) 200 if (individual_chars && old_line && new_line)
199 lcs = longest_common_subsequence(old_line, new_line); 201 lcs = longest_common_subsequence(old_line, new_line);
200 html("<tr>"); 202 html("<tr>\n");
201 if (old_line_no > 0) 203 if (old_line_no > 0) {
202 htmlf("<td class='lineno'>%d</td><td class='%s'>", 204 struct diff_filespec *old_file = cgit_get_current_old_file();
203 old_line_no, class); 205 char *lineno_str = fmt("n%d", old_line_no);
204 else if (old_line) 206 char *id_str = fmt("%s#%s", is_null_sha1(old_file->sha1)?"HEAD":sha1_to_hex(old_rev_sha1), lineno_str);
207 html("<td class='lineno'><a class='no' href='");
208 html(cgit_fileurl(ctx.repo->url, "tree", old_file->path, id_str));
209 htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1);
210 html("</td>");
211 htmlf("<td class='%s'>", class);
212 } else if (old_line)
205 htmlf("<td class='lineno'></td><td class='%s'>", class); 213 htmlf("<td class='lineno'></td><td class='%s'>", class);
206 else 214 else
207 htmlf("<td class='lineno'></td><td class='%s_dark'>", class); 215 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
@@ -212,11 +220,17 @@ static void print_ssdiff_line(char *class,
212 html_txt(old_line); 220 html_txt(old_line);
213 } 221 }
214 222
215 html("</td>"); 223 html("</td>\n");
216 if (new_line_no > 0) 224 if (new_line_no > 0) {
217 htmlf("<td class='lineno'>%d</td><td class='%s'>", 225 struct diff_filespec *new_file = cgit_get_current_new_file();
218 new_line_no, class); 226 char *lineno_str = fmt("n%d", new_line_no);
219 else if (new_line) 227 char *id_str = fmt("%s#%s", is_null_sha1(new_file->sha1)?"HEAD":sha1_to_hex(new_rev_sha1), lineno_str);
228 html("<td class='lineno'><a class='no' href='");
229 html(cgit_fileurl(ctx.repo->url, "tree", new_file->path, id_str));
230 htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1);
231 html("</td>");
232 htmlf("<td class='%s'>", class);
233 } else if (new_line)
220 htmlf("<td class='lineno'></td><td class='%s'>", class); 234 htmlf("<td class='lineno'></td><td class='%s'>", class);
221 else 235 else
222 htmlf("<td class='lineno'></td><td class='%s_dark'>", class); 236 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
diff --git a/ui-summary.c b/ui-summary.c
index b203bcc..5be2545 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -59,7 +59,7 @@ void cgit_print_summary()
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);
diff --git a/ui-tree.c b/ui-tree.c
index 0b1b531..442b6be 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -48,6 +48,8 @@ static void print_text_buffer(const char *name, char *buf, unsigned long size)
48 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
49 html_raw(buf, size); 49 html_raw(buf, size);
50 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
51 free(ctx.repo->source_filter->argv[1]);
52 ctx.repo->source_filter->argv[1] = NULL;
51 html("</code></pre></td></tr></table>\n"); 53 html("</code></pre></td></tr></table>\n");
52 return; 54 return;
53 } 55 }
diff --git a/vector.c b/vector.c
new file mode 100644
index 0000000..0863908
--- /dev/null
+++ b/vector.c
@@ -0,0 +1,38 @@
1#include <stdio.h>
2#include <string.h>
3#include <errno.h>
4#include "vector.h"
5
6static int grow(struct vector *vec, int gently)
7{
8 size_t new_alloc;
9 void *new_data;
10
11 new_alloc = vec->alloc * 3 / 2;
12 if (!new_alloc)
13 new_alloc = 8;
14 new_data = realloc(vec->data, new_alloc * vec->size);
15 if (!new_data) {
16 if (gently)
17 return ENOMEM;
18 perror("vector.c:grow()");
19 exit(1);
20 }
21 vec->data = new_data;
22 vec->alloc = new_alloc;
23 return 0;
24}
25
26int vector_push(struct vector *vec, const void *data, int gently)
27{
28 int rc;
29
30 if (vec->count == vec->alloc && (rc = grow(vec, gently)))
31 return rc;
32 if (data)
33 memmove(vec->data + vec->count * vec->size, data, vec->size);
34 else
35 memset(vec->data + vec->count * vec->size, 0, vec->size);
36 vec->count++;
37 return 0;
38}
diff --git a/vector.h b/vector.h
new file mode 100644
index 0000000..c64eb1f
--- /dev/null
+++ b/vector.h
@@ -0,0 +1,17 @@
1#ifndef CGIT_VECTOR_H
2#define CGIT_VECTOR_H
3
4#include <stdlib.h>
5
6struct vector {
7 size_t size;
8 size_t count;
9 size_t alloc;
10 void *data;
11};
12
13#define VECTOR_INIT(type) {sizeof(type), 0, 0, NULL}
14
15int vector_push(struct vector *vec, const void *data, int gently);
16
17#endif /* CGIT_VECTOR_H */