summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2010-09-19 17:00:05 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2010-09-19 17:00:05 (UTC)
commita9d6e6e695da6c6ed7f4bb32630ab2f3d9314806 (patch) (unidiff)
treede8271ebfabd244437cd68021c8af86391afb9bd
parent536c7a1eb201b44b9266babe087cb6f2b75e4a7f (diff)
parentd187b98557d91b874836f286b955ba76ab26fb02 (diff)
downloadcgit-a9d6e6e695da6c6ed7f4bb32630ab2f3d9314806.zip
cgit-a9d6e6e695da6c6ed7f4bb32630ab2f3d9314806.tar.gz
cgit-a9d6e6e695da6c6ed7f4bb32630ab2f3d9314806.tar.bz2
Merge branch 'ml/bugfix'
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile8
-rw-r--r--cache.h1
-rw-r--r--cgit.c2
-rw-r--r--cgit.h1
-rw-r--r--html.c18
-rw-r--r--html.h3
-rw-r--r--ui-blob.c4
-rw-r--r--ui-diff.c2
-rw-r--r--ui-log.c3
-rw-r--r--ui-repolist.c6
-rw-r--r--ui-stats.c18
-rw-r--r--ui-tree.c6
12 files changed, 42 insertions, 30 deletions
diff --git a/Makefile b/Makefile
index 23fdd53..6a47ed2 100644
--- a/Makefile
+++ b/Makefile
@@ -1,183 +1,191 @@
1CGIT_VERSION = v0.8.3.3 1CGIT_VERSION = v0.8.3.3
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
7SHA1_HEADER = <openssl/sha.h> 7SHA1_HEADER = <openssl/sha.h>
8GIT_VER = 1.7.3 8GIT_VER = 1.7.3
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 14# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
15# implementation (slower). 15# implementation (slower).
16# 16#
17# 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).
18# 18#
19# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
20# do not support the 'size specifiers' introduced by C99, namely ll, hh,
21# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
22# some C compilers supported these specifiers prior to C99 as an extension.
23#
19 24
20#-include config.mak 25#-include config.mak
21 26
22# 27#
23# Platform specific tweaks 28# Platform specific tweaks
24# 29#
25 30
26uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 31uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
27uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') 32uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
28uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') 33uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
29 34
30ifeq ($(uname_O),Cygwin) 35ifeq ($(uname_O),Cygwin)
31 NO_STRCASESTR = YesPlease 36 NO_STRCASESTR = YesPlease
32 NEEDS_LIBICONV = YesPlease 37 NEEDS_LIBICONV = YesPlease
33endif 38endif
34 39
35# 40#
36# Let the user override the above settings. 41# Let the user override the above settings.
37# 42#
38-include cgit.conf 43-include cgit.conf
39 44
40# 45#
41# Define a way to invoke make in subdirs quietly, shamelessly ripped 46# Define a way to invoke make in subdirs quietly, shamelessly ripped
42# from git.git 47# from git.git
43# 48#
44QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir 49QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
45QUIET_SUBDIR1 = 50QUIET_SUBDIR1 =
46 51
47ifneq ($(findstring $(MAKEFLAGS),w),w) 52ifneq ($(findstring $(MAKEFLAGS),w),w)
48PRINT_DIR = --no-print-directory 53PRINT_DIR = --no-print-directory
49else # "make -w" 54else # "make -w"
50NO_SUBDIR = : 55NO_SUBDIR = :
51endif 56endif
52 57
53ifndef V 58ifndef V
54 QUIET_CC = @echo ' ' CC $@; 59 QUIET_CC = @echo ' ' CC $@;
55 QUIET_MM = @echo ' ' MM $@; 60 QUIET_MM = @echo ' ' MM $@;
56 QUIET_SUBDIR0 = +@subdir= 61 QUIET_SUBDIR0 = +@subdir=
57 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ 62 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
58 $(MAKE) $(PRINT_DIR) -C $$subdir 63 $(MAKE) $(PRINT_DIR) -C $$subdir
59endif 64endif
60 65
61# 66#
62# Define a pattern rule for automatic dependency building 67# Define a pattern rule for automatic dependency building
63# 68#
64%.d: %.c 69%.d: %.c
65 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 70 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
66 71
67# 72#
68# Define a pattern rule for silent object building 73# Define a pattern rule for silent object building
69# 74#
70%.o: %.c 75%.o: %.c
71 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 76 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
72 77
73 78
74EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread 79EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread
75OBJECTS = 80OBJECTS =
76OBJECTS += cache.o 81OBJECTS += cache.o
77OBJECTS += cgit.o 82OBJECTS += cgit.o
78OBJECTS += cmd.o 83OBJECTS += cmd.o
79OBJECTS += configfile.o 84OBJECTS += configfile.o
80OBJECTS += html.o 85OBJECTS += html.o
81OBJECTS += parsing.o 86OBJECTS += parsing.o
82OBJECTS += scan-tree.o 87OBJECTS += scan-tree.o
83OBJECTS += shared.o 88OBJECTS += shared.o
84OBJECTS += ui-atom.o 89OBJECTS += ui-atom.o
85OBJECTS += ui-blob.o 90OBJECTS += ui-blob.o
86OBJECTS += ui-clone.o 91OBJECTS += ui-clone.o
87OBJECTS += ui-commit.o 92OBJECTS += ui-commit.o
88OBJECTS += ui-diff.o 93OBJECTS += ui-diff.o
89OBJECTS += ui-log.o 94OBJECTS += ui-log.o
90OBJECTS += ui-patch.o 95OBJECTS += ui-patch.o
91OBJECTS += ui-plain.o 96OBJECTS += ui-plain.o
92OBJECTS += ui-refs.o 97OBJECTS += ui-refs.o
93OBJECTS += ui-repolist.o 98OBJECTS += ui-repolist.o
94OBJECTS += ui-shared.o 99OBJECTS += ui-shared.o
95OBJECTS += ui-snapshot.o 100OBJECTS += ui-snapshot.o
96OBJECTS += ui-ssdiff.o 101OBJECTS += ui-ssdiff.o
97OBJECTS += ui-stats.o 102OBJECTS += ui-stats.o
98OBJECTS += ui-summary.o 103OBJECTS += ui-summary.o
99OBJECTS += ui-tag.o 104OBJECTS += ui-tag.o
100OBJECTS += ui-tree.o 105OBJECTS += ui-tree.o
101 106
102ifdef NEEDS_LIBICONV 107ifdef NEEDS_LIBICONV
103 EXTLIBS += -liconv 108 EXTLIBS += -liconv
104endif 109endif
105 110
106 111
107.PHONY: all libgit test install uninstall clean force-version get-git \ 112.PHONY: all libgit test install uninstall clean force-version get-git \
108 doc man-doc html-doc clean-doc 113 doc man-doc html-doc clean-doc
109 114
110all: cgit 115all: cgit
111 116
112VERSION: force-version 117VERSION: force-version
113 @./gen-version.sh "$(CGIT_VERSION)" 118 @./gen-version.sh "$(CGIT_VERSION)"
114-include VERSION 119-include VERSION
115 120
116 121
117CFLAGS += -g -Wall -Igit 122CFLAGS += -g -Wall -Igit
118CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 123CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
119CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 124CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
120CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 125CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
121CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 126CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
122CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 127CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
123 128
124ifdef NO_ICONV 129ifdef NO_ICONV
125 CFLAGS += -DNO_ICONV 130 CFLAGS += -DNO_ICONV
126endif 131endif
127ifdef NO_STRCASESTR 132ifdef NO_STRCASESTR
128 CFLAGS += -DNO_STRCASESTR 133 CFLAGS += -DNO_STRCASESTR
129endif 134endif
135ifdef NO_C99_FORMAT
136 CFLAGS += -DNO_C99_FORMAT
137endif
130ifdef NO_OPENSSL 138ifdef NO_OPENSSL
131 CFLAGS += -DNO_OPENSSL 139 CFLAGS += -DNO_OPENSSL
132 GIT_OPTIONS += NO_OPENSSL=1 140 GIT_OPTIONS += NO_OPENSSL=1
133else 141else
134 EXTLIBS += -lcrypto 142 EXTLIBS += -lcrypto
135endif 143endif
136 144
137cgit: $(OBJECTS) libgit 145cgit: $(OBJECTS) libgit
138 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 146 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
139 147
140cgit.o: VERSION 148cgit.o: VERSION
141 149
142ifneq "$(MAKECMDGOALS)" "clean" 150ifneq "$(MAKECMDGOALS)" "clean"
143 -include $(OBJECTS:.o=.d) 151 -include $(OBJECTS:.o=.d)
144endif 152endif
145 153
146libgit: 154libgit:
147 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a 155 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a
148 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a 156 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a
149 157
150test: all 158test: all
151 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 159 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
152 160
153install: all 161install: all
154 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 162 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
155 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 163 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
156 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 164 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
157 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 165 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
158 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 166 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
159 167
160uninstall: 168uninstall:
161 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 169 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
162 rm -f $(CGIT_DATA_PATH)/cgit.css 170 rm -f $(CGIT_DATA_PATH)/cgit.css
163 rm -f $(CGIT_DATA_PATH)/cgit.png 171 rm -f $(CGIT_DATA_PATH)/cgit.png
164 172
165doc: man-doc html-doc pdf-doc 173doc: man-doc html-doc pdf-doc
166 174
167man-doc: cgitrc.5.txt 175man-doc: cgitrc.5.txt
168 a2x -f manpage cgitrc.5.txt 176 a2x -f manpage cgitrc.5.txt
169 177
170html-doc: cgitrc.5.txt 178html-doc: cgitrc.5.txt
171 a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt 179 a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt
172 180
173pdf-doc: cgitrc.5.txt 181pdf-doc: cgitrc.5.txt
174 a2x -f pdf cgitrc.5.txt 182 a2x -f pdf cgitrc.5.txt
175 183
176clean: clean-doc 184clean: clean-doc
177 rm -f cgit VERSION *.o *.d 185 rm -f cgit VERSION *.o *.d
178 186
179clean-doc: 187clean-doc:
180 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo 188 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
181 189
182get-git: 190get-git:
183 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 191 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cache.h b/cache.h
index ac9276b..5cfdb4f 100644
--- a/cache.h
+++ b/cache.h
@@ -1,37 +1,38 @@
1/* 1/*
2 * Since git has it's own cache.h which we include, 2 * Since git has it's own cache.h which we include,
3 * lets test on CGIT_CACHE_H to avoid confusion 3 * lets test on CGIT_CACHE_H to avoid confusion
4 */ 4 */
5 5
6#ifndef CGIT_CACHE_H 6#ifndef CGIT_CACHE_H
7#define CGIT_CACHE_H 7#define CGIT_CACHE_H
8 8
9typedef void (*cache_fill_fn)(void *cbdata); 9typedef void (*cache_fill_fn)(void *cbdata);
10 10
11 11
12/* Print cached content to stdout, generate the content if necessary. 12/* Print cached content to stdout, generate the content if necessary.
13 * 13 *
14 * Parameters 14 * Parameters
15 * size max number of cache files 15 * size max number of cache files
16 * path directory used to store cache files 16 * path directory used to store cache files
17 * key the key used to lookup cache files 17 * key the key used to lookup cache files
18 * ttl max cache time in seconds for this key 18 * ttl max cache time in seconds for this key
19 * fn content generator function for this key 19 * fn content generator function for this key
20 * cbdata user-supplied data to the content generator function 20 * cbdata user-supplied data to the content generator function
21 * 21 *
22 * Return value 22 * Return value
23 * 0 indicates success, everyting else is an error 23 * 0 indicates success, everyting else is an error
24 */ 24 */
25extern int cache_process(int size, const char *path, const char *key, int ttl, 25extern int cache_process(int size, const char *path, const char *key, int ttl,
26 cache_fill_fn fn, void *cbdata); 26 cache_fill_fn fn, void *cbdata);
27 27
28 28
29/* List info about all cache entries on stdout */ 29/* List info about all cache entries on stdout */
30extern int cache_ls(const char *path); 30extern int cache_ls(const char *path);
31 31
32/* Print a message to stdout */ 32/* Print a message to stdout */
33__attribute__((format (printf,1,2)))
33extern void cache_log(const char *format, ...); 34extern void cache_log(const char *format, ...);
34 35
35extern unsigned long hash_str(const char *str); 36extern unsigned long hash_str(const char *str);
36 37
37#endif /* CGIT_CACHE_H */ 38#endif /* CGIT_CACHE_H */
diff --git a/cgit.c b/cgit.c
index e1d2216..96900bb 100644
--- a/cgit.c
+++ b/cgit.c
@@ -485,257 +485,257 @@ int cmp_repos(const void *a, const void *b)
485{ 485{
486 const struct cgit_repo *ra = a, *rb = b; 486 const struct cgit_repo *ra = a, *rb = b;
487 return strcmp(ra->url, rb->url); 487 return strcmp(ra->url, rb->url);
488} 488}
489 489
490char *build_snapshot_setting(int bitmap) 490char *build_snapshot_setting(int bitmap)
491{ 491{
492 const struct cgit_snapshot_format *f; 492 const struct cgit_snapshot_format *f;
493 char *result = xstrdup(""); 493 char *result = xstrdup("");
494 char *tmp; 494 char *tmp;
495 int len; 495 int len;
496 496
497 for (f = cgit_snapshot_formats; f->suffix; f++) { 497 for (f = cgit_snapshot_formats; f->suffix; f++) {
498 if (f->bit & bitmap) { 498 if (f->bit & bitmap) {
499 tmp = result; 499 tmp = result;
500 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 500 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
501 free(tmp); 501 free(tmp);
502 } 502 }
503 } 503 }
504 len = strlen(result); 504 len = strlen(result);
505 if (len) 505 if (len)
506 result[len - 1] = '\0'; 506 result[len - 1] = '\0';
507 return result; 507 return result;
508} 508}
509 509
510char *get_first_line(char *txt) 510char *get_first_line(char *txt)
511{ 511{
512 char *t = xstrdup(txt); 512 char *t = xstrdup(txt);
513 char *p = strchr(t, '\n'); 513 char *p = strchr(t, '\n');
514 if (p) 514 if (p)
515 *p = '\0'; 515 *p = '\0';
516 return t; 516 return t;
517} 517}
518 518
519void print_repo(FILE *f, struct cgit_repo *repo) 519void print_repo(FILE *f, struct cgit_repo *repo)
520{ 520{
521 fprintf(f, "repo.url=%s\n", repo->url); 521 fprintf(f, "repo.url=%s\n", repo->url);
522 fprintf(f, "repo.name=%s\n", repo->name); 522 fprintf(f, "repo.name=%s\n", repo->name);
523 fprintf(f, "repo.path=%s\n", repo->path); 523 fprintf(f, "repo.path=%s\n", repo->path);
524 if (repo->owner) 524 if (repo->owner)
525 fprintf(f, "repo.owner=%s\n", repo->owner); 525 fprintf(f, "repo.owner=%s\n", repo->owner);
526 if (repo->desc) { 526 if (repo->desc) {
527 char *tmp = get_first_line(repo->desc); 527 char *tmp = get_first_line(repo->desc);
528 fprintf(f, "repo.desc=%s\n", tmp); 528 fprintf(f, "repo.desc=%s\n", tmp);
529 free(tmp); 529 free(tmp);
530 } 530 }
531 if (repo->readme) 531 if (repo->readme)
532 fprintf(f, "repo.readme=%s\n", repo->readme); 532 fprintf(f, "repo.readme=%s\n", repo->readme);
533 if (repo->defbranch) 533 if (repo->defbranch)
534 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 534 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
535 if (repo->module_link) 535 if (repo->module_link)
536 fprintf(f, "repo.module-link=%s\n", repo->module_link); 536 fprintf(f, "repo.module-link=%s\n", repo->module_link);
537 if (repo->section) 537 if (repo->section)
538 fprintf(f, "repo.section=%s\n", repo->section); 538 fprintf(f, "repo.section=%s\n", repo->section);
539 if (repo->clone_url) 539 if (repo->clone_url)
540 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 540 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
541 fprintf(f, "repo.enable-log-filecount=%d\n", 541 fprintf(f, "repo.enable-log-filecount=%d\n",
542 repo->enable_log_filecount); 542 repo->enable_log_filecount);
543 fprintf(f, "repo.enable-log-linecount=%d\n", 543 fprintf(f, "repo.enable-log-linecount=%d\n",
544 repo->enable_log_linecount); 544 repo->enable_log_linecount);
545 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 545 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
546 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 546 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
547 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 547 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
548 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 548 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
549 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 549 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
550 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 550 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
551 if (repo->snapshots != ctx.cfg.snapshots) { 551 if (repo->snapshots != ctx.cfg.snapshots) {
552 char *tmp = build_snapshot_setting(repo->snapshots); 552 char *tmp = build_snapshot_setting(repo->snapshots);
553 fprintf(f, "repo.snapshots=%s\n", tmp); 553 fprintf(f, "repo.snapshots=%s\n", tmp);
554 free(tmp); 554 free(tmp);
555 } 555 }
556 if (repo->max_stats != ctx.cfg.max_stats) 556 if (repo->max_stats != ctx.cfg.max_stats)
557 fprintf(f, "repo.max-stats=%s\n", 557 fprintf(f, "repo.max-stats=%s\n",
558 cgit_find_stats_periodname(repo->max_stats)); 558 cgit_find_stats_periodname(repo->max_stats));
559 fprintf(f, "\n"); 559 fprintf(f, "\n");
560} 560}
561 561
562void print_repolist(FILE *f, struct cgit_repolist *list, int start) 562void print_repolist(FILE *f, struct cgit_repolist *list, int start)
563{ 563{
564 int i; 564 int i;
565 565
566 for(i = start; i < list->count; i++) 566 for(i = start; i < list->count; i++)
567 print_repo(f, &list->repos[i]); 567 print_repo(f, &list->repos[i]);
568} 568}
569 569
570/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 570/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
571 * and return 0 on success. 571 * and return 0 on success.
572 */ 572 */
573static int generate_cached_repolist(const char *path, const char *cached_rc) 573static int generate_cached_repolist(const char *path, const char *cached_rc)
574{ 574{
575 char *locked_rc; 575 char *locked_rc;
576 int idx; 576 int idx;
577 FILE *f; 577 FILE *f;
578 578
579 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 579 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
580 f = fopen(locked_rc, "wx"); 580 f = fopen(locked_rc, "wx");
581 if (!f) { 581 if (!f) {
582 /* Inform about the error unless the lockfile already existed, 582 /* Inform about the error unless the lockfile already existed,
583 * since that only means we've got concurrent requests. 583 * since that only means we've got concurrent requests.
584 */ 584 */
585 if (errno != EEXIST) 585 if (errno != EEXIST)
586 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 586 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
587 locked_rc, strerror(errno), errno); 587 locked_rc, strerror(errno), errno);
588 return errno; 588 return errno;
589 } 589 }
590 idx = cgit_repolist.count; 590 idx = cgit_repolist.count;
591 if (ctx.cfg.project_list) 591 if (ctx.cfg.project_list)
592 scan_projects(path, ctx.cfg.project_list, repo_config); 592 scan_projects(path, ctx.cfg.project_list, repo_config);
593 else 593 else
594 scan_tree(path, repo_config); 594 scan_tree(path, repo_config);
595 print_repolist(f, &cgit_repolist, idx); 595 print_repolist(f, &cgit_repolist, idx);
596 if (rename(locked_rc, cached_rc)) 596 if (rename(locked_rc, cached_rc))
597 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 597 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
598 locked_rc, cached_rc, strerror(errno), errno); 598 locked_rc, cached_rc, strerror(errno), errno);
599 fclose(f); 599 fclose(f);
600 return 0; 600 return 0;
601} 601}
602 602
603static void process_cached_repolist(const char *path) 603static void process_cached_repolist(const char *path)
604{ 604{
605 struct stat st; 605 struct stat st;
606 char *cached_rc; 606 char *cached_rc;
607 time_t age; 607 time_t age;
608 unsigned long hash; 608 unsigned long hash;
609 609
610 hash = hash_str(path); 610 hash = hash_str(path);
611 if (ctx.cfg.project_list) 611 if (ctx.cfg.project_list)
612 hash += hash_str(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)); 613 cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash));
614 614
615 if (stat(cached_rc, &st)) { 615 if (stat(cached_rc, &st)) {
616 /* Nothing is cached, we need to scan without forking. And 616 /* Nothing is cached, we need to scan without forking. And
617 * if we fail to generate a cached repolist, we need to 617 * if we fail to generate a cached repolist, we need to
618 * invoke scan_tree manually. 618 * invoke scan_tree manually.
619 */ 619 */
620 if (generate_cached_repolist(path, cached_rc)) { 620 if (generate_cached_repolist(path, cached_rc)) {
621 if (ctx.cfg.project_list) 621 if (ctx.cfg.project_list)
622 scan_projects(path, ctx.cfg.project_list, 622 scan_projects(path, ctx.cfg.project_list,
623 repo_config); 623 repo_config);
624 else 624 else
625 scan_tree(path, repo_config); 625 scan_tree(path, repo_config);
626 } 626 }
627 return; 627 return;
628 } 628 }
629 629
630 parse_configfile(cached_rc, config_cb); 630 parse_configfile(cached_rc, config_cb);
631 631
632 /* If the cached configfile hasn't expired, lets exit now */ 632 /* If the cached configfile hasn't expired, lets exit now */
633 age = time(NULL) - st.st_mtime; 633 age = time(NULL) - st.st_mtime;
634 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 634 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
635 return; 635 return;
636 636
637 /* The cached repolist has been parsed, but it was old. So lets 637 /* The cached repolist has been parsed, but it was old. So lets
638 * rescan the specified path and generate a new cached repolist 638 * rescan the specified path and generate a new cached repolist
639 * in a child-process to avoid latency for the current request. 639 * in a child-process to avoid latency for the current request.
640 */ 640 */
641 if (fork()) 641 if (fork())
642 return; 642 return;
643 643
644 exit(generate_cached_repolist(path, cached_rc)); 644 exit(generate_cached_repolist(path, cached_rc));
645} 645}
646 646
647static void cgit_parse_args(int argc, const char **argv) 647static void cgit_parse_args(int argc, const char **argv)
648{ 648{
649 int i; 649 int i;
650 int scan = 0; 650 int scan = 0;
651 651
652 for (i = 1; i < argc; i++) { 652 for (i = 1; i < argc; i++) {
653 if (!strncmp(argv[i], "--cache=", 8)) { 653 if (!strncmp(argv[i], "--cache=", 8)) {
654 ctx.cfg.cache_root = xstrdup(argv[i]+8); 654 ctx.cfg.cache_root = xstrdup(argv[i]+8);
655 } 655 }
656 if (!strcmp(argv[i], "--nocache")) { 656 if (!strcmp(argv[i], "--nocache")) {
657 ctx.cfg.nocache = 1; 657 ctx.cfg.nocache = 1;
658 } 658 }
659 if (!strcmp(argv[i], "--nohttp")) { 659 if (!strcmp(argv[i], "--nohttp")) {
660 ctx.env.no_http = "1"; 660 ctx.env.no_http = "1";
661 } 661 }
662 if (!strncmp(argv[i], "--query=", 8)) { 662 if (!strncmp(argv[i], "--query=", 8)) {
663 ctx.qry.raw = xstrdup(argv[i]+8); 663 ctx.qry.raw = xstrdup(argv[i]+8);
664 } 664 }
665 if (!strncmp(argv[i], "--repo=", 7)) { 665 if (!strncmp(argv[i], "--repo=", 7)) {
666 ctx.qry.repo = xstrdup(argv[i]+7); 666 ctx.qry.repo = xstrdup(argv[i]+7);
667 } 667 }
668 if (!strncmp(argv[i], "--page=", 7)) { 668 if (!strncmp(argv[i], "--page=", 7)) {
669 ctx.qry.page = xstrdup(argv[i]+7); 669 ctx.qry.page = xstrdup(argv[i]+7);
670 } 670 }
671 if (!strncmp(argv[i], "--head=", 7)) { 671 if (!strncmp(argv[i], "--head=", 7)) {
672 ctx.qry.head = xstrdup(argv[i]+7); 672 ctx.qry.head = xstrdup(argv[i]+7);
673 ctx.qry.has_symref = 1; 673 ctx.qry.has_symref = 1;
674 } 674 }
675 if (!strncmp(argv[i], "--sha1=", 7)) { 675 if (!strncmp(argv[i], "--sha1=", 7)) {
676 ctx.qry.sha1 = xstrdup(argv[i]+7); 676 ctx.qry.sha1 = xstrdup(argv[i]+7);
677 ctx.qry.has_sha1 = 1; 677 ctx.qry.has_sha1 = 1;
678 } 678 }
679 if (!strncmp(argv[i], "--ofs=", 6)) { 679 if (!strncmp(argv[i], "--ofs=", 6)) {
680 ctx.qry.ofs = atoi(argv[i]+6); 680 ctx.qry.ofs = atoi(argv[i]+6);
681 } 681 }
682 if (!strncmp(argv[i], "--scan-tree=", 12) || 682 if (!strncmp(argv[i], "--scan-tree=", 12) ||
683 !strncmp(argv[i], "--scan-path=", 12)) { 683 !strncmp(argv[i], "--scan-path=", 12)) {
684 /* HACK: the global snapshot bitmask defines the 684 /* HACK: the global snapshot bitmask defines the
685 * set of allowed snapshot formats, but the config 685 * set of allowed snapshot formats, but the config
686 * file hasn't been parsed yet so the mask is 686 * file hasn't been parsed yet so the mask is
687 * currently 0. By setting all bits high before 687 * currently 0. By setting all bits high before
688 * scanning we make sure that any in-repo cgitrc 688 * scanning we make sure that any in-repo cgitrc
689 * snapshot setting is respected by scan_tree(). 689 * snapshot setting is respected by scan_tree().
690 * BTW: we assume that there'll never be more than 690 * BTW: we assume that there'll never be more than
691 * 255 different snapshot formats supported by cgit... 691 * 255 different snapshot formats supported by cgit...
692 */ 692 */
693 ctx.cfg.snapshots = 0xFF; 693 ctx.cfg.snapshots = 0xFF;
694 scan++; 694 scan++;
695 scan_tree(argv[i] + 12, repo_config); 695 scan_tree(argv[i] + 12, repo_config);
696 } 696 }
697 } 697 }
698 if (scan) { 698 if (scan) {
699 qsort(cgit_repolist.repos, cgit_repolist.count, 699 qsort(cgit_repolist.repos, cgit_repolist.count,
700 sizeof(struct cgit_repo), cmp_repos); 700 sizeof(struct cgit_repo), cmp_repos);
701 print_repolist(stdout, &cgit_repolist, 0); 701 print_repolist(stdout, &cgit_repolist, 0);
702 exit(0); 702 exit(0);
703 } 703 }
704} 704}
705 705
706static int calc_ttl() 706static int calc_ttl()
707{ 707{
708 if (!ctx.repo) 708 if (!ctx.repo)
709 return ctx.cfg.cache_root_ttl; 709 return ctx.cfg.cache_root_ttl;
710 710
711 if (!ctx.qry.page) 711 if (!ctx.qry.page)
712 return ctx.cfg.cache_repo_ttl; 712 return ctx.cfg.cache_repo_ttl;
713 713
714 if (ctx.qry.has_symref) 714 if (ctx.qry.has_symref)
715 return ctx.cfg.cache_dynamic_ttl; 715 return ctx.cfg.cache_dynamic_ttl;
716 716
717 if (ctx.qry.has_sha1) 717 if (ctx.qry.has_sha1)
718 return ctx.cfg.cache_static_ttl; 718 return ctx.cfg.cache_static_ttl;
719 719
720 return ctx.cfg.cache_repo_ttl; 720 return ctx.cfg.cache_repo_ttl;
721} 721}
722 722
723int main(int argc, const char **argv) 723int main(int argc, const char **argv)
724{ 724{
725 const char *path; 725 const char *path;
726 char *qry; 726 char *qry;
727 int err, ttl; 727 int err, ttl;
728 728
729 prepare_context(&ctx); 729 prepare_context(&ctx);
730 cgit_repolist.length = 0; 730 cgit_repolist.length = 0;
731 cgit_repolist.count = 0; 731 cgit_repolist.count = 0;
732 cgit_repolist.repos = NULL; 732 cgit_repolist.repos = NULL;
733 733
734 cgit_parse_args(argc, argv); 734 cgit_parse_args(argc, argv);
735 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb); 735 parse_configfile(expand_macros(ctx.env.cgit_config), config_cb);
736 ctx.repo = NULL; 736 ctx.repo = NULL;
737 http_parse_querystring(ctx.qry.raw, querystring_cb); 737 http_parse_querystring(ctx.qry.raw, querystring_cb);
738 738
739 /* If virtual-root isn't specified in cgitrc, lets pretend 739 /* If virtual-root isn't specified in cgitrc, lets pretend
740 * that virtual-root equals SCRIPT_NAME. 740 * that virtual-root equals SCRIPT_NAME.
741 */ 741 */
diff --git a/cgit.h b/cgit.h
index f8076c5..8f5dd2a 100644
--- a/cgit.h
+++ b/cgit.h
@@ -170,146 +170,147 @@ struct cgit_config {
170 char *project_list; 170 char *project_list;
171 char *readme; 171 char *readme;
172 char *robots; 172 char *robots;
173 char *root_title; 173 char *root_title;
174 char *root_desc; 174 char *root_desc;
175 char *root_readme; 175 char *root_readme;
176 char *script_name; 176 char *script_name;
177 char *section; 177 char *section;
178 char *virtual_root; 178 char *virtual_root;
179 int cache_size; 179 int cache_size;
180 int cache_dynamic_ttl; 180 int cache_dynamic_ttl;
181 int cache_max_create_time; 181 int cache_max_create_time;
182 int cache_repo_ttl; 182 int cache_repo_ttl;
183 int cache_root_ttl; 183 int cache_root_ttl;
184 int cache_scanrc_ttl; 184 int cache_scanrc_ttl;
185 int cache_static_ttl; 185 int cache_static_ttl;
186 int embedded; 186 int embedded;
187 int enable_filter_overrides; 187 int enable_filter_overrides;
188 int enable_gitweb_owner; 188 int enable_gitweb_owner;
189 int enable_index_links; 189 int enable_index_links;
190 int enable_log_filecount; 190 int enable_log_filecount;
191 int enable_log_linecount; 191 int enable_log_linecount;
192 int enable_remote_branches; 192 int enable_remote_branches;
193 int enable_subject_links; 193 int enable_subject_links;
194 int enable_tree_linenumbers; 194 int enable_tree_linenumbers;
195 int local_time; 195 int local_time;
196 int max_atom_items; 196 int max_atom_items;
197 int max_repo_count; 197 int max_repo_count;
198 int max_commit_count; 198 int max_commit_count;
199 int max_lock_attempts; 199 int max_lock_attempts;
200 int max_msg_len; 200 int max_msg_len;
201 int max_repodesc_len; 201 int max_repodesc_len;
202 int max_blob_size; 202 int max_blob_size;
203 int max_stats; 203 int max_stats;
204 int nocache; 204 int nocache;
205 int noplainemail; 205 int noplainemail;
206 int noheader; 206 int noheader;
207 int renamelimit; 207 int renamelimit;
208 int remove_suffix; 208 int remove_suffix;
209 int section_from_path; 209 int section_from_path;
210 int snapshots; 210 int snapshots;
211 int summary_branches; 211 int summary_branches;
212 int summary_log; 212 int summary_log;
213 int summary_tags; 213 int summary_tags;
214 int ssdiff; 214 int ssdiff;
215 struct string_list mimetypes; 215 struct string_list mimetypes;
216 struct cgit_filter *about_filter; 216 struct cgit_filter *about_filter;
217 struct cgit_filter *commit_filter; 217 struct cgit_filter *commit_filter;
218 struct cgit_filter *source_filter; 218 struct cgit_filter *source_filter;
219}; 219};
220 220
221struct cgit_page { 221struct cgit_page {
222 time_t modified; 222 time_t modified;
223 time_t expires; 223 time_t expires;
224 size_t size; 224 size_t size;
225 char *mimetype; 225 char *mimetype;
226 char *charset; 226 char *charset;
227 char *filename; 227 char *filename;
228 char *etag; 228 char *etag;
229 char *title; 229 char *title;
230 int status; 230 int status;
231 char *statusmsg; 231 char *statusmsg;
232}; 232};
233 233
234struct cgit_environment { 234struct cgit_environment {
235 char *cgit_config; 235 char *cgit_config;
236 char *http_host; 236 char *http_host;
237 char *https; 237 char *https;
238 char *no_http; 238 char *no_http;
239 char *path_info; 239 char *path_info;
240 char *query_string; 240 char *query_string;
241 char *request_method; 241 char *request_method;
242 char *script_name; 242 char *script_name;
243 char *server_name; 243 char *server_name;
244 char *server_port; 244 char *server_port;
245}; 245};
246 246
247struct cgit_context { 247struct cgit_context {
248 struct cgit_environment env; 248 struct cgit_environment env;
249 struct cgit_query qry; 249 struct cgit_query qry;
250 struct cgit_config cfg; 250 struct cgit_config cfg;
251 struct cgit_repo *repo; 251 struct cgit_repo *repo;
252 struct cgit_page page; 252 struct cgit_page page;
253}; 253};
254 254
255struct cgit_snapshot_format { 255struct cgit_snapshot_format {
256 const char *suffix; 256 const char *suffix;
257 const char *mimetype; 257 const char *mimetype;
258 write_archive_fn_t write_func; 258 write_archive_fn_t write_func;
259 int bit; 259 int bit;
260}; 260};
261 261
262extern const char *cgit_version; 262extern const char *cgit_version;
263 263
264extern struct cgit_repolist cgit_repolist; 264extern struct cgit_repolist cgit_repolist;
265extern struct cgit_context ctx; 265extern struct cgit_context ctx;
266extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 266extern const struct cgit_snapshot_format cgit_snapshot_formats[];
267 267
268extern struct cgit_repo *cgit_add_repo(const char *url); 268extern struct cgit_repo *cgit_add_repo(const char *url);
269extern struct cgit_repo *cgit_get_repoinfo(const char *url); 269extern struct cgit_repo *cgit_get_repoinfo(const char *url);
270extern void cgit_repo_config_cb(const char *name, const char *value); 270extern void cgit_repo_config_cb(const char *name, const char *value);
271 271
272extern int chk_zero(int result, char *msg); 272extern int chk_zero(int result, char *msg);
273extern int chk_positive(int result, char *msg); 273extern int chk_positive(int result, char *msg);
274extern int chk_non_negative(int result, char *msg); 274extern int chk_non_negative(int result, char *msg);
275 275
276extern char *trim_end(const char *str, char c); 276extern char *trim_end(const char *str, char c);
277extern char *strlpart(char *txt, int maxlen); 277extern char *strlpart(char *txt, int maxlen);
278extern char *strrpart(char *txt, int maxlen); 278extern char *strrpart(char *txt, int maxlen);
279 279
280extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 280extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
281extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 281extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
282 int flags, void *cb_data); 282 int flags, void *cb_data);
283 283
284extern void *cgit_free_commitinfo(struct commitinfo *info); 284extern void *cgit_free_commitinfo(struct commitinfo *info);
285 285
286extern int cgit_diff_files(const unsigned char *old_sha1, 286extern int cgit_diff_files(const unsigned char *old_sha1,
287 const unsigned char *new_sha1, 287 const unsigned char *new_sha1,
288 unsigned long *old_size, unsigned long *new_size, 288 unsigned long *old_size, unsigned long *new_size,
289 int *binary, int context, int ignorews, 289 int *binary, int context, int ignorews,
290 linediff_fn fn); 290 linediff_fn fn);
291 291
292extern void cgit_diff_tree(const unsigned char *old_sha1, 292extern void cgit_diff_tree(const unsigned char *old_sha1,
293 const unsigned char *new_sha1, 293 const unsigned char *new_sha1,
294 filepair_fn fn, const char *prefix, int ignorews); 294 filepair_fn fn, const char *prefix, int ignorews);
295 295
296extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 296extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
297 297
298__attribute__((format (printf,1,2)))
298extern char *fmt(const char *format,...); 299extern char *fmt(const char *format,...);
299 300
300extern struct commitinfo *cgit_parse_commit(struct commit *commit); 301extern struct commitinfo *cgit_parse_commit(struct commit *commit);
301extern struct taginfo *cgit_parse_tag(struct tag *tag); 302extern struct taginfo *cgit_parse_tag(struct tag *tag);
302extern void cgit_parse_url(const char *url); 303extern void cgit_parse_url(const char *url);
303 304
304extern const char *cgit_repobasename(const char *reponame); 305extern const char *cgit_repobasename(const char *reponame);
305 306
306extern int cgit_parse_snapshots_mask(const char *str); 307extern int cgit_parse_snapshots_mask(const char *str);
307 308
308extern int cgit_open_filter(struct cgit_filter *filter); 309extern int cgit_open_filter(struct cgit_filter *filter);
309extern int cgit_close_filter(struct cgit_filter *filter); 310extern int cgit_close_filter(struct cgit_filter *filter);
310 311
311extern int readfile(const char *path, char **buf, size_t *size); 312extern int readfile(const char *path, char **buf, size_t *size);
312 313
313extern char *expand_macros(const char *txt); 314extern char *expand_macros(const char *txt);
314 315
315#endif /* CGIT_H */ 316#endif /* CGIT_H */
diff --git a/html.c b/html.c
index eaabf72..1305910 100644
--- a/html.c
+++ b/html.c
@@ -1,320 +1,320 @@
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", "%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", 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{
46 static char buf[8][1024]; 46 static char buf[8][1024];
47 static int bufidx; 47 static int bufidx;
48 int len; 48 int len;
49 va_list args; 49 va_list args;
50 50
51 bufidx++; 51 bufidx++;
52 bufidx &= 7; 52 bufidx &= 7;
53 53
54 va_start(args, format); 54 va_start(args, format);
55 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); 55 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args);
56 va_end(args); 56 va_end(args);
57 if (len>sizeof(buf[bufidx])) { 57 if (len>sizeof(buf[bufidx])) {
58 fprintf(stderr, "[html.c] string truncated: %s\n", format); 58 fprintf(stderr, "[html.c] string truncated: %s\n", format);
59 exit(1); 59 exit(1);
60 } 60 }
61 return buf[bufidx]; 61 return buf[bufidx];
62} 62}
63 63
64void html_raw(const char *data, size_t size) 64void html_raw(const char *data, size_t size)
65{ 65{
66 write(htmlfd, data, size); 66 write(htmlfd, data, size);
67} 67}
68 68
69void html(const char *txt) 69void html(const char *txt)
70{ 70{
71 write(htmlfd, txt, strlen(txt)); 71 write(htmlfd, txt, strlen(txt));
72} 72}
73 73
74void htmlf(const char *format, ...) 74void htmlf(const char *format, ...)
75{ 75{
76 static char buf[65536]; 76 static char buf[65536];
77 va_list args; 77 va_list args;
78 78
79 va_start(args, format); 79 va_start(args, format);
80 vsnprintf(buf, sizeof(buf), format, args); 80 vsnprintf(buf, sizeof(buf), format, args);
81 va_end(args); 81 va_end(args);
82 html(buf); 82 html(buf);
83} 83}
84 84
85void html_status(int code, const char *msg, int more_headers) 85void html_status(int code, const char *msg, int more_headers)
86{ 86{
87 htmlf("Status: %d %s\n", code, msg); 87 htmlf("Status: %d %s\n", code, msg);
88 if (!more_headers) 88 if (!more_headers)
89 html("\n"); 89 html("\n");
90} 90}
91 91
92void html_txt(const char *txt) 92void html_txt(const char *txt)
93{ 93{
94 const char *t = txt; 94 const char *t = txt;
95 while(t && *t){ 95 while(t && *t){
96 int c = *t; 96 int c = *t;
97 if (c=='<' || c=='>' || c=='&') { 97 if (c=='<' || c=='>' || c=='&') {
98 write(htmlfd, txt, t - txt); 98 html_raw(txt, t - txt);
99 if (c=='>') 99 if (c=='>')
100 html("&gt;"); 100 html("&gt;");
101 else if (c=='<') 101 else if (c=='<')
102 html("&lt;"); 102 html("&lt;");
103 else if (c=='&') 103 else if (c=='&')
104 html("&amp;"); 104 html("&amp;");
105 txt = t+1; 105 txt = t+1;
106 } 106 }
107 t++; 107 t++;
108 } 108 }
109 if (t!=txt) 109 if (t!=txt)
110 html(txt); 110 html(txt);
111} 111}
112 112
113void html_ntxt(int len, const char *txt) 113void html_ntxt(int len, const char *txt)
114{ 114{
115 const char *t = txt; 115 const char *t = txt;
116 while(t && *t && len--){ 116 while(t && *t && len--){
117 int c = *t; 117 int c = *t;
118 if (c=='<' || c=='>' || c=='&') { 118 if (c=='<' || c=='>' || c=='&') {
119 write(htmlfd, txt, t - txt); 119 html_raw(txt, t - txt);
120 if (c=='>') 120 if (c=='>')
121 html("&gt;"); 121 html("&gt;");
122 else if (c=='<') 122 else if (c=='<')
123 html("&lt;"); 123 html("&lt;");
124 else if (c=='&') 124 else if (c=='&')
125 html("&amp;"); 125 html("&amp;");
126 txt = t+1; 126 txt = t+1;
127 } 127 }
128 t++; 128 t++;
129 } 129 }
130 if (t!=txt) 130 if (t!=txt)
131 write(htmlfd, txt, t - txt); 131 html_raw(txt, t - txt);
132 if (len<0) 132 if (len<0)
133 html("..."); 133 html("...");
134} 134}
135 135
136void html_attr(const char *txt) 136void html_attr(const char *txt)
137{ 137{
138 const char *t = txt; 138 const char *t = txt;
139 while(t && *t){ 139 while(t && *t){
140 int c = *t; 140 int c = *t;
141 if (c=='<' || c=='>' || c=='\'' || c=='\"') { 141 if (c=='<' || c=='>' || c=='\'' || c=='\"') {
142 write(htmlfd, txt, t - txt); 142 html_raw(txt, t - txt);
143 if (c=='>') 143 if (c=='>')
144 html("&gt;"); 144 html("&gt;");
145 else if (c=='<') 145 else if (c=='<')
146 html("&lt;"); 146 html("&lt;");
147 else if (c=='\'') 147 else if (c=='\'')
148 html("&#x27;"); 148 html("&#x27;");
149 else if (c=='"') 149 else if (c=='"')
150 html("&quot;"); 150 html("&quot;");
151 txt = t+1; 151 txt = t+1;
152 } 152 }
153 t++; 153 t++;
154 } 154 }
155 if (t!=txt) 155 if (t!=txt)
156 html(txt); 156 html(txt);
157} 157}
158 158
159void html_url_path(const char *txt) 159void 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 write(htmlfd, txt, t - txt); 166 html_raw(txt, t - txt);
167 write(htmlfd, 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 write(htmlfd, txt, t - txt); 183 html_raw(txt, t - txt);
184 write(htmlfd, e, 3); 184 html_raw(e, 3);
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'");
209 html(">"); 209 html(">");
210 html_txt(text); 210 html_txt(text);
211 html("</option>\n"); 211 html("</option>\n");
212} 212}
213 213
214void html_link_open(const char *url, const char *title, const char *class) 214void html_link_open(const char *url, const char *title, const char *class)
215{ 215{
216 html("<a href='"); 216 html("<a href='");
217 html_attr(url); 217 html_attr(url);
218 if (title) { 218 if (title) {
219 html("' title='"); 219 html("' title='");
220 html_attr(title); 220 html_attr(title);
221 } 221 }
222 if (class) { 222 if (class) {
223 html("' class='"); 223 html("' class='");
224 html_attr(class); 224 html_attr(class);
225 } 225 }
226 html("'>"); 226 html("'>");
227} 227}
228 228
229void html_link_close(void) 229void html_link_close(void)
230{ 230{
231 html("</a>"); 231 html("</a>");
232} 232}
233 233
234void html_fileperm(unsigned short mode) 234void html_fileperm(unsigned short mode)
235{ 235{
236 htmlf("%c%c%c", (mode & 4 ? 'r' : '-'), 236 htmlf("%c%c%c", (mode & 4 ? 'r' : '-'),
237 (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-')); 237 (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-'));
238} 238}
239 239
240int html_include(const char *filename) 240int html_include(const char *filename)
241{ 241{
242 FILE *f; 242 FILE *f;
243 char buf[4096]; 243 char buf[4096];
244 size_t len; 244 size_t len;
245 245
246 if (!(f = fopen(filename, "r"))) { 246 if (!(f = fopen(filename, "r"))) {
247 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n", 247 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n",
248 filename, strerror(errno), errno); 248 filename, strerror(errno), errno);
249 return -1; 249 return -1;
250 } 250 }
251 while((len = fread(buf, 1, 4096, f)) > 0) 251 while((len = fread(buf, 1, 4096, f)) > 0)
252 write(htmlfd, buf, len); 252 html_raw(buf, len);
253 fclose(f); 253 fclose(f);
254 return 0; 254 return 0;
255} 255}
256 256
257int hextoint(char c) 257int hextoint(char c)
258{ 258{
259 if (c >= 'a' && c <= 'f') 259 if (c >= 'a' && c <= 'f')
260 return 10 + c - 'a'; 260 return 10 + c - 'a';
261 else if (c >= 'A' && c <= 'F') 261 else if (c >= 'A' && c <= 'F')
262 return 10 + c - 'A'; 262 return 10 + c - 'A';
263 else if (c >= '0' && c <= '9') 263 else if (c >= '0' && c <= '9')
264 return c - '0'; 264 return c - '0';
265 else 265 else
266 return -1; 266 return -1;
267} 267}
268 268
269char *convert_query_hexchar(char *txt) 269char *convert_query_hexchar(char *txt)
270{ 270{
271 int d1, d2, n; 271 int d1, d2, n;
272 n = strlen(txt); 272 n = strlen(txt);
273 if (n < 3) { 273 if (n < 3) {
274 *txt = '\0'; 274 *txt = '\0';
275 return txt-1; 275 return txt-1;
276 } 276 }
277 d1 = hextoint(*(txt+1)); 277 d1 = hextoint(*(txt+1));
278 d2 = hextoint(*(txt+2)); 278 d2 = hextoint(*(txt+2));
279 if (d1<0 || d2<0) { 279 if (d1<0 || d2<0) {
280 memmove(txt, txt+3, n-3); 280 memmove(txt, txt+3, n-3);
281 return txt-1; 281 return txt-1;
282 } else { 282 } else {
283 *txt = d1 * 16 + d2; 283 *txt = d1 * 16 + d2;
284 memmove(txt+1, txt+3, n-2); 284 memmove(txt+1, txt+3, n-2);
285 return txt; 285 return txt;
286 } 286 }
287} 287}
288 288
289int http_parse_querystring(const 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))
290{ 290{
291 char *t, *txt, *value = NULL, c; 291 char *t, *txt, *value = NULL, c;
292 292
293 if (!txt_) 293 if (!txt_)
294 return 0; 294 return 0;
295 295
296 t = txt = strdup(txt_); 296 t = txt = strdup(txt_);
297 if (t == NULL) { 297 if (t == NULL) {
298 printf("Out of memory\n"); 298 printf("Out of memory\n");
299 exit(1); 299 exit(1);
300 } 300 }
301 while((c=*t) != '\0') { 301 while((c=*t) != '\0') {
302 if (c=='=') { 302 if (c=='=') {
303 *t = '\0'; 303 *t = '\0';
304 value = t+1; 304 value = t+1;
305 } else if (c=='+') { 305 } else if (c=='+') {
306 *t = ' '; 306 *t = ' ';
307 } else if (c=='%') { 307 } else if (c=='%') {
308 t = convert_query_hexchar(t); 308 t = convert_query_hexchar(t);
309 } else if (c=='&') { 309 } else if (c=='&') {
310 *t = '\0'; 310 *t = '\0';
311 (*fn)(txt, value); 311 (*fn)(txt, value);
312 txt = t+1; 312 txt = t+1;
313 value = NULL; 313 value = NULL;
314 } 314 }
315 t++; 315 t++;
316 } 316 }
317 if (t!=txt) 317 if (t!=txt)
318 (*fn)(txt, value); 318 (*fn)(txt, value);
319 return 0; 319 return 0;
320} 320}
diff --git a/html.h b/html.h
index 16d55ec..1135fb8 100644
--- a/html.h
+++ b/html.h
@@ -1,24 +1,27 @@
1#ifndef HTML_H 1#ifndef HTML_H
2#define HTML_H 2#define HTML_H
3 3
4extern int htmlfd; 4extern int htmlfd;
5 5
6extern void html_raw(const char *txt, size_t size); 6extern void html_raw(const char *txt, size_t size);
7extern void html(const char *txt); 7extern void html(const char *txt);
8
9__attribute__((format (printf,1,2)))
8extern void htmlf(const char *format,...); 10extern void htmlf(const char *format,...);
11
9extern void html_status(int code, const char *msg, int more_headers); 12extern void html_status(int code, const char *msg, int more_headers);
10extern void html_txt(const char *txt); 13extern void html_txt(const char *txt);
11extern void html_ntxt(int len, const char *txt); 14extern void html_ntxt(int len, const char *txt);
12extern void html_attr(const char *txt); 15extern void html_attr(const char *txt);
13extern void html_url_path(const char *txt); 16extern void html_url_path(const char *txt);
14extern void html_url_arg(const char *txt); 17extern void html_url_arg(const char *txt);
15extern void html_hidden(const char *name, const char *value); 18extern void html_hidden(const char *name, const char *value);
16extern void html_option(const char *value, const char *text, const char *selected_value); 19extern void html_option(const char *value, const char *text, const char *selected_value);
17extern void html_link_open(const char *url, const char *title, const char *class); 20extern void html_link_open(const char *url, const char *title, const char *class);
18extern void html_link_close(void); 21extern void html_link_close(void);
19extern void html_fileperm(unsigned short mode); 22extern void html_fileperm(unsigned short mode);
20extern int html_include(const char *filename); 23extern int html_include(const char *filename);
21 24
22extern int http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value)); 25extern int http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value));
23 26
24#endif /* HTML_H */ 27#endif /* HTML_H */
diff --git a/ui-blob.c b/ui-blob.c
index 667a451..ec435e1 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -1,112 +1,112 @@
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 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 * 5 *
6 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text) 7 * (see COPYING for full license text)
8 */ 8 */
9 9
10#include "cgit.h" 10#include "cgit.h"
11#include "html.h" 11#include "html.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13 13
14static char *match_path; 14static char *match_path;
15static unsigned char *matched_sha1; 15static unsigned char *matched_sha1;
16static int found_path; 16static int found_path;
17 17
18static 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,
19 const char *pathname, unsigned mode, int stage, void *cbdata) { 19 const char *pathname, unsigned mode, int stage, void *cbdata) {
20 if(strncmp(base,match_path,baselen) 20 if(strncmp(base,match_path,baselen)
21 || strcmp(match_path+baselen,pathname) ) 21 || strcmp(match_path+baselen,pathname) )
22 return READ_TREE_RECURSIVE; 22 return READ_TREE_RECURSIVE;
23 memmove(matched_sha1,sha1,20); 23 memmove(matched_sha1,sha1,20);
24 found_path = 1; 24 found_path = 1;
25 return 0; 25 return 0;
26} 26}
27 27
28int cgit_print_file(char *path, const char *head) 28int cgit_print_file(char *path, const char *head)
29{ 29{
30 unsigned char sha1[20]; 30 unsigned char sha1[20];
31 enum object_type type; 31 enum object_type type;
32 char *buf; 32 char *buf;
33 unsigned long size; 33 unsigned long size;
34 struct commit *commit; 34 struct commit *commit;
35 const char *paths[] = {path, NULL}; 35 const char *paths[] = {path, NULL};
36 if (get_sha1(head, sha1)) 36 if (get_sha1(head, sha1))
37 return -1; 37 return -1;
38 type = sha1_object_info(sha1, &size); 38 type = sha1_object_info(sha1, &size);
39 if(type == OBJ_COMMIT && path) { 39 if(type == OBJ_COMMIT && path) {
40 commit = lookup_commit_reference(sha1); 40 commit = lookup_commit_reference(sha1);
41 match_path = path; 41 match_path = path;
42 matched_sha1 = sha1; 42 matched_sha1 = sha1;
43 found_path = 0; 43 found_path = 0;
44 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 44 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
45 if (!found_path) 45 if (!found_path)
46 return -1; 46 return -1;
47 type = sha1_object_info(sha1, &size); 47 type = sha1_object_info(sha1, &size);
48 } 48 }
49 if (type == OBJ_BAD) 49 if (type == OBJ_BAD)
50 return -1; 50 return -1;
51 buf = read_sha1_file(sha1, &type, &size); 51 buf = read_sha1_file(sha1, &type, &size);
52 if (!buf) 52 if (!buf)
53 return -1; 53 return -1;
54 buf[size] = '\0'; 54 buf[size] = '\0';
55 write(htmlfd, buf, size); 55 html_raw(buf, size);
56 return 0; 56 return 0;
57} 57}
58 58
59void cgit_print_blob(const char *hex, char *path, const char *head) 59void cgit_print_blob(const char *hex, char *path, const char *head)
60{ 60{
61 unsigned char sha1[20]; 61 unsigned char sha1[20];
62 enum object_type type; 62 enum object_type type;
63 char *buf; 63 char *buf;
64 unsigned long size; 64 unsigned long size;
65 struct commit *commit; 65 struct commit *commit;
66 const char *paths[] = {path, NULL}; 66 const char *paths[] = {path, NULL};
67 67
68 if (hex) { 68 if (hex) {
69 if (get_sha1_hex(hex, sha1)){ 69 if (get_sha1_hex(hex, sha1)){
70 cgit_print_error(fmt("Bad hex value: %s", hex)); 70 cgit_print_error(fmt("Bad hex value: %s", hex));
71 return; 71 return;
72 } 72 }
73 } else { 73 } else {
74 if (get_sha1(head,sha1)) { 74 if (get_sha1(head,sha1)) {
75 cgit_print_error(fmt("Bad ref: %s", head)); 75 cgit_print_error(fmt("Bad ref: %s", head));
76 return; 76 return;
77 } 77 }
78 } 78 }
79 79
80 type = sha1_object_info(sha1, &size); 80 type = sha1_object_info(sha1, &size);
81 81
82 if((!hex) && type == OBJ_COMMIT && path) { 82 if((!hex) && type == OBJ_COMMIT && path) {
83 commit = lookup_commit_reference(sha1); 83 commit = lookup_commit_reference(sha1);
84 match_path = path; 84 match_path = path;
85 matched_sha1 = sha1; 85 matched_sha1 = sha1;
86 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 86 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
87 type = sha1_object_info(sha1,&size); 87 type = sha1_object_info(sha1,&size);
88 } 88 }
89 89
90 if (type == OBJ_BAD) { 90 if (type == OBJ_BAD) {
91 cgit_print_error(fmt("Bad object name: %s", hex)); 91 cgit_print_error(fmt("Bad object name: %s", hex));
92 return; 92 return;
93 } 93 }
94 94
95 buf = read_sha1_file(sha1, &type, &size); 95 buf = read_sha1_file(sha1, &type, &size);
96 if (!buf) { 96 if (!buf) {
97 cgit_print_error(fmt("Error reading object %s", hex)); 97 cgit_print_error(fmt("Error reading object %s", hex));
98 return; 98 return;
99 } 99 }
100 100
101 buf[size] = '\0'; 101 buf[size] = '\0';
102 ctx.page.mimetype = ctx.qry.mimetype; 102 ctx.page.mimetype = ctx.qry.mimetype;
103 if (!ctx.page.mimetype) { 103 if (!ctx.page.mimetype) {
104 if (buffer_is_binary(buf, size)) 104 if (buffer_is_binary(buf, size))
105 ctx.page.mimetype = "application/octet-stream"; 105 ctx.page.mimetype = "application/octet-stream";
106 else 106 else
107 ctx.page.mimetype = "text/plain"; 107 ctx.page.mimetype = "text/plain";
108 } 108 }
109 ctx.page.filename = path; 109 ctx.page.filename = path;
110 cgit_print_http_headers(&ctx); 110 cgit_print_http_headers(&ctx);
111 write(htmlfd, buf, size); 111 html_raw(buf, size);
112} 112}
diff --git a/ui-diff.c b/ui-diff.c
index 0dcabe9..7ff7e46 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,223 +1,223 @@
1/* ui-diff.c: show diff between two blobs 1/* ui-diff.c: show diff between two blobs
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 "ui-ssdiff.h" 12#include "ui-ssdiff.h"
13 13
14unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
15unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
16 16
17static int files, slots; 17static int files, slots;
18static int total_adds, total_rems, max_changes; 18static int total_adds, total_rems, max_changes;
19static int lines_added, lines_removed; 19static int lines_added, lines_removed;
20 20
21static struct fileinfo { 21static struct fileinfo {
22 char status; 22 char status;
23 unsigned char old_sha1[20]; 23 unsigned char old_sha1[20];
24 unsigned char new_sha1[20]; 24 unsigned char new_sha1[20];
25 unsigned short old_mode; 25 unsigned short old_mode;
26 unsigned short new_mode; 26 unsigned short new_mode;
27 char *old_path; 27 char *old_path;
28 char *new_path; 28 char *new_path;
29 unsigned int added; 29 unsigned int added;
30 unsigned int removed; 30 unsigned int removed;
31 unsigned long old_size; 31 unsigned long old_size;
32 unsigned long new_size; 32 unsigned long new_size;
33 int binary:1; 33 int binary:1;
34} *items; 34} *items;
35 35
36static int use_ssdiff = 0; 36static int use_ssdiff = 0;
37 37
38static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
39{ 39{
40 char *class; 40 char *class;
41 41
42 switch (info->status) { 42 switch (info->status) {
43 case DIFF_STATUS_ADDED: 43 case DIFF_STATUS_ADDED:
44 class = "add"; 44 class = "add";
45 break; 45 break;
46 case DIFF_STATUS_COPIED: 46 case DIFF_STATUS_COPIED:
47 class = "cpy"; 47 class = "cpy";
48 break; 48 break;
49 case DIFF_STATUS_DELETED: 49 case DIFF_STATUS_DELETED:
50 class = "del"; 50 class = "del";
51 break; 51 break;
52 case DIFF_STATUS_MODIFIED: 52 case DIFF_STATUS_MODIFIED:
53 class = "upd"; 53 class = "upd";
54 break; 54 break;
55 case DIFF_STATUS_RENAMED: 55 case DIFF_STATUS_RENAMED:
56 class = "mov"; 56 class = "mov";
57 break; 57 break;
58 case DIFF_STATUS_TYPE_CHANGED: 58 case DIFF_STATUS_TYPE_CHANGED:
59 class = "typ"; 59 class = "typ";
60 break; 60 break;
61 case DIFF_STATUS_UNKNOWN: 61 case DIFF_STATUS_UNKNOWN:
62 class = "unk"; 62 class = "unk";
63 break; 63 break;
64 case DIFF_STATUS_UNMERGED: 64 case DIFF_STATUS_UNMERGED:
65 class = "stg"; 65 class = "stg";
66 break; 66 break;
67 default: 67 default:
68 die("bug: unhandled diff status %c", info->status); 68 die("bug: unhandled diff status %c", info->status);
69 } 69 }
70 70
71 html("<tr>"); 71 html("<tr>");
72 htmlf("<td class='mode'>"); 72 htmlf("<td class='mode'>");
73 if (is_null_sha1(info->new_sha1)) { 73 if (is_null_sha1(info->new_sha1)) {
74 cgit_print_filemode(info->old_mode); 74 cgit_print_filemode(info->old_mode);
75 } else { 75 } else {
76 cgit_print_filemode(info->new_mode); 76 cgit_print_filemode(info->new_mode);
77 } 77 }
78 78
79 if (info->old_mode != info->new_mode && 79 if (info->old_mode != info->new_mode &&
80 !is_null_sha1(info->old_sha1) && 80 !is_null_sha1(info->old_sha1) &&
81 !is_null_sha1(info->new_sha1)) { 81 !is_null_sha1(info->new_sha1)) {
82 html("<span class='modechange'>["); 82 html("<span class='modechange'>[");
83 cgit_print_filemode(info->old_mode); 83 cgit_print_filemode(info->old_mode);
84 html("]</span>"); 84 html("]</span>");
85 } 85 }
86 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
87 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,
88 ctx.qry.sha2, info->new_path, 0); 88 ctx.qry.sha2, info->new_path, 0);
89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
90 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
92 info->old_path); 92 info->old_path);
93 html("</td><td class='right'>"); 93 html("</td><td class='right'>");
94 if (info->binary) { 94 if (info->binary) {
95 htmlf("bin</td><td class='graph'>%d -> %d bytes", 95 htmlf("bin</td><td class='graph'>%ld -> %ld bytes",
96 info->old_size, info->new_size); 96 info->old_size, info->new_size);
97 return; 97 return;
98 } 98 }
99 htmlf("%d", info->added + info->removed); 99 htmlf("%d", info->added + info->removed);
100 html("</td><td class='graph'>"); 100 html("</td><td class='graph'>");
101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
102 htmlf("<td class='add' style='width: %.1f%%;'/>", 102 htmlf("<td class='add' style='width: %.1f%%;'/>",
103 info->added * 100.0 / max_changes); 103 info->added * 100.0 / max_changes);
104 htmlf("<td class='rem' style='width: %.1f%%;'/>", 104 htmlf("<td class='rem' style='width: %.1f%%;'/>",
105 info->removed * 100.0 / max_changes); 105 info->removed * 100.0 / max_changes);
106 htmlf("<td class='none' style='width: %.1f%%;'/>", 106 htmlf("<td class='none' style='width: %.1f%%;'/>",
107 (max_changes - info->removed - info->added) * 100.0 / max_changes); 107 (max_changes - info->removed - info->added) * 100.0 / max_changes);
108 html("</tr></table></td></tr>\n"); 108 html("</tr></table></td></tr>\n");
109} 109}
110 110
111static void count_diff_lines(char *line, int len) 111static void count_diff_lines(char *line, int len)
112{ 112{
113 if (line && (len > 0)) { 113 if (line && (len > 0)) {
114 if (line[0] == '+') 114 if (line[0] == '+')
115 lines_added++; 115 lines_added++;
116 else if (line[0] == '-') 116 else if (line[0] == '-')
117 lines_removed++; 117 lines_removed++;
118 } 118 }
119} 119}
120 120
121static void inspect_filepair(struct diff_filepair *pair) 121static void inspect_filepair(struct diff_filepair *pair)
122{ 122{
123 int binary = 0; 123 int binary = 0;
124 unsigned long old_size = 0; 124 unsigned long old_size = 0;
125 unsigned long new_size = 0; 125 unsigned long new_size = 0;
126 files++; 126 files++;
127 lines_added = 0; 127 lines_added = 0;
128 lines_removed = 0; 128 lines_removed = 0;
129 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,
130 &binary, 0, ctx.qry.ignorews, count_diff_lines); 130 &binary, 0, ctx.qry.ignorews, count_diff_lines);
131 if (files >= slots) { 131 if (files >= slots) {
132 if (slots == 0) 132 if (slots == 0)
133 slots = 4; 133 slots = 4;
134 else 134 else
135 slots = slots * 2; 135 slots = slots * 2;
136 items = xrealloc(items, slots * sizeof(struct fileinfo)); 136 items = xrealloc(items, slots * sizeof(struct fileinfo));
137 } 137 }
138 items[files-1].status = pair->status; 138 items[files-1].status = pair->status;
139 hashcpy(items[files-1].old_sha1, pair->one->sha1); 139 hashcpy(items[files-1].old_sha1, pair->one->sha1);
140 hashcpy(items[files-1].new_sha1, pair->two->sha1); 140 hashcpy(items[files-1].new_sha1, pair->two->sha1);
141 items[files-1].old_mode = pair->one->mode; 141 items[files-1].old_mode = pair->one->mode;
142 items[files-1].new_mode = pair->two->mode; 142 items[files-1].new_mode = pair->two->mode;
143 items[files-1].old_path = xstrdup(pair->one->path); 143 items[files-1].old_path = xstrdup(pair->one->path);
144 items[files-1].new_path = xstrdup(pair->two->path); 144 items[files-1].new_path = xstrdup(pair->two->path);
145 items[files-1].added = lines_added; 145 items[files-1].added = lines_added;
146 items[files-1].removed = lines_removed; 146 items[files-1].removed = lines_removed;
147 items[files-1].old_size = old_size; 147 items[files-1].old_size = old_size;
148 items[files-1].new_size = new_size; 148 items[files-1].new_size = new_size;
149 items[files-1].binary = binary; 149 items[files-1].binary = binary;
150 if (lines_added + lines_removed > max_changes) 150 if (lines_added + lines_removed > max_changes)
151 max_changes = lines_added + lines_removed; 151 max_changes = lines_added + lines_removed;
152 total_adds += lines_added; 152 total_adds += lines_added;
153 total_rems += lines_removed; 153 total_rems += lines_removed;
154} 154}
155 155
156void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
157 const unsigned char *new_sha1, const char *prefix) 157 const unsigned char *new_sha1, const char *prefix)
158{ 158{
159 int i, save_context = ctx.qry.context; 159 int i, save_context = ctx.qry.context;
160 160
161 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
162 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,
163 ctx.qry.sha2, NULL, 0); 163 ctx.qry.sha2, NULL, 0);
164 if (prefix) 164 if (prefix)
165 htmlf(" (limited to '%s')", prefix); 165 htmlf(" (limited to '%s')", prefix);
166 html(" ("); 166 html(" (");
167 ctx.qry.context = (save_context > 0 ? save_context : 3) << 1; 167 ctx.qry.context = (save_context > 0 ? save_context : 3) << 1;
168 cgit_self_link("more", NULL, NULL, &ctx); 168 cgit_self_link("more", NULL, NULL, &ctx);
169 html("/"); 169 html("/");
170 ctx.qry.context = (save_context > 3 ? save_context : 3) >> 1; 170 ctx.qry.context = (save_context > 3 ? save_context : 3) >> 1;
171 cgit_self_link("less", NULL, NULL, &ctx); 171 cgit_self_link("less", NULL, NULL, &ctx);
172 ctx.qry.context = save_context; 172 ctx.qry.context = save_context;
173 html(" context)"); 173 html(" context)");
174 html(" ("); 174 html(" (");
175 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2; 175 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2;
176 cgit_self_link(ctx.qry.ignorews ? "ignore" : "show", NULL, NULL, &ctx); 176 cgit_self_link(ctx.qry.ignorews ? "ignore" : "show", NULL, NULL, &ctx);
177 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2; 177 ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2;
178 html(" whitespace changes)"); 178 html(" whitespace changes)");
179 html("</div>"); 179 html("</div>");
180 html("<table summary='diffstat' class='diffstat'>"); 180 html("<table summary='diffstat' class='diffstat'>");
181 max_changes = 0; 181 max_changes = 0;
182 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix, 182 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix,
183 ctx.qry.ignorews); 183 ctx.qry.ignorews);
184 for(i = 0; i<files; i++) 184 for(i = 0; i<files; i++)
185 print_fileinfo(&items[i]); 185 print_fileinfo(&items[i]);
186 html("</table>"); 186 html("</table>");
187 html("<div class='diffstat-summary'>"); 187 html("<div class='diffstat-summary'>");
188 htmlf("%d files changed, %d insertions, %d deletions", 188 htmlf("%d files changed, %d insertions, %d deletions",
189 files, total_adds, total_rems); 189 files, total_adds, total_rems);
190 html("</div>"); 190 html("</div>");
191} 191}
192 192
193 193
194/* 194/*
195 * print a single line returned from xdiff 195 * print a single line returned from xdiff
196 */ 196 */
197static void print_line(char *line, int len) 197static void print_line(char *line, int len)
198{ 198{
199 char *class = "ctx"; 199 char *class = "ctx";
200 char c = line[len-1]; 200 char c = line[len-1];
201 201
202 if (line[0] == '+') 202 if (line[0] == '+')
203 class = "add"; 203 class = "add";
204 else if (line[0] == '-') 204 else if (line[0] == '-')
205 class = "del"; 205 class = "del";
206 else if (line[0] == '@') 206 else if (line[0] == '@')
207 class = "hunk"; 207 class = "hunk";
208 208
209 htmlf("<div class='%s'>", class); 209 htmlf("<div class='%s'>", class);
210 line[len-1] = '\0'; 210 line[len-1] = '\0';
211 html_txt(line); 211 html_txt(line);
212 html("</div>"); 212 html("</div>");
213 line[len-1] = c; 213 line[len-1] = c;
214} 214}
215 215
216static void header(unsigned char *sha1, char *path1, int mode1, 216static void header(unsigned char *sha1, char *path1, int mode1,
217 unsigned char *sha2, char *path2, int mode2) 217 unsigned char *sha2, char *path2, int mode2)
218{ 218{
219 char *abbrev1, *abbrev2; 219 char *abbrev1, *abbrev2;
220 int subproject; 220 int subproject;
221 221
222 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 222 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
223 html("<div class='head'>"); 223 html("<div class='head'>");
diff --git a/ui-log.c b/ui-log.c
index 0536b23..41b5225 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -103,151 +103,150 @@ void print_commit(struct commit *commit)
103 rem_lines = 0; 103 rem_lines = 0;
104 cgit_diff_commit(commit, inspect_files); 104 cgit_diff_commit(commit, inspect_files);
105 html("</td><td>"); 105 html("</td><td>");
106 htmlf("%d", files); 106 htmlf("%d", files);
107 if (ctx.repo->enable_log_linecount) { 107 if (ctx.repo->enable_log_linecount) {
108 html("</td><td>"); 108 html("</td><td>");
109 htmlf("-%d/+%d", rem_lines, add_lines); 109 htmlf("-%d/+%d", rem_lines, add_lines);
110 } 110 }
111 } 111 }
112 html("</td></tr>\n"); 112 html("</td></tr>\n");
113 if (ctx.qry.showmsg) { 113 if (ctx.qry.showmsg) {
114 struct strbuf notes = STRBUF_INIT; 114 struct strbuf notes = STRBUF_INIT;
115 format_note(NULL, commit->object.sha1, &notes, PAGE_ENCODING, 0); 115 format_note(NULL, commit->object.sha1, &notes, PAGE_ENCODING, 0);
116 116
117 if (ctx.repo->enable_log_filecount) { 117 if (ctx.repo->enable_log_filecount) {
118 cols++; 118 cols++;
119 if (ctx.repo->enable_log_linecount) 119 if (ctx.repo->enable_log_linecount)
120 cols++; 120 cols++;
121 } 121 }
122 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 122 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
123 cols); 123 cols);
124 html_txt(info->msg); 124 html_txt(info->msg);
125 html("</td></tr>\n"); 125 html("</td></tr>\n");
126 if (notes.len != 0) { 126 if (notes.len != 0) {
127 html("<tr class='nohover'>"); 127 html("<tr class='nohover'>");
128 html("<td class='lognotes-label'>Notes:</td>"); 128 html("<td class='lognotes-label'>Notes:</td>");
129 htmlf("<td colspan='%d' class='lognotes'>", 129 htmlf("<td colspan='%d' class='lognotes'>",
130 cols); 130 cols);
131 html_txt(notes.buf); 131 html_txt(notes.buf);
132 html("</td></tr>\n"); 132 html("</td></tr>\n");
133 } 133 }
134 strbuf_release(&notes); 134 strbuf_release(&notes);
135 } 135 }
136 cgit_free_commitinfo(info); 136 cgit_free_commitinfo(info);
137} 137}
138 138
139static const char *disambiguate_ref(const char *ref) 139static const char *disambiguate_ref(const char *ref)
140{ 140{
141 unsigned char sha1[20]; 141 unsigned char sha1[20];
142 const char *longref; 142 const char *longref;
143 143
144 longref = fmt("refs/heads/%s", ref); 144 longref = fmt("refs/heads/%s", ref);
145 if (get_sha1(longref, sha1) == 0) 145 if (get_sha1(longref, sha1) == 0)
146 return longref; 146 return longref;
147 147
148 return ref; 148 return ref;
149} 149}
150 150
151void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 151void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
152 char *path, int pager) 152 char *path, int pager)
153{ 153{
154 struct rev_info rev; 154 struct rev_info rev;
155 struct commit *commit; 155 struct commit *commit;
156 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 156 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
157 int argc = 2; 157 int argc = 2;
158 int i, columns = 3; 158 int i, columns = 3;
159 159
160 if (!tip) 160 if (!tip)
161 tip = ctx.qry.head; 161 tip = ctx.qry.head;
162 162
163 argv[1] = disambiguate_ref(tip); 163 argv[1] = disambiguate_ref(tip);
164 164
165 if (grep && pattern) { 165 if (grep && pattern) {
166 if (!strcmp(grep, "grep") || !strcmp(grep, "author") || 166 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
167 !strcmp(grep, "committer")) 167 !strcmp(grep, "committer"))
168 argv[argc++] = fmt("--%s=%s", grep, pattern); 168 argv[argc++] = fmt("--%s=%s", grep, pattern);
169 if (!strcmp(grep, "range")) 169 if (!strcmp(grep, "range"))
170 argv[1] = pattern; 170 argv[1] = pattern;
171 } 171 }
172 172
173 if (path) { 173 if (path) {
174 argv[argc++] = "--"; 174 argv[argc++] = "--";
175 argv[argc++] = path; 175 argv[argc++] = path;
176 } 176 }
177 init_revisions(&rev, NULL); 177 init_revisions(&rev, NULL);
178 rev.abbrev = DEFAULT_ABBREV; 178 rev.abbrev = DEFAULT_ABBREV;
179 rev.commit_format = CMIT_FMT_DEFAULT; 179 rev.commit_format = CMIT_FMT_DEFAULT;
180 rev.verbose_header = 1; 180 rev.verbose_header = 1;
181 rev.show_root_diff = 0; 181 rev.show_root_diff = 0;
182 setup_revisions(argc, argv, &rev, NULL); 182 setup_revisions(argc, argv, &rev, NULL);
183 load_ref_decorations(DECORATE_FULL_REFS); 183 load_ref_decorations(DECORATE_FULL_REFS);
184 rev.show_decorations = 1; 184 rev.show_decorations = 1;
185 rev.grep_filter.regflags |= REG_ICASE; 185 rev.grep_filter.regflags |= REG_ICASE;
186 compile_grep_patterns(&rev.grep_filter); 186 compile_grep_patterns(&rev.grep_filter);
187 prepare_revision_walk(&rev); 187 prepare_revision_walk(&rev);
188 188
189 if (pager) 189 if (pager)
190 html("<table class='list nowrap'>"); 190 html("<table class='list nowrap'>");
191 191
192 html("<tr class='nohover'><th class='left'>Age</th>" 192 html("<tr class='nohover'><th class='left'>Age</th>"
193 "<th class='left'>Commit message"); 193 "<th class='left'>Commit message");
194 if (pager) { 194 if (pager) {
195 html(" ("); 195 html(" (");
196 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 196 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
197 NULL, ctx.qry.head, ctx.qry.sha1, 197 NULL, ctx.qry.head, ctx.qry.sha1,
198 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, 198 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
199 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 199 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
200 html(")"); 200 html(")");
201 } 201 }
202 html("</th><th class='left'>Author</th>"); 202 html("</th><th class='left'>Author</th>");
203 if (ctx.repo->enable_log_filecount) { 203 if (ctx.repo->enable_log_filecount) {
204 html("<th class='left'>Files</th>"); 204 html("<th class='left'>Files</th>");
205 columns++; 205 columns++;
206 if (ctx.repo->enable_log_linecount) { 206 if (ctx.repo->enable_log_linecount) {
207 html("<th class='left'>Lines</th>"); 207 html("<th class='left'>Lines</th>");
208 columns++; 208 columns++;
209 } 209 }
210 } 210 }
211 html("</tr>\n"); 211 html("</tr>\n");
212 212
213 if (ofs<0) 213 if (ofs<0)
214 ofs = 0; 214 ofs = 0;
215 215
216 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 216 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
217 free(commit->buffer); 217 free(commit->buffer);
218 commit->buffer = NULL; 218 commit->buffer = NULL;
219 free_commit_list(commit->parents); 219 free_commit_list(commit->parents);
220 commit->parents = NULL; 220 commit->parents = NULL;
221 } 221 }
222 222
223 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 223 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
224 print_commit(commit); 224 print_commit(commit);
225 free(commit->buffer); 225 free(commit->buffer);
226 commit->buffer = NULL; 226 commit->buffer = NULL;
227 free_commit_list(commit->parents); 227 free_commit_list(commit->parents);
228 commit->parents = NULL; 228 commit->parents = NULL;
229 } 229 }
230 if (pager) { 230 if (pager) {
231 htmlf("</table><div class='pager'>", 231 html("</table><div class='pager'>");
232 columns);
233 if (ofs > 0) { 232 if (ofs > 0) {
234 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 233 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
235 ctx.qry.sha1, ctx.qry.vpath, 234 ctx.qry.sha1, ctx.qry.vpath,
236 ofs - cnt, ctx.qry.grep, 235 ofs - cnt, ctx.qry.grep,
237 ctx.qry.search, ctx.qry.showmsg); 236 ctx.qry.search, ctx.qry.showmsg);
238 html("&nbsp;"); 237 html("&nbsp;");
239 } 238 }
240 if ((commit = get_revision(&rev)) != NULL) { 239 if ((commit = get_revision(&rev)) != NULL) {
241 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 240 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
242 ctx.qry.sha1, ctx.qry.vpath, 241 ctx.qry.sha1, ctx.qry.vpath,
243 ofs + cnt, ctx.qry.grep, 242 ofs + cnt, ctx.qry.grep,
244 ctx.qry.search, ctx.qry.showmsg); 243 ctx.qry.search, ctx.qry.showmsg);
245 } 244 }
246 html("</div>"); 245 html("</div>");
247 } else if ((commit = get_revision(&rev)) != NULL) { 246 } else if ((commit = get_revision(&rev)) != NULL) {
248 html("<tr class='nohover'><td colspan='3'>"); 247 html("<tr class='nohover'><td colspan='3'>");
249 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, 248 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
250 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); 249 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
251 html("</td></tr>\n"); 250 html("</td></tr>\n");
252 } 251 }
253} 252}
diff --git a/ui-repolist.c b/ui-repolist.c
index 0a0b6ca..2c98668 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -1,142 +1,136 @@
1/* ui-repolist.c: functions for generating the repolist page 1/* ui-repolist.c: functions for generating the repolist page
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/* This is needed for strcasestr to be defined by <string.h> */
10#define _GNU_SOURCE 1
11#include <string.h>
12
13#include <time.h>
14
15#include "cgit.h" 9#include "cgit.h"
16#include "html.h" 10#include "html.h"
17#include "ui-shared.h" 11#include "ui-shared.h"
18 12
19time_t read_agefile(char *path) 13time_t read_agefile(char *path)
20{ 14{
21 time_t result; 15 time_t result;
22 size_t size; 16 size_t size;
23 char *buf; 17 char *buf;
24 static char buf2[64]; 18 static char buf2[64];
25 19
26 if (readfile(path, &buf, &size)) 20 if (readfile(path, &buf, &size))
27 return -1; 21 return -1;
28 22
29 if (parse_date(buf, buf2, sizeof(buf2))) 23 if (parse_date(buf, buf2, sizeof(buf2)))
30 result = strtoul(buf2, NULL, 10); 24 result = strtoul(buf2, NULL, 10);
31 else 25 else
32 result = 0; 26 result = 0;
33 free(buf); 27 free(buf);
34 return result; 28 return result;
35} 29}
36 30
37static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) 31static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
38{ 32{
39 char *path; 33 char *path;
40 struct stat s; 34 struct stat s;
41 struct cgit_repo *r = (struct cgit_repo *)repo; 35 struct cgit_repo *r = (struct cgit_repo *)repo;
42 36
43 if (repo->mtime != -1) { 37 if (repo->mtime != -1) {
44 *mtime = repo->mtime; 38 *mtime = repo->mtime;
45 return 1; 39 return 1;
46 } 40 }
47 path = fmt("%s/%s", repo->path, ctx.cfg.agefile); 41 path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
48 if (stat(path, &s) == 0) { 42 if (stat(path, &s) == 0) {
49 *mtime = read_agefile(path); 43 *mtime = read_agefile(path);
50 r->mtime = *mtime; 44 r->mtime = *mtime;
51 return 1; 45 return 1;
52 } 46 }
53 47
54 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); 48 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch);
55 if (stat(path, &s) == 0) 49 if (stat(path, &s) == 0)
56 *mtime = s.st_mtime; 50 *mtime = s.st_mtime;
57 else 51 else
58 *mtime = 0; 52 *mtime = 0;
59 53
60 r->mtime = *mtime; 54 r->mtime = *mtime;
61 return (r->mtime != 0); 55 return (r->mtime != 0);
62} 56}
63 57
64static void print_modtime(struct cgit_repo *repo) 58static void print_modtime(struct cgit_repo *repo)
65{ 59{
66 time_t t; 60 time_t t;
67 if (get_repo_modtime(repo, &t)) 61 if (get_repo_modtime(repo, &t))
68 cgit_print_age(t, -1, NULL); 62 cgit_print_age(t, -1, NULL);
69} 63}
70 64
71int is_match(struct cgit_repo *repo) 65int is_match(struct cgit_repo *repo)
72{ 66{
73 if (!ctx.qry.search) 67 if (!ctx.qry.search)
74 return 1; 68 return 1;
75 if (repo->url && strcasestr(repo->url, ctx.qry.search)) 69 if (repo->url && strcasestr(repo->url, ctx.qry.search))
76 return 1; 70 return 1;
77 if (repo->name && strcasestr(repo->name, ctx.qry.search)) 71 if (repo->name && strcasestr(repo->name, ctx.qry.search))
78 return 1; 72 return 1;
79 if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) 73 if (repo->desc && strcasestr(repo->desc, ctx.qry.search))
80 return 1; 74 return 1;
81 if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) 75 if (repo->owner && strcasestr(repo->owner, ctx.qry.search))
82 return 1; 76 return 1;
83 return 0; 77 return 0;
84} 78}
85 79
86int is_in_url(struct cgit_repo *repo) 80int is_in_url(struct cgit_repo *repo)
87{ 81{
88 if (!ctx.qry.url) 82 if (!ctx.qry.url)
89 return 1; 83 return 1;
90 if (repo->url && !prefixcmp(repo->url, ctx.qry.url)) 84 if (repo->url && !prefixcmp(repo->url, ctx.qry.url))
91 return 1; 85 return 1;
92 return 0; 86 return 0;
93} 87}
94 88
95void print_sort_header(const char *title, const char *sort) 89void print_sort_header(const char *title, const char *sort)
96{ 90{
97 htmlf("<th class='left'><a href='%s?s=%s", cgit_rooturl(), sort); 91 htmlf("<th class='left'><a href='%s?s=%s", cgit_rooturl(), sort);
98 if (ctx.qry.search) { 92 if (ctx.qry.search) {
99 html("&q="); 93 html("&q=");
100 html_url_arg(ctx.qry.search); 94 html_url_arg(ctx.qry.search);
101 } 95 }
102 htmlf("'>%s</a></th>", title); 96 htmlf("'>%s</a></th>", title);
103} 97}
104 98
105void print_header(int columns) 99void print_header(int columns)
106{ 100{
107 html("<tr class='nohover'>"); 101 html("<tr class='nohover'>");
108 print_sort_header("Name", "name"); 102 print_sort_header("Name", "name");
109 print_sort_header("Description", "desc"); 103 print_sort_header("Description", "desc");
110 print_sort_header("Owner", "owner"); 104 print_sort_header("Owner", "owner");
111 print_sort_header("Idle", "idle"); 105 print_sort_header("Idle", "idle");
112 if (ctx.cfg.enable_index_links) 106 if (ctx.cfg.enable_index_links)
113 html("<th class='left'>Links</th>"); 107 html("<th class='left'>Links</th>");
114 html("</tr>\n"); 108 html("</tr>\n");
115} 109}
116 110
117 111
118void print_pager(int items, int pagelen, char *search) 112void print_pager(int items, int pagelen, char *search)
119{ 113{
120 int i; 114 int i;
121 html("<div class='pager'>"); 115 html("<div class='pager'>");
122 for(i = 0; i * pagelen < items; i++) 116 for(i = 0; i * pagelen < items; i++)
123 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL, 117 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL,
124 search, i * pagelen); 118 search, i * pagelen);
125 html("</div>"); 119 html("</div>");
126} 120}
127 121
128static int cmp(const char *s1, const char *s2) 122static int cmp(const char *s1, const char *s2)
129{ 123{
130 if (s1 && s2) 124 if (s1 && s2)
131 return strcmp(s1, s2); 125 return strcmp(s1, s2);
132 if (s1 && !s2) 126 if (s1 && !s2)
133 return -1; 127 return -1;
134 if (s2 && !s1) 128 if (s2 && !s1)
135 return 1; 129 return 1;
136 return 0; 130 return 0;
137} 131}
138 132
139static int sort_section(const void *a, const void *b) 133static int sort_section(const void *a, const void *b)
140{ 134{
141 const struct cgit_repo *r1 = a; 135 const struct cgit_repo *r1 = a;
142 const struct cgit_repo *r2 = b; 136 const struct cgit_repo *r2 = b;
diff --git a/ui-stats.c b/ui-stats.c
index 50c2540..946a6ea 100644
--- a/ui-stats.c
+++ b/ui-stats.c
@@ -1,135 +1,141 @@
1#include <string-list.h> 1#include <string-list.h>
2 2
3#include "cgit.h" 3#include "cgit.h"
4#include "html.h" 4#include "html.h"
5#include "ui-shared.h" 5#include "ui-shared.h"
6#include "ui-stats.h" 6#include "ui-stats.h"
7 7
8#ifdef NO_C99_FORMAT
9#define SZ_FMT "%u"
10#else
11#define SZ_FMT "%zu"
12#endif
13
8#define MONTHS 6 14#define MONTHS 6
9 15
10struct authorstat { 16struct authorstat {
11 long total; 17 long total;
12 struct string_list list; 18 struct string_list list;
13}; 19};
14 20
15#define DAY_SECS (60 * 60 * 24) 21#define DAY_SECS (60 * 60 * 24)
16#define WEEK_SECS (DAY_SECS * 7) 22#define WEEK_SECS (DAY_SECS * 7)
17 23
18static void trunc_week(struct tm *tm) 24static void trunc_week(struct tm *tm)
19{ 25{
20 time_t t = timegm(tm); 26 time_t t = timegm(tm);
21 t -= ((tm->tm_wday + 6) % 7) * DAY_SECS; 27 t -= ((tm->tm_wday + 6) % 7) * DAY_SECS;
22 gmtime_r(&t, tm); 28 gmtime_r(&t, tm);
23} 29}
24 30
25static void dec_week(struct tm *tm) 31static void dec_week(struct tm *tm)
26{ 32{
27 time_t t = timegm(tm); 33 time_t t = timegm(tm);
28 t -= WEEK_SECS; 34 t -= WEEK_SECS;
29 gmtime_r(&t, tm); 35 gmtime_r(&t, tm);
30} 36}
31 37
32static void inc_week(struct tm *tm) 38static void inc_week(struct tm *tm)
33{ 39{
34 time_t t = timegm(tm); 40 time_t t = timegm(tm);
35 t += WEEK_SECS; 41 t += WEEK_SECS;
36 gmtime_r(&t, tm); 42 gmtime_r(&t, tm);
37} 43}
38 44
39static char *pretty_week(struct tm *tm) 45static char *pretty_week(struct tm *tm)
40{ 46{
41 static char buf[10]; 47 static char buf[10];
42 48
43 strftime(buf, sizeof(buf), "W%V %G", tm); 49 strftime(buf, sizeof(buf), "W%V %G", tm);
44 return buf; 50 return buf;
45} 51}
46 52
47static void trunc_month(struct tm *tm) 53static void trunc_month(struct tm *tm)
48{ 54{
49 tm->tm_mday = 1; 55 tm->tm_mday = 1;
50} 56}
51 57
52static void dec_month(struct tm *tm) 58static void dec_month(struct tm *tm)
53{ 59{
54 tm->tm_mon--; 60 tm->tm_mon--;
55 if (tm->tm_mon < 0) { 61 if (tm->tm_mon < 0) {
56 tm->tm_year--; 62 tm->tm_year--;
57 tm->tm_mon = 11; 63 tm->tm_mon = 11;
58 } 64 }
59} 65}
60 66
61static void inc_month(struct tm *tm) 67static void inc_month(struct tm *tm)
62{ 68{
63 tm->tm_mon++; 69 tm->tm_mon++;
64 if (tm->tm_mon > 11) { 70 if (tm->tm_mon > 11) {
65 tm->tm_year++; 71 tm->tm_year++;
66 tm->tm_mon = 0; 72 tm->tm_mon = 0;
67 } 73 }
68} 74}
69 75
70static char *pretty_month(struct tm *tm) 76static char *pretty_month(struct tm *tm)
71{ 77{
72 static const char *months[] = { 78 static const char *months[] = {
73 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 79 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
74 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" 80 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
75 }; 81 };
76 return fmt("%s %d", months[tm->tm_mon], tm->tm_year + 1900); 82 return fmt("%s %d", months[tm->tm_mon], tm->tm_year + 1900);
77} 83}
78 84
79static void trunc_quarter(struct tm *tm) 85static void trunc_quarter(struct tm *tm)
80{ 86{
81 trunc_month(tm); 87 trunc_month(tm);
82 while(tm->tm_mon % 3 != 0) 88 while(tm->tm_mon % 3 != 0)
83 dec_month(tm); 89 dec_month(tm);
84} 90}
85 91
86static void dec_quarter(struct tm *tm) 92static void dec_quarter(struct tm *tm)
87{ 93{
88 dec_month(tm); 94 dec_month(tm);
89 dec_month(tm); 95 dec_month(tm);
90 dec_month(tm); 96 dec_month(tm);
91} 97}
92 98
93static void inc_quarter(struct tm *tm) 99static void inc_quarter(struct tm *tm)
94{ 100{
95 inc_month(tm); 101 inc_month(tm);
96 inc_month(tm); 102 inc_month(tm);
97 inc_month(tm); 103 inc_month(tm);
98} 104}
99 105
100static char *pretty_quarter(struct tm *tm) 106static char *pretty_quarter(struct tm *tm)
101{ 107{
102 return fmt("Q%d %d", tm->tm_mon / 3 + 1, tm->tm_year + 1900); 108 return fmt("Q%d %d", tm->tm_mon / 3 + 1, tm->tm_year + 1900);
103} 109}
104 110
105static void trunc_year(struct tm *tm) 111static void trunc_year(struct tm *tm)
106{ 112{
107 trunc_month(tm); 113 trunc_month(tm);
108 tm->tm_mon = 0; 114 tm->tm_mon = 0;
109} 115}
110 116
111static void dec_year(struct tm *tm) 117static void dec_year(struct tm *tm)
112{ 118{
113 tm->tm_year--; 119 tm->tm_year--;
114} 120}
115 121
116static void inc_year(struct tm *tm) 122static void inc_year(struct tm *tm)
117{ 123{
118 tm->tm_year++; 124 tm->tm_year++;
119} 125}
120 126
121static char *pretty_year(struct tm *tm) 127static char *pretty_year(struct tm *tm)
122{ 128{
123 return fmt("%d", tm->tm_year + 1900); 129 return fmt("%d", tm->tm_year + 1900);
124} 130}
125 131
126struct cgit_period periods[] = { 132struct cgit_period periods[] = {
127 {'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week}, 133 {'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week},
128 {'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month}, 134 {'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month},
129 {'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter}, 135 {'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter},
130 {'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year}, 136 {'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year},
131}; 137};
132 138
133/* Given a period code or name, return a period index (1, 2, 3 or 4) 139/* Given a period code or name, return a period index (1, 2, 3 or 4)
134 * and update the period pointer to the correcsponding struct. 140 * and update the period pointer to the correcsponding struct.
135 * If no matching code is found, return 0. 141 * If no matching code is found, return 0.
@@ -158,261 +164,261 @@ const char *cgit_find_stats_periodname(int idx)
158{ 164{
159 if (idx > 0 && idx < 4) 165 if (idx > 0 && idx < 4)
160 return periods[idx - 1].name; 166 return periods[idx - 1].name;
161 else 167 else
162 return ""; 168 return "";
163} 169}
164 170
165static void add_commit(struct string_list *authors, struct commit *commit, 171static void add_commit(struct string_list *authors, struct commit *commit,
166 struct cgit_period *period) 172 struct cgit_period *period)
167{ 173{
168 struct commitinfo *info; 174 struct commitinfo *info;
169 struct string_list_item *author, *item; 175 struct string_list_item *author, *item;
170 struct authorstat *authorstat; 176 struct authorstat *authorstat;
171 struct string_list *items; 177 struct string_list *items;
172 char *tmp; 178 char *tmp;
173 struct tm *date; 179 struct tm *date;
174 time_t t; 180 time_t t;
175 181
176 info = cgit_parse_commit(commit); 182 info = cgit_parse_commit(commit);
177 tmp = xstrdup(info->author); 183 tmp = xstrdup(info->author);
178 author = string_list_insert(authors, tmp); 184 author = string_list_insert(authors, tmp);
179 if (!author->util) 185 if (!author->util)
180 author->util = xcalloc(1, sizeof(struct authorstat)); 186 author->util = xcalloc(1, sizeof(struct authorstat));
181 else 187 else
182 free(tmp); 188 free(tmp);
183 authorstat = author->util; 189 authorstat = author->util;
184 items = &authorstat->list; 190 items = &authorstat->list;
185 t = info->committer_date; 191 t = info->committer_date;
186 date = gmtime(&t); 192 date = gmtime(&t);
187 period->trunc(date); 193 period->trunc(date);
188 tmp = xstrdup(period->pretty(date)); 194 tmp = xstrdup(period->pretty(date));
189 item = string_list_insert(items, tmp); 195 item = string_list_insert(items, tmp);
190 if (item->util) 196 if (item->util)
191 free(tmp); 197 free(tmp);
192 item->util++; 198 item->util++;
193 authorstat->total++; 199 authorstat->total++;
194 cgit_free_commitinfo(info); 200 cgit_free_commitinfo(info);
195} 201}
196 202
197static int cmp_total_commits(const void *a1, const void *a2) 203static int cmp_total_commits(const void *a1, const void *a2)
198{ 204{
199 const struct string_list_item *i1 = a1; 205 const struct string_list_item *i1 = a1;
200 const struct string_list_item *i2 = a2; 206 const struct string_list_item *i2 = a2;
201 const struct authorstat *auth1 = i1->util; 207 const struct authorstat *auth1 = i1->util;
202 const struct authorstat *auth2 = i2->util; 208 const struct authorstat *auth2 = i2->util;
203 209
204 return auth2->total - auth1->total; 210 return auth2->total - auth1->total;
205} 211}
206 212
207/* Walk the commit DAG and collect number of commits per author per 213/* Walk the commit DAG and collect number of commits per author per
208 * timeperiod into a nested string_list collection. 214 * timeperiod into a nested string_list collection.
209 */ 215 */
210struct string_list collect_stats(struct cgit_context *ctx, 216struct string_list collect_stats(struct cgit_context *ctx,
211 struct cgit_period *period) 217 struct cgit_period *period)
212{ 218{
213 struct string_list authors; 219 struct string_list authors;
214 struct rev_info rev; 220 struct rev_info rev;
215 struct commit *commit; 221 struct commit *commit;
216 const char *argv[] = {NULL, ctx->qry.head, NULL, NULL, NULL, NULL}; 222 const char *argv[] = {NULL, ctx->qry.head, NULL, NULL, NULL, NULL};
217 int argc = 3; 223 int argc = 3;
218 time_t now; 224 time_t now;
219 long i; 225 long i;
220 struct tm *tm; 226 struct tm *tm;
221 char tmp[11]; 227 char tmp[11];
222 228
223 time(&now); 229 time(&now);
224 tm = gmtime(&now); 230 tm = gmtime(&now);
225 period->trunc(tm); 231 period->trunc(tm);
226 for (i = 1; i < period->count; i++) 232 for (i = 1; i < period->count; i++)
227 period->dec(tm); 233 period->dec(tm);
228 strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm); 234 strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm);
229 argv[2] = xstrdup(fmt("--since=%s", tmp)); 235 argv[2] = xstrdup(fmt("--since=%s", tmp));
230 if (ctx->qry.path) { 236 if (ctx->qry.path) {
231 argv[3] = "--"; 237 argv[3] = "--";
232 argv[4] = ctx->qry.path; 238 argv[4] = ctx->qry.path;
233 argc += 2; 239 argc += 2;
234 } 240 }
235 init_revisions(&rev, NULL); 241 init_revisions(&rev, NULL);
236 rev.abbrev = DEFAULT_ABBREV; 242 rev.abbrev = DEFAULT_ABBREV;
237 rev.commit_format = CMIT_FMT_DEFAULT; 243 rev.commit_format = CMIT_FMT_DEFAULT;
238 rev.no_merges = 1; 244 rev.no_merges = 1;
239 rev.verbose_header = 1; 245 rev.verbose_header = 1;
240 rev.show_root_diff = 0; 246 rev.show_root_diff = 0;
241 setup_revisions(argc, argv, &rev, NULL); 247 setup_revisions(argc, argv, &rev, NULL);
242 prepare_revision_walk(&rev); 248 prepare_revision_walk(&rev);
243 memset(&authors, 0, sizeof(authors)); 249 memset(&authors, 0, sizeof(authors));
244 while ((commit = get_revision(&rev)) != NULL) { 250 while ((commit = get_revision(&rev)) != NULL) {
245 add_commit(&authors, commit, period); 251 add_commit(&authors, commit, period);
246 free(commit->buffer); 252 free(commit->buffer);
247 free_commit_list(commit->parents); 253 free_commit_list(commit->parents);
248 } 254 }
249 return authors; 255 return authors;
250} 256}
251 257
252void print_combined_authorrow(struct string_list *authors, int from, int to, 258void print_combined_authorrow(struct string_list *authors, int from, int to,
253 const char *name, const char *leftclass, const char *centerclass, 259 const char *name, const char *leftclass, const char *centerclass,
254 const char *rightclass, struct cgit_period *period) 260 const char *rightclass, struct cgit_period *period)
255{ 261{
256 struct string_list_item *author; 262 struct string_list_item *author;
257 struct authorstat *authorstat; 263 struct authorstat *authorstat;
258 struct string_list *items; 264 struct string_list *items;
259 struct string_list_item *date; 265 struct string_list_item *date;
260 time_t now; 266 time_t now;
261 long i, j, total, subtotal; 267 long i, j, total, subtotal;
262 struct tm *tm; 268 struct tm *tm;
263 char *tmp; 269 char *tmp;
264 270
265 time(&now); 271 time(&now);
266 tm = gmtime(&now); 272 tm = gmtime(&now);
267 period->trunc(tm); 273 period->trunc(tm);
268 for (i = 1; i < period->count; i++) 274 for (i = 1; i < period->count; i++)
269 period->dec(tm); 275 period->dec(tm);
270 276
271 total = 0; 277 total = 0;
272 htmlf("<tr><td class='%s'>%s</td>", leftclass, 278 htmlf("<tr><td class='%s'>%s</td>", leftclass,
273 fmt(name, to - from + 1)); 279 fmt(name, to - from + 1));
274 for (j = 0; j < period->count; j++) { 280 for (j = 0; j < period->count; j++) {
275 tmp = period->pretty(tm); 281 tmp = period->pretty(tm);
276 period->inc(tm); 282 period->inc(tm);
277 subtotal = 0; 283 subtotal = 0;
278 for (i = from; i <= to; i++) { 284 for (i = from; i <= to; i++) {
279 author = &authors->items[i]; 285 author = &authors->items[i];
280 authorstat = author->util; 286 authorstat = author->util;
281 items = &authorstat->list; 287 items = &authorstat->list;
282 date = string_list_lookup(items, tmp); 288 date = string_list_lookup(items, tmp);
283 if (date) 289 if (date)
284 subtotal += (size_t)date->util; 290 subtotal += (size_t)date->util;
285 } 291 }
286 htmlf("<td class='%s'>%d</td>", centerclass, subtotal); 292 htmlf("<td class='%s'>%ld</td>", centerclass, subtotal);
287 total += subtotal; 293 total += subtotal;
288 } 294 }
289 htmlf("<td class='%s'>%d</td></tr>", rightclass, total); 295 htmlf("<td class='%s'>%ld</td></tr>", rightclass, total);
290} 296}
291 297
292void print_authors(struct string_list *authors, int top, 298void print_authors(struct string_list *authors, int top,
293 struct cgit_period *period) 299 struct cgit_period *period)
294{ 300{
295 struct string_list_item *author; 301 struct string_list_item *author;
296 struct authorstat *authorstat; 302 struct authorstat *authorstat;
297 struct string_list *items; 303 struct string_list *items;
298 struct string_list_item *date; 304 struct string_list_item *date;
299 time_t now; 305 time_t now;
300 long i, j, total; 306 long i, j, total;
301 struct tm *tm; 307 struct tm *tm;
302 char *tmp; 308 char *tmp;
303 309
304 time(&now); 310 time(&now);
305 tm = gmtime(&now); 311 tm = gmtime(&now);
306 period->trunc(tm); 312 period->trunc(tm);
307 for (i = 1; i < period->count; i++) 313 for (i = 1; i < period->count; i++)
308 period->dec(tm); 314 period->dec(tm);
309 315
310 html("<table class='stats'><tr><th>Author</th>"); 316 html("<table class='stats'><tr><th>Author</th>");
311 for (j = 0; j < period->count; j++) { 317 for (j = 0; j < period->count; j++) {
312 tmp = period->pretty(tm); 318 tmp = period->pretty(tm);
313 htmlf("<th>%s</th>", tmp); 319 htmlf("<th>%s</th>", tmp);
314 period->inc(tm); 320 period->inc(tm);
315 } 321 }
316 html("<th>Total</th></tr>\n"); 322 html("<th>Total</th></tr>\n");
317 323
318 if (top <= 0 || top > authors->nr) 324 if (top <= 0 || top > authors->nr)
319 top = authors->nr; 325 top = authors->nr;
320 326
321 for (i = 0; i < top; i++) { 327 for (i = 0; i < top; i++) {
322 author = &authors->items[i]; 328 author = &authors->items[i];
323 html("<tr><td class='left'>"); 329 html("<tr><td class='left'>");
324 html_txt(author->string); 330 html_txt(author->string);
325 html("</td>"); 331 html("</td>");
326 authorstat = author->util; 332 authorstat = author->util;
327 items = &authorstat->list; 333 items = &authorstat->list;
328 total = 0; 334 total = 0;
329 for (j = 0; j < period->count; j++) 335 for (j = 0; j < period->count; j++)
330 period->dec(tm); 336 period->dec(tm);
331 for (j = 0; j < period->count; j++) { 337 for (j = 0; j < period->count; j++) {
332 tmp = period->pretty(tm); 338 tmp = period->pretty(tm);
333 period->inc(tm); 339 period->inc(tm);
334 date = string_list_lookup(items, tmp); 340 date = string_list_lookup(items, tmp);
335 if (!date) 341 if (!date)
336 html("<td>0</td>"); 342 html("<td>0</td>");
337 else { 343 else {
338 htmlf("<td>%d</td>", date->util); 344 htmlf("<td>"SZ_FMT"</td>", (size_t)date->util);
339 total += (size_t)date->util; 345 total += (size_t)date->util;
340 } 346 }
341 } 347 }
342 htmlf("<td class='sum'>%d</td></tr>", total); 348 htmlf("<td class='sum'>%ld</td></tr>", total);
343 } 349 }
344 350
345 if (top < authors->nr) 351 if (top < authors->nr)
346 print_combined_authorrow(authors, top, authors->nr - 1, 352 print_combined_authorrow(authors, top, authors->nr - 1,
347 "Others (%d)", "left", "", "sum", period); 353 "Others (%ld)", "left", "", "sum", period);
348 354
349 print_combined_authorrow(authors, 0, authors->nr - 1, "Total", 355 print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
350 "total", "sum", "sum", period); 356 "total", "sum", "sum", period);
351 html("</table>"); 357 html("</table>");
352} 358}
353 359
354/* Create a sorted string_list with one entry per author. The util-field 360/* Create a sorted string_list with one entry per author. The util-field
355 * for each author is another string_list which is used to calculate the 361 * for each author is another string_list which is used to calculate the
356 * number of commits per time-interval. 362 * number of commits per time-interval.
357 */ 363 */
358void cgit_show_stats(struct cgit_context *ctx) 364void cgit_show_stats(struct cgit_context *ctx)
359{ 365{
360 struct string_list authors; 366 struct string_list authors;
361 struct cgit_period *period; 367 struct cgit_period *period;
362 int top, i; 368 int top, i;
363 const char *code = "w"; 369 const char *code = "w";
364 370
365 if (ctx->qry.period) 371 if (ctx->qry.period)
366 code = ctx->qry.period; 372 code = ctx->qry.period;
367 373
368 i = cgit_find_stats_period(code, &period); 374 i = cgit_find_stats_period(code, &period);
369 if (!i) { 375 if (!i) {
370 cgit_print_error(fmt("Unknown statistics type: %c", code)); 376 cgit_print_error(fmt("Unknown statistics type: %c", code[0]));
371 return; 377 return;
372 } 378 }
373 if (i > ctx->repo->max_stats) { 379 if (i > ctx->repo->max_stats) {
374 cgit_print_error(fmt("Statistics type disabled: %s", 380 cgit_print_error(fmt("Statistics type disabled: %s",
375 period->name)); 381 period->name));
376 return; 382 return;
377 } 383 }
378 authors = collect_stats(ctx, period); 384 authors = collect_stats(ctx, period);
379 qsort(authors.items, authors.nr, sizeof(struct string_list_item), 385 qsort(authors.items, authors.nr, sizeof(struct string_list_item),
380 cmp_total_commits); 386 cmp_total_commits);
381 387
382 top = ctx->qry.ofs; 388 top = ctx->qry.ofs;
383 if (!top) 389 if (!top)
384 top = 10; 390 top = 10;
385 htmlf("<h2>Commits per author per %s", period->name); 391 htmlf("<h2>Commits per author per %s", period->name);
386 if (ctx->qry.path) { 392 if (ctx->qry.path) {
387 html(" (path '"); 393 html(" (path '");
388 html_txt(ctx->qry.path); 394 html_txt(ctx->qry.path);
389 html("')"); 395 html("')");
390 } 396 }
391 html("</h2>"); 397 html("</h2>");
392 398
393 html("<form method='get' action='' style='float: right; text-align: right;'>"); 399 html("<form method='get' action='' style='float: right; text-align: right;'>");
394 cgit_add_hidden_formfields(1, 0, "stats"); 400 cgit_add_hidden_formfields(1, 0, "stats");
395 if (ctx->repo->max_stats > 1) { 401 if (ctx->repo->max_stats > 1) {
396 html("Period: "); 402 html("Period: ");
397 html("<select name='period' onchange='this.form.submit();'>"); 403 html("<select name='period' onchange='this.form.submit();'>");
398 for (i = 0; i < ctx->repo->max_stats; i++) 404 for (i = 0; i < ctx->repo->max_stats; i++)
399 htmlf("<option value='%c'%s>%s</option>", 405 htmlf("<option value='%c'%s>%s</option>",
400 periods[i].code, 406 periods[i].code,
401 period == &periods[i] ? " selected" : "", 407 period == &periods[i] ? " selected" : "",
402 periods[i].name); 408 periods[i].name);
403 html("</select><br/><br/>"); 409 html("</select><br/><br/>");
404 } 410 }
405 html("Authors: "); 411 html("Authors: ");
406 html(""); 412 html("");
407 html("<select name='ofs' onchange='this.form.submit();'>"); 413 html("<select name='ofs' onchange='this.form.submit();'>");
408 htmlf("<option value='10'%s>10</option>", top == 10 ? " selected" : ""); 414 htmlf("<option value='10'%s>10</option>", top == 10 ? " selected" : "");
409 htmlf("<option value='25'%s>25</option>", top == 25 ? " selected" : ""); 415 htmlf("<option value='25'%s>25</option>", top == 25 ? " selected" : "");
410 htmlf("<option value='50'%s>50</option>", top == 50 ? " selected" : ""); 416 htmlf("<option value='50'%s>50</option>", top == 50 ? " selected" : "");
411 htmlf("<option value='100'%s>100</option>", top == 100 ? " selected" : ""); 417 htmlf("<option value='100'%s>100</option>", top == 100 ? " selected" : "");
412 htmlf("<option value='-1'%s>All</option>", top == -1 ? " selected" : ""); 418 htmlf("<option value='-1'%s>All</option>", top == -1 ? " selected" : "");
413 html("</select>"); 419 html("</select>");
414 html("<noscript>&nbsp;&nbsp;<input type='submit' value='Reload'/></noscript>"); 420 html("<noscript>&nbsp;&nbsp;<input type='submit' value='Reload'/></noscript>");
415 html("</form>"); 421 html("</form>");
416 print_authors(&authors, top, period); 422 print_authors(&authors, top, period);
417} 423}
418 424
diff --git a/ui-tree.c b/ui-tree.c
index 75ec9cb..0b1b531 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,239 +1,239 @@
1/* ui-tree.c: functions for tree output 1/* ui-tree.c: functions for tree 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 <ctype.h> 9#include <ctype.h>
10#include "cgit.h" 10#include "cgit.h"
11#include "html.h" 11#include "html.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13 13
14char *curr_rev; 14char *curr_rev;
15char *match_path; 15char *match_path;
16int header = 0; 16int header = 0;
17 17
18static void print_text_buffer(const char *name, char *buf, unsigned long size) 18static void print_text_buffer(const char *name, char *buf, unsigned long size)
19{ 19{
20 unsigned long lineno, idx; 20 unsigned long lineno, idx;
21 const char *numberfmt = 21 const char *numberfmt =
22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n"; 22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n";
23 23
24 html("<table summary='blob content' class='blob'>\n"); 24 html("<table summary='blob content' class='blob'>\n");
25 25
26 if (ctx.cfg.enable_tree_linenumbers) { 26 if (ctx.cfg.enable_tree_linenumbers) {
27 html("<tr><td class='linenumbers'><pre>"); 27 html("<tr><td class='linenumbers'><pre>");
28 idx = 0; 28 idx = 0;
29 lineno = 0; 29 lineno = 0;
30 30
31 if (size) { 31 if (size) {
32 htmlf(numberfmt, ++lineno); 32 htmlf(numberfmt, ++lineno);
33 while(idx < size - 1) { // skip absolute last newline 33 while(idx < size - 1) { // skip absolute last newline
34 if (buf[idx] == '\n') 34 if (buf[idx] == '\n')
35 htmlf(numberfmt, ++lineno); 35 htmlf(numberfmt, ++lineno);
36 idx++; 36 idx++;
37 } 37 }
38 } 38 }
39 html("</pre></td>\n"); 39 html("</pre></td>\n");
40 } 40 }
41 else { 41 else {
42 html("<tr>\n"); 42 html("<tr>\n");
43 } 43 }
44 44
45 if (ctx.repo->source_filter) { 45 if (ctx.repo->source_filter) {
46 html("<td class='lines'><pre><code>"); 46 html("<td class='lines'><pre><code>");
47 ctx.repo->source_filter->argv[1] = xstrdup(name); 47 ctx.repo->source_filter->argv[1] = xstrdup(name);
48 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
49 write(STDOUT_FILENO, buf, size); 49 html_raw(buf, size);
50 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
51 html("</code></pre></td></tr></table>\n"); 51 html("</code></pre></td></tr></table>\n");
52 return; 52 return;
53 } 53 }
54 54
55 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
56 html_txt(buf); 56 html_txt(buf);
57 html("</code></pre></td></tr></table>\n"); 57 html("</code></pre></td></tr></table>\n");
58} 58}
59 59
60#define ROWLEN 32 60#define ROWLEN 32
61 61
62static void print_binary_buffer(char *buf, unsigned long size) 62static void print_binary_buffer(char *buf, unsigned long size)
63{ 63{
64 unsigned long ofs, idx; 64 unsigned long ofs, idx;
65 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
66 66
67 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); 68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04lx</td><td class='hex'>", ofs);
71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
72 htmlf("%*s%02x", 72 htmlf("%*s%02x",
73 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
74 buf[idx] & 0xff); 74 buf[idx] & 0xff);
75 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
78 ascii[idx] = '\0'; 78 ascii[idx] = '\0';
79 html_txt(ascii); 79 html_txt(ascii);
80 html("</td></tr>\n"); 80 html("</td></tr>\n");
81 } 81 }
82 html("</table>\n"); 82 html("</table>\n");
83} 83}
84 84
85static void print_object(const unsigned char *sha1, char *path, const char *basename) 85static void print_object(const unsigned char *sha1, char *path, const char *basename)
86{ 86{
87 enum object_type type; 87 enum object_type type;
88 char *buf; 88 char *buf;
89 unsigned long size; 89 unsigned long size;
90 90
91 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
92 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
93 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 return; 95 return;
96 } 96 }
97 97
98 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
99 if (!buf) { 99 if (!buf) {
100 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
101 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
102 return; 102 return;
103 } 103 }
104 104
105 htmlf("blob: %s (", sha1_to_hex(sha1)); 105 htmlf("blob: %s (", sha1_to_hex(sha1));
106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
107 curr_rev, path); 107 curr_rev, path);
108 html(")\n"); 108 html(")\n");
109 109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { 110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>", 111 htmlf("<div class='error'>blob size (%ldKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size); 112 size / 1024, ctx.cfg.max_blob_size);
113 return; 113 return;
114 } 114 }
115 115
116 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
117 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
118 else 118 else
119 print_text_buffer(basename, buf, size); 119 print_text_buffer(basename, buf, size);
120} 120}
121 121
122 122
123static int ls_item(const unsigned char *sha1, const char *base, int baselen, 123static int ls_item(const unsigned char *sha1, const char *base, int baselen,
124 const char *pathname, unsigned int mode, int stage, 124 const char *pathname, unsigned int mode, int stage,
125 void *cbdata) 125 void *cbdata)
126{ 126{
127 char *name; 127 char *name;
128 char *fullpath; 128 char *fullpath;
129 char *class; 129 char *class;
130 enum object_type type; 130 enum object_type type;
131 unsigned long size = 0; 131 unsigned long size = 0;
132 132
133 name = xstrdup(pathname); 133 name = xstrdup(pathname);
134 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 134 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
135 ctx.qry.path ? "/" : "", name); 135 ctx.qry.path ? "/" : "", name);
136 136
137 if (!S_ISGITLINK(mode)) { 137 if (!S_ISGITLINK(mode)) {
138 type = sha1_object_info(sha1, &size); 138 type = sha1_object_info(sha1, &size);
139 if (type == OBJ_BAD) { 139 if (type == OBJ_BAD) {
140 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 140 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
141 name, 141 name,
142 sha1_to_hex(sha1)); 142 sha1_to_hex(sha1));
143 return 0; 143 return 0;
144 } 144 }
145 } 145 }
146 146
147 html("<tr><td class='ls-mode'>"); 147 html("<tr><td class='ls-mode'>");
148 cgit_print_filemode(mode); 148 cgit_print_filemode(mode);
149 html("</td><td>"); 149 html("</td><td>");
150 if (S_ISGITLINK(mode)) { 150 if (S_ISGITLINK(mode)) {
151 htmlf("<a class='ls-mod' href='"); 151 htmlf("<a class='ls-mod' href='");
152 html_attr(fmt(ctx.repo->module_link, 152 html_attr(fmt(ctx.repo->module_link,
153 name, 153 name,
154 sha1_to_hex(sha1))); 154 sha1_to_hex(sha1)));
155 html("'>"); 155 html("'>");
156 html_txt(name); 156 html_txt(name);
157 html("</a>"); 157 html("</a>");
158 } else if (S_ISDIR(mode)) { 158 } else if (S_ISDIR(mode)) {
159 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 159 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
160 curr_rev, fullpath); 160 curr_rev, fullpath);
161 } else { 161 } else {
162 class = strrchr(name, '.'); 162 class = strrchr(name, '.');
163 if (class != NULL) { 163 if (class != NULL) {
164 class = fmt("ls-blob %s", class + 1); 164 class = fmt("ls-blob %s", class + 1);
165 } else 165 } else
166 class = "ls-blob"; 166 class = "ls-blob";
167 cgit_tree_link(name, NULL, class, ctx.qry.head, 167 cgit_tree_link(name, NULL, class, ctx.qry.head,
168 curr_rev, fullpath); 168 curr_rev, fullpath);
169 } 169 }
170 htmlf("</td><td class='ls-size'>%li</td>", size); 170 htmlf("</td><td class='ls-size'>%li</td>", size);
171 171
172 html("<td>"); 172 html("<td>");
173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
174 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 174 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
175 if (ctx.repo->max_stats) 175 if (ctx.repo->max_stats)
176 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 176 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
177 fullpath); 177 fullpath);
178 cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev, 178 cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev,
179 fullpath); 179 fullpath);
180 html("</td></tr>\n"); 180 html("</td></tr>\n");
181 free(name); 181 free(name);
182 return 0; 182 return 0;
183} 183}
184 184
185static void ls_head() 185static void ls_head()
186{ 186{
187 html("<table summary='tree listing' class='list'>\n"); 187 html("<table summary='tree listing' class='list'>\n");
188 html("<tr class='nohover'>"); 188 html("<tr class='nohover'>");
189 html("<th class='left'>Mode</th>"); 189 html("<th class='left'>Mode</th>");
190 html("<th class='left'>Name</th>"); 190 html("<th class='left'>Name</th>");
191 html("<th class='right'>Size</th>"); 191 html("<th class='right'>Size</th>");
192 html("<th/>"); 192 html("<th/>");
193 html("</tr>\n"); 193 html("</tr>\n");
194 header = 1; 194 header = 1;
195} 195}
196 196
197static void ls_tail() 197static void ls_tail()
198{ 198{
199 if (!header) 199 if (!header)
200 return; 200 return;
201 html("</table>\n"); 201 html("</table>\n");
202 header = 0; 202 header = 0;
203} 203}
204 204
205static void ls_tree(const unsigned char *sha1, char *path) 205static void ls_tree(const unsigned char *sha1, char *path)
206{ 206{
207 struct tree *tree; 207 struct tree *tree;
208 208
209 tree = parse_tree_indirect(sha1); 209 tree = parse_tree_indirect(sha1);
210 if (!tree) { 210 if (!tree) {
211 cgit_print_error(fmt("Not a tree object: %s", 211 cgit_print_error(fmt("Not a tree object: %s",
212 sha1_to_hex(sha1))); 212 sha1_to_hex(sha1)));
213 return; 213 return;
214 } 214 }
215 215
216 ls_head(); 216 ls_head();
217 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 217 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
218 ls_tail(); 218 ls_tail();
219} 219}
220 220
221 221
222static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 222static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
223 const char *pathname, unsigned mode, int stage, 223 const char *pathname, unsigned mode, int stage,
224 void *cbdata) 224 void *cbdata)
225{ 225{
226 static int state; 226 static int state;
227 static char buffer[PATH_MAX]; 227 static char buffer[PATH_MAX];
228 228
229 if (state == 0) { 229 if (state == 0) {
230 memcpy(buffer, base, baselen); 230 memcpy(buffer, base, baselen);
231 strcpy(buffer+baselen, pathname); 231 strcpy(buffer+baselen, pathname);
232 if (strcmp(match_path, buffer)) 232 if (strcmp(match_path, buffer))
233 return READ_TREE_RECURSIVE; 233 return READ_TREE_RECURSIVE;
234 234
235 if (S_ISDIR(mode)) { 235 if (S_ISDIR(mode)) {
236 state = 1; 236 state = 1;
237 ls_head(); 237 ls_head();
238 return READ_TREE_RECURSIVE; 238 return READ_TREE_RECURSIVE;
239 } else { 239 } else {