summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--Makefile3
-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
m---------git0
-rw-r--r--html.c4
-rw-r--r--shared.c1
-rw-r--r--ui-log.c254
-rw-r--r--ui-log.h3
-rw-r--r--ui-summary.c2
-rw-r--r--vector.c38
-rw-r--r--vector.h17
14 files changed, 331 insertions, 56 deletions
diff --git a/Makefile b/Makefile
index fe4b10e..a988751 100644
--- a/Makefile
+++ b/Makefile
@@ -1,39 +1,39 @@
1CGIT_VERSION = v0.8.3.4 1CGIT_VERSION = v0.8.3.4
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)
5CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
6CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
7prefix = /usr 7prefix = /usr
8libdir = $(prefix)/lib 8libdir = $(prefix)/lib
9filterdir = $(libdir)/cgit/filters 9filterdir = $(libdir)/cgit/filters
10docdir = $(prefix)/share/doc/cgit 10docdir = $(prefix)/share/doc/cgit
11htmldir = $(docdir) 11htmldir = $(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)
19MAN_TXT = $(MAN5_TXT) 19MAN_TXT = $(MAN5_TXT)
20DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT)) 20DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
21DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT)) 21DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
22DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT)) 22DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT))
23 23
24# Define NO_STRCASESTR if you don't have strcasestr. 24# Define NO_STRCASESTR if you don't have strcasestr.
25# 25#
26# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1 26# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
27# implementation (slower). 27# implementation (slower).
28# 28#
29# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 29# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
30# 30#
31# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.) 31# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
32# do not support the 'size specifiers' introduced by C99, namely ll, hh, 32# do not support the 'size specifiers' introduced by C99, namely ll, hh,
33# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t). 33# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
34# some C compilers supported these specifiers prior to C99 as an extension. 34# some C compilers supported these specifiers prior to C99 as an extension.
35# 35#
36 36
37#-include config.mak 37#-include config.mak
38 38
39# 39#
@@ -94,48 +94,49 @@ OBJECTS += cache.o
94OBJECTS += cgit.o 94OBJECTS += cgit.o
95OBJECTS += cmd.o 95OBJECTS += cmd.o
96OBJECTS += configfile.o 96OBJECTS += configfile.o
97OBJECTS += html.o 97OBJECTS += html.o
98OBJECTS += parsing.o 98OBJECTS += parsing.o
99OBJECTS += scan-tree.o 99OBJECTS += scan-tree.o
100OBJECTS += shared.o 100OBJECTS += shared.o
101OBJECTS += ui-atom.o 101OBJECTS += ui-atom.o
102OBJECTS += ui-blob.o 102OBJECTS += ui-blob.o
103OBJECTS += ui-clone.o 103OBJECTS += ui-clone.o
104OBJECTS += ui-commit.o 104OBJECTS += ui-commit.o
105OBJECTS += ui-diff.o 105OBJECTS += ui-diff.o
106OBJECTS += ui-log.o 106OBJECTS += ui-log.o
107OBJECTS += ui-patch.o 107OBJECTS += ui-patch.o
108OBJECTS += ui-plain.o 108OBJECTS += ui-plain.o
109OBJECTS += ui-refs.o 109OBJECTS += ui-refs.o
110OBJECTS += ui-repolist.o 110OBJECTS += ui-repolist.o
111OBJECTS += ui-shared.o 111OBJECTS += ui-shared.o
112OBJECTS += ui-snapshot.o 112OBJECTS += ui-snapshot.o
113OBJECTS += ui-ssdiff.o 113OBJECTS += ui-ssdiff.o
114OBJECTS += ui-stats.o 114OBJECTS += 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
121endif 122endif
122 123
123 124
124.PHONY: all libgit test install uninstall clean force-version get-git \ 125.PHONY: all libgit test install uninstall clean force-version get-git \
125 doc clean-doc install-doc install-man install-html install-pdf \ 126 doc clean-doc install-doc install-man install-html install-pdf \
126 uninstall-doc uninstall-man uninstall-html uninstall-pdf 127 uninstall-doc uninstall-man uninstall-html uninstall-pdf
127 128
128all: cgit 129all: cgit
129 130
130VERSION: force-version 131VERSION: force-version
131 @./gen-version.sh "$(CGIT_VERSION)" 132 @./gen-version.sh "$(CGIT_VERSION)"
132-include VERSION 133-include VERSION
133 134
134 135
135CFLAGS += -g -Wall -Igit 136CFLAGS += -g -Wall -Igit
136CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 137CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
137CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 138CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
138CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 139CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
139CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 140CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
140CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 141CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
141 142
diff --git a/cgit.c b/cgit.c
index 6a76281..71f3fc8 100644
--- a/cgit.c
+++ b/cgit.c
@@ -36,48 +36,50 @@ struct cgit_filter *new_filter(const char *cmd, int extra_args)
36 f = xmalloc(sizeof(struct cgit_filter)); 36 f = xmalloc(sizeof(struct cgit_filter));
37 f->cmd = xstrdup(cmd); 37 f->cmd = xstrdup(cmd);
38 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 38 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
39 f->argv[0] = f->cmd; 39 f->argv[0] = f->cmd;
40 f->argv[1] = NULL; 40 f->argv[1] = NULL;
41 return f; 41 return f;
42} 42}
43 43
44static void process_cached_repolist(const char *path); 44static void process_cached_repolist(const char *path);
45 45
46void repo_config(struct cgit_repo *repo, const char *name, const char *value) 46void repo_config(struct cgit_repo *repo, const char *name, const char *value)
47{ 47{
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);
72 else if (!strcmp(name, "section")) 74 else if (!strcmp(name, "section"))
73 repo->section = xstrdup(value); 75 repo->section = xstrdup(value);
74 else if (!strcmp(name, "readme") && value != NULL) { 76 else if (!strcmp(name, "readme") && value != NULL) {
75 repo->readme = xstrdup(value); 77 repo->readme = xstrdup(value);
76 } else if (ctx.cfg.enable_filter_overrides) { 78 } else if (ctx.cfg.enable_filter_overrides) {
77 if (!strcmp(name, "about-filter")) 79 if (!strcmp(name, "about-filter"))
78 repo->about_filter = new_filter(value, 0); 80 repo->about_filter = new_filter(value, 0);
79 else if (!strcmp(name, "commit-filter")) 81 else if (!strcmp(name, "commit-filter"))
80 repo->commit_filter = new_filter(value, 0); 82 repo->commit_filter = new_filter(value, 0);
81 else if (!strcmp(name, "source-filter")) 83 else if (!strcmp(name, "source-filter"))
82 repo->source_filter = new_filter(value, 1); 84 repo->source_filter = new_filter(value, 1);
83 } 85 }
@@ -120,48 +122,50 @@ void config_cb(const char *name, const char *value)
120 else if (!strcmp(name, "logo-link")) 122 else if (!strcmp(name, "logo-link"))
121 ctx.cfg.logo_link = xstrdup(value); 123 ctx.cfg.logo_link = xstrdup(value);
122 else if (!strcmp(name, "module-link")) 124 else if (!strcmp(name, "module-link"))
123 ctx.cfg.module_link = xstrdup(value); 125 ctx.cfg.module_link = xstrdup(value);
124 else if (!strcmp(name, "strict-export")) 126 else if (!strcmp(name, "strict-export"))
125 ctx.cfg.strict_export = xstrdup(value); 127 ctx.cfg.strict_export = xstrdup(value);
126 else if (!strcmp(name, "virtual-root")) { 128 else if (!strcmp(name, "virtual-root")) {
127 ctx.cfg.virtual_root = trim_end(value, '/'); 129 ctx.cfg.virtual_root = trim_end(value, '/');
128 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 130 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
129 ctx.cfg.virtual_root = ""; 131 ctx.cfg.virtual_root = "";
130 } else if (!strcmp(name, "nocache")) 132 } else if (!strcmp(name, "nocache"))
131 ctx.cfg.nocache = atoi(value); 133 ctx.cfg.nocache = atoi(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);
156 else if (!strcmp(name, "cache-size")) 160 else if (!strcmp(name, "cache-size"))
157 ctx.cfg.cache_size = atoi(value); 161 ctx.cfg.cache_size = atoi(value);
158 else if (!strcmp(name, "cache-root")) 162 else if (!strcmp(name, "cache-root"))
159 ctx.cfg.cache_root = xstrdup(expand_macros(value)); 163 ctx.cfg.cache_root = xstrdup(expand_macros(value));
160 else if (!strcmp(name, "cache-root-ttl")) 164 else if (!strcmp(name, "cache-root-ttl"))
161 ctx.cfg.cache_root_ttl = atoi(value); 165 ctx.cfg.cache_root_ttl = atoi(value);
162 else if (!strcmp(name, "cache-repo-ttl")) 166 else if (!strcmp(name, "cache-repo-ttl"))
163 ctx.cfg.cache_repo_ttl = atoi(value); 167 ctx.cfg.cache_repo_ttl = atoi(value);
164 else if (!strcmp(name, "cache-scanrc-ttl")) 168 else if (!strcmp(name, "cache-scanrc-ttl"))
165 ctx.cfg.cache_scanrc_ttl = atoi(value); 169 ctx.cfg.cache_scanrc_ttl = atoi(value);
166 else if (!strcmp(name, "cache-static-ttl")) 170 else if (!strcmp(name, "cache-static-ttl"))
167 ctx.cfg.cache_static_ttl = atoi(value); 171 ctx.cfg.cache_static_ttl = atoi(value);
@@ -522,48 +526,50 @@ char *get_first_line(char *txt)
522} 526}
523 527
524void print_repo(FILE *f, struct cgit_repo *repo) 528void print_repo(FILE *f, struct cgit_repo *repo)
525{ 529{
526 fprintf(f, "repo.url=%s\n", repo->url); 530 fprintf(f, "repo.url=%s\n", repo->url);
527 fprintf(f, "repo.name=%s\n", repo->name); 531 fprintf(f, "repo.name=%s\n", repo->name);
528 fprintf(f, "repo.path=%s\n", repo->path); 532 fprintf(f, "repo.path=%s\n", repo->path);
529 if (repo->owner) 533 if (repo->owner)
530 fprintf(f, "repo.owner=%s\n", repo->owner); 534 fprintf(f, "repo.owner=%s\n", repo->owner);
531 if (repo->desc) { 535 if (repo->desc) {
532 char *tmp = get_first_line(repo->desc); 536 char *tmp = get_first_line(repo->desc);
533 fprintf(f, "repo.desc=%s\n", tmp); 537 fprintf(f, "repo.desc=%s\n", tmp);
534 free(tmp); 538 free(tmp);
535 } 539 }
536 if (repo->readme) 540 if (repo->readme)
537 fprintf(f, "repo.readme=%s\n", repo->readme); 541 fprintf(f, "repo.readme=%s\n", repo->readme);
538 if (repo->defbranch) 542 if (repo->defbranch)
539 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 543 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
540 if (repo->module_link) 544 if (repo->module_link)
541 fprintf(f, "repo.module-link=%s\n", repo->module_link); 545 fprintf(f, "repo.module-link=%s\n", repo->module_link);
542 if (repo->section) 546 if (repo->section)
543 fprintf(f, "repo.section=%s\n", repo->section); 547 fprintf(f, "repo.section=%s\n", repo->section);
544 if (repo->clone_url) 548 if (repo->clone_url)
545 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 549 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
550 fprintf(f, "repo.enable-commit-graph=%d\n",
551 repo->enable_commit_graph);
546 fprintf(f, "repo.enable-log-filecount=%d\n", 552 fprintf(f, "repo.enable-log-filecount=%d\n",
547 repo->enable_log_filecount); 553 repo->enable_log_filecount);
548 fprintf(f, "repo.enable-log-linecount=%d\n", 554 fprintf(f, "repo.enable-log-linecount=%d\n",
549 repo->enable_log_linecount); 555 repo->enable_log_linecount);
550 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 556 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
551 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 557 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
552 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 558 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
553 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 559 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
554 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 560 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
555 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 561 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
556 if (repo->snapshots != ctx.cfg.snapshots) { 562 if (repo->snapshots != ctx.cfg.snapshots) {
557 char *tmp = build_snapshot_setting(repo->snapshots); 563 char *tmp = build_snapshot_setting(repo->snapshots);
558 fprintf(f, "repo.snapshots=%s\n", tmp); 564 fprintf(f, "repo.snapshots=%s\n", tmp);
559 free(tmp); 565 free(tmp);
560 } 566 }
561 if (repo->max_stats != ctx.cfg.max_stats) 567 if (repo->max_stats != ctx.cfg.max_stats)
562 fprintf(f, "repo.max-stats=%s\n", 568 fprintf(f, "repo.max-stats=%s\n",
563 cgit_find_stats_periodname(repo->max_stats)); 569 cgit_find_stats_periodname(repo->max_stats));
564 fprintf(f, "\n"); 570 fprintf(f, "\n");
565} 571}
566 572
567void print_repolist(FILE *f, struct cgit_repolist *list, int start) 573void print_repolist(FILE *f, struct cgit_repolist *list, int start)
568{ 574{
569 int i; 575 int i;
diff --git a/cgit.css b/cgit.css
index a2a685b..008cff8 100644
--- a/cgit.css
+++ b/cgit.css
@@ -132,68 +132,86 @@ table.list tr.logheader {
132 132
133table.list tr:hover { 133table.list tr:hover {
134 background: #eee; 134 background: #eee;
135} 135}
136 136
137table.list tr.nohover:hover { 137table.list tr.nohover:hover {
138 background: white; 138 background: white;
139} 139}
140 140
141table.list th { 141table.list th {
142 font-weight: bold; 142 font-weight: bold;
143 /* color: #888; 143 /* color: #888;
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 {
188 color: #00f; 206 color: #00f;
189} 207}
190 208
191img { 209img {
192 border: none; 210 border: none;
193} 211}
194 212
195input#switch-btn { 213input#switch-btn {
196 margin: 2px 0px 0px 0px; 214 margin: 2px 0px 0px 0px;
197} 215}
198 216
199td#sidebar input.txt { 217td#sidebar input.txt {
diff --git a/cgit.h b/cgit.h
index ad94905..74aa340 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,46 +1,47 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
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
35 */ 36 */
36#define TM_MIN 60 37#define TM_MIN 60
37#define TM_HOUR (TM_MIN * 60) 38#define TM_HOUR (TM_MIN * 60)
38#define TM_DAY (TM_HOUR * 24) 39#define TM_DAY (TM_HOUR * 24)
39#define TM_WEEK (TM_DAY * 7) 40#define TM_WEEK (TM_DAY * 7)
40#define TM_YEAR (TM_DAY * 365) 41#define TM_YEAR (TM_DAY * 365)
41#define TM_MONTH (TM_YEAR / 12.0) 42#define TM_MONTH (TM_YEAR / 12.0)
42 43
43 44
44/* 45/*
45 * Default encoding 46 * Default encoding
46 */ 47 */
@@ -50,48 +51,49 @@ typedef void (*configfn)(const char *name, const char *value);
50typedef void (*filepair_fn)(struct diff_filepair *pair); 51typedef void (*filepair_fn)(struct diff_filepair *pair);
51typedef void (*linediff_fn)(char *line, int len); 52typedef void (*linediff_fn)(char *line, int len);
52 53
53struct cgit_filter { 54struct cgit_filter {
54 char *cmd; 55 char *cmd;
55 char **argv; 56 char **argv;
56 int old_stdout; 57 int old_stdout;
57 int pipe_fh[2]; 58 int pipe_fh[2];
58 int pid; 59 int pid;
59 int exitstatus; 60 int exitstatus;
60}; 61};
61 62
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,
86 const char *value); 88 const char *value);
87 89
88struct cgit_repolist { 90struct cgit_repolist {
89 int length; 91 int length;
90 int count; 92 int count;
91 struct cgit_repo *repos; 93 struct cgit_repo *repos;
92}; 94};
93 95
94struct commitinfo { 96struct commitinfo {
95 struct commit *commit; 97 struct commit *commit;
96 char *author; 98 char *author;
97 char *author_email; 99 char *author_email;
@@ -167,48 +169,49 @@ struct cgit_config {
167 char *logo; 169 char *logo;
168 char *logo_link; 170 char *logo_link;
169 char *module_link; 171 char *module_link;
170 char *project_list; 172 char *project_list;
171 char *readme; 173 char *readme;
172 char *robots; 174 char *robots;
173 char *root_title; 175 char *root_title;
174 char *root_desc; 176 char *root_desc;
175 char *root_readme; 177 char *root_readme;
176 char *script_name; 178 char *script_name;
177 char *section; 179 char *section;
178 char *virtual_root; 180 char *virtual_root;
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;
203 int max_blob_size; 206 int max_blob_size;
204 int max_stats; 207 int max_stats;
205 int nocache; 208 int nocache;
206 int noplainemail; 209 int noplainemail;
207 int noheader; 210 int noheader;
208 int renamelimit; 211 int renamelimit;
209 int remove_suffix; 212 int remove_suffix;
210 int scan_hidden_path; 213 int scan_hidden_path;
211 int section_from_path; 214 int section_from_path;
212 int snapshots; 215 int snapshots;
213 int summary_branches; 216 int summary_branches;
214 int summary_log; 217 int summary_log;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 1dc3cce..a832830 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -70,48 +70,53 @@ cache-static-ttl::
70 version of repository pages accessed with a fixed SHA1. Default value: 70 version of repository pages accessed with a fixed SHA1. Default value:
71 "5". 71 "5".
72 72
73clone-prefix:: 73clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
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",
106 "commit" and "tree" pages). Default value: "0". 111 "commit" and "tree" pages). Default value: "0".
107 112
108enable-log-filecount:: 113enable-log-filecount::
109 Flag which, when set to "1", will make cgit print the number of 114 Flag which, when set to "1", will make cgit print the number of
110 modified files for each commit on the repository log page. Default 115 modified files for each commit on the repository log page. Default
111 value: "0". 116 value: "0".
112 117
113enable-log-linecount:: 118enable-log-linecount::
114 Flag which, when set to "1", will make cgit print the number of added 119 Flag which, when set to "1", will make cgit print the number of added
115 and removed lines for each commit on the repository log page. Default 120 and removed lines for each commit on the repository log page. Default
116 value: "0". 121 value: "0".
117 122
@@ -341,48 +346,52 @@ virtual-root::
341 same kind of virtual urls, so this option will probably be deprecated. 346 same kind of virtual urls, so this option will probably be deprecated.
342 347
343REPOSITORY SETTINGS 348REPOSITORY SETTINGS
344------------------- 349-------------------
345repo.about-filter:: 350repo.about-filter::
346 Override the default about-filter. Default value: none. See also: 351 Override the default about-filter. Default value: none. See also:
347 "enable-filter-overrides". 352 "enable-filter-overrides".
348 353
349repo.clone-url:: 354repo.clone-url::
350 A list of space-separated urls which can be used to clone this repo. 355 A list of space-separated urls which can be used to clone this repo.
351 Default value: none. 356 Default value: none.
352 357
353repo.commit-filter:: 358repo.commit-filter::
354 Override the default commit-filter. Default value: none. See also: 359 Override the default commit-filter. Default value: none. See also:
355 "enable-filter-overrides". 360 "enable-filter-overrides".
356 361
357repo.defbranch:: 362repo.defbranch::
358 The name of the default branch for this repository. If no such branch 363 The name of the default branch for this repository. If no such branch
359 exists in the repository, the first branch name (when sorted) is used 364 exists in the repository, the first branch name (when sorted) is used
360 as default instead. Default value: "master". 365 as default instead. Default value: "master".
361 366
362repo.desc:: 367repo.desc::
363 The value to show as repository description. Default value: none. 368 The value to show as repository description. Default value: none.
364 369
370repo.enable-commit-graph::
371 A flag which can be used to disable the global setting
372 `enable-commit-graph'. Default value: none.
373
365repo.enable-log-filecount:: 374repo.enable-log-filecount::
366 A flag which can be used to disable the global setting 375 A flag which can be used to disable the global setting
367 `enable-log-filecount'. Default value: none. 376 `enable-log-filecount'. Default value: none.
368 377
369repo.enable-log-linecount:: 378repo.enable-log-linecount::
370 A flag which can be used to disable the global setting 379 A flag which can be used to disable the global setting
371 `enable-log-linecount'. Default value: none. 380 `enable-log-linecount'. Default value: none.
372 381
373repo.enable-remote-branches:: 382repo.enable-remote-branches::
374 Flag which, when set to "1", will make cgit display remote branches 383 Flag which, when set to "1", will make cgit display remote branches
375 in the summary and refs views. Default value: <enable-remote-branches>. 384 in the summary and refs views. Default value: <enable-remote-branches>.
376 385
377repo.enable-subject-links:: 386repo.enable-subject-links::
378 A flag which can be used to override the global setting 387 A flag which can be used to override the global setting
379 `enable-subject-links'. Default value: none. 388 `enable-subject-links'. Default value: none.
380 389
381repo.max-stats:: 390repo.max-stats::
382 Override the default maximum statistics period. Valid values are equal 391 Override the default maximum statistics period. Valid values are equal
383 to the values specified for the global "max-stats" setting. Default 392 to the values specified for the global "max-stats" setting. Default
384 value: none. 393 value: none.
385 394
386repo.name:: 395repo.name::
387 The value to show as repository name. Default value: <repo.url>. 396 The value to show as repository name. Default value: <repo.url>.
388 397
@@ -428,48 +437,52 @@ options are only acknowledged in repo-specific config files when
428 437
429Note: the "repo." prefix is dropped from the option names in repo-specific 438Note: the "repo." prefix is dropped from the option names in repo-specific
430config files, e.g. "repo.desc" becomes "desc". 439config files, e.g. "repo.desc" becomes "desc".
431 440
432 441
433EXAMPLE CGITRC FILE 442EXAMPLE CGITRC FILE
434------------------- 443-------------------
435 444
436.... 445....
437# Enable caching of up to 1000 output entriess 446# Enable caching of up to 1000 output entriess
438cache-size=1000 447cache-size=1000
439 448
440 449
441# Specify some default clone prefixes 450# Specify some default clone prefixes
442clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git 451clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git
443 452
444# Specify the css url 453# Specify the css url
445css=/css/cgit.css 454css=/css/cgit.css
446 455
447 456
448# Show extra links for each repository on the index page 457# Show extra links for each repository on the index page
449enable-index-links=1 458enable-index-links=1
450 459
451 460
461# Enable ASCII art commit history graph on the log pages
462enable-commit-graph=1
463
464
452# Show number of affected files per commit on the log pages 465# Show number of affected files per commit on the log pages
453enable-log-filecount=1 466enable-log-filecount=1
454 467
455 468
456# Show number of added/removed lines per commit on the log pages 469# Show number of added/removed lines per commit on the log pages
457enable-log-linecount=1 470enable-log-linecount=1
458 471
459 472
460# Add a cgit favicon 473# Add a cgit favicon
461favicon=/favicon.ico 474favicon=/favicon.ico
462 475
463 476
464# Use a custom logo 477# Use a custom logo
465logo=/img/mylogo.png 478logo=/img/mylogo.png
466 479
467 480
468# Enable statistics per week, month and quarter 481# Enable statistics per week, month and quarter
469max-stats=quarter 482max-stats=quarter
470 483
471 484
472# Set the title and heading of the repository index page 485# Set the title and heading of the repository index page
473root-title=example.com git repositories 486root-title=example.com git repositories
474 487
475 488
diff --git a/cmd.c b/cmd.c
index 6dc9f5e..536515b 100644
--- a/cmd.c
+++ b/cmd.c
@@ -46,49 +46,50 @@ static void about_fn(struct cgit_context *ctx)
46 46
47static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
48{ 48{
49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
50} 50}
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path); 54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static 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{
83 cgit_clone_objects(ctx); 84 cgit_clone_objects(ctx);
84} 85}
85 86
86static void repolist_fn(struct cgit_context *ctx) 87static void repolist_fn(struct cgit_context *ctx)
87{ 88{
88 cgit_print_repolist(); 89 cgit_print_repolist();
89} 90}
90 91
91static void patch_fn(struct cgit_context *ctx) 92static void patch_fn(struct cgit_context *ctx)
92{ 93{
93 cgit_print_patch(ctx->qry.sha1, ctx->qry.path); 94 cgit_print_patch(ctx->qry.sha1, ctx->qry.path);
94} 95}
diff --git a/git b/git
Subproject 87b50542a08ac6caa083ddc376e674424e37940 Subproject 7ed863a85a6ce2c4ac4476848310b8f917ab41f
diff --git a/html.c b/html.c
index 1305910..a1fe87d 100644
--- a/html.c
+++ b/html.c
@@ -1,45 +1,45 @@
1/* html.c: helper functions for html output 1/* html.c: helper functions for html output
2 * 2 *
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 <unistd.h> 9#include <unistd.h>
10#include <stdio.h> 10#include <stdio.h>
11#include <stdlib.h> 11#include <stdlib.h>
12#include <stdarg.h> 12#include <stdarg.h>
13#include <string.h> 13#include <string.h>
14#include <errno.h> 14#include <errno.h>
15 15
16/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */ 16/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
17static const char* url_escape_table[256] = { 17static 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,
25 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%7b", 25 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%7b",
26 "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85", 26 "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85",
27 "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f", 27 "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
28 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99", 28 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99",
29 "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3", 29 "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3",
30 "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", 30 "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad",
31 "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7", 31 "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
32 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1", 32 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1",
33 "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb", 33 "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb",
34 "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", 34 "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5",
35 "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df", 35 "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
36 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9", 36 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9",
37 "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3", 37 "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3",
38 "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", 38 "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd",
39 "%fe", "%ff" 39 "%fe", "%ff"
40}; 40};
41 41
42int htmlfd = STDOUT_FILENO; 42int htmlfd = STDOUT_FILENO;
43 43
44char *fmt(const char *format, ...) 44char *fmt(const char *format, ...)
45{ 45{
@@ -160,49 +160,49 @@ void html_url_path(const char *txt)
160{ 160{
161 const char *t = txt; 161 const char *t = txt;
162 while(t && *t){ 162 while(t && *t){
163 int c = *t; 163 int c = *t;
164 const char *e = url_escape_table[c]; 164 const char *e = url_escape_table[c];
165 if (e && c!='+' && c!='&' && c!='+') { 165 if (e && c!='+' && c!='&' && c!='+') {
166 html_raw(txt, t - txt); 166 html_raw(txt, t - txt);
167 html_raw(e, 3); 167 html_raw(e, 3);
168 txt = t+1; 168 txt = t+1;
169 } 169 }
170 t++; 170 t++;
171 } 171 }
172 if (t!=txt) 172 if (t!=txt)
173 html(txt); 173 html(txt);
174} 174}
175 175
176void html_url_arg(const char *txt) 176void html_url_arg(const char *txt)
177{ 177{
178 const char *t = txt; 178 const char *t = txt;
179 while(t && *t){ 179 while(t && *t){
180 int c = *t; 180 int c = *t;
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++;
188 } 188 }
189 if (t!=txt) 189 if (t!=txt)
190 html(txt); 190 html(txt);
191} 191}
192 192
193void html_hidden(const char *name, const char *value) 193void html_hidden(const char *name, const char *value)
194{ 194{
195 html("<input type='hidden' name='"); 195 html("<input type='hidden' name='");
196 html_attr(name); 196 html_attr(name);
197 html("' value='"); 197 html("' value='");
198 html_attr(value); 198 html_attr(value);
199 html("'/>"); 199 html("'/>");
200} 200}
201 201
202void html_option(const char *value, const char *text, const char *selected_value) 202void html_option(const char *value, const char *text, const char *selected_value)
203{ 203{
204 html("<option value='"); 204 html("<option value='");
205 html_attr(value); 205 html_attr(value);
206 html("'"); 206 html("'");
207 if (selected_value && !strcmp(selected_value, value)) 207 if (selected_value && !strcmp(selected_value, value))
208 html(" selected='selected'"); 208 html(" selected='selected'");
diff --git a/shared.c b/shared.c
index 765cd27..7ec2e19 100644
--- a/shared.c
+++ b/shared.c
@@ -35,48 +35,49 @@ int chk_non_negative(int result, char *msg)
35struct cgit_repo *cgit_add_repo(const char *url) 35struct cgit_repo *cgit_add_repo(const char *url)
36{ 36{
37 struct cgit_repo *ret; 37 struct cgit_repo *ret;
38 38
39 if (++cgit_repolist.count > cgit_repolist.length) { 39 if (++cgit_repolist.count > cgit_repolist.length) {
40 if (cgit_repolist.length == 0) 40 if (cgit_repolist.length == 0)
41 cgit_repolist.length = 8; 41 cgit_repolist.length = 8;
42 else 42 else
43 cgit_repolist.length *= 2; 43 cgit_repolist.length *= 2;
44 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 44 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
45 cgit_repolist.length * 45 cgit_repolist.length *
46 sizeof(struct cgit_repo)); 46 sizeof(struct cgit_repo));
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;
71} 72}
72 73
73struct cgit_repo *cgit_get_repoinfo(const char *url) 74struct cgit_repo *cgit_get_repoinfo(const char *url)
74{ 75{
75 int i; 76 int i;
76 struct cgit_repo *repo; 77 struct cgit_repo *repo;
77 78
78 for (i=0; i<cgit_repolist.count; i++) { 79 for (i=0; i<cgit_repolist.count; i++) {
79 repo = &cgit_repolist.repos[i]; 80 repo = &cgit_repolist.repos[i];
80 if (!strcmp(repo->url, url)) 81 if (!strcmp(repo->url, url))
81 return repo; 82 return repo;
82 } 83 }
diff --git a/ui-log.c b/ui-log.c
index b9771fa..8add66a 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,38 +1,54 @@
1/* ui-log.c: functions for log output 1/* ui-log.c: functions for log output
2 * 2 *
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 "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)
18 return; 34 return;
19 35
20 if (line[0] == '+') 36 if (line[0] == '+')
21 add_lines++; 37 add_lines++;
22 38
23 else if (line[0] == '-') 39 else if (line[0] == '-')
24 rem_lines++; 40 rem_lines++;
25} 41}
26 42
27void inspect_files(struct diff_filepair *pair) 43void inspect_files(struct diff_filepair *pair)
28{ 44{
29 unsigned long old_size = 0; 45 unsigned long old_size = 0;
30 unsigned long new_size = 0; 46 unsigned long new_size = 0;
31 int binary = 0; 47 int binary = 0;
32 48
33 files++; 49 files++;
34 if (ctx.repo->enable_log_linecount) 50 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 51 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, 0, ctx.qry.ignorews, 52 &new_size, &binary, 0, ctx.qry.ignorews,
37 count_lines); 53 count_lines);
38} 54}
@@ -55,194 +71,354 @@ void show_commit_decorations(struct commit *commit)
55 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 71 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
56 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 72 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
57 } 73 }
58 else if (!prefixcmp(deco->name, "refs/tags/")) { 74 else if (!prefixcmp(deco->name, "refs/tags/")) {
59 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 75 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
60 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 76 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
61 } 77 }
62 else if (!prefixcmp(deco->name, "refs/remotes/")) { 78 else if (!prefixcmp(deco->name, "refs/remotes/")) {
63 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 79 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
64 cgit_log_link(buf, NULL, "remote-deco", NULL, 80 cgit_log_link(buf, NULL, "remote-deco", NULL,
65 sha1_to_hex(commit->object.sha1), 81 sha1_to_hex(commit->object.sha1),
66 ctx.qry.vpath, 0, NULL, NULL, 82 ctx.qry.vpath, 0, NULL, NULL,
67 ctx.qry.showmsg); 83 ctx.qry.showmsg);
68 } 84 }
69 else { 85 else {
70 strncpy(buf, deco->name, sizeof(buf) - 1); 86 strncpy(buf, deco->name, sizeof(buf) - 1);
71 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 87 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
72 sha1_to_hex(commit->object.sha1), 88 sha1_to_hex(commit->object.sha1),
73 ctx.qry.vpath, 0); 89 ctx.qry.vpath, 0);
74 } 90 }
75 deco = deco->next; 91 deco = deco->next;
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
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 }
84 120
85 info = cgit_parse_commit(commit); 121 info = cgit_parse_commit(commit);
86 htmlf("<tr%s><td>", 122 htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
87 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>");
88 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 133 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
89 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); 134 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
90 html_link_open(tmp, NULL, NULL); 135 html_link_open(tmp, NULL, NULL);
91 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 136 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
92 html_link_close(); 137 html_link_close();
93 htmlf("</td><td%s>", 138 html("</td>");
94 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 }
95 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 167 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
96 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); 168 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
97 show_commit_decorations(commit); 169 show_commit_decorations(commit);
98 html("</td><td>"); 170 html("</td><td>");
99 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
100 if (ctx.repo->enable_log_filecount) { 182 if (ctx.repo->enable_log_filecount) {
101 files = 0; 183 files = 0;
102 add_lines = 0; 184 add_lines = 0;
103 rem_lines = 0; 185 rem_lines = 0;
104 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath); 186 cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
105 html("</td><td>"); 187 html("</td><td>");
106 htmlf("%d", files); 188 htmlf("%d", files);
107 if (ctx.repo->enable_log_linecount) { 189 if (ctx.repo->enable_log_linecount) {
108 html("</td><td>"); 190 html("</td><td>");
109 htmlf("-%d/+%d", rem_lines, add_lines); 191 htmlf("-%d/+%d", rem_lines, add_lines);
110 } 192 }
111 } 193 }
112 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
113 if (ctx.qry.showmsg) { 199 if (ctx.qry.showmsg) {
114 struct strbuf notes = STRBUF_INIT; 200 /* Concatenate commit message + notes in msgbuf */
115 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 }
116 211
117 if (ctx.repo->enable_log_filecount) { 212 if (revs->graph) {
118 cols++; 213 int lines = 0;
119 if (ctx.repo->enable_log_linecount) 214
120 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++;
121 } 223 }
122 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
123 cols);
124 html_txt(info->msg);
125 html("</td></tr>\n");
126 if (notes.len != 0) {
127 html("<tr class='nohover'>");
128 html("<td class='lognotes-label'>Notes:</td>");
129 htmlf("<td colspan='%d' class='lognotes'>",
130 cols);
131 html_txt(notes.buf);
132 html("</td></tr>\n");
133 } 224 }
134 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");
135 } 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");
246 }
247
248 strbuf_release(&msgbuf);
249 strbuf_release(&graphbuf);
136 cgit_free_commitinfo(info); 250 cgit_free_commitinfo(info);
137} 251}
138 252
139static const char *disambiguate_ref(const char *ref) 253static const char *disambiguate_ref(const char *ref)
140{ 254{
141 unsigned char sha1[20]; 255 unsigned char sha1[20];
142 const char *longref; 256 const char *longref;
143 257
144 longref = fmt("refs/heads/%s", ref); 258 longref = fmt("refs/heads/%s", ref);
145 if (get_sha1(longref, sha1) == 0) 259 if (get_sha1(longref, sha1) == 0)
146 return longref; 260 return longref;
147 261
148 return ref; 262 return ref;
149} 263}
150 264
265static char *next_token(char **src)
266{
267 char *result;
268
269 if (!src || !*src)
270 return NULL;
271 while (isspace(**src))
272 (*src)++;
273 if (!**src)
274 return NULL;
275 result = *src;
276 while (**src) {
277 if (isspace(**src)) {
278 **src = '\0';
279 (*src)++;
280 break;
281 }
282 (*src)++;
283 }
284 return result;
285}
286
151void 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,
152 char *path, int pager) 288 char *path, int pager, int commit_graph)
153{ 289{
154 struct rev_info rev; 290 struct rev_info rev;
155 struct commit *commit; 291 struct commit *commit;
156 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 292 struct vector vec = VECTOR_INIT(char *);
157 int argc = 2;
158 int i, columns = 3; 293 int i, columns = 3;
294 char *arg;
295
296 /* First argv is NULL */
297 vector_push(&vec, NULL, 0);
159 298
160 if (!tip) 299 if (!tip)
161 tip = ctx.qry.head; 300 tip = ctx.qry.head;
162 301 tip = disambiguate_ref(tip);
163 argv[1] = disambiguate_ref(tip); 302 vector_push(&vec, &tip, 0);
164 303
165 if (grep && pattern && *pattern) { 304 if (grep && pattern && *pattern) {
305 pattern = xstrdup(pattern);
166 if (!strcmp(grep, "grep") || !strcmp(grep, "author") || 306 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
167 !strcmp(grep, "committer")) 307 !strcmp(grep, "committer")) {
168 argv[argc++] = fmt("--%s=%s", grep, pattern); 308 arg = fmt("--%s=%s", grep, pattern);
169 if (!strcmp(grep, "range")) 309 vector_push(&vec, &arg, 0);
170 argv[1] = pattern; 310 }
311 if (!strcmp(grep, "range")) {
312 /* Split the pattern at whitespace and add each token
313 * as a revision expression. Do not accept other
314 * rev-list options. Also, replace the previously
315 * pushed tip (it's no longer relevant).
316 */
317 vec.count--;
318 while ((arg = next_token(&pattern))) {
319 if (*arg == '-') {
320 fprintf(stderr, "Bad range expr: %s\n",
321 arg);
322 break;
323 }
324 vector_push(&vec, &arg, 0);
325 }
326 }
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);
171 } 335 }
172 336
173 if (path) { 337 if (path) {
174 argv[argc++] = "--"; 338 arg = "--";
175 argv[argc++] = path; 339 vector_push(&vec, &arg, 0);
340 vector_push(&vec, &path, 0);
176 } 341 }
342
343 /* Make sure the vector is NULL-terminated */
344 vector_push(&vec, NULL, 0);
345 vec.count--;
346
177 init_revisions(&rev, NULL); 347 init_revisions(&rev, NULL);
178 rev.abbrev = DEFAULT_ABBREV; 348 rev.abbrev = DEFAULT_ABBREV;
179 rev.commit_format = CMIT_FMT_DEFAULT; 349 rev.commit_format = CMIT_FMT_DEFAULT;
180 rev.verbose_header = 1; 350 rev.verbose_header = 1;
181 rev.show_root_diff = 0; 351 rev.show_root_diff = 0;
182 setup_revisions(argc, argv, &rev, NULL); 352 setup_revisions(vec.count, vec.data, &rev, NULL);
183 load_ref_decorations(DECORATE_FULL_REFS); 353 load_ref_decorations(DECORATE_FULL_REFS);
184 rev.show_decorations = 1; 354 rev.show_decorations = 1;
185 rev.grep_filter.regflags |= REG_ICASE; 355 rev.grep_filter.regflags |= REG_ICASE;
186 compile_grep_patterns(&rev.grep_filter); 356 compile_grep_patterns(&rev.grep_filter);
187 prepare_revision_walk(&rev); 357 prepare_revision_walk(&rev);
188 358
189 if (pager) 359 if (pager)
190 html("<table class='list nowrap'>"); 360 html("<table class='list nowrap'>");
191 361
192 html("<tr class='nohover'><th class='left'>Age</th>" 362 html("<tr class='nohover'>");
193 "<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");
194 if (pager) { 368 if (pager) {
195 html(" ("); 369 html(" (");
196 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 370 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
197 NULL, ctx.qry.head, ctx.qry.sha1, 371 NULL, ctx.qry.head, ctx.qry.sha1,
198 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, 372 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
199 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 373 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
200 html(")"); 374 html(")");
201 } 375 }
202 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>");
203 if (ctx.repo->enable_log_filecount) { 379 if (ctx.repo->enable_log_filecount) {
204 html("<th class='left'>Files</th>"); 380 html("<th class='left'>Files</th>");
205 columns++; 381 columns++;
206 if (ctx.repo->enable_log_linecount) { 382 if (ctx.repo->enable_log_linecount) {
207 html("<th class='left'>Lines</th>"); 383 html("<th class='left'>Lines</th>");
208 columns++; 384 columns++;
209 } 385 }
210 } 386 }
211 html("</tr>\n"); 387 html("</tr>\n");
212 388
213 if (ofs<0) 389 if (ofs<0)
214 ofs = 0; 390 ofs = 0;
215 391
216 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 392 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
217 free(commit->buffer); 393 free(commit->buffer);
218 commit->buffer = NULL; 394 commit->buffer = NULL;
219 free_commit_list(commit->parents); 395 free_commit_list(commit->parents);
220 commit->parents = NULL; 396 commit->parents = NULL;
221 } 397 }
222 398
223 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 399 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
224 print_commit(commit); 400 print_commit(commit, &rev);
225 free(commit->buffer); 401 free(commit->buffer);
226 commit->buffer = NULL; 402 commit->buffer = NULL;
227 free_commit_list(commit->parents); 403 free_commit_list(commit->parents);
228 commit->parents = NULL; 404 commit->parents = NULL;
229 } 405 }
230 if (pager) { 406 if (pager) {
231 html("</table><div class='pager'>"); 407 html("</table><div class='pager'>");
232 if (ofs > 0) { 408 if (ofs > 0) {
233 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 409 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
234 ctx.qry.sha1, ctx.qry.vpath, 410 ctx.qry.sha1, ctx.qry.vpath,
235 ofs - cnt, ctx.qry.grep, 411 ofs - cnt, ctx.qry.grep,
236 ctx.qry.search, ctx.qry.showmsg); 412 ctx.qry.search, ctx.qry.showmsg);
237 html("&nbsp;"); 413 html("&nbsp;");
238 } 414 }
239 if ((commit = get_revision(&rev)) != NULL) { 415 if ((commit = get_revision(&rev)) != NULL) {
240 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 416 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
241 ctx.qry.sha1, ctx.qry.vpath, 417 ctx.qry.sha1, ctx.qry.vpath,
242 ofs + cnt, ctx.qry.grep, 418 ofs + cnt, ctx.qry.grep,
243 ctx.qry.search, ctx.qry.showmsg); 419 ctx.qry.search, ctx.qry.showmsg);
244 } 420 }
245 html("</div>"); 421 html("</div>");
246 } else if ((commit = get_revision(&rev)) != NULL) { 422 } else if ((commit = get_revision(&rev)) != NULL) {
247 html("<tr class='nohover'><td colspan='3'>"); 423 html("<tr class='nohover'><td colspan='3'>");
248 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, 424 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
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
@@ -38,49 +38,49 @@ static void print_urls(char *txt, char *suffix)
38 38
39 while (h && *h) { 39 while (h && *h) {
40 while (h && *h == ' ') 40 while (h && *h == ' ')
41 h++; 41 h++;
42 t = h; 42 t = h;
43 while (t && *t && *t != ' ') 43 while (t && *t && *t != ' ')
44 t++; 44 t++;
45 c = *t; 45 c = *t;
46 *t = 0; 46 *t = 0;
47 print_url(h, suffix); 47 print_url(h, suffix);
48 *t = c; 48 *t = c;
49 h = t; 49 h = t;
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
75 if (!ctx.repo->readme || !(*ctx.repo->readme)) 75 if (!ctx.repo->readme || !(*ctx.repo->readme))
76 return; 76 return;
77 77
78 ref = NULL; 78 ref = NULL;
79 79
80 /* Check if the readme is tracked in the git repo. */ 80 /* Check if the readme is tracked in the git repo. */
81 colon = strchr(ctx.repo->readme, ':'); 81 colon = strchr(ctx.repo->readme, ':');
82 if (colon && strlen(colon) > 1) { 82 if (colon && strlen(colon) > 1) {
83 *colon = '\0'; 83 *colon = '\0';
84 ref = ctx.repo->readme; 84 ref = ctx.repo->readme;
85 ctx.repo->readme = colon + 1; 85 ctx.repo->readme = colon + 1;
86 if (!(*ctx.repo->readme)) 86 if (!(*ctx.repo->readme))
diff --git a/vector.c b/vector.c
new file mode 100644
index 0000000..0863908
--- a/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
--- a/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 */