aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile22
-rw-r--r--cgit.c82
-rw-r--r--cgit.css148
-rw-r--r--cgit.h23
-rw-r--r--cgitrc.5.txt58
-rw-r--r--cmd.c48
-rw-r--r--cmd.h3
-rwxr-xr-xfilters/commit-links.sh16
-rwxr-xr-xfilters/syntax-highlighting.sh29
m---------git0
-rw-r--r--html.c70
-rw-r--r--html.h18
-rw-r--r--scan-tree.c73
-rw-r--r--scan-tree.h3
-rw-r--r--shared.c88
-rw-r--r--ui-atom.c4
-rw-r--r--ui-blob.c35
-rw-r--r--ui-blob.h1
-rw-r--r--ui-commit.c46
-rw-r--r--ui-commit.h2
-rw-r--r--ui-diff.c92
-rw-r--r--ui-log.c53
-rw-r--r--ui-patch.c8
-rw-r--r--ui-patch.h2
-rw-r--r--ui-plain.c70
-rw-r--r--ui-refs.c4
-rw-r--r--ui-shared.c270
-rw-r--r--ui-shared.h71
-rw-r--r--ui-snapshot.c14
-rw-r--r--ui-ssdiff.c369
-rw-r--r--ui-ssdiff.h13
-rw-r--r--ui-stats.c8
-rw-r--r--ui-summary.c26
-rw-r--r--ui-tag.c24
-rw-r--r--ui-tree.c23
35 files changed, 1511 insertions, 305 deletions
diff --git a/Makefile b/Makefile
index 5162020..2a15469 100644
--- a/Makefile
+++ b/Makefile
@@ -5,12 +5,15 @@ CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
5CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
6CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
7SHA1_HEADER = <openssl/sha.h> 7SHA1_HEADER = <openssl/sha.h>
8GIT_VER = 1.6.4.3 8GIT_VER = 1.7.2.2
9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
10INSTALL = install 10INSTALL = install
11 11
12# Define NO_STRCASESTR if you don't have strcasestr. 12# Define NO_STRCASESTR if you don't have strcasestr.
13# 13#
14# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
15# implementation (slower).
16#
14# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 17# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
15# 18#
16 19
@@ -68,7 +71,7 @@ endif
68 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 71 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
69 72
70 73
71EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto 74EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread
72OBJECTS = 75OBJECTS =
73OBJECTS += cache.o 76OBJECTS += cache.o
74OBJECTS += cgit.o 77OBJECTS += cgit.o
@@ -90,6 +93,7 @@ OBJECTS += ui-refs.o
90OBJECTS += ui-repolist.o 93OBJECTS += ui-repolist.o
91OBJECTS += ui-shared.o 94OBJECTS += ui-shared.o
92OBJECTS += ui-snapshot.o 95OBJECTS += ui-snapshot.o
96OBJECTS += ui-ssdiff.o
93OBJECTS += ui-stats.o 97OBJECTS += ui-stats.o
94OBJECTS += ui-summary.o 98OBJECTS += ui-summary.o
95OBJECTS += ui-tag.o 99OBJECTS += ui-tag.o
@@ -123,17 +127,25 @@ endif
123ifdef NO_STRCASESTR 127ifdef NO_STRCASESTR
124 CFLAGS += -DNO_STRCASESTR 128 CFLAGS += -DNO_STRCASESTR
125endif 129endif
130ifdef NO_OPENSSL
131 CFLAGS += -DNO_OPENSSL
132 GIT_OPTIONS += NO_OPENSSL=1
133else
134 EXTLIBS += -lcrypto
135endif
126 136
127cgit: $(OBJECTS) libgit 137cgit: $(OBJECTS) libgit
128 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 138 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
129 139
130cgit.o: VERSION 140cgit.o: VERSION
131 141
132-include $(OBJECTS:.o=.d) 142ifneq "$(MAKECMDGOALS)" "clean"
143 -include $(OBJECTS:.o=.d)
144endif
133 145
134libgit: 146libgit:
135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a 147 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a
136 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a 148 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a
137 149
138test: all 150test: all
139 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 151 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
diff --git a/cgit.c b/cgit.c
index 6c7e811..d6146e2 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,6 +1,7 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
4 * 5 *
5 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 7 * (see COPYING for full license text)
@@ -21,7 +22,7 @@ void add_mimetype(const char *name, const char *value)
21{ 22{
22 struct string_list_item *item; 23 struct string_list_item *item;
23 24
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name));
25 item->util = xstrdup(value); 26 item->util = xstrdup(value);
26} 27}
27 28
@@ -60,6 +61,10 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 61 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 62 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 63 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
64 else if (!strcmp(name, "enable-remote-branches"))
65 repo->enable_remote_branches = atoi(value);
66 else if (!strcmp(name, "enable-subject-links"))
67 repo->enable_subject_links = atoi(value);
63 else if (!strcmp(name, "max-stats")) 68 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 69 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 70 else if (!strcmp(name, "module-link"))
@@ -67,7 +72,8 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
67 else if (!strcmp(name, "section")) 72 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 73 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) { 74 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/') 75 char *colon;
76 if (*value == '/' || ((colon = strchr(value, ':')) != NULL && colon != value && *(colon + 1) != '\0'))
71 repo->readme = xstrdup(value); 77 repo->readme = xstrdup(value);
72 else 78 else
73 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 79 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
@@ -131,12 +137,18 @@ void config_cb(const char *name, const char *value)
131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 137 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides")) 138 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value); 139 ctx.cfg.enable_filter_overrides = atoi(value);
140 else if (!strcmp(name, "enable-gitweb-owner"))
141 ctx.cfg.enable_gitweb_owner = atoi(value);
134 else if (!strcmp(name, "enable-index-links")) 142 else if (!strcmp(name, "enable-index-links"))
135 ctx.cfg.enable_index_links = atoi(value); 143 ctx.cfg.enable_index_links = atoi(value);
136 else if (!strcmp(name, "enable-log-filecount")) 144 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 145 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 146 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 147 ctx.cfg.enable_log_linecount = atoi(value);
148 else if (!strcmp(name, "enable-remote-branches"))
149 ctx.cfg.enable_remote_branches = atoi(value);
150 else if (!strcmp(name, "enable-subject-links"))
151 ctx.cfg.enable_subject_links = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 152 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 153 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 154 else if (!strcmp(name, "max-stats"))
@@ -144,7 +156,7 @@ void config_cb(const char *name, const char *value)
144 else if (!strcmp(name, "cache-size")) 156 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 157 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 158 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 159 ctx.cfg.cache_root = xstrdup(expand_macros(value));
148 else if (!strcmp(name, "cache-root-ttl")) 160 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 161 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 162 else if (!strcmp(name, "cache-repo-ttl"))
@@ -161,19 +173,28 @@ void config_cb(const char *name, const char *value)
161 ctx.cfg.commit_filter = new_filter(value, 0); 173 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 174 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 175 ctx.cfg.embedded = atoi(value);
176 else if (!strcmp(name, "max-atom-items"))
177 ctx.cfg.max_atom_items = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 178 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 179 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 180 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 181 ctx.cfg.max_repodesc_len = atoi(value);
182 else if (!strcmp(name, "max-blob-size"))
183 ctx.cfg.max_blob_size = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 184 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 185 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 186 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 187 ctx.cfg.max_commit_count = atoi(value);
188 else if (!strcmp(name, "project-list"))
189 ctx.cfg.project_list = xstrdup(expand_macros(value));
172 else if (!strcmp(name, "scan-path")) 190 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 191 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value); 192 process_cached_repolist(expand_macros(value));
193 else if (ctx.cfg.project_list)
194 scan_projects(expand_macros(value),
195 ctx.cfg.project_list, repo_config);
175 else 196 else
176 scan_tree(value, repo_config); 197 scan_tree(expand_macros(value), repo_config);
177 else if (!strcmp(name, "source-filter")) 198 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1); 199 ctx.cfg.source_filter = new_filter(value, 1);
179 else if (!strcmp(name, "summary-log")) 200 else if (!strcmp(name, "summary-log"))
@@ -182,10 +203,14 @@ void config_cb(const char *name, const char *value)
182 ctx.cfg.summary_branches = atoi(value); 203 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 204 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 205 ctx.cfg.summary_tags = atoi(value);
206 else if (!strcmp(name, "side-by-side-diffs"))
207 ctx.cfg.ssdiff = atoi(value);
185 else if (!strcmp(name, "agefile")) 208 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 209 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 210 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 211 ctx.cfg.renamelimit = atoi(value);
212 else if (!strcmp(name, "remove-suffix"))
213 ctx.cfg.remove_suffix = atoi(value);
189 else if (!strcmp(name, "robots")) 214 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 215 ctx.cfg.robots = xstrdup(value);
191 else if (!strcmp(name, "clone-prefix")) 216 else if (!strcmp(name, "clone-prefix"))
@@ -195,7 +220,7 @@ void config_cb(const char *name, const char *value)
195 else if (!prefixcmp(name, "mimetype.")) 220 else if (!prefixcmp(name, "mimetype."))
196 add_mimetype(name + 9, value); 221 add_mimetype(name + 9, value);
197 else if (!strcmp(name, "include")) 222 else if (!strcmp(name, "include"))
198 parse_configfile(value, config_cb); 223 parse_configfile(expand_macros(value), config_cb);
199} 224}
200 225
201static void querystring_cb(const char *name, const char *value) 226static void querystring_cb(const char *name, const char *value)
@@ -209,6 +234,8 @@ static void querystring_cb(const char *name, const char *value)
209 } else if (!strcmp(name, "p")) { 234 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 235 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 236 } else if (!strcmp(name, "url")) {
237 if (*value == '/')
238 value++;
212 ctx.qry.url = xstrdup(value); 239 ctx.qry.url = xstrdup(value);
213 cgit_parse_url(value); 240 cgit_parse_url(value);
214 } else if (!strcmp(name, "qt")) { 241 } else if (!strcmp(name, "qt")) {
@@ -238,6 +265,14 @@ static void querystring_cb(const char *name, const char *value)
238 ctx.qry.showmsg = atoi(value); 265 ctx.qry.showmsg = atoi(value);
239 } else if (!strcmp(name, "period")) { 266 } else if (!strcmp(name, "period")) {
240 ctx.qry.period = xstrdup(value); 267 ctx.qry.period = xstrdup(value);
268 } else if (!strcmp(name, "ss")) {
269 ctx.qry.ssdiff = atoi(value);
270 } else if (!strcmp(name, "all")) {
271 ctx.qry.show_all = atoi(value);
272 } else if (!strcmp(name, "context")) {
273 ctx.qry.context = atoi(value);
274 } else if (!strcmp(name, "ignorews")) {
275 ctx.qry.ignorews = atoi(value);
241 } 276 }
242} 277}
243 278
@@ -262,15 +297,19 @@ static void prepare_context(struct cgit_context *ctx)
262 ctx->cfg.css = "/cgit.css"; 297 ctx->cfg.css = "/cgit.css";
263 ctx->cfg.logo = "/cgit.png"; 298 ctx->cfg.logo = "/cgit.png";
264 ctx->cfg.local_time = 0; 299 ctx->cfg.local_time = 0;
300 ctx->cfg.enable_gitweb_owner = 1;
265 ctx->cfg.enable_tree_linenumbers = 1; 301 ctx->cfg.enable_tree_linenumbers = 1;
266 ctx->cfg.max_repo_count = 50; 302 ctx->cfg.max_repo_count = 50;
267 ctx->cfg.max_commit_count = 50; 303 ctx->cfg.max_commit_count = 50;
268 ctx->cfg.max_lock_attempts = 5; 304 ctx->cfg.max_lock_attempts = 5;
269 ctx->cfg.max_msg_len = 80; 305 ctx->cfg.max_msg_len = 80;
270 ctx->cfg.max_repodesc_len = 80; 306 ctx->cfg.max_repodesc_len = 80;
307 ctx->cfg.max_blob_size = 0;
271 ctx->cfg.max_stats = 0; 308 ctx->cfg.max_stats = 0;
272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 309 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
310 ctx->cfg.project_list = NULL;
273 ctx->cfg.renamelimit = -1; 311 ctx->cfg.renamelimit = -1;
312 ctx->cfg.remove_suffix = 0;
274 ctx->cfg.robots = "index, nofollow"; 313 ctx->cfg.robots = "index, nofollow";
275 ctx->cfg.root_title = "Git repository browser"; 314 ctx->cfg.root_title = "Git repository browser";
276 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 315 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
@@ -279,6 +318,8 @@ static void prepare_context(struct cgit_context *ctx)
279 ctx->cfg.summary_branches = 10; 318 ctx->cfg.summary_branches = 10;
280 ctx->cfg.summary_log = 10; 319 ctx->cfg.summary_log = 10;
281 ctx->cfg.summary_tags = 10; 320 ctx->cfg.summary_tags = 10;
321 ctx->cfg.max_atom_items = 10;
322 ctx->cfg.ssdiff = 0;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 323 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 324 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS")); 325 ctx->env.https = xstrdupn(getenv("HTTPS"));
@@ -410,6 +451,12 @@ static void process_request(void *cbdata)
410 return; 451 return;
411 } 452 }
412 453
454 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
455 * in-project path limit to be made available at ctx->qry.vpath.
456 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
457 */
458 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
459
413 if (cmd->want_repo && !ctx->repo) { 460 if (cmd->want_repo && !ctx->repo) {
414 cgit_print_http_headers(ctx); 461 cgit_print_http_headers(ctx);
415 cgit_print_docstart(ctx); 462 cgit_print_docstart(ctx);
@@ -541,7 +588,10 @@ static int generate_cached_repolist(const char *path, const char *cached_rc)
541 return errno; 588 return errno;
542 } 589 }
543 idx = cgit_repolist.count; 590 idx = cgit_repolist.count;
544 scan_tree(path, repo_config); 591 if (ctx.cfg.project_list)
592 scan_projects(path, ctx.cfg.project_list, repo_config);
593 else
594 scan_tree(path, repo_config);
545 print_repolist(f, &cgit_repolist, idx); 595 print_repolist(f, &cgit_repolist, idx);
546 if (rename(locked_rc, cached_rc)) 596 if (rename(locked_rc, cached_rc))
547 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 597 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
@@ -555,17 +605,25 @@ static void process_cached_repolist(const char *path)
555 struct stat st; 605 struct stat st;
556 char *cached_rc; 606 char *cached_rc;
557 time_t age; 607 time_t age;
608 unsigned long hash;
558 609
559 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 610 hash = hash_str(path);
560 hash_str(path))); 611 if (ctx.cfg.project_list)
612 hash += hash_str(ctx.cfg.project_list);
613 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, hash));
561 614
562 if (stat(cached_rc, &st)) { 615 if (stat(cached_rc, &st)) {
563 /* Nothing is cached, we need to scan without forking. And 616 /* Nothing is cached, we need to scan without forking. And
564 * if we fail to generate a cached repolist, we need to 617 * if we fail to generate a cached repolist, we need to
565 * invoke scan_tree manually. 618 * invoke scan_tree manually.
566 */ 619 */
567 if (generate_cached_repolist(path, cached_rc)) 620 if (generate_cached_repolist(path, cached_rc)) {
568 scan_tree(path, repo_config); 621 if (ctx.cfg.project_list)
622 scan_projects(path, ctx.cfg.project_list,
623 repo_config);
624 else
625 scan_tree(path, repo_config);
626 }
569 return; 627 return;
570 } 628 }
571 629
@@ -674,7 +732,7 @@ int main(int argc, const char **argv)
674 cgit_repolist.repos = NULL; 732 cgit_repolist.repos = NULL;
675 733
676 cgit_parse_args(argc, argv); 734 cgit_parse_args(argc, argv);
677 parse_configfile(ctx.env.cgit_config, config_cb); 735 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
678 ctx.repo = NULL; 736 ctx.repo = NULL;
679 http_parse_querystring(ctx.qry.raw, querystring_cb); 737 http_parse_querystring(ctx.qry.raw, querystring_cb);
680 738
diff --git a/cgit.css b/cgit.css
index c47ebc9..0c88b65 100644
--- a/cgit.css
+++ b/cgit.css
@@ -64,7 +64,7 @@ table#header td.sub {
64} 64}
65 65
66table.tabs { 66table.tabs {
67 /* border-bottom: solid 2px #ccc; */ 67 border-bottom: solid 3px #ccc;
68 border-collapse: collapse; 68 border-collapse: collapse;
69 margin-top: 2em; 69 margin-top: 2em;
70 margin-bottom: 0px; 70 margin-bottom: 0px;
@@ -102,10 +102,16 @@ table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.path {
106 margin: 0px;
107 padding: 5px 2em 2px 2em;
108 color: #000;
109 background-color: #eee;
110}
111
105div.content { 112div.content {
106 margin: 0px; 113 margin: 0px;
107 padding: 2em; 114 padding: 2em;
108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 115 border-bottom: solid 3px #ccc;
110} 116}
111 117
@@ -158,10 +164,26 @@ table.list td.logmsg {
158 padding: 1em 0.5em 2em 0.5em; 164 padding: 1em 0.5em 2em 0.5em;
159} 165}
160 166
167table.list td.lognotes-label {
168 text-align:right;
169 vertical-align:top;
170}
171
172table.list td.lognotes {
173 font-family: monospace;
174 white-space: pre;
175 padding: 0em 0.5em 2em 0.5em;
176}
177
161table.list td a { 178table.list td a {
162 color: black; 179 color: black;
163} 180}
164 181
182table.list td a.ls-dir {
183 font-weight: bold;
184 color: #00f;
185}
186
165table.list td a:hover { 187table.list td a:hover {
166 color: #00f; 188 color: #00f;
167} 189}
@@ -315,6 +337,24 @@ div.commit-msg {
315 font-family: monospace; 337 font-family: monospace;
316} 338}
317 339
340div.notes-header {
341 font-weight: bold;
342 padding-top: 1.5em;
343}
344
345div.notes {
346 white-space: pre;
347 font-family: monospace;
348 border: solid 1px #ee9;
349 background-color: #ffd;
350 padding: 0.3em 2em 0.3em 1em;
351 float: left;
352}
353
354div.notes-footer {
355 clear: left;
356}
357
318div.diffstat-header { 358div.diffstat-header {
319 font-weight: bold; 359 font-weight: bold;
320 padding-top: 1.5em; 360 padding-top: 1.5em;
@@ -520,7 +560,10 @@ a.deco {
520 border: solid 1px #770000; 560 border: solid 1px #770000;
521} 561}
522 562
523div.commit-subject a { 563div.commit-subject a.branch-deco,
564div.commit-subject a.tag-deco,
565div.commit-subject a.remote-deco,
566div.commit-subject a.deco {
524 margin-left: 1em; 567 margin-left: 1em;
525 font-size: 75%; 568 font-size: 75%;
526} 569}
@@ -601,3 +644,102 @@ table.hgraph div.bar {
601 background-color: #eee; 644 background-color: #eee;
602 height: 1em; 645 height: 1em;
603} 646}
647
648table.ssdiff {
649 width: 100%;
650}
651
652table.ssdiff td {
653 font-size: 75%;
654 font-family: monospace;
655 white-space: pre;
656 padding: 1px 4px 1px 4px;
657 border-left: solid 1px #aaa;
658 border-right: solid 1px #aaa;
659}
660
661table.ssdiff td.add {
662 color: black;
663 background: #cfc;
664 min-width: 50%;
665}
666
667table.ssdiff td.add_dark {
668 color: black;
669 background: #aca;
670 min-width: 50%;
671}
672
673table.ssdiff span.add {
674 background: #cfc;
675 font-weight: bold;
676}
677
678table.ssdiff td.del {
679 color: black;
680 background: #fcc;
681 min-width: 50%;
682}
683
684table.ssdiff td.del_dark {
685 color: black;
686 background: #caa;
687 min-width: 50%;
688}
689
690table.ssdiff span.del {
691 background: #fcc;
692 font-weight: bold;
693}
694
695table.ssdiff td.changed {
696 color: black;
697 background: #ffc;
698 min-width: 50%;
699}
700
701table.ssdiff td.changed_dark {
702 color: black;
703 background: #cca;
704 min-width: 50%;
705}
706
707table.ssdiff td.lineno {
708 color: black;
709 background: #eee;
710 text-align: right;
711 width: 3em;
712 min-width: 3em;
713}
714
715table.ssdiff td.hunk {
716 color: #black;
717 background: #ccf;
718 border-top: solid 1px #aaa;
719 border-bottom: solid 1px #aaa;
720}
721
722table.ssdiff td.head {
723 border-top: solid 1px #aaa;
724 border-bottom: solid 1px #aaa;
725}
726
727table.ssdiff td.head div.head {
728 font-weight: bold;
729 color: black;
730}
731
732table.ssdiff td.foot {
733 border-top: solid 1px #aaa;
734 border-left: none;
735 border-right: none;
736 border-bottom: none;
737}
738
739table.ssdiff td.space {
740 border: none;
741}
742
743table.ssdiff td.space div {
744 min-height: 3em;
745} \ No newline at end of file
diff --git a/cgit.h b/cgit.h
index 6c6c460..4090cd4 100644
--- a/cgit.h
+++ b/cgit.h
@@ -19,6 +19,7 @@
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 23
23 24
24/* 25/*
@@ -72,6 +73,8 @@ struct cgit_repo {
72 int snapshots; 73 int snapshots;
73 int enable_log_filecount; 74 int enable_log_filecount;
74 int enable_log_linecount; 75 int enable_log_linecount;
76 int enable_remote_branches;
77 int enable_subject_links;
75 int max_stats; 78 int max_stats;
76 time_t mtime; 79 time_t mtime;
77 struct cgit_filter *about_filter; 80 struct cgit_filter *about_filter;
@@ -143,6 +146,11 @@ struct cgit_query {
143 int nohead; 146 int nohead;
144 char *sort; 147 char *sort;
145 int showmsg; 148 int showmsg;
149 int ssdiff;
150 int show_all;
151 int context;
152 int ignorews;
153 char *vpath;
146}; 154};
147 155
148struct cgit_config { 156struct cgit_config {
@@ -159,6 +167,7 @@ struct cgit_config {
159 char *logo; 167 char *logo;
160 char *logo_link; 168 char *logo_link;
161 char *module_link; 169 char *module_link;
170 char *project_list;
162 char *robots; 171 char *robots;
163 char *root_title; 172 char *root_title;
164 char *root_desc; 173 char *root_desc;
@@ -175,25 +184,32 @@ struct cgit_config {
175 int cache_static_ttl; 184 int cache_static_ttl;
176 int embedded; 185 int embedded;
177 int enable_filter_overrides; 186 int enable_filter_overrides;
187 int enable_gitweb_owner;
178 int enable_index_links; 188 int enable_index_links;
179 int enable_log_filecount; 189 int enable_log_filecount;
180 int enable_log_linecount; 190 int enable_log_linecount;
191 int enable_remote_branches;
192 int enable_subject_links;
181 int enable_tree_linenumbers; 193 int enable_tree_linenumbers;
182 int local_time; 194 int local_time;
195 int max_atom_items;
183 int max_repo_count; 196 int max_repo_count;
184 int max_commit_count; 197 int max_commit_count;
185 int max_lock_attempts; 198 int max_lock_attempts;
186 int max_msg_len; 199 int max_msg_len;
187 int max_repodesc_len; 200 int max_repodesc_len;
201 int max_blob_size;
188 int max_stats; 202 int max_stats;
189 int nocache; 203 int nocache;
190 int noplainemail; 204 int noplainemail;
191 int noheader; 205 int noheader;
192 int renamelimit; 206 int renamelimit;
207 int remove_suffix;
193 int snapshots; 208 int snapshots;
194 int summary_branches; 209 int summary_branches;
195 int summary_log; 210 int summary_log;
196 int summary_tags; 211 int summary_tags;
212 int ssdiff;
197 struct string_list mimetypes; 213 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 214 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 215 struct cgit_filter *commit_filter;
@@ -268,11 +284,12 @@ extern void *cgit_free_commitinfo(struct commitinfo *info);
268extern int cgit_diff_files(const unsigned char *old_sha1, 284extern int cgit_diff_files(const unsigned char *old_sha1,
269 const unsigned char *new_sha1, 285 const unsigned char *new_sha1,
270 unsigned long *old_size, unsigned long *new_size, 286 unsigned long *old_size, unsigned long *new_size,
271 int *binary, linediff_fn fn); 287 int *binary, int context, int ignorews,
288 linediff_fn fn);
272 289
273extern void cgit_diff_tree(const unsigned char *old_sha1, 290extern void cgit_diff_tree(const unsigned char *old_sha1,
274 const unsigned char *new_sha1, 291 const unsigned char *new_sha1,
275 filepair_fn fn, const char *prefix); 292 filepair_fn fn, const char *prefix, int ignorews);
276 293
277extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 294extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
278 295
@@ -291,4 +308,6 @@ extern int cgit_close_filter(struct cgit_filter *filter);
291 308
292extern int readfile(const char *path, char **buf, size_t *size); 309extern int readfile(const char *path, char **buf, size_t *size);
293 310
311extern char *expand_macros(const char *txt);
312
294#endif /* CGIT_H */ 313#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0c13485..c643fae 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -95,6 +95,11 @@ enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 95 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 96 overridden in repository-specific cgitrc files. Default value: none.
97 97
98enable-gitweb-owner::
99 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.
101 Default value: "1". See also: scan-path.
102
98enable-index-links:: 103enable-index-links::
99 Flag which, when set to "1", will make cgit generate extra links for 104 Flag which, when set to "1", will make cgit generate extra links for
100 each repo in the repository index (specifically, to the "summary", 105 each repo in the repository index (specifically, to the "summary",
@@ -110,6 +115,17 @@ enable-log-linecount::
110 and removed lines for each commit on the repository log page. Default 115 and removed lines for each commit on the repository log page. Default
111 value: "0". 116 value: "0".
112 117
118enable-remote-branches::
119 Flag which, when set to "1", will make cgit display remote branches
120 in the summary and refs views. Default value: "0". See also:
121 "repo.enable-remote-branches".
122
123enable-subject-links::
124 Flag which, when set to "1", will make cgit use the subject of the
125 parent commit as link text when generating links to parent commits
126 in commit view. Default value: "0". See also:
127 "repo.enable-subject-links".
128
113enable-tree-linenumbers:: 129enable-tree-linenumbers::
114 Flag which, when set to "1", will make cgit generate linenumber links 130 Flag which, when set to "1", will make cgit generate linenumber links
115 for plaintext blobs printed in the tree view. Default value: "1". 131 for plaintext blobs printed in the tree view. Default value: "1".
@@ -161,6 +177,10 @@ logo-link::
161 calculated url of the repository index page will be used. Default 177 calculated url of the repository index page will be used. Default
162 value: none. 178 value: none.
163 179
180max-atom-items::
181 Specifies the number of items to display in atom feeds view. Default
182 value: "10".
183
164max-commit-count:: 184max-commit-count::
165 Specifies the number of entries to list per page in "log" view. Default 185 Specifies the number of entries to list per page in "log" view. Default
166 value: "50". 186 value: "50".
@@ -177,6 +197,10 @@ max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 197 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 198 on the repository index page. Default value: "80".
179 199
200max-blob-size::
201 Specifies the maximum size of a blob to display HTML for in KBytes.
202 Default value: "0" (limit disabled).
203
180max-stats:: 204max-stats::
181 Set the default maximum statistics period. Valid values are "week", 205 Set the default maximum statistics period. Valid values are "week",
182 "month", "quarter" and "year". If unspecified, statistics are 206 "month", "quarter" and "year". If unspecified, statistics are
@@ -205,6 +229,16 @@ noheader::
205 Flag which, when set to "1", will make cgit omit the standard header 229 Flag which, when set to "1", will make cgit omit the standard header
206 on all pages. Default value: none. See also: "embedded". 230 on all pages. Default value: none. See also: "embedded".
207 231
232project-list::
233 A list of subdirectories inside of scan-path, relative to it, that
234 should loaded as git repositories. This must be defined prior to
235 scan-path. Default value: none. See also: scan-path.
236
237remove-suffix::
238 If set to "1" and scan-path is enabled, if any repositories are found
239 with a suffix of ".git", this suffix will be removed for the url and
240 name. Default value: "0". See also: scan-path.
241
208renamelimit:: 242renamelimit::
209 Maximum number of files to consider when detecting renames. The value 243 Maximum number of files to consider when detecting renames. The value
210 "-1" uses the compiletime value in git (for further info, look at 244 "-1" uses the compiletime value in git (for further info, look at
@@ -234,13 +268,20 @@ root-title::
234scan-path:: 268scan-path::
235 A path which will be scanned for repositories. If caching is enabled, 269 A path which will be scanned for repositories. If caching is enabled,
236 the result will be cached as a cgitrc include-file in the cache 270 the result will be cached as a cgitrc include-file in the cache
237 directory. Default value: none. See also: cache-scanrc-ttl. 271 directory. If project-list has been defined prior to scan-path,
272 scan-path loads only the directories listed in the file pointed to by
273 project-list. Default value: none. See also: cache-scanrc-ttl,
274 project-list.
238 275
239section:: 276section::
240 The name of the current repository section - all repositories defined 277 The name of the current repository section - all repositories defined
241 after this option will inherit the current section name. Default value: 278 after this option will inherit the current section name. Default value:
242 none. 279 none.
243 280
281side-by-side-diffs::
282 If set to "1" shows side-by-side diffs instead of unidiffs per
283 default. Default value: "0".
284
244snapshots:: 285snapshots::
245 Text which specifies the default set of snapshot formats generated by 286 Text which specifies the default set of snapshot formats generated by
246 cgit. The value is a space-separated list of zero or more of the 287 cgit. The value is a space-separated list of zero or more of the
@@ -304,6 +345,14 @@ repo.enable-log-linecount::
304 A flag which can be used to disable the global setting 345 A flag which can be used to disable the global setting
305 `enable-log-linecount'. Default value: none. 346 `enable-log-linecount'. Default value: none.
306 347
348repo.enable-remote-branches::
349 Flag which, when set to "1", will make cgit display remote branches
350 in the summary and refs views. Default value: <enable-remote-branches>.
351
352repo.enable-subject-links::
353 A flag which can be used to override the global setting
354 `enable-subject-links'. Default value: none.
355
307repo.max-stats:: 356repo.max-stats::
308 Override the default maximum statistics period. Valid values are equal 357 Override the default maximum statistics period. Valid values are equal
309 to the values specified for the global "max-stats" setting. Default 358 to the values specified for the global "max-stats" setting. Default
@@ -322,7 +371,9 @@ repo.path::
322 371
323repo.readme:: 372repo.readme::
324 A path (relative to <repo.path>) which specifies a file to include 373 A path (relative to <repo.path>) which specifies a file to include
325 verbatim as the "About" page for this repo. Default value: none. 374 verbatim as the "About" page for this repo. You may also specify a
375 git refspec by head or by hash by prepending the refspec followed by
376 a colon. For example, "master:docs/readme.mkd" Default value: none.
326 377
327repo.snapshots:: 378repo.snapshots::
328 A mask of allowed snapshot-formats for this repo, restricted by the 379 A mask of allowed snapshot-formats for this repo, restricted by the
@@ -413,7 +464,7 @@ snapshots=tar.gz tar.bz2 zip
413## List of common mimetypes 464## List of common mimetypes
414## 465##
415 466
416mimetype.git=image/git 467mimetype.gif=image/gif
417mimetype.html=text/html 468mimetype.html=text/html
418mimetype.jpg=image/jpeg 469mimetype.jpg=image/jpeg
419mimetype.jpeg=image/jpeg 470mimetype.jpeg=image/jpeg
@@ -499,3 +550,4 @@ will generate the following html element:
499AUTHOR 550AUTHOR
500------ 551------
501Lars Hjemli <hjemli@gmail.com> 552Lars Hjemli <hjemli@gmail.com>
553Jason A. Donenfeld <Jason@zx2c4.com>
diff --git a/cmd.c b/cmd.c
index 766f903..6dc9f5e 100644
--- a/cmd.c
+++ b/cmd.c
@@ -33,7 +33,7 @@ static void HEAD_fn(struct cgit_context *ctx)
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items);
37} 37}
38 38
39static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
@@ -51,7 +51,7 @@ static void blob_fn(struct cgit_context *ctx)
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); 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)
@@ -90,7 +90,7 @@ static void repolist_fn(struct cgit_context *ctx)
90 90
91static void patch_fn(struct cgit_context *ctx) 91static void patch_fn(struct cgit_context *ctx)
92{ 92{
93 cgit_print_patch(ctx->qry.sha1); 93 cgit_print_patch(ctx->qry.sha1, ctx->qry.path);
94} 94}
95 95
96static void plain_fn(struct cgit_context *ctx) 96static void plain_fn(struct cgit_context *ctx)
@@ -129,31 +129,31 @@ static void tree_fn(struct cgit_context *ctx)
129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
130} 130}
131 131
132#define def_cmd(name, want_repo, want_layout) \ 132#define def_cmd(name, want_repo, want_layout, want_vpath) \
133 {#name, name##_fn, want_repo, want_layout} 133 {#name, name##_fn, want_repo, want_layout, want_vpath}
134 134
135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
136{ 136{
137 static struct cgit_cmd cmds[] = { 137 static struct cgit_cmd cmds[] = {
138 def_cmd(HEAD, 1, 0), 138 def_cmd(HEAD, 1, 0, 0),
139 def_cmd(atom, 1, 0), 139 def_cmd(atom, 1, 0, 0),
140 def_cmd(about, 0, 1), 140 def_cmd(about, 0, 1, 0),
141 def_cmd(blob, 1, 0), 141 def_cmd(blob, 1, 0, 0),
142 def_cmd(commit, 1, 1), 142 def_cmd(commit, 1, 1, 1),
143 def_cmd(diff, 1, 1), 143 def_cmd(diff, 1, 1, 1),
144 def_cmd(info, 1, 0), 144 def_cmd(info, 1, 0, 0),
145 def_cmd(log, 1, 1), 145 def_cmd(log, 1, 1, 1),
146 def_cmd(ls_cache, 0, 0), 146 def_cmd(ls_cache, 0, 0, 0),
147 def_cmd(objects, 1, 0), 147 def_cmd(objects, 1, 0, 0),
148 def_cmd(patch, 1, 0), 148 def_cmd(patch, 1, 0, 1),
149 def_cmd(plain, 1, 0), 149 def_cmd(plain, 1, 0, 0),
150 def_cmd(refs, 1, 1), 150 def_cmd(refs, 1, 1, 0),
151 def_cmd(repolist, 0, 0), 151 def_cmd(repolist, 0, 0, 0),
152 def_cmd(snapshot, 1, 0), 152 def_cmd(snapshot, 1, 0, 0),
153 def_cmd(stats, 1, 1), 153 def_cmd(stats, 1, 1, 1),
154 def_cmd(summary, 1, 1), 154 def_cmd(summary, 1, 1, 0),
155 def_cmd(tag, 1, 1), 155 def_cmd(tag, 1, 1, 0),
156 def_cmd(tree, 1, 1), 156 def_cmd(tree, 1, 1, 1),
157 }; 157 };
158 int i; 158 int i;
159 159
diff --git a/cmd.h b/cmd.h
index ec9e691..8dc01bd 100644
--- a/cmd.h
+++ b/cmd.h
@@ -7,7 +7,8 @@ struct cgit_cmd {
7 const char *name; 7 const char *name;
8 cgit_cmd_fn fn; 8 cgit_cmd_fn fn;
9 unsigned int want_repo:1, 9 unsigned int want_repo:1,
10 want_layout:1; 10 want_layout:1,
11 want_vpath:1;
11}; 12};
12 13
13extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx); 14extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx);
diff --git a/filters/commit-links.sh b/filters/commit-links.sh
index 165a533..110c609 100755
--- a/filters/commit-links.sh
+++ b/filters/commit-links.sh
@@ -1,12 +1,14 @@
1#!/bin/sh 1#!/bin/sh
2# This script can be used to generate links in commit messages - the first 2# This script can be used to generate links in commit messages.
3# sed expression generates links to commits referenced by their SHA1, while
4# the second expression generates links to a fictional bugtracker.
5# 3#
6# To use this script, refer to this file with either the commit-filter or the 4# To use this script, refer to this file with either the commit-filter or the
7# repo.commit-filter options in cgitrc. 5# repo.commit-filter options in cgitrc.
8 6
9sed -re ' 7# This expression generates links to commits referenced by their SHA1.
10s|\b([0-9a-fA-F]{8,40})\b|<a href="./?id=\1">\1</a>|g 8regex=$regex'
11s| #([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g 9s|\b([0-9a-fA-F]{8,40})\b|<a href="./?id=\1">\1</a>|g'
12' 10# This expression generates links to a fictional bugtracker.
11regex=$regex'
12s| #([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g'
13
14sed -re "$regex"
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
index 999ad0c..6b1c576 100755
--- a/filters/syntax-highlighting.sh
+++ b/filters/syntax-highlighting.sh
@@ -3,6 +3,10 @@
3# tree-view by refering to this file with the source-filter or repo.source- 3# tree-view by refering to this file with the source-filter or repo.source-
4# filter options in cgitrc. 4# filter options in cgitrc.
5# 5#
6# This script requires a shell supporting the ${var##pattern} syntax.
7# It is supported by at least dash and bash, however busybox environments
8# might have to use an external call to sed instead.
9#
6# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax 10# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
7# highlighting, so you'll probably want something like the following included 11# highlighting, so you'll probably want something like the following included
8# in your css file (generated by highlight 2.4.8 and adapted for cgit): 12# in your css file (generated by highlight 2.4.8 and adapted for cgit):
@@ -20,20 +24,11 @@
20# table.blob .kwc { color:#000000; font-weight:bold; } 24# table.blob .kwc { color:#000000; font-weight:bold; }
21# table.blob .kwd { color:#010181; } 25# table.blob .kwd { color:#010181; }
22 26
23case "$1" in 27# store filename and extension in local vars
24 *.c) 28BASENAME="$1"
25 highlight -f -I -X -S c 29EXTENSION="${BASENAME##*.}"
26 ;; 30
27 *.h) 31# map Makefile and Makefile.* to .mk
28 highlight -f -I -X -S c 32[ "${BASENAME%%.*}" == "Makefile" ] && EXTENSION=mk
29 ;; 33
30 *.sh) 34exec highlight --force -f -I -X -S $EXTENSION 2>/dev/null
31 highlight -f -I -X -S sh
32 ;;
33 *.css)
34 highlight -f -I -X -S css
35 ;;
36 *)
37 highlight -f -I -X -S txt
38 ;;
39esac
diff --git a/git b/git
Subproject 7fb6bcff2dece2ff9fbc5ebfe526d9b2a7e764c Subproject 8c67c392e1620fc3b749aa9e0b8da13bd84226f
diff --git a/html.c b/html.c
index d86b2c1..eaabf72 100644
--- a/html.c
+++ b/html.c
@@ -13,6 +13,32 @@
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!$()*,./:;@- */
17static const char* url_escape_table[256] = {
18 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09",
19 "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13",
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,
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,
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",
26 "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85",
27 "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
28 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99",
29 "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3",
30 "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad",
31 "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
32 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1",
33 "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb",
34 "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5",
35 "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
36 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9",
37 "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3",
38 "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd",
39 "%fe", "%ff"
40};
41
16int htmlfd = STDOUT_FILENO; 42int htmlfd = STDOUT_FILENO;
17 43
18char *fmt(const char *format, ...) 44char *fmt(const char *format, ...)
@@ -63,9 +89,9 @@ void html_status(int code, const char *msg, int more_headers)
63 html("\n"); 89 html("\n");
64} 90}
65 91
66void html_txt(char *txt) 92void html_txt(const char *txt)
67{ 93{
68 char *t = txt; 94 const char *t = txt;
69 while(t && *t){ 95 while(t && *t){
70 int c = *t; 96 int c = *t;
71 if (c=='<' || c=='>' || c=='&') { 97 if (c=='<' || c=='>' || c=='&') {
@@ -84,9 +110,9 @@ void html_txt(char *txt)
84 html(txt); 110 html(txt);
85} 111}
86 112
87void html_ntxt(int len, char *txt) 113void html_ntxt(int len, const char *txt)
88{ 114{
89 char *t = txt; 115 const char *t = txt;
90 while(t && *t && len--){ 116 while(t && *t && len--){
91 int c = *t; 117 int c = *t;
92 if (c=='<' || c=='>' || c=='&') { 118 if (c=='<' || c=='>' || c=='&') {
@@ -107,9 +133,9 @@ void html_ntxt(int len, char *txt)
107 html("..."); 133 html("...");
108} 134}
109 135
110void html_attr(char *txt) 136void html_attr(const char *txt)
111{ 137{
112 char *t = txt; 138 const char *t = txt;
113 while(t && *t){ 139 while(t && *t){
114 int c = *t; 140 int c = *t;
115 if (c=='<' || c=='>' || c=='\'' || c=='\"') { 141 if (c=='<' || c=='>' || c=='\'' || c=='\"') {
@@ -130,14 +156,15 @@ void html_attr(char *txt)
130 html(txt); 156 html(txt);
131} 157}
132 158
133void html_url_path(char *txt) 159void html_url_path(const char *txt)
134{ 160{
135 char *t = txt; 161 const char *t = txt;
136 while(t && *t){ 162 while(t && *t){
137 int c = *t; 163 int c = *t;
138 if (c=='"' || c=='#' || c=='\'' || c=='?') { 164 const char *e = url_escape_table[c];
165 if (e && c!='+' && c!='&' && c!='+') {
139 write(htmlfd, txt, t - txt); 166 write(htmlfd, txt, t - txt);
140 write(htmlfd, fmt("%%%2x", c), 3); 167 write(htmlfd, e, 3);
141 txt = t+1; 168 txt = t+1;
142 } 169 }
143 t++; 170 t++;
@@ -146,14 +173,15 @@ void html_url_path(char *txt)
146 html(txt); 173 html(txt);
147} 174}
148 175
149void html_url_arg(char *txt) 176void html_url_arg(const char *txt)
150{ 177{
151 char *t = txt; 178 const char *t = txt;
152 while(t && *t){ 179 while(t && *t){
153 int c = *t; 180 int c = *t;
154 if (c=='"' || c=='#' || c=='%' || c=='&' || c=='\'' || c=='+' || c=='?') { 181 const char *e = url_escape_table[c];
182 if (e) {
155 write(htmlfd, txt, t - txt); 183 write(htmlfd, txt, t - txt);
156 write(htmlfd, fmt("%%%2x", c), 3); 184 write(htmlfd, e, 3);
157 txt = t+1; 185 txt = t+1;
158 } 186 }
159 t++; 187 t++;
@@ -162,7 +190,7 @@ void html_url_arg(char *txt)
162 html(txt); 190 html(txt);
163} 191}
164 192
165void html_hidden(char *name, char *value) 193void html_hidden(const char *name, const char *value)
166{ 194{
167 html("<input type='hidden' name='"); 195 html("<input type='hidden' name='");
168 html_attr(name); 196 html_attr(name);
@@ -171,7 +199,7 @@ void html_hidden(char *name, char *value)
171 html("'/>"); 199 html("'/>");
172} 200}
173 201
174void html_option(char *value, char *text, char *selected_value) 202void html_option(const char *value, const char *text, const char *selected_value)
175{ 203{
176 html("<option value='"); 204 html("<option value='");
177 html_attr(value); 205 html_attr(value);
@@ -183,7 +211,7 @@ void html_option(char *value, char *text, char *selected_value)
183 html("</option>\n"); 211 html("</option>\n");
184} 212}
185 213
186void html_link_open(char *url, char *title, char *class) 214void html_link_open(const char *url, const char *title, const char *class)
187{ 215{
188 html("<a href='"); 216 html("<a href='");
189 html_attr(url); 217 html_attr(url);
@@ -258,14 +286,14 @@ char *convert_query_hexchar(char *txt)
258 } 286 }
259} 287}
260 288
261int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)) 289int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const char *value))
262{ 290{
263 char *t, *value = NULL, c; 291 char *t, *txt, *value = NULL, c;
264 292
265 if (!txt) 293 if (!txt_)
266 return 0; 294 return 0;
267 295
268 t = txt = strdup(txt); 296 t = txt = strdup(txt_);
269 if (t == NULL) { 297 if (t == NULL) {
270 printf("Out of memory\n"); 298 printf("Out of memory\n");
271 exit(1); 299 exit(1);
diff --git a/html.h b/html.h
index a55d4b2..16d55ec 100644
--- a/html.h
+++ b/html.h
@@ -7,18 +7,18 @@ extern void html_raw(const char *txt, size_t size);
7extern void html(const char *txt); 7extern void html(const char *txt);
8extern void htmlf(const char *format,...); 8extern void htmlf(const char *format,...);
9extern void html_status(int code, const char *msg, int more_headers); 9extern void html_status(int code, const char *msg, int more_headers);
10extern void html_txt(char *txt); 10extern void html_txt(const char *txt);
11extern void html_ntxt(int len, char *txt); 11extern void html_ntxt(int len, const char *txt);
12extern void html_attr(char *txt); 12extern void html_attr(const char *txt);
13extern void html_url_path(char *txt); 13extern void html_url_path(const char *txt);
14extern void html_url_arg(char *txt); 14extern void html_url_arg(const char *txt);
15extern void html_hidden(char *name, char *value); 15extern void html_hidden(const char *name, const char *value);
16extern void html_option(char *value, char *text, char *selected_value); 16extern void html_option(const char *value, const char *text, const char *selected_value);
17extern void html_link_open(char *url, char *title, char *class); 17extern void html_link_open(const char *url, const char *title, const char *class);
18extern void html_link_close(void); 18extern void html_link_close(void);
19extern void html_fileperm(unsigned short mode); 19extern void html_fileperm(unsigned short mode);
20extern int html_include(const char *filename); 20extern int html_include(const char *filename);
21 21
22extern int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)); 22extern int http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value));
23 23
24#endif /* HTML_H */ 24#endif /* HTML_H */
diff --git a/scan-tree.c b/scan-tree.c
index dbca797..e987824 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,3 +1,12 @@
1/* scan-tree.c
2 *
3 * Copyright (C) 2008-2009 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 *
6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text)
8 */
9
1#include "cgit.h" 10#include "cgit.h"
2#include "configfile.h" 11#include "configfile.h"
3#include "html.h" 12#include "html.h"
@@ -38,12 +47,20 @@ static int is_git_dir(const char *path)
38 47
39struct cgit_repo *repo; 48struct cgit_repo *repo;
40repo_config_fn config_fn; 49repo_config_fn config_fn;
50char *owner;
41 51
42static void repo_config(const char *name, const char *value) 52static void repo_config(const char *name, const char *value)
43{ 53{
44 config_fn(repo, name, value); 54 config_fn(repo, name, value);
45} 55}
46 56
57static int git_owner_config(const char *key, const char *value, void *cb)
58{
59 if (!strcmp(key, "gitweb.owner"))
60 owner = xstrdup(value);
61 return 0;
62}
63
47static void add_repo(const char *base, const char *path, repo_config_fn fn) 64static void add_repo(const char *base, const char *path, repo_config_fn fn)
48{ 65{
49 struct stat st; 66 struct stat st;
@@ -56,11 +73,12 @@ static void add_repo(const char *base, const char *path, repo_config_fn fn)
56 path, strerror(errno), errno); 73 path, strerror(errno), errno);
57 return; 74 return;
58 } 75 }
59 if ((pwd = getpwuid(st.st_uid)) == NULL) { 76 if (!stat(fmt("%s/noweb", path), &st))
60 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
61 path, strerror(errno), errno);
62 return; 77 return;
63 } 78
79 owner = NULL;
80 if (ctx.cfg.enable_gitweb_owner)
81 git_config_from_file(git_owner_config, fmt("%s/config", path), NULL);
64 if (base == path) 82 if (base == path)
65 p = fmt("%s", path); 83 p = fmt("%s", path);
66 else 84 else
@@ -70,12 +88,23 @@ static void add_repo(const char *base, const char *path, repo_config_fn fn)
70 p[strlen(p) - 5] = '\0'; 88 p[strlen(p) - 5] = '\0';
71 89
72 repo = cgit_add_repo(xstrdup(p)); 90 repo = cgit_add_repo(xstrdup(p));
91 if (ctx.cfg.remove_suffix)
92 if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git"))
93 *p = '\0';
73 repo->name = repo->url; 94 repo->name = repo->url;
74 repo->path = xstrdup(path); 95 repo->path = xstrdup(path);
75 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; 96 while (!owner) {
76 if (p) 97 if ((pwd = getpwuid(st.st_uid)) == NULL) {
77 *p = '\0'; 98 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
78 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 99 path, strerror(errno), errno);
100 break;
101 }
102 if (pwd->pw_gecos)
103 if ((p = strchr(pwd->pw_gecos, ',')))
104 *p = '\0';
105 owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
106 }
107 repo->owner = owner;
79 108
80 p = fmt("%s/description", path); 109 p = fmt("%s/description", path);
81 if (!stat(p, &st)) 110 if (!stat(p, &st))
@@ -140,6 +169,34 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
140 closedir(dir); 169 closedir(dir);
141} 170}
142 171
172#define lastc(s) s[strlen(s) - 1]
173
174void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
175{
176 char line[MAX_PATH * 2], *z;
177 FILE *projects;
178 int err;
179
180 projects = fopen(projectsfile, "r");
181 if (!projects) {
182 fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
183 projectsfile, strerror(errno), errno);
184 }
185 while (fgets(line, sizeof(line), projects) != NULL) {
186 for (z = &lastc(line);
187 strlen(line) && strchr("\n\r", *z);
188 z = &lastc(line))
189 *z = '\0';
190 if (strlen(line))
191 scan_path(path, fmt("%s/%s", path, line), fn);
192 }
193 if ((err = ferror(projects))) {
194 fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
195 projectsfile, strerror(err), err);
196 }
197 fclose(projects);
198}
199
143void scan_tree(const char *path, repo_config_fn fn) 200void scan_tree(const char *path, repo_config_fn fn)
144{ 201{
145 scan_path(path, path, fn); 202 scan_path(path, path, fn);
diff --git a/scan-tree.h b/scan-tree.h
index 11539f4..1afbd4b 100644
--- a/scan-tree.h
+++ b/scan-tree.h
@@ -1,3 +1,2 @@
1 1extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn);
2
3extern void scan_tree(const char *path, repo_config_fn fn); 2extern void scan_tree(const char *path, repo_config_fn fn);
diff --git a/shared.c b/shared.c
index 6adf2b6..b42c2a2 100644
--- a/shared.c
+++ b/shared.c
@@ -10,7 +10,6 @@
10 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd;
14 13
15int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
16{ 15{
@@ -59,6 +58,8 @@ struct cgit_repo *cgit_add_repo(const char *url)
59 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->enable_subject_links = ctx.cfg.enable_subject_links;
62 ret->max_stats = ctx.cfg.max_stats; 63 ret->max_stats = ctx.cfg.max_stats;
63 ret->module_link = ctx.cfg.module_link; 64 ret->module_link = ctx.cfg.module_link;
64 ret->readme = NULL; 65 ret->readme = NULL;
@@ -262,7 +263,8 @@ int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
262 263
263int cgit_diff_files(const unsigned char *old_sha1, 264int cgit_diff_files(const unsigned char *old_sha1,
264 const unsigned char *new_sha1, unsigned long *old_size, 265 const unsigned char *new_sha1, unsigned long *old_size,
265 unsigned long *new_size, int *binary, linediff_fn fn) 266 unsigned long *new_size, int *binary, int context,
267 int ignorews, linediff_fn fn)
266{ 268{
267 mmfile_t file1, file2; 269 mmfile_t file1, file2;
268 xpparam_t diff_params; 270 xpparam_t diff_params;
@@ -289,7 +291,9 @@ int cgit_diff_files(const unsigned char *old_sha1,
289 memset(&emit_params, 0, sizeof(emit_params)); 291 memset(&emit_params, 0, sizeof(emit_params));
290 memset(&emit_cb, 0, sizeof(emit_cb)); 292 memset(&emit_cb, 0, sizeof(emit_cb));
291 diff_params.flags = XDF_NEED_MINIMAL; 293 diff_params.flags = XDF_NEED_MINIMAL;
292 emit_params.ctxlen = 3; 294 if (ignorews)
295 diff_params.flags |= XDF_IGNORE_WHITESPACE;
296 emit_params.ctxlen = context > 0 ? context : 3;
293 emit_params.flags = XDL_EMIT_FUNCNAMES; 297 emit_params.flags = XDL_EMIT_FUNCNAMES;
294 emit_cb.outf = filediff_cb; 298 emit_cb.outf = filediff_cb;
295 emit_cb.priv = fn; 299 emit_cb.priv = fn;
@@ -303,7 +307,7 @@ int cgit_diff_files(const unsigned char *old_sha1,
303 307
304void cgit_diff_tree(const unsigned char *old_sha1, 308void cgit_diff_tree(const unsigned char *old_sha1,
305 const unsigned char *new_sha1, 309 const unsigned char *new_sha1,
306 filepair_fn fn, const char *prefix) 310 filepair_fn fn, const char *prefix, int ignorews)
307{ 311{
308 struct diff_options opt; 312 struct diff_options opt;
309 int ret; 313 int ret;
@@ -314,6 +318,8 @@ void cgit_diff_tree(const unsigned char *old_sha1,
314 opt.detect_rename = 1; 318 opt.detect_rename = 1;
315 opt.rename_limit = ctx.cfg.renamelimit; 319 opt.rename_limit = ctx.cfg.renamelimit;
316 DIFF_OPT_SET(&opt, RECURSIVE); 320 DIFF_OPT_SET(&opt, RECURSIVE);
321 if (ignorews)
322 DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
317 opt.format_callback = cgit_diff_tree_cb; 323 opt.format_callback = cgit_diff_tree_cb;
318 opt.format_callback_data = fn; 324 opt.format_callback_data = fn;
319 if (prefix) { 325 if (prefix) {
@@ -338,7 +344,8 @@ void cgit_diff_commit(struct commit *commit, filepair_fn fn)
338 344
339 if (commit->parents) 345 if (commit->parents)
340 old_sha1 = commit->parents->item->object.sha1; 346 old_sha1 = commit->parents->item->object.sha1;
341 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); 347 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL,
348 ctx.qry.ignorews);
342} 349}
343 350
344int cgit_parse_snapshots_mask(const char *str) 351int cgit_parse_snapshots_mask(const char *str)
@@ -430,3 +437,74 @@ int readfile(const char *path, char **buf, size_t *size)
430 close(fd); 437 close(fd);
431 return (*size == st.st_size ? 0 : e); 438 return (*size == st.st_size ? 0 : e);
432} 439}
440
441int is_token_char(char c)
442{
443 return isalnum(c) || c == '_';
444}
445
446/* Replace name with getenv(name), return pointer to zero-terminating char
447 */
448char *expand_macro(char *name, int maxlength)
449{
450 char *value;
451 int len;
452
453 len = 0;
454 value = getenv(name);
455 if (value) {
456 len = strlen(value);
457 if (len > maxlength)
458 len = maxlength;
459 strncpy(name, value, len);
460 }
461 return name + len;
462}
463
464#define EXPBUFSIZE (1024 * 8)
465
466/* Replace all tokens prefixed by '$' in the specified text with the
467 * value of the named environment variable.
468 * NB: the return value is a static buffer, i.e. it must be strdup'd
469 * by the caller.
470 */
471char *expand_macros(const char *txt)
472{
473 static char result[EXPBUFSIZE];
474 char *p, *start;
475 int len;
476
477 p = result;
478 start = NULL;
479 while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
480 *p = *txt;
481 if (start) {
482 if (!is_token_char(*txt)) {
483 if (p - start > 0) {
484 *p = '\0';
485 len = result + EXPBUFSIZE - start - 1;
486 p = expand_macro(start, len) - 1;
487 }
488 start = NULL;
489 txt--;
490 }
491 p++;
492 txt++;
493 continue;
494 }
495 if (*txt == '$') {
496 start = p;
497 txt++;
498 continue;
499 }
500 p++;
501 txt++;
502 }
503 *p = '\0';
504 if (start && p - start > 0) {
505 len = result + EXPBUFSIZE - start - 1;
506 p = expand_macro(start, len);
507 *p = '\0';
508 }
509 return result;
510}
diff --git a/ui-atom.c b/ui-atom.c
index 808b2d0..9f049ae 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -85,7 +85,9 @@ void cgit_print_atom(char *tip, char *path, int max_count)
85 struct rev_info rev; 85 struct rev_info rev;
86 int argc = 2; 86 int argc = 2;
87 87
88 if (!tip) 88 if (ctx.qry.show_all)
89 argv[1] = "--all";
90 else if (!tip)
89 argv[1] = ctx.qry.head; 91 argv[1] = ctx.qry.head;
90 92
91 if (path) { 93 if (path) {
diff --git a/ui-blob.c b/ui-blob.c
index 89330ce..667a451 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -1,6 +1,7 @@
1/* ui-blob.c: show blob content 1/* ui-blob.c: show blob content
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
4 * 5 *
5 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 7 * (see COPYING for full license text)
@@ -12,6 +13,7 @@
12 13
13static char *match_path; 14static char *match_path;
14static unsigned char *matched_sha1; 15static unsigned char *matched_sha1;
16static int found_path;
15 17
16static int walk_tree(const unsigned char *sha1, const char *base,int baselen, 18static int walk_tree(const unsigned char *sha1, const char *base,int baselen,
17 const char *pathname, unsigned mode, int stage, void *cbdata) { 19 const char *pathname, unsigned mode, int stage, void *cbdata) {
@@ -19,12 +21,43 @@ static int walk_tree(const unsigned char *sha1, const char *base,int baselen,
19 || strcmp(match_path+baselen,pathname) ) 21 || strcmp(match_path+baselen,pathname) )
20 return READ_TREE_RECURSIVE; 22 return READ_TREE_RECURSIVE;
21 memmove(matched_sha1,sha1,20); 23 memmove(matched_sha1,sha1,20);
24 found_path = 1;
22 return 0; 25 return 0;
23} 26}
24 27
25void cgit_print_blob(const char *hex, char *path, const char *head) 28int cgit_print_file(char *path, const char *head)
26{ 29{
30 unsigned char sha1[20];
31 enum object_type type;
32 char *buf;
33 unsigned long size;
34 struct commit *commit;
35 const char *paths[] = {path, NULL};
36 if (get_sha1(head, sha1))
37 return -1;
38 type = sha1_object_info(sha1, &size);
39 if(type == OBJ_COMMIT && path) {
40 commit = lookup_commit_reference(sha1);
41 match_path = path;
42 matched_sha1 = sha1;
43 found_path = 0;
44 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
45 if (!found_path)
46 return -1;
47 type = sha1_object_info(sha1, &size);
48 }
49 if (type == OBJ_BAD)
50 return -1;
51 buf = read_sha1_file(sha1, &type, &size);
52 if (!buf)
53 return -1;
54 buf[size] = '\0';
55 write(htmlfd, buf, size);
56 return 0;
57}
27 58
59void cgit_print_blob(const char *hex, char *path, const char *head)
60{
28 unsigned char sha1[20]; 61 unsigned char sha1[20];
29 enum object_type type; 62 enum object_type type;
30 char *buf; 63 char *buf;
diff --git a/ui-blob.h b/ui-blob.h
index dad275a..d7e7d45 100644
--- a/ui-blob.h
+++ b/ui-blob.h
@@ -1,6 +1,7 @@
1#ifndef UI_BLOB_H 1#ifndef UI_BLOB_H
2#define UI_BLOB_H 2#define UI_BLOB_H
3 3
4extern int cgit_print_file(char *path, const char *head);
4extern void cgit_print_blob(const char *hex, char *path, const char *head); 5extern void cgit_print_blob(const char *hex, char *path, const char *head);
5 6
6#endif /* UI_BLOB_H */ 7#endif /* UI_BLOB_H */
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..2b4f677 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -12,13 +12,14 @@
12#include "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex, const char *prefix)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info, *parent_info;
19 struct commit_list *p; 19 struct commit_list *p;
20 struct strbuf notes = STRBUF_INIT;
20 unsigned char sha1[20]; 21 unsigned char sha1[20];
21 char *tmp; 22 char *tmp, *tmp2;
22 int parents = 0; 23 int parents = 0;
23 24
24 if (!hex) 25 if (!hex)
@@ -35,6 +36,8 @@ void cgit_print_commit(char *hex)
35 } 36 }
36 info = cgit_parse_commit(commit); 37 info = cgit_parse_commit(commit);
37 38
39 format_note(NULL, sha1, &notes, PAGE_ENCODING, 0);
40
38 load_ref_decorations(DECORATE_FULL_REFS); 41 load_ref_decorations(DECORATE_FULL_REFS);
39 42
40 html("<table summary='commit info' class='commit-info'>\n"); 43 html("<table summary='commit info' class='commit-info'>\n");
@@ -58,14 +61,23 @@ void cgit_print_commit(char *hex)
58 html("</td></tr>\n"); 61 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 62 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 63 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 64 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
62 html(" ("); 65 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 66 cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
67 html(") (");
68 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
69 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
70 else
71 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
64 html(")</td></tr>\n"); 72 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 73 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 74 tmp = xstrdup(hex);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 75 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 76 ctx.qry.head, tmp, NULL);
77 if (prefix) {
78 html(" /");
79 cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
80 }
69 html("</td></tr>\n"); 81 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 82 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 83 parent = lookup_commit_reference(p->item->object.sha1);
@@ -77,11 +89,15 @@ void cgit_print_commit(char *hex)
77 } 89 }
78 html("<tr><th>parent</th>" 90 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 91 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 92 tmp = tmp2 = sha1_to_hex(p->item->object.sha1);
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 93 if (ctx.repo->enable_subject_links) {
94 parent_info = cgit_parse_commit(parent);
95 tmp2 = parent_info->subject;
96 }
97 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
82 html(" ("); 98 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 99 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 100 sha1_to_hex(p->item->object.sha1), prefix, 0);
85 html(")</td></tr>"); 101 html(")</td></tr>");
86 parents++; 102 parents++;
87 } 103 }
@@ -107,12 +123,24 @@ void cgit_print_commit(char *hex)
107 if (ctx.repo->commit_filter) 123 if (ctx.repo->commit_filter)
108 cgit_close_filter(ctx.repo->commit_filter); 124 cgit_close_filter(ctx.repo->commit_filter);
109 html("</div>"); 125 html("</div>");
126 if (notes.len != 0) {
127 html("<div class='notes-header'>Notes</div>");
128 html("<div class='notes'>");
129 if (ctx.repo->commit_filter)
130 cgit_open_filter(ctx.repo->commit_filter);
131 html_txt(notes.buf);
132 if (ctx.repo->commit_filter)
133 cgit_close_filter(ctx.repo->commit_filter);
134 html("</div>");
135 html("<div class='notes-footer'></div>");
136 }
110 if (parents < 3) { 137 if (parents < 3) {
111 if (parents) 138 if (parents)
112 tmp = sha1_to_hex(commit->parents->item->object.sha1); 139 tmp = sha1_to_hex(commit->parents->item->object.sha1);
113 else 140 else
114 tmp = NULL; 141 tmp = NULL;
115 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 142 cgit_print_diff(ctx.qry.sha1, tmp, prefix);
116 } 143 }
144 strbuf_release(&notes);
117 cgit_free_commitinfo(info); 145 cgit_free_commitinfo(info);
118} 146}
diff --git a/ui-commit.h b/ui-commit.h
index 40bcb31..8198b4b 100644
--- a/ui-commit.h
+++ b/ui-commit.h
@@ -1,6 +1,6 @@
1#ifndef UI_COMMIT_H 1#ifndef UI_COMMIT_H
2#define UI_COMMIT_H 2#define UI_COMMIT_H
3 3
4extern void cgit_print_commit(char *hex); 4extern void cgit_print_commit(char *hex, const char *prefix);
5 5
6#endif /* UI_COMMIT_H */ 6#endif /* UI_COMMIT_H */
diff --git a/ui-diff.c b/ui-diff.c
index 2196745..0dcabe9 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -9,6 +9,7 @@
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 "ui-ssdiff.h"
12 13
13unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
14unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
@@ -32,6 +33,7 @@ static struct fileinfo {
32 int binary:1; 33 int binary:1;
33} *items; 34} *items;
34 35
36static int use_ssdiff = 0;
35 37
36static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
37{ 39{
@@ -83,7 +85,7 @@ static void print_fileinfo(struct fileinfo *info)
83 } 85 }
84 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
85 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
86 ctx.qry.sha2, info->new_path); 88 ctx.qry.sha2, info->new_path, 0);
87 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
88 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
89 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
@@ -125,7 +127,7 @@ static void inspect_filepair(struct diff_filepair *pair)
125 lines_added = 0; 127 lines_added = 0;
126 lines_removed = 0; 128 lines_removed = 0;
127 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
128 &binary, count_diff_lines); 130 &binary, 0, ctx.qry.ignorews, count_diff_lines);
129 if (files >= slots) { 131 if (files >= slots) {
130 if (slots == 0) 132 if (slots == 0)
131 slots = 4; 133 slots = 4;
@@ -152,17 +154,33 @@ static void inspect_filepair(struct diff_filepair *pair)
152} 154}
153 155
154void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
155 const unsigned char *new_sha1) 157 const unsigned char *new_sha1, const char *prefix)
156{ 158{
157 int i; 159 int i, save_context = ctx.qry.context;
158 160
159 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
160 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
161 ctx.qry.sha2, NULL); 163 ctx.qry.sha2, NULL, 0);
164 if (prefix)
165 htmlf(" (limited to '%s')", prefix);
166 html(" (");
167 ctx.qry.context = (save_context > 0 ? save_context : 3) << 1;
168 cgit_self_link("more", NULL, NULL, &ctx);
169 html("/");
170 ctx.qry.context = (save_context > 3 ? save_context : 3) >> 1;
171 cgit_self_link("less", NULL, NULL, &ctx);
172 ctx.qry.context = save_context;
173 html(" context)");
174 html(" (");
175 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2;
176 cgit_self_link(ctx.qry.ignorews ? "ignore" : "show", NULL, NULL, &ctx);
177 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2;
178 html(" whitespace changes)");
162 html("</div>"); 179 html("</div>");
163 html("<table summary='diffstat' class='diffstat'>"); 180 html("<table summary='diffstat' class='diffstat'>");
164 max_changes = 0; 181 max_changes = 0;
165 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 182 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix,
183 ctx.qry.ignorews);
166 for(i = 0; i<files; i++) 184 for(i = 0; i<files; i++)
167 print_fileinfo(&items[i]); 185 print_fileinfo(&items[i]);
168 html("</table>"); 186 html("</table>");
@@ -246,26 +264,55 @@ static void header(unsigned char *sha1, char *path1, int mode1,
246 html("</div>"); 264 html("</div>");
247} 265}
248 266
267static void print_ssdiff_link()
268{
269 if (!strcmp(ctx.qry.page, "diff")) {
270 if (use_ssdiff)
271 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
272 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
273 else
274 cgit_diff_link("Side-by-side diff", NULL, NULL,
275 ctx.qry.head, ctx.qry.sha1,
276 ctx.qry.sha2, ctx.qry.path, 1);
277 }
278}
279
249static void filepair_cb(struct diff_filepair *pair) 280static void filepair_cb(struct diff_filepair *pair)
250{ 281{
251 unsigned long old_size = 0; 282 unsigned long old_size = 0;
252 unsigned long new_size = 0; 283 unsigned long new_size = 0;
253 int binary = 0; 284 int binary = 0;
285 linediff_fn print_line_fn = print_line;
254 286
287 if (use_ssdiff) {
288 cgit_ssdiff_header_begin();
289 print_line_fn = cgit_ssdiff_line_cb;
290 }
255 header(pair->one->sha1, pair->one->path, pair->one->mode, 291 header(pair->one->sha1, pair->one->path, pair->one->mode,
256 pair->two->sha1, pair->two->path, pair->two->mode); 292 pair->two->sha1, pair->two->path, pair->two->mode);
293 if (use_ssdiff)
294 cgit_ssdiff_header_end();
257 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 295 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
258 if (S_ISGITLINK(pair->one->mode)) 296 if (S_ISGITLINK(pair->one->mode))
259 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 297 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
260 if (S_ISGITLINK(pair->two->mode)) 298 if (S_ISGITLINK(pair->two->mode))
261 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 299 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
300 if (use_ssdiff)
301 cgit_ssdiff_footer();
262 return; 302 return;
263 } 303 }
264 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 304 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
265 &new_size, &binary, print_line)) 305 &new_size, &binary, ctx.qry.context,
306 ctx.qry.ignorews, print_line_fn))
266 cgit_print_error("Error running diff"); 307 cgit_print_error("Error running diff");
267 if (binary) 308 if (binary) {
268 html("Binary files differ"); 309 if (use_ssdiff)
310 html("<tr><td colspan='4'>Binary files differ</td></tr>");
311 else
312 html("Binary files differ");
313 }
314 if (use_ssdiff)
315 cgit_ssdiff_footer();
269} 316}
270 317
271void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 318void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
@@ -303,11 +350,22 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi
303 if (!commit2 || parse_commit(commit2)) 350 if (!commit2 || parse_commit(commit2))
304 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 351 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
305 } 352 }
306 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
307 353
308 html("<table summary='diff' class='diff'>"); 354 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
309 html("<tr><td>"); 355 use_ssdiff = 1;
310 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 356
311 html("</td></tr>"); 357 print_ssdiff_link();
358 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix);
359
360 if (use_ssdiff) {
361 html("<table summary='ssdiff' class='ssdiff'>");
362 } else {
363 html("<table summary='diff' class='diff'>");
364 html("<tr><td>");
365 }
366 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix,
367 ctx.qry.ignorews);
368 if (!use_ssdiff)
369 html("</td></tr>");
312 html("</table>"); 370 html("</table>");
313} 371}
diff --git a/ui-log.c b/ui-log.c
index f3132c9..0536b23 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -33,7 +33,8 @@ void inspect_files(struct diff_filepair *pair)
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, 0, ctx.qry.ignorews,
37 count_lines);
37} 38}
38 39
39void show_commit_decorations(struct commit *commit) 40void show_commit_decorations(struct commit *commit)
@@ -46,8 +47,9 @@ void show_commit_decorations(struct commit *commit)
46 while (deco) { 47 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 48 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 49 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 50 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 51 ctx.qry.vpath, 0, NULL, NULL,
52 ctx.qry.showmsg);
51 } 53 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 54 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 55 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
@@ -60,13 +62,15 @@ void show_commit_decorations(struct commit *commit)
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 62 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 63 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 64 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 65 sha1_to_hex(commit->object.sha1),
64 0, NULL, NULL, ctx.qry.showmsg); 66 ctx.qry.vpath, 0, NULL, NULL,
67 ctx.qry.showmsg);
65 } 68 }
66 else { 69 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 70 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 71 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 72 sha1_to_hex(commit->object.sha1),
73 ctx.qry.vpath, 0);
70 } 74 }
71 deco = deco->next; 75 deco = deco->next;
72 } 76 }
@@ -82,14 +86,14 @@ void print_commit(struct commit *commit)
82 htmlf("<tr%s><td>", 86 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 87 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 88 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 89 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
86 html_link_open(tmp, NULL, NULL); 90 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 91 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 92 html_link_close();
89 htmlf("</td><td%s>", 93 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 94 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 95 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 96 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
93 show_commit_decorations(commit); 97 show_commit_decorations(commit);
94 html("</td><td>"); 98 html("</td><td>");
95 html_txt(info->author); 99 html_txt(info->author);
@@ -107,6 +111,9 @@ void print_commit(struct commit *commit)
107 } 111 }
108 html("</td></tr>\n"); 112 html("</td></tr>\n");
109 if (ctx.qry.showmsg) { 113 if (ctx.qry.showmsg) {
114 struct strbuf notes = STRBUF_INIT;
115 format_note(NULL, commit->object.sha1, &notes, PAGE_ENCODING, 0);
116
110 if (ctx.repo->enable_log_filecount) { 117 if (ctx.repo->enable_log_filecount) {
111 cols++; 118 cols++;
112 if (ctx.repo->enable_log_linecount) 119 if (ctx.repo->enable_log_linecount)
@@ -116,6 +123,15 @@ void print_commit(struct commit *commit)
116 cols); 123 cols);
117 html_txt(info->msg); 124 html_txt(info->msg);
118 html("</td></tr>\n"); 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 }
134 strbuf_release(&notes);
119 } 135 }
120 cgit_free_commitinfo(info); 136 cgit_free_commitinfo(info);
121} 137}
@@ -146,10 +162,13 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
146 162
147 argv[1] = disambiguate_ref(tip); 163 argv[1] = disambiguate_ref(tip);
148 164
149 if (grep && pattern && (!strcmp(grep, "grep") || 165 if (grep && pattern) {
150 !strcmp(grep, "author") || 166 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
151 !strcmp(grep, "committer"))) 167 !strcmp(grep, "committer"))
152 argv[argc++] = fmt("--%s=%s", grep, pattern); 168 argv[argc++] = fmt("--%s=%s", grep, pattern);
169 if (!strcmp(grep, "range"))
170 argv[1] = pattern;
171 }
153 172
154 if (path) { 173 if (path) {
155 argv[argc++] = "--"; 174 argv[argc++] = "--";
@@ -176,7 +195,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
176 html(" ("); 195 html(" (");
177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 196 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
178 NULL, ctx.qry.head, ctx.qry.sha1, 197 NULL, ctx.qry.head, ctx.qry.sha1,
179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 198 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 199 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
181 html(")"); 200 html(")");
182 } 201 }
@@ -213,22 +232,22 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
213 columns); 232 columns);
214 if (ofs > 0) { 233 if (ofs > 0) {
215 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 234 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
216 ctx.qry.sha1, ctx.qry.path, 235 ctx.qry.sha1, ctx.qry.vpath,
217 ofs - cnt, ctx.qry.grep, 236 ofs - cnt, ctx.qry.grep,
218 ctx.qry.search, ctx.qry.showmsg); 237 ctx.qry.search, ctx.qry.showmsg);
219 html("&nbsp;"); 238 html("&nbsp;");
220 } 239 }
221 if ((commit = get_revision(&rev)) != NULL) { 240 if ((commit = get_revision(&rev)) != NULL) {
222 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 241 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
223 ctx.qry.sha1, ctx.qry.path, 242 ctx.qry.sha1, ctx.qry.vpath,
224 ofs + cnt, ctx.qry.grep, 243 ofs + cnt, ctx.qry.grep,
225 ctx.qry.search, ctx.qry.showmsg); 244 ctx.qry.search, ctx.qry.showmsg);
226 } 245 }
227 html("</div>"); 246 html("</div>");
228 } else if ((commit = get_revision(&rev)) != NULL) { 247 } else if ((commit = get_revision(&rev)) != NULL) {
229 html("<tr class='nohover'><td colspan='3'>"); 248 html("<tr class='nohover'><td colspan='3'>");
230 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 249 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
231 NULL, NULL, ctx.qry.showmsg); 250 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
232 html("</td></tr>\n"); 251 html("</td></tr>\n");
233 } 252 }
234} 253}
diff --git a/ui-patch.c b/ui-patch.c
index 2a8f7a5..ca008f3 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -71,13 +71,13 @@ static void filepair_cb(struct diff_filepair *pair)
71 return; 71 return;
72 } 72 }
73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
74 &new_size, &binary, print_line)) 74 &new_size, &binary, 0, 0, print_line))
75 html("Error running diff"); 75 html("Error running diff");
76 if (binary) 76 if (binary)
77 html("Binary files differ\n"); 77 html("Binary files differ\n");
78} 78}
79 79
80void cgit_print_patch(char *hex) 80void cgit_print_patch(char *hex, const char *prefix)
81{ 81{
82 struct commit *commit; 82 struct commit *commit;
83 struct commitinfo *info; 83 struct commitinfo *info;
@@ -122,7 +122,9 @@ void cgit_print_patch(char *hex)
122 html("\n"); 122 html("\n");
123 } 123 }
124 html("---\n"); 124 html("---\n");
125 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); 125 if (prefix)
126 htmlf("(limited to '%s')\n\n", prefix);
127 cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix, 0);
126 html("--\n"); 128 html("--\n");
127 htmlf("cgit %s\n", CGIT_VERSION); 129 htmlf("cgit %s\n", CGIT_VERSION);
128 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
diff --git a/ui-patch.h b/ui-patch.h
index 9f68212..1641cea 100644
--- a/ui-patch.h
+++ b/ui-patch.h
@@ -1,6 +1,6 @@
1#ifndef UI_PATCH_H 1#ifndef UI_PATCH_H
2#define UI_PATCH_H 2#define UI_PATCH_H
3 3
4extern void cgit_print_patch(char *hex); 4extern void cgit_print_patch(char *hex, const char *prefix);
5 5
6#endif /* UI_PATCH_H */ 6#endif /* UI_PATCH_H */
diff --git a/ui-plain.c b/ui-plain.c
index 66cb19c..1b2b672 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -10,8 +10,7 @@
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13char *curr_rev; 13int match_baselen;
14char *match_path;
15int match; 14int match;
16 15
17static void print_object(const unsigned char *sha1, const char *path) 16static void print_object(const unsigned char *sha1, const char *path)
@@ -35,7 +34,7 @@ static void print_object(const unsigned char *sha1, const char *path)
35 ctx.page.mimetype = NULL; 34 ctx.page.mimetype = NULL;
36 ext = strrchr(path, '.'); 35 ext = strrchr(path, '.');
37 if (ext && *(++ext)) { 36 if (ext && *(++ext)) {
38 mime = string_list_lookup(ext, &ctx.cfg.mimetypes); 37 mime = string_list_lookup(&ctx.cfg.mimetypes, ext);
39 if (mime) 38 if (mime)
40 ctx.page.mimetype = (char *)mime->util; 39 ctx.page.mimetype = (char *)mime->util;
41 } 40 }
@@ -53,17 +52,63 @@ static void print_object(const unsigned char *sha1, const char *path)
53 match = 1; 52 match = 1;
54} 53}
55 54
55static void print_dir(const unsigned char *sha1, const char *path,
56 const char *base)
57{
58 char *fullpath;
59 if (path[0] || base[0])
60 fullpath = fmt("/%s%s/", base, path);
61 else
62 fullpath = "/";
63 ctx.page.etag = sha1_to_hex(sha1);
64 cgit_print_http_headers(&ctx);
65 htmlf("<html><head><title>%s</title></head>\n<body>\n"
66 " <h2>%s</h2>\n <ul>\n", fullpath, fullpath);
67 if (path[0] || base[0])
68 html(" <li><a href=\"../\">../</a></li>\n");
69 match = 2;
70}
71
72static void print_dir_entry(const unsigned char *sha1, const char *path,
73 unsigned mode)
74{
75 const char *sep = "";
76 if (S_ISDIR(mode))
77 sep = "/";
78 htmlf(" <li><a href=\"%s%s\">%s%s</a></li>\n", path, sep, path, sep);
79 match = 2;
80}
81
82static void print_dir_tail(void)
83{
84 html(" </ul>\n</body></html>\n");
85}
86
56static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 87static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
57 const char *pathname, unsigned mode, int stage, 88 const char *pathname, unsigned mode, int stage,
58 void *cbdata) 89 void *cbdata)
59{ 90{
60 if (S_ISDIR(mode)) 91 if (baselen == match_baselen) {
92 if (S_ISREG(mode))
93 print_object(sha1, pathname);
94 else if (S_ISDIR(mode)) {
95 print_dir(sha1, pathname, base);
96 return READ_TREE_RECURSIVE;
97 }
98 }
99 else if (baselen > match_baselen)
100 print_dir_entry(sha1, pathname, mode);
101 else if (S_ISDIR(mode))
61 return READ_TREE_RECURSIVE; 102 return READ_TREE_RECURSIVE;
62 103
63 if (S_ISREG(mode) && !strncmp(base, match_path, baselen) && 104 return 0;
64 !strcmp(pathname, match_path + baselen)) 105}
65 print_object(sha1, pathname);
66 106
107static int basedir_len(const char *path)
108{
109 char *p = strrchr(path, '/');
110 if (p)
111 return p - path + 1;
67 return 0; 112 return 0;
68} 113}
69 114
@@ -77,7 +122,6 @@ void cgit_print_plain(struct cgit_context *ctx)
77 if (!rev) 122 if (!rev)
78 rev = ctx->qry.head; 123 rev = ctx->qry.head;
79 124
80 curr_rev = xstrdup(rev);
81 if (get_sha1(rev, sha1)) { 125 if (get_sha1(rev, sha1)) {
82 html_status(404, "Not found", 0); 126 html_status(404, "Not found", 0);
83 return; 127 return;
@@ -87,8 +131,16 @@ void cgit_print_plain(struct cgit_context *ctx)
87 html_status(404, "Not found", 0); 131 html_status(404, "Not found", 0);
88 return; 132 return;
89 } 133 }
90 match_path = ctx->qry.path; 134 if (!paths[0]) {
135 paths[0] = "";
136 match_baselen = -1;
137 print_dir(commit->tree->object.sha1, "", "");
138 }
139 else
140 match_baselen = basedir_len(paths[0]);
91 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 141 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
92 if (!match) 142 if (!match)
93 html_status(404, "Not found", 0); 143 html_status(404, "Not found", 0);
144 else if (match == 2)
145 print_dir_tail();
94} 146}
diff --git a/ui-refs.c b/ui-refs.c
index 6571cc4..caddfbc 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -76,7 +76,7 @@ static int print_branch(struct refinfo *ref)
76 html("</td><td>"); 76 html("</td><td>");
77 77
78 if (ref->object->type == OBJ_COMMIT) { 78 if (ref->object->type == OBJ_COMMIT) {
79 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 79 cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL, 0);
80 html("</td><td>"); 80 html("</td><td>");
81 html_txt(info->author); 81 html_txt(info->author);
82 html("</td><td colspan='2'>"); 82 html("</td><td colspan='2'>");
@@ -189,6 +189,8 @@ void cgit_print_branches(int maxcount)
189 list.refs = NULL; 189 list.refs = NULL;
190 list.alloc = list.count = 0; 190 list.alloc = list.count = 0;
191 for_each_branch_ref(cgit_refs_cb, &list); 191 for_each_branch_ref(cgit_refs_cb, &list);
192 if (ctx.repo->enable_remote_branches)
193 for_each_remote_ref(cgit_refs_cb, &list);
192 194
193 if (maxcount == 0 || maxcount > list.count) 195 if (maxcount == 0 || maxcount > list.count)
194 maxcount = list.count; 196 maxcount = list.count;
diff --git a/ui-shared.c b/ui-shared.c
index 8a7cc32..ae29615 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -27,7 +27,7 @@ static char *http_date(time_t t)
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(const char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
@@ -133,7 +133,7 @@ char *cgit_currurl()
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(char *page, char *search, int ofs) 136static void site_url(const char *page, const char *search, int ofs)
137{ 137