summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile1
-rw-r--r--cgit.c8
-rw-r--r--cgit.css76
-rw-r--r--cgit.h3
-rw-r--r--cgitrc.5.txt17
-rw-r--r--cmd.c7
-rw-r--r--shared.c1
-rw-r--r--ui-shared.c15
-rw-r--r--ui-shared.h5
-rw-r--r--ui-stats.c410
-rw-r--r--ui-stats.h27
-rw-r--r--ui-tree.c3
12 files changed, 569 insertions, 4 deletions
diff --git a/Makefile b/Makefile
index 7793c0b..a52285e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,155 +1,156 @@
1CGIT_VERSION = v0.8.1 1CGIT_VERSION = v0.8.1
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.6.1 8GIT_VER = 1.6.1
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 NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 14# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
15# 15#
16 16
17#-include config.mak 17#-include config.mak
18 18
19# 19#
20# Platform specific tweaks 20# Platform specific tweaks
21# 21#
22 22
23uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 23uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
24uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') 24uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
25uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') 25uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
26 26
27ifeq ($(uname_O),Cygwin) 27ifeq ($(uname_O),Cygwin)
28 NO_STRCASESTR = YesPlease 28 NO_STRCASESTR = YesPlease
29 NEEDS_LIBICONV = YesPlease 29 NEEDS_LIBICONV = YesPlease
30endif 30endif
31 31
32# 32#
33# Let the user override the above settings. 33# Let the user override the above settings.
34# 34#
35-include cgit.conf 35-include cgit.conf
36 36
37# 37#
38# Define a way to invoke make in subdirs quietly, shamelessly ripped 38# Define a way to invoke make in subdirs quietly, shamelessly ripped
39# from git.git 39# from git.git
40# 40#
41QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir 41QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
42QUIET_SUBDIR1 = 42QUIET_SUBDIR1 =
43 43
44ifneq ($(findstring $(MAKEFLAGS),w),w) 44ifneq ($(findstring $(MAKEFLAGS),w),w)
45PRINT_DIR = --no-print-directory 45PRINT_DIR = --no-print-directory
46else # "make -w" 46else # "make -w"
47NO_SUBDIR = : 47NO_SUBDIR = :
48endif 48endif
49 49
50ifndef V 50ifndef V
51 QUIET_CC = @echo ' ' CC $@; 51 QUIET_CC = @echo ' ' CC $@;
52 QUIET_MM = @echo ' ' MM $@; 52 QUIET_MM = @echo ' ' MM $@;
53 QUIET_SUBDIR0 = +@subdir= 53 QUIET_SUBDIR0 = +@subdir=
54 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ 54 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
55 $(MAKE) $(PRINT_DIR) -C $$subdir 55 $(MAKE) $(PRINT_DIR) -C $$subdir
56endif 56endif
57 57
58# 58#
59# Define a pattern rule for automatic dependency building 59# Define a pattern rule for automatic dependency building
60# 60#
61%.d: %.c 61%.d: %.c
62 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 62 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
63 63
64# 64#
65# Define a pattern rule for silent object building 65# Define a pattern rule for silent object building
66# 66#
67%.o: %.c 67%.o: %.c
68 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 68 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
69 69
70 70
71EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto 71EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto
72OBJECTS = 72OBJECTS =
73OBJECTS += cache.o 73OBJECTS += cache.o
74OBJECTS += cgit.o 74OBJECTS += cgit.o
75OBJECTS += cmd.o 75OBJECTS += cmd.o
76OBJECTS += configfile.o 76OBJECTS += configfile.o
77OBJECTS += html.o 77OBJECTS += html.o
78OBJECTS += parsing.o 78OBJECTS += parsing.o
79OBJECTS += scan-tree.o 79OBJECTS += scan-tree.o
80OBJECTS += shared.o 80OBJECTS += shared.o
81OBJECTS += ui-atom.o 81OBJECTS += ui-atom.o
82OBJECTS += ui-blob.o 82OBJECTS += ui-blob.o
83OBJECTS += ui-clone.o 83OBJECTS += ui-clone.o
84OBJECTS += ui-commit.o 84OBJECTS += ui-commit.o
85OBJECTS += ui-diff.o 85OBJECTS += ui-diff.o
86OBJECTS += ui-log.o 86OBJECTS += ui-log.o
87OBJECTS += ui-patch.o 87OBJECTS += ui-patch.o
88OBJECTS += ui-plain.o 88OBJECTS += ui-plain.o
89OBJECTS += ui-refs.o 89OBJECTS += ui-refs.o
90OBJECTS += ui-repolist.o 90OBJECTS += ui-repolist.o
91OBJECTS += ui-shared.o 91OBJECTS += ui-shared.o
92OBJECTS += ui-snapshot.o 92OBJECTS += ui-snapshot.o
93OBJECTS += ui-stats.o
93OBJECTS += ui-summary.o 94OBJECTS += ui-summary.o
94OBJECTS += ui-tag.o 95OBJECTS += ui-tag.o
95OBJECTS += ui-tree.o 96OBJECTS += ui-tree.o
96 97
97ifdef NEEDS_LIBICONV 98ifdef NEEDS_LIBICONV
98 EXTLIBS += -liconv 99 EXTLIBS += -liconv
99endif 100endif
100 101
101 102
102.PHONY: all libgit test install uninstall clean force-version get-git 103.PHONY: all libgit test install uninstall clean force-version get-git
103 104
104all: cgit 105all: cgit
105 106
106VERSION: force-version 107VERSION: force-version
107 @./gen-version.sh "$(CGIT_VERSION)" 108 @./gen-version.sh "$(CGIT_VERSION)"
108-include VERSION 109-include VERSION
109 110
110 111
111CFLAGS += -g -Wall -Igit 112CFLAGS += -g -Wall -Igit
112CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 113CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
113CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 114CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
114CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 115CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
115CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 116CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
116CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 117CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
117 118
118ifdef NO_ICONV 119ifdef NO_ICONV
119 CFLAGS += -DNO_ICONV 120 CFLAGS += -DNO_ICONV
120endif 121endif
121ifdef NO_STRCASESTR 122ifdef NO_STRCASESTR
122 CFLAGS += -DNO_STRCASESTR 123 CFLAGS += -DNO_STRCASESTR
123endif 124endif
124 125
125cgit: $(OBJECTS) libgit 126cgit: $(OBJECTS) libgit
126 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 127 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
127 128
128cgit.o: VERSION 129cgit.o: VERSION
129 130
130-include $(OBJECTS:.o=.d) 131-include $(OBJECTS:.o=.d)
131 132
132libgit: 133libgit:
133 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a 134 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a
134 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a 135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a
135 136
136test: all 137test: all
137 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 138 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
138 139
139install: all 140install: all
140 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 141 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
141 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 142 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
142 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 143 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
143 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 144 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
144 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 145 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
145 146
146uninstall: 147uninstall:
147 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 148 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
148 rm -f $(CGIT_DATA_PATH)/cgit.css 149 rm -f $(CGIT_DATA_PATH)/cgit.css
149 rm -f $(CGIT_DATA_PATH)/cgit.png 150 rm -f $(CGIT_DATA_PATH)/cgit.png
150 151
151clean: 152clean:
152 rm -f cgit VERSION *.o *.d 153 rm -f cgit VERSION *.o *.d
153 154
154get-git: 155get-git:
155 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 156 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cgit.c b/cgit.c
index f35f605..608cab6 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,375 +1,383 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 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 "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h"
15#include "scan-tree.h" 16#include "scan-tree.h"
16 17
17const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
18 19
19void config_cb(const char *name, const char *value) 20void config_cb(const char *name, const char *value)
20{ 21{
21 if (!strcmp(name, "root-title")) 22 if (!strcmp(name, "root-title"))
22 ctx.cfg.root_title = xstrdup(value); 23 ctx.cfg.root_title = xstrdup(value);
23 else if (!strcmp(name, "root-desc")) 24 else if (!strcmp(name, "root-desc"))
24 ctx.cfg.root_desc = xstrdup(value); 25 ctx.cfg.root_desc = xstrdup(value);
25 else if (!strcmp(name, "root-readme")) 26 else if (!strcmp(name, "root-readme"))
26 ctx.cfg.root_readme = xstrdup(value); 27 ctx.cfg.root_readme = xstrdup(value);
27 else if (!strcmp(name, "css")) 28 else if (!strcmp(name, "css"))
28 ctx.cfg.css = xstrdup(value); 29 ctx.cfg.css = xstrdup(value);
29 else if (!strcmp(name, "favicon")) 30 else if (!strcmp(name, "favicon"))
30 ctx.cfg.favicon = xstrdup(value); 31 ctx.cfg.favicon = xstrdup(value);
31 else if (!strcmp(name, "footer")) 32 else if (!strcmp(name, "footer"))
32 ctx.cfg.footer = xstrdup(value); 33 ctx.cfg.footer = xstrdup(value);
33 else if (!strcmp(name, "logo")) 34 else if (!strcmp(name, "logo"))
34 ctx.cfg.logo = xstrdup(value); 35 ctx.cfg.logo = xstrdup(value);
35 else if (!strcmp(name, "index-header")) 36 else if (!strcmp(name, "index-header"))
36 ctx.cfg.index_header = xstrdup(value); 37 ctx.cfg.index_header = xstrdup(value);
37 else if (!strcmp(name, "index-info")) 38 else if (!strcmp(name, "index-info"))
38 ctx.cfg.index_info = xstrdup(value); 39 ctx.cfg.index_info = xstrdup(value);
39 else if (!strcmp(name, "logo-link")) 40 else if (!strcmp(name, "logo-link"))
40 ctx.cfg.logo_link = xstrdup(value); 41 ctx.cfg.logo_link = xstrdup(value);
41 else if (!strcmp(name, "module-link")) 42 else if (!strcmp(name, "module-link"))
42 ctx.cfg.module_link = xstrdup(value); 43 ctx.cfg.module_link = xstrdup(value);
43 else if (!strcmp(name, "virtual-root")) { 44 else if (!strcmp(name, "virtual-root")) {
44 ctx.cfg.virtual_root = trim_end(value, '/'); 45 ctx.cfg.virtual_root = trim_end(value, '/');
45 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 46 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
46 ctx.cfg.virtual_root = ""; 47 ctx.cfg.virtual_root = "";
47 } else if (!strcmp(name, "nocache")) 48 } else if (!strcmp(name, "nocache"))
48 ctx.cfg.nocache = atoi(value); 49 ctx.cfg.nocache = atoi(value);
49 else if (!strcmp(name, "snapshots")) 50 else if (!strcmp(name, "snapshots"))
50 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 51 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
51 else if (!strcmp(name, "enable-index-links")) 52 else if (!strcmp(name, "enable-index-links"))
52 ctx.cfg.enable_index_links = atoi(value); 53 ctx.cfg.enable_index_links = atoi(value);
53 else if (!strcmp(name, "enable-log-filecount")) 54 else if (!strcmp(name, "enable-log-filecount"))
54 ctx.cfg.enable_log_filecount = atoi(value); 55 ctx.cfg.enable_log_filecount = atoi(value);
55 else if (!strcmp(name, "enable-log-linecount")) 56 else if (!strcmp(name, "enable-log-linecount"))
56 ctx.cfg.enable_log_linecount = atoi(value); 57 ctx.cfg.enable_log_linecount = atoi(value);
58 else if (!strcmp(name, "max-stats"))
59 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
57 else if (!strcmp(name, "cache-size")) 60 else if (!strcmp(name, "cache-size"))
58 ctx.cfg.cache_size = atoi(value); 61 ctx.cfg.cache_size = atoi(value);
59 else if (!strcmp(name, "cache-root")) 62 else if (!strcmp(name, "cache-root"))
60 ctx.cfg.cache_root = xstrdup(value); 63 ctx.cfg.cache_root = xstrdup(value);
61 else if (!strcmp(name, "cache-root-ttl")) 64 else if (!strcmp(name, "cache-root-ttl"))
62 ctx.cfg.cache_root_ttl = atoi(value); 65 ctx.cfg.cache_root_ttl = atoi(value);
63 else if (!strcmp(name, "cache-repo-ttl")) 66 else if (!strcmp(name, "cache-repo-ttl"))
64 ctx.cfg.cache_repo_ttl = atoi(value); 67 ctx.cfg.cache_repo_ttl = atoi(value);
65 else if (!strcmp(name, "cache-static-ttl")) 68 else if (!strcmp(name, "cache-static-ttl"))
66 ctx.cfg.cache_static_ttl = atoi(value); 69 ctx.cfg.cache_static_ttl = atoi(value);
67 else if (!strcmp(name, "cache-dynamic-ttl")) 70 else if (!strcmp(name, "cache-dynamic-ttl"))
68 ctx.cfg.cache_dynamic_ttl = atoi(value); 71 ctx.cfg.cache_dynamic_ttl = atoi(value);
69 else if (!strcmp(name, "max-message-length")) 72 else if (!strcmp(name, "max-message-length"))
70 ctx.cfg.max_msg_len = atoi(value); 73 ctx.cfg.max_msg_len = atoi(value);
71 else if (!strcmp(name, "max-repodesc-length")) 74 else if (!strcmp(name, "max-repodesc-length"))
72 ctx.cfg.max_repodesc_len = atoi(value); 75 ctx.cfg.max_repodesc_len = atoi(value);
73 else if (!strcmp(name, "max-repo-count")) 76 else if (!strcmp(name, "max-repo-count"))
74 ctx.cfg.max_repo_count = atoi(value); 77 ctx.cfg.max_repo_count = atoi(value);
75 else if (!strcmp(name, "max-commit-count")) 78 else if (!strcmp(name, "max-commit-count"))
76 ctx.cfg.max_commit_count = atoi(value); 79 ctx.cfg.max_commit_count = atoi(value);
77 else if (!strcmp(name, "summary-log")) 80 else if (!strcmp(name, "summary-log"))
78 ctx.cfg.summary_log = atoi(value); 81 ctx.cfg.summary_log = atoi(value);
79 else if (!strcmp(name, "summary-branches")) 82 else if (!strcmp(name, "summary-branches"))
80 ctx.cfg.summary_branches = atoi(value); 83 ctx.cfg.summary_branches = atoi(value);
81 else if (!strcmp(name, "summary-tags")) 84 else if (!strcmp(name, "summary-tags"))
82 ctx.cfg.summary_tags = atoi(value); 85 ctx.cfg.summary_tags = atoi(value);
83 else if (!strcmp(name, "agefile")) 86 else if (!strcmp(name, "agefile"))
84 ctx.cfg.agefile = xstrdup(value); 87 ctx.cfg.agefile = xstrdup(value);
85 else if (!strcmp(name, "renamelimit")) 88 else if (!strcmp(name, "renamelimit"))
86 ctx.cfg.renamelimit = atoi(value); 89 ctx.cfg.renamelimit = atoi(value);
87 else if (!strcmp(name, "robots")) 90 else if (!strcmp(name, "robots"))
88 ctx.cfg.robots = xstrdup(value); 91 ctx.cfg.robots = xstrdup(value);
89 else if (!strcmp(name, "clone-prefix")) 92 else if (!strcmp(name, "clone-prefix"))
90 ctx.cfg.clone_prefix = xstrdup(value); 93 ctx.cfg.clone_prefix = xstrdup(value);
91 else if (!strcmp(name, "local-time")) 94 else if (!strcmp(name, "local-time"))
92 ctx.cfg.local_time = atoi(value); 95 ctx.cfg.local_time = atoi(value);
93 else if (!strcmp(name, "repo.group")) 96 else if (!strcmp(name, "repo.group"))
94 ctx.cfg.repo_group = xstrdup(value); 97 ctx.cfg.repo_group = xstrdup(value);
95 else if (!strcmp(name, "repo.url")) 98 else if (!strcmp(name, "repo.url"))
96 ctx.repo = cgit_add_repo(value); 99 ctx.repo = cgit_add_repo(value);
97 else if (!strcmp(name, "repo.name")) 100 else if (!strcmp(name, "repo.name"))
98 ctx.repo->name = xstrdup(value); 101 ctx.repo->name = xstrdup(value);
99 else if (ctx.repo && !strcmp(name, "repo.path")) 102 else if (ctx.repo && !strcmp(name, "repo.path"))
100 ctx.repo->path = trim_end(value, '/'); 103 ctx.repo->path = trim_end(value, '/');
101 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 104 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
102 ctx.repo->clone_url = xstrdup(value); 105 ctx.repo->clone_url = xstrdup(value);
103 else if (ctx.repo && !strcmp(name, "repo.desc")) 106 else if (ctx.repo && !strcmp(name, "repo.desc"))
104 ctx.repo->desc = xstrdup(value); 107 ctx.repo->desc = xstrdup(value);
105 else if (ctx.repo && !strcmp(name, "repo.owner")) 108 else if (ctx.repo && !strcmp(name, "repo.owner"))
106 ctx.repo->owner = xstrdup(value); 109 ctx.repo->owner = xstrdup(value);
107 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 110 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
108 ctx.repo->defbranch = xstrdup(value); 111 ctx.repo->defbranch = xstrdup(value);
109 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 112 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
110 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 113 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
111 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 114 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
112 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 115 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
113 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 116 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
114 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 117 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
118 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
119 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
115 else if (ctx.repo && !strcmp(name, "repo.module-link")) 120 else if (ctx.repo && !strcmp(name, "repo.module-link"))
116 ctx.repo->module_link= xstrdup(value); 121 ctx.repo->module_link= xstrdup(value);
117 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 122 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
118 if (*value == '/') 123 if (*value == '/')
119 ctx.repo->readme = xstrdup(value); 124 ctx.repo->readme = xstrdup(value);
120 else 125 else
121 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 126 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
122 } else if (!strcmp(name, "include")) 127 } else if (!strcmp(name, "include"))
123 parse_configfile(value, config_cb); 128 parse_configfile(value, config_cb);
124} 129}
125 130
126static void querystring_cb(const char *name, const char *value) 131static void querystring_cb(const char *name, const char *value)
127{ 132{
128 if (!strcmp(name,"r")) { 133 if (!strcmp(name,"r")) {
129 ctx.qry.repo = xstrdup(value); 134 ctx.qry.repo = xstrdup(value);
130 ctx.repo = cgit_get_repoinfo(value); 135 ctx.repo = cgit_get_repoinfo(value);
131 } else if (!strcmp(name, "p")) { 136 } else if (!strcmp(name, "p")) {
132 ctx.qry.page = xstrdup(value); 137 ctx.qry.page = xstrdup(value);
133 } else if (!strcmp(name, "url")) { 138 } else if (!strcmp(name, "url")) {
134 ctx.qry.url = xstrdup(value); 139 ctx.qry.url = xstrdup(value);
135 cgit_parse_url(value); 140 cgit_parse_url(value);
136 } else if (!strcmp(name, "qt")) { 141 } else if (!strcmp(name, "qt")) {
137 ctx.qry.grep = xstrdup(value); 142 ctx.qry.grep = xstrdup(value);
138 } else if (!strcmp(name, "q")) { 143 } else if (!strcmp(name, "q")) {
139 ctx.qry.search = xstrdup(value); 144 ctx.qry.search = xstrdup(value);
140 } else if (!strcmp(name, "h")) { 145 } else if (!strcmp(name, "h")) {
141 ctx.qry.head = xstrdup(value); 146 ctx.qry.head = xstrdup(value);
142 ctx.qry.has_symref = 1; 147 ctx.qry.has_symref = 1;
143 } else if (!strcmp(name, "id")) { 148 } else if (!strcmp(name, "id")) {
144 ctx.qry.sha1 = xstrdup(value); 149 ctx.qry.sha1 = xstrdup(value);
145 ctx.qry.has_sha1 = 1; 150 ctx.qry.has_sha1 = 1;
146 } else if (!strcmp(name, "id2")) { 151 } else if (!strcmp(name, "id2")) {
147 ctx.qry.sha2 = xstrdup(value); 152 ctx.qry.sha2 = xstrdup(value);
148 ctx.qry.has_sha1 = 1; 153 ctx.qry.has_sha1 = 1;
149 } else if (!strcmp(name, "ofs")) { 154 } else if (!strcmp(name, "ofs")) {
150 ctx.qry.ofs = atoi(value); 155 ctx.qry.ofs = atoi(value);
151 } else if (!strcmp(name, "path")) { 156 } else if (!strcmp(name, "path")) {
152 ctx.qry.path = trim_end(value, '/'); 157 ctx.qry.path = trim_end(value, '/');
153 } else if (!strcmp(name, "name")) { 158 } else if (!strcmp(name, "name")) {
154 ctx.qry.name = xstrdup(value); 159 ctx.qry.name = xstrdup(value);
155 } else if (!strcmp(name, "mimetype")) { 160 } else if (!strcmp(name, "mimetype")) {
156 ctx.qry.mimetype = xstrdup(value); 161 ctx.qry.mimetype = xstrdup(value);
157 } else if (!strcmp(name, "s")){ 162 } else if (!strcmp(name, "s")){
158 ctx.qry.sort = xstrdup(value); 163 ctx.qry.sort = xstrdup(value);
159 } else if (!strcmp(name, "showmsg")) { 164 } else if (!strcmp(name, "showmsg")) {
160 ctx.qry.showmsg = atoi(value); 165 ctx.qry.showmsg = atoi(value);
166 } else if (!strcmp(name, "period")) {
167 ctx.qry.period = xstrdup(value);
161 } 168 }
162} 169}
163 170
164static void prepare_context(struct cgit_context *ctx) 171static void prepare_context(struct cgit_context *ctx)
165{ 172{
166 memset(ctx, 0, sizeof(ctx)); 173 memset(ctx, 0, sizeof(ctx));
167 ctx->cfg.agefile = "info/web/last-modified"; 174 ctx->cfg.agefile = "info/web/last-modified";
168 ctx->cfg.nocache = 0; 175 ctx->cfg.nocache = 0;
169 ctx->cfg.cache_size = 0; 176 ctx->cfg.cache_size = 0;
170 ctx->cfg.cache_dynamic_ttl = 5; 177 ctx->cfg.cache_dynamic_ttl = 5;
171 ctx->cfg.cache_max_create_time = 5; 178 ctx->cfg.cache_max_create_time = 5;
172 ctx->cfg.cache_repo_ttl = 5; 179 ctx->cfg.cache_repo_ttl = 5;
173 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 180 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
174 ctx->cfg.cache_root_ttl = 5; 181 ctx->cfg.cache_root_ttl = 5;
175 ctx->cfg.cache_static_ttl = -1; 182 ctx->cfg.cache_static_ttl = -1;
176 ctx->cfg.css = "/cgit.css"; 183 ctx->cfg.css = "/cgit.css";
177 ctx->cfg.logo = "/git-logo.png"; 184 ctx->cfg.logo = "/git-logo.png";
178 ctx->cfg.local_time = 0; 185 ctx->cfg.local_time = 0;
179 ctx->cfg.max_repo_count = 50; 186 ctx->cfg.max_repo_count = 50;
180 ctx->cfg.max_commit_count = 50; 187 ctx->cfg.max_commit_count = 50;
181 ctx->cfg.max_lock_attempts = 5; 188 ctx->cfg.max_lock_attempts = 5;
182 ctx->cfg.max_msg_len = 80; 189 ctx->cfg.max_msg_len = 80;
183 ctx->cfg.max_repodesc_len = 80; 190 ctx->cfg.max_repodesc_len = 80;
191 ctx->cfg.max_stats = 0;
184 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 192 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
185 ctx->cfg.renamelimit = -1; 193 ctx->cfg.renamelimit = -1;
186 ctx->cfg.robots = "index, nofollow"; 194 ctx->cfg.robots = "index, nofollow";
187 ctx->cfg.root_title = "Git repository browser"; 195 ctx->cfg.root_title = "Git repository browser";
188 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 196 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
189 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 197 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
190 ctx->cfg.summary_branches = 10; 198 ctx->cfg.summary_branches = 10;
191 ctx->cfg.summary_log = 10; 199 ctx->cfg.summary_log = 10;
192 ctx->cfg.summary_tags = 10; 200 ctx->cfg.summary_tags = 10;
193 ctx->page.mimetype = "text/html"; 201 ctx->page.mimetype = "text/html";
194 ctx->page.charset = PAGE_ENCODING; 202 ctx->page.charset = PAGE_ENCODING;
195 ctx->page.filename = NULL; 203 ctx->page.filename = NULL;
196 ctx->page.size = 0; 204 ctx->page.size = 0;
197 ctx->page.modified = time(NULL); 205 ctx->page.modified = time(NULL);
198 ctx->page.expires = ctx->page.modified; 206 ctx->page.expires = ctx->page.modified;
199} 207}
200 208
201struct refmatch { 209struct refmatch {
202 char *req_ref; 210 char *req_ref;
203 char *first_ref; 211 char *first_ref;
204 int match; 212 int match;
205}; 213};
206 214
207int find_current_ref(const char *refname, const unsigned char *sha1, 215int find_current_ref(const char *refname, const unsigned char *sha1,
208 int flags, void *cb_data) 216 int flags, void *cb_data)
209{ 217{
210 struct refmatch *info; 218 struct refmatch *info;
211 219
212 info = (struct refmatch *)cb_data; 220 info = (struct refmatch *)cb_data;
213 if (!strcmp(refname, info->req_ref)) 221 if (!strcmp(refname, info->req_ref))
214 info->match = 1; 222 info->match = 1;
215 if (!info->first_ref) 223 if (!info->first_ref)
216 info->first_ref = xstrdup(refname); 224 info->first_ref = xstrdup(refname);
217 return info->match; 225 return info->match;
218} 226}
219 227
220char *find_default_branch(struct cgit_repo *repo) 228char *find_default_branch(struct cgit_repo *repo)
221{ 229{
222 struct refmatch info; 230 struct refmatch info;
223 char *ref; 231 char *ref;
224 232
225 info.req_ref = repo->defbranch; 233 info.req_ref = repo->defbranch;
226 info.first_ref = NULL; 234 info.first_ref = NULL;
227 info.match = 0; 235 info.match = 0;
228 for_each_branch_ref(find_current_ref, &info); 236 for_each_branch_ref(find_current_ref, &info);
229 if (info.match) 237 if (info.match)
230 ref = info.req_ref; 238 ref = info.req_ref;
231 else 239 else
232 ref = info.first_ref; 240 ref = info.first_ref;
233 if (ref) 241 if (ref)
234 ref = xstrdup(ref); 242 ref = xstrdup(ref);
235 return ref; 243 return ref;
236} 244}
237 245
238static int prepare_repo_cmd(struct cgit_context *ctx) 246static int prepare_repo_cmd(struct cgit_context *ctx)
239{ 247{
240 char *tmp; 248 char *tmp;
241 unsigned char sha1[20]; 249 unsigned char sha1[20];
242 int nongit = 0; 250 int nongit = 0;
243 251
244 setenv("GIT_DIR", ctx->repo->path, 1); 252 setenv("GIT_DIR", ctx->repo->path, 1);
245 setup_git_directory_gently(&nongit); 253 setup_git_directory_gently(&nongit);
246 if (nongit) { 254 if (nongit) {
247 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 255 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
248 "config error"); 256 "config error");
249 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 257 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
250 ctx->repo = NULL; 258 ctx->repo = NULL;
251 cgit_print_http_headers(ctx); 259 cgit_print_http_headers(ctx);
252 cgit_print_docstart(ctx); 260 cgit_print_docstart(ctx);
253 cgit_print_pageheader(ctx); 261 cgit_print_pageheader(ctx);
254 cgit_print_error(tmp); 262 cgit_print_error(tmp);
255 cgit_print_docend(); 263 cgit_print_docend();
256 return 1; 264 return 1;
257 } 265 }
258 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 266 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
259 267
260 if (!ctx->qry.head) { 268 if (!ctx->qry.head) {
261 ctx->qry.nohead = 1; 269 ctx->qry.nohead = 1;
262 ctx->qry.head = find_default_branch(ctx->repo); 270 ctx->qry.head = find_default_branch(ctx->repo);
263 ctx->repo->defbranch = ctx->qry.head; 271 ctx->repo->defbranch = ctx->qry.head;
264 } 272 }
265 273
266 if (!ctx->qry.head) { 274 if (!ctx->qry.head) {
267 cgit_print_http_headers(ctx); 275 cgit_print_http_headers(ctx);
268 cgit_print_docstart(ctx); 276 cgit_print_docstart(ctx);
269 cgit_print_pageheader(ctx); 277 cgit_print_pageheader(ctx);
270 cgit_print_error("Repository seems to be empty"); 278 cgit_print_error("Repository seems to be empty");
271 cgit_print_docend(); 279 cgit_print_docend();
272 return 1; 280 return 1;
273 } 281 }
274 282
275 if (get_sha1(ctx->qry.head, sha1)) { 283 if (get_sha1(ctx->qry.head, sha1)) {
276 tmp = xstrdup(ctx->qry.head); 284 tmp = xstrdup(ctx->qry.head);
277 ctx->qry.head = ctx->repo->defbranch; 285 ctx->qry.head = ctx->repo->defbranch;
278 cgit_print_http_headers(ctx); 286 cgit_print_http_headers(ctx);
279 cgit_print_docstart(ctx); 287 cgit_print_docstart(ctx);
280 cgit_print_pageheader(ctx); 288 cgit_print_pageheader(ctx);
281 cgit_print_error(fmt("Invalid branch: %s", tmp)); 289 cgit_print_error(fmt("Invalid branch: %s", tmp));
282 cgit_print_docend(); 290 cgit_print_docend();
283 return 1; 291 return 1;
284 } 292 }
285 return 0; 293 return 0;
286} 294}
287 295
288static void process_request(void *cbdata) 296static void process_request(void *cbdata)
289{ 297{
290 struct cgit_context *ctx = cbdata; 298 struct cgit_context *ctx = cbdata;
291 struct cgit_cmd *cmd; 299 struct cgit_cmd *cmd;
292 300
293 cmd = cgit_get_cmd(ctx); 301 cmd = cgit_get_cmd(ctx);
294 if (!cmd) { 302 if (!cmd) {
295 ctx->page.title = "cgit error"; 303 ctx->page.title = "cgit error";
296 cgit_print_http_headers(ctx); 304 cgit_print_http_headers(ctx);
297 cgit_print_docstart(ctx); 305 cgit_print_docstart(ctx);
298 cgit_print_pageheader(ctx); 306 cgit_print_pageheader(ctx);
299 cgit_print_error("Invalid request"); 307 cgit_print_error("Invalid request");
300 cgit_print_docend(); 308 cgit_print_docend();
301 return; 309 return;
302 } 310 }
303 311
304 if (cmd->want_repo && !ctx->repo) { 312 if (cmd->want_repo && !ctx->repo) {
305 cgit_print_http_headers(ctx); 313 cgit_print_http_headers(ctx);
306 cgit_print_docstart(ctx); 314 cgit_print_docstart(ctx);
307 cgit_print_pageheader(ctx); 315 cgit_print_pageheader(ctx);
308 cgit_print_error(fmt("No repository selected")); 316 cgit_print_error(fmt("No repository selected"));
309 cgit_print_docend(); 317 cgit_print_docend();
310 return; 318 return;
311 } 319 }
312 320
313 if (ctx->repo && prepare_repo_cmd(ctx)) 321 if (ctx->repo && prepare_repo_cmd(ctx))
314 return; 322 return;
315 323
316 if (cmd->want_layout) { 324 if (cmd->want_layout) {
317 cgit_print_http_headers(ctx); 325 cgit_print_http_headers(ctx);
318 cgit_print_docstart(ctx); 326 cgit_print_docstart(ctx);
319 cgit_print_pageheader(ctx); 327 cgit_print_pageheader(ctx);
320 } 328 }
321 329
322 cmd->fn(ctx); 330 cmd->fn(ctx);
323 331
324 if (cmd->want_layout) 332 if (cmd->want_layout)
325 cgit_print_docend(); 333 cgit_print_docend();
326} 334}
327 335
328int cmp_repos(const void *a, const void *b) 336int cmp_repos(const void *a, const void *b)
329{ 337{
330 const struct cgit_repo *ra = a, *rb = b; 338 const struct cgit_repo *ra = a, *rb = b;
331 return strcmp(ra->url, rb->url); 339 return strcmp(ra->url, rb->url);
332} 340}
333 341
334void print_repo(struct cgit_repo *repo) 342void print_repo(struct cgit_repo *repo)
335{ 343{
336 printf("repo.url=%s\n", repo->url); 344 printf("repo.url=%s\n", repo->url);
337 printf("repo.name=%s\n", repo->name); 345 printf("repo.name=%s\n", repo->name);
338 printf("repo.path=%s\n", repo->path); 346 printf("repo.path=%s\n", repo->path);
339 if (repo->owner) 347 if (repo->owner)
340 printf("repo.owner=%s\n", repo->owner); 348 printf("repo.owner=%s\n", repo->owner);
341 if (repo->desc) 349 if (repo->desc)
342 printf("repo.desc=%s\n", repo->desc); 350 printf("repo.desc=%s\n", repo->desc);
343 if (repo->readme) 351 if (repo->readme)
344 printf("repo.readme=%s\n", repo->readme); 352 printf("repo.readme=%s\n", repo->readme);
345 printf("\n"); 353 printf("\n");
346} 354}
347 355
348void print_repolist(struct cgit_repolist *list) 356void print_repolist(struct cgit_repolist *list)
349{ 357{
350 int i; 358 int i;
351 359
352 for(i = 0; i < list->count; i++) 360 for(i = 0; i < list->count; i++)
353 print_repo(&list->repos[i]); 361 print_repo(&list->repos[i]);
354} 362}
355 363
356 364
357static void cgit_parse_args(int argc, const char **argv) 365static void cgit_parse_args(int argc, const char **argv)
358{ 366{
359 int i; 367 int i;
360 int scan = 0; 368 int scan = 0;
361 369
362 for (i = 1; i < argc; i++) { 370 for (i = 1; i < argc; i++) {
363 if (!strncmp(argv[i], "--cache=", 8)) { 371 if (!strncmp(argv[i], "--cache=", 8)) {
364 ctx.cfg.cache_root = xstrdup(argv[i]+8); 372 ctx.cfg.cache_root = xstrdup(argv[i]+8);
365 } 373 }
366 if (!strcmp(argv[i], "--nocache")) { 374 if (!strcmp(argv[i], "--nocache")) {
367 ctx.cfg.nocache = 1; 375 ctx.cfg.nocache = 1;
368 } 376 }
369 if (!strncmp(argv[i], "--query=", 8)) { 377 if (!strncmp(argv[i], "--query=", 8)) {
370 ctx.qry.raw = xstrdup(argv[i]+8); 378 ctx.qry.raw = xstrdup(argv[i]+8);
371 } 379 }
372 if (!strncmp(argv[i], "--repo=", 7)) { 380 if (!strncmp(argv[i], "--repo=", 7)) {
373 ctx.qry.repo = xstrdup(argv[i]+7); 381 ctx.qry.repo = xstrdup(argv[i]+7);
374 } 382 }
375 if (!strncmp(argv[i], "--page=", 7)) { 383 if (!strncmp(argv[i], "--page=", 7)) {
diff --git a/cgit.css b/cgit.css
index f19446d..e8214de 100644
--- a/cgit.css
+++ b/cgit.css
@@ -306,192 +306,268 @@ table.diffstat th {
306 font-weight: normal; 306 font-weight: normal;
307 text-align: left; 307 text-align: left;
308 text-decoration: underline; 308 text-decoration: underline;
309 padding: 0.1em 1em 0.1em 0.1em; 309 padding: 0.1em 1em 0.1em 0.1em;
310 font-size: 100%; 310 font-size: 100%;
311} 311}
312 312
313table.diffstat td { 313table.diffstat td {
314 padding: 0.2em 0.2em 0.1em 0.1em; 314 padding: 0.2em 0.2em 0.1em 0.1em;
315 font-size: 100%; 315 font-size: 100%;
316 border: none; 316 border: none;
317} 317}
318 318
319table.diffstat td.mode { 319table.diffstat td.mode {
320 white-space: nowrap; 320 white-space: nowrap;
321} 321}
322 322
323table.diffstat td span.modechange { 323table.diffstat td span.modechange {
324 padding-left: 1em; 324 padding-left: 1em;
325 color: red; 325 color: red;
326} 326}
327 327
328table.diffstat td.add a { 328table.diffstat td.add a {
329 color: green; 329 color: green;
330} 330}
331 331
332table.diffstat td.del a { 332table.diffstat td.del a {
333 color: red; 333 color: red;
334} 334}
335 335
336table.diffstat td.upd a { 336table.diffstat td.upd a {
337 color: blue; 337 color: blue;
338} 338}
339 339
340table.diffstat td.graph { 340table.diffstat td.graph {
341 width: 500px; 341 width: 500px;
342 vertical-align: middle; 342 vertical-align: middle;
343} 343}
344 344
345table.diffstat td.graph table { 345table.diffstat td.graph table {
346 border: none; 346 border: none;
347} 347}
348 348
349table.diffstat td.graph td { 349table.diffstat td.graph td {
350 padding: 0px; 350 padding: 0px;
351 border: 0px; 351 border: 0px;
352 height: 7pt; 352 height: 7pt;
353} 353}
354 354
355table.diffstat td.graph td.add { 355table.diffstat td.graph td.add {
356 background-color: #5c5; 356 background-color: #5c5;
357} 357}
358 358
359table.diffstat td.graph td.rem { 359table.diffstat td.graph td.rem {
360 background-color: #c55; 360 background-color: #c55;
361} 361}
362 362
363div.diffstat-summary { 363div.diffstat-summary {
364 color: #888; 364 color: #888;
365 padding-top: 0.5em; 365 padding-top: 0.5em;
366} 366}
367 367
368table.diff { 368table.diff {
369 width: 100%; 369 width: 100%;
370} 370}
371 371
372table.diff td { 372table.diff td {
373 font-family: monospace; 373 font-family: monospace;
374 white-space: pre; 374 white-space: pre;
375} 375}
376 376
377table.diff td div.head { 377table.diff td div.head {
378 font-weight: bold; 378 font-weight: bold;
379 margin-top: 1em; 379 margin-top: 1em;
380 color: black; 380 color: black;
381} 381}
382 382
383table.diff td div.hunk { 383table.diff td div.hunk {
384 color: #009; 384 color: #009;
385} 385}
386 386
387table.diff td div.add { 387table.diff td div.add {
388 color: green; 388 color: green;
389} 389}
390 390
391table.diff td div.del { 391table.diff td div.del {
392 color: red; 392 color: red;
393} 393}
394 394
395.sha1 { 395.sha1 {
396 font-family: monospace; 396 font-family: monospace;
397 font-size: 90%; 397 font-size: 90%;
398} 398}
399 399
400.left { 400.left {
401 text-align: left; 401 text-align: left;
402} 402}
403 403
404.right { 404.right {
405 text-align: right; 405 text-align: right;
406} 406}
407 407
408table.list td.repogroup { 408table.list td.repogroup {
409 font-style: italic; 409 font-style: italic;
410 color: #888; 410 color: #888;
411} 411}
412 412
413a.button { 413a.button {
414 font-size: 80%; 414 font-size: 80%;
415 padding: 0em 0.5em; 415 padding: 0em 0.5em;
416} 416}
417 417
418a.primary { 418a.primary {
419 font-size: 100%; 419 font-size: 100%;
420} 420}
421 421
422a.secondary { 422a.secondary {
423 font-size: 90%; 423 font-size: 90%;
424} 424}
425 425
426td.toplevel-repo { 426td.toplevel-repo {
427 427
428} 428}
429 429
430table.list td.sublevel-repo { 430table.list td.sublevel-repo {
431 padding-left: 1.5em; 431 padding-left: 1.5em;
432} 432}
433 433
434div.pager { 434div.pager {
435 text-align: center; 435 text-align: center;
436 margin: 1em 0em 0em 0em; 436 margin: 1em 0em 0em 0em;
437} 437}
438 438
439div.pager a { 439div.pager a {
440 color: #777; 440 color: #777;
441 margin: 0em 0.5em; 441 margin: 0em 0.5em;
442} 442}
443 443
444span.age-mins { 444span.age-mins {
445 font-weight: bold; 445 font-weight: bold;
446 color: #080; 446 color: #080;
447} 447}
448 448
449span.age-hours { 449span.age-hours {
450 color: #080; 450 color: #080;
451} 451}
452 452
453span.age-days { 453span.age-days {
454 color: #040; 454 color: #040;
455} 455}
456 456
457span.age-weeks { 457span.age-weeks {
458 color: #444; 458 color: #444;
459} 459}
460 460
461span.age-months { 461span.age-months {
462 color: #888; 462 color: #888;
463} 463}
464 464
465span.age-years { 465span.age-years {
466 color: #bbb; 466 color: #bbb;
467} 467}
468div.footer { 468div.footer {
469 margin-top: 0.5em; 469 margin-top: 0.5em;
470 text-align: center; 470 text-align: center;
471 font-size: 80%; 471 font-size: 80%;
472 color: #ccc; 472 color: #ccc;
473} 473}
474a.branch-deco { 474a.branch-deco {
475 margin: 0px 0.5em; 475 margin: 0px 0.5em;
476 padding: 0px 0.25em; 476 padding: 0px 0.25em;
477 background-color: #88ff88; 477 background-color: #88ff88;
478 border: solid 1px #007700; 478 border: solid 1px #007700;
479} 479}
480a.tag-deco { 480a.tag-deco {
481 margin: 0px 0.5em; 481 margin: 0px 0.5em;
482 padding: 0px 0.25em; 482 padding: 0px 0.25em;
483 background-color: #ffff88; 483 background-color: #ffff88;
484 border: solid 1px #777700; 484 border: solid 1px #777700;
485} 485}
486a.remote-deco { 486a.remote-deco {
487 margin: 0px 0.5em; 487 margin: 0px 0.5em;
488 padding: 0px 0.25em; 488 padding: 0px 0.25em;
489 background-color: #ccccff; 489 background-color: #ccccff;
490 border: solid 1px #000077; 490 border: solid 1px #000077;
491} 491}
492a.deco { 492a.deco {
493 margin: 0px 0.5em; 493 margin: 0px 0.5em;
494 padding: 0px 0.25em; 494 padding: 0px 0.25em;
495 background-color: #ff8888; 495 background-color: #ff8888;
496 border: solid 1px #770000; 496 border: solid 1px #770000;
497} 497}
498table.stats {
499 border: solid 1px black;
500 border-collapse: collapse;
501}
502
503table.stats th {
504 text-align: left;
505 padding: 1px 0.5em;
506 background-color: #eee;
507 border: solid 1px black;
508}
509
510table.stats td {
511 text-align: right;
512 padding: 1px 0.5em;
513 border: solid 1px black;
514}
515
516table.stats td.total {
517 font-weight: bold;
518 text-align: left;
519}
520
521table.stats td.sum {
522 color: #c00;
523 font-weight: bold;
524 /*background-color: #eee; */
525}
526
527table.stats td.left {
528 text-align: left;
529}
530
531table.vgraph {
532 border-collapse: separate;
533 border: solid 1px black;
534 height: 200px;
535}
536
537table.vgraph th {
538 background-color: #eee;
539 font-weight: bold;
540 border: solid 1px white;
541 padding: 1px 0.5em;
542}
543
544table.vgraph td {
545 vertical-align: bottom;
546 padding: 0px 10px;
547}
548
549table.vgraph div.bar {
550 background-color: #eee;
551}
552
553table.hgraph {
554 border: solid 1px black;
555 width: 800px;
556}
557
558table.hgraph th {
559 background-color: #eee;
560 font-weight: bold;
561 border: solid 1px black;
562 padding: 1px 0.5em;
563}
564
565table.hgraph td {
566 vertical-align: center;
567 padding: 2px 2px;
568}
569
570table.hgraph div.bar {
571 background-color: #eee;
572 height: 1em;
573}
diff --git a/cgit.h b/cgit.h
index cb2f176..4fe94c6 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,240 +1,243 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <xdiff/xdiff.h> 18#include <xdiff/xdiff.h>
19#include <utf8.h> 19#include <utf8.h>
20 20
21 21
22/* 22/*
23 * Dateformats used on misc. pages 23 * Dateformats used on misc. pages
24 */ 24 */
25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
26#define FMT_SHORTDATE "%Y-%m-%d" 26#define FMT_SHORTDATE "%Y-%m-%d"
27#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 27#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
28 28
29 29
30/* 30/*
31 * Limits used for relative dates 31 * Limits used for relative dates
32 */ 32 */
33#define TM_MIN 60 33#define TM_MIN 60
34#define TM_HOUR (TM_MIN * 60) 34#define TM_HOUR (TM_MIN * 60)
35#define TM_DAY (TM_HOUR * 24) 35#define TM_DAY (TM_HOUR * 24)
36#define TM_WEEK (TM_DAY * 7) 36#define TM_WEEK (TM_DAY * 7)
37#define TM_YEAR (TM_DAY * 365) 37#define TM_YEAR (TM_DAY * 365)
38#define TM_MONTH (TM_YEAR / 12.0) 38#define TM_MONTH (TM_YEAR / 12.0)
39 39
40 40
41/* 41/*
42 * Default encoding 42 * Default encoding
43 */ 43 */
44#define PAGE_ENCODING "UTF-8" 44#define PAGE_ENCODING "UTF-8"
45 45
46typedef void (*configfn)(const char *name, const char *value); 46typedef void (*configfn)(const char *name, const char *value);
47typedef void (*filepair_fn)(struct diff_filepair *pair); 47typedef void (*filepair_fn)(struct diff_filepair *pair);
48typedef void (*linediff_fn)(char *line, int len); 48typedef void (*linediff_fn)(char *line, int len);
49 49
50struct cgit_repo { 50struct cgit_repo {
51 char *url; 51 char *url;
52 char *name; 52 char *name;
53 char *path; 53 char *path;
54 char *desc; 54 char *desc;
55 char *owner; 55 char *owner;
56 char *defbranch; 56 char *defbranch;
57 char *group; 57 char *group;
58 char *module_link; 58 char *module_link;
59 char *readme; 59 char *readme;
60 char *clone_url; 60 char *clone_url;
61 int snapshots; 61 int snapshots;
62 int enable_log_filecount; 62 int enable_log_filecount;
63 int enable_log_linecount; 63 int enable_log_linecount;
64 int max_stats;
64 time_t mtime; 65 time_t mtime;
65}; 66};
66 67
67struct cgit_repolist { 68struct cgit_repolist {
68 int length; 69 int length;
69 int count; 70 int count;
70 struct cgit_repo *repos; 71 struct cgit_repo *repos;
71}; 72};
72 73
73struct commitinfo { 74struct commitinfo {
74 struct commit *commit; 75 struct commit *commit;
75 char *author; 76 char *author;
76 char *author_email; 77 char *author_email;
77 unsigned long author_date; 78 unsigned long author_date;
78 char *committer; 79 char *committer;
79 char *committer_email; 80 char *committer_email;
80 unsigned long committer_date; 81 unsigned long committer_date;
81 char *subject; 82 char *subject;
82 char *msg; 83 char *msg;
83 char *msg_encoding; 84 char *msg_encoding;
84}; 85};
85 86
86struct taginfo { 87struct taginfo {
87 char *tagger; 88 char *tagger;
88 char *tagger_email; 89 char *tagger_email;
89 unsigned long tagger_date; 90 unsigned long tagger_date;
90 char *msg; 91 char *msg;
91}; 92};
92 93
93struct refinfo { 94struct refinfo {
94 const char *refname; 95 const char *refname;
95 struct object *object; 96 struct object *object;
96 union { 97 union {
97 struct taginfo *tag; 98 struct taginfo *tag;
98 struct commitinfo *commit; 99 struct commitinfo *commit;
99 }; 100 };
100}; 101};
101 102
102struct reflist { 103struct reflist {
103 struct refinfo **refs; 104 struct refinfo **refs;
104 int alloc; 105 int alloc;
105 int count; 106 int count;
106}; 107};
107 108
108struct cgit_query { 109struct cgit_query {
109 int has_symref; 110 int has_symref;
110 int has_sha1; 111 int has_sha1;
111 char *raw; 112 char *raw;
112 char *repo; 113 char *repo;
113 char *page; 114 char *page;
114 char *search; 115 char *search;
115 char *grep; 116 char *grep;
116 char *head; 117 char *head;
117 char *sha1; 118 char *sha1;
118 char *sha2; 119 char *sha2;
119 char *path; 120 char *path;
120 char *name; 121 char *name;
121 char *mimetype; 122 char *mimetype;
122 char *url; 123 char *url;
124 char *period;
123 int ofs; 125 int ofs;
124 int nohead; 126 int nohead;
125 char *sort; 127 char *sort;
126 int showmsg; 128 int showmsg;
127}; 129};
128 130
129struct cgit_config { 131struct cgit_config {
130 char *agefile; 132 char *agefile;
131 char *cache_root; 133 char *cache_root;
132 char *clone_prefix; 134 char *clone_prefix;
133 char *css; 135 char *css;
134 char *favicon; 136 char *favicon;
135 char *footer; 137 char *footer;
136 char *index_header; 138 char *index_header;
137 char *index_info; 139 char *index_info;
138 char *logo; 140 char *logo;
139 char *logo_link; 141 char *logo_link;
140 char *module_link; 142 char *module_link;
141 char *repo_group; 143 char *repo_group;
142 char *robots; 144 char *robots;
143 char *root_title; 145 char *root_title;
144 char *root_desc; 146 char *root_desc;
145 char *root_readme; 147 char *root_readme;
146 char *script_name; 148 char *script_name;
147 char *virtual_root; 149 char *virtual_root;
148 int cache_size; 150 int cache_size;
149 int cache_dynamic_ttl; 151 int cache_dynamic_ttl;
150 int cache_max_create_time; 152 int cache_max_create_time;
151 int cache_repo_ttl; 153 int cache_repo_ttl;
152 int cache_root_ttl; 154 int cache_root_ttl;
153 int cache_static_ttl; 155 int cache_static_ttl;
154 int enable_index_links; 156 int enable_index_links;
155 int enable_log_filecount; 157 int enable_log_filecount;
156 int enable_log_linecount; 158 int enable_log_linecount;
157 int local_time; 159 int local_time;
158 int max_repo_count; 160 int max_repo_count;
159 int max_commit_count; 161 int max_commit_count;
160 int max_lock_attempts; 162 int max_lock_attempts;
161 int max_msg_len; 163 int max_msg_len;
162 int max_repodesc_len; 164 int max_repodesc_len;
165 int max_stats;
163 int nocache; 166 int nocache;
164 int renamelimit; 167 int renamelimit;
165 int snapshots; 168 int snapshots;
166 int summary_branches; 169 int summary_branches;
167 int summary_log; 170 int summary_log;
168 int summary_tags; 171 int summary_tags;
169}; 172};
170 173
171struct cgit_page { 174struct cgit_page {
172 time_t modified; 175 time_t modified;
173 time_t expires; 176 time_t expires;
174 size_t size; 177 size_t size;
175 char *mimetype; 178 char *mimetype;
176 char *charset; 179 char *charset;
177 char *filename; 180 char *filename;
178 char *title; 181 char *title;
179}; 182};
180 183
181struct cgit_context { 184struct cgit_context {
182 struct cgit_query qry; 185 struct cgit_query qry;
183 struct cgit_config cfg; 186 struct cgit_config cfg;
184 struct cgit_repo *repo; 187 struct cgit_repo *repo;
185 struct cgit_page page; 188 struct cgit_page page;
186}; 189};
187 190
188struct cgit_snapshot_format { 191struct cgit_snapshot_format {
189 const char *suffix; 192 const char *suffix;
190 const char *mimetype; 193 const char *mimetype;
191 write_archive_fn_t write_func; 194 write_archive_fn_t write_func;
192 int bit; 195 int bit;
193}; 196};
194 197
195extern const char *cgit_version; 198extern const char *cgit_version;
196 199
197extern struct cgit_repolist cgit_repolist; 200extern struct cgit_repolist cgit_repolist;
198extern struct cgit_context ctx; 201extern struct cgit_context ctx;
199extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 202extern const struct cgit_snapshot_format cgit_snapshot_formats[];
200 203
201extern struct cgit_repo *cgit_add_repo(const char *url); 204extern struct cgit_repo *cgit_add_repo(const char *url);
202extern struct cgit_repo *cgit_get_repoinfo(const char *url); 205extern struct cgit_repo *cgit_get_repoinfo(const char *url);
203extern void cgit_repo_config_cb(const char *name, const char *value); 206extern void cgit_repo_config_cb(const char *name, const char *value);
204 207
205extern int chk_zero(int result, char *msg); 208extern int chk_zero(int result, char *msg);
206extern int chk_positive(int result, char *msg); 209extern int chk_positive(int result, char *msg);
207extern int chk_non_negative(int result, char *msg); 210extern int chk_non_negative(int result, char *msg);
208 211
209extern char *trim_end(const char *str, char c); 212extern char *trim_end(const char *str, char c);
210extern char *strlpart(char *txt, int maxlen); 213extern char *strlpart(char *txt, int maxlen);
211extern char *strrpart(char *txt, int maxlen); 214extern char *strrpart(char *txt, int maxlen);
212 215
213extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 216extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
214extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 217extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
215 int flags, void *cb_data); 218 int flags, void *cb_data);
216 219
217extern void *cgit_free_commitinfo(struct commitinfo *info); 220extern void *cgit_free_commitinfo(struct commitinfo *info);
218 221
219extern int cgit_diff_files(const unsigned char *old_sha1, 222extern int cgit_diff_files(const unsigned char *old_sha1,
220 const unsigned char *new_sha1, 223 const unsigned char *new_sha1,
221 linediff_fn fn); 224 linediff_fn fn);
222 225
223extern void cgit_diff_tree(const unsigned char *old_sha1, 226extern void cgit_diff_tree(const unsigned char *old_sha1,
224 const unsigned char *new_sha1, 227 const unsigned char *new_sha1,
225 filepair_fn fn, const char *prefix); 228 filepair_fn fn, const char *prefix);
226 229
227extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 230extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
228 231
229extern char *fmt(const char *format,...); 232extern char *fmt(const char *format,...);
230 233
231extern struct commitinfo *cgit_parse_commit(struct commit *commit); 234extern struct commitinfo *cgit_parse_commit(struct commit *commit);
232extern struct taginfo *cgit_parse_tag(struct tag *tag); 235extern struct taginfo *cgit_parse_tag(struct tag *tag);
233extern void cgit_parse_url(const char *url); 236extern void cgit_parse_url(const char *url);
234 237
235extern const char *cgit_repobasename(const char *reponame); 238extern const char *cgit_repobasename(const char *reponame);
236 239
237extern int cgit_parse_snapshots_mask(const char *str); 240extern int cgit_parse_snapshots_mask(const char *str);
238 241
239 242
240#endif /* CGIT_H */ 243#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index ab9ab66..09f56a6 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,367 +1,384 @@
1CGITRC 1CGITRC
2====== 2======
3 3
4 4
5NAME 5NAME
6---- 6----
7 cgitrc - runtime configuration for cgit 7 cgitrc - runtime configuration for cgit
8 8
9 9
10DESCRIPTION 10DESCRIPTION
11----------- 11-----------
12Cgitrc contains all runtime settings for cgit, including the list of git 12Cgitrc contains all runtime settings for cgit, including the list of git
13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
14lines, and lines starting with '#', are ignored. 14lines, and lines starting with '#', are ignored.
15 15
16 16
17GLOBAL SETTINGS 17GLOBAL SETTINGS
18--------------- 18---------------
19agefile 19agefile
20 Specifies a path, relative to each repository path, which can be used 20 Specifies a path, relative to each repository path, which can be used
21 to specify the date and time of the youngest commit in the repository. 21 to specify the date and time of the youngest commit in the repository.
22 The first line in the file is used as input to the "parse_date" 22 The first line in the file is used as input to the "parse_date"
23 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 23 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
24 hh:mm:ss". Default value: "info/web/last-modified". 24 hh:mm:ss". Default value: "info/web/last-modified".
25 25
26cache-root 26cache-root
27 Path used to store the cgit cache entries. Default value: 27 Path used to store the cgit cache entries. Default value:
28 "/var/cache/cgit". 28 "/var/cache/cgit".
29 29
30cache-dynamic-ttl 30cache-dynamic-ttl
31 Number which specifies the time-to-live, in minutes, for the cached 31 Number which specifies the time-to-live, in minutes, for the cached
32 version of repository pages accessed without a fixed SHA1. Default 32 version of repository pages accessed without a fixed SHA1. Default
33 value: "5". 33 value: "5".
34 34
35cache-repo-ttl 35cache-repo-ttl
36 Number which specifies the time-to-live, in minutes, for the cached 36 Number which specifies the time-to-live, in minutes, for the cached
37 version of the repository summary page. Default value: "5". 37 version of the repository summary page. Default value: "5".
38 38
39cache-root-ttl 39cache-root-ttl
40 Number which specifies the time-to-live, in minutes, for the cached 40 Number which specifies the time-to-live, in minutes, for the cached
41 version of the repository index page. Default value: "5". 41 version of the repository index page. Default value: "5".
42 42
43cache-size 43cache-size
44 The maximum number of entries in the cgit cache. Default value: "0" 44 The maximum number of entries in the cgit cache. Default value: "0"
45 (i.e. caching is disabled). 45 (i.e. caching is disabled).
46 46
47cache-static-ttl 47cache-static-ttl
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed with a fixed SHA1. Default value: 49 version of repository pages accessed with a fixed SHA1. Default value:
50 "5". 50 "5".
51 51
52clone-prefix 52clone-prefix
53 Space-separated list of common prefixes which, when combined with a 53 Space-separated list of common prefixes which, when combined with a
54 repository url, generates valid clone urls for the repository. This 54 repository url, generates valid clone urls for the repository. This
55 setting is only used if `repo.clone-url` is unspecified. Default value: 55 setting is only used if `repo.clone-url` is unspecified. Default value:
56 none. 56 none.
57 57
58css 58css
59 Url which specifies the css document to include in all cgit pages. 59 Url which specifies the css document to include in all cgit pages.
60 Default value: "/cgit.css". 60 Default value: "/cgit.css".
61 61
62enable-index-links 62enable-index-links
63 Flag which, when set to "1", will make cgit generate extra links for 63 Flag which, when set to "1", will make cgit generate extra links for
64 each repo in the repository index (specifically, to the "summary", 64 each repo in the repository index (specifically, to the "summary",
65 "commit" and "tree" pages). Default value: "0". 65 "commit" and "tree" pages). Default value: "0".
66 66
67enable-log-filecount 67enable-log-filecount
68 Flag which, when set to "1", will make cgit print the number of 68 Flag which, when set to "1", will make cgit print the number of
69 modified files for each commit on the repository log page. Default 69 modified files for each commit on the repository log page. Default
70 value: "0". 70 value: "0".
71 71
72enable-log-linecount 72enable-log-linecount
73 Flag which, when set to "1", will make cgit print the number of added 73 Flag which, when set to "1", will make cgit print the number of added
74 and removed lines for each commit on the repository log page. Default 74 and removed lines for each commit on the repository log page. Default
75 value: "0". 75 value: "0".
76 76
77favicon 77favicon
78 Url used as link to a shortcut icon for cgit. If specified, it is 78 Url used as link to a shortcut icon for cgit. If specified, it is
79 suggested to use the value "/favicon.ico" since certain browsers will 79 suggested to use the value "/favicon.ico" since certain browsers will
80 ignore other values. Default value: none. 80 ignore other values. Default value: none.
81 81
82footer 82footer
83 The content of the file specified with this option will be included 83 The content of the file specified with this option will be included
84 verbatim at the bottom of all pages (i.e. it replaces the standard 84 verbatim at the bottom of all pages (i.e. it replaces the standard
85 "generated by..." message. Default value: none. 85 "generated by..." message. Default value: none.
86 86
87include 87include
88 Name of a configfile to include before the rest of the current config- 88 Name of a configfile to include before the rest of the current config-
89 file is parsed. Default value: none. 89 file is parsed. Default value: none.
90 90
91index-header 91index-header
92 The content of the file specified with this option will be included 92 The content of the file specified with this option will be included
93 verbatim above the repository index. This setting is deprecated, and 93 verbatim above the repository index. This setting is deprecated, and
94 will not be supported by cgit-1.0 (use root-readme instead). Default 94 will not be supported by cgit-1.0 (use root-readme instead). Default
95 value: none. 95 value: none.
96 96
97index-info 97index-info
98 The content of the file specified with this option will be included 98 The content of the file specified with this option will be included
99 verbatim below the heading on the repository index page. This setting 99 verbatim below the heading on the repository index page. This setting
100 is deprecated, and will not be supported by cgit-1.0 (use root-desc 100 is deprecated, and will not be supported by cgit-1.0 (use root-desc
101 instead). Default value: none. 101 instead). Default value: none.
102 102
103local-time 103local-time
104 Flag which, if set to "1", makes cgit print commit and tag times in the 104 Flag which, if set to "1", makes cgit print commit and tag times in the
105 servers timezone. Default value: "0". 105 servers timezone. Default value: "0".
106 106
107logo 107logo
108 Url which specifies the source of an image which will be used as a logo 108 Url which specifies the source of an image which will be used as a logo
109 on all cgit pages. 109 on all cgit pages.
110 110
111logo-link 111logo-link
112 Url loaded when clicking on the cgit logo image. If unspecified the 112 Url loaded when clicking on the cgit logo image. If unspecified the
113 calculated url of the repository index page will be used. Default 113 calculated url of the repository index page will be used. Default
114 value: none. 114 value: none.
115 115
116max-commit-count 116max-commit-count
117 Specifies the number of entries to list per page in "log" view. Default 117 Specifies the number of entries to list per page in "log" view. Default
118 value: "50". 118 value: "50".
119 119
120max-message-length 120max-message-length
121 Specifies the maximum number of commit message characters to display in 121 Specifies the maximum number of commit message characters to display in
122 "log" view. Default value: "80". 122 "log" view. Default value: "80".
123 123
124max-repo-count 124max-repo-count
125 Specifies the number of entries to list per page on therepository 125 Specifies the number of entries to list per page on therepository
126 index page. Default value: "50". 126 index page. Default value: "50".
127 127
128max-repodesc-length 128max-repodesc-length
129 Specifies the maximum number of repo description characters to display 129 Specifies the maximum number of repo description characters to display
130 on the repository index page. Default value: "80". 130 on the repository index page. Default value: "80".
131 131
132max-stats
133 Set the default maximum statistics period. Valid values are "week",
134 "month", "quarter" and "year". If unspecified, statistics are
135 disabled. Default value: none. See also: "repo.max-stats".
136
132module-link 137module-link
133 Text which will be used as the formatstring for a hyperlink when a 138 Text which will be used as the formatstring for a hyperlink when a
134 submodule is printed in a directory listing. The arguments for the 139 submodule is printed in a directory listing. The arguments for the
135 formatstring are the path and SHA1 of the submodule commit. Default 140 formatstring are the path and SHA1 of the submodule commit. Default
136 value: "./?repo=%s&page=commit&id=%s" 141 value: "./?repo=%s&page=commit&id=%s"
137 142
138nocache 143nocache
139 If set to the value "1" caching will be disabled. This settings is 144 If set to the value "1" caching will be disabled. This settings is
140 deprecated, and will not be honored starting with cgit-1.0. Default 145 deprecated, and will not be honored starting with cgit-1.0. Default
141 value: "0". 146 value: "0".
142 147
143renamelimit 148renamelimit
144 Maximum number of files to consider when detecting renames. The value 149 Maximum number of files to consider when detecting renames. The value
145 "-1" uses the compiletime value in git (for further info, look at 150 "-1" uses the compiletime value in git (for further info, look at
146 `man git-diff`). Default value: "-1". 151 `man git-diff`). Default value: "-1".
147 152
148repo.group 153repo.group
149 A value for the current repository group, which all repositories 154 A value for the current repository group, which all repositories
150 specified after this setting will inherit. Default value: none. 155 specified after this setting will inherit. Default value: none.
151 156
152robots 157robots
153 Text used as content for the "robots" meta-tag. Default value: 158 Text used as content for the "robots" meta-tag. Default value:
154 "index, nofollow". 159 "index, nofollow".
155 160
156root-desc 161root-desc
157 Text printed below the heading on the repository index page. Default 162 Text printed below the heading on the repository index page. Default
158 value: "a fast webinterface for the git dscm". 163 value: "a fast webinterface for the git dscm".
159 164
160root-readme: 165root-readme:
161 The content of the file specified with this option will be included 166 The content of the file specified with this option will be included
162 verbatim below the "about" link on the repository index page. Default 167 verbatim below the "about" link on the repository index page. Default
163 value: none. 168 value: none.
164 169
165root-title 170root-title
166 Text printed as heading on the repository index page. Default value: 171 Text printed as heading on the repository index page. Default value:
167 "Git Repository Browser". 172 "Git Repository Browser".
168 173
169snapshots 174snapshots
170 Text which specifies the default (and allowed) set of snapshot formats 175 Text which specifies the default (and allowed) set of snapshot formats
171 supported by cgit. The value is a space-separated list of zero or more 176 supported by cgit. The value is a space-separated list of zero or more
172 of the following values: 177 of the following values:
173 "tar" uncompressed tar-file 178 "tar" uncompressed tar-file
174 "tar.gz"gzip-compressed tar-file 179 "tar.gz"gzip-compressed tar-file
175 "tar.bz2"bzip-compressed tar-file 180 "tar.bz2"bzip-compressed tar-file
176 "zip" zip-file 181 "zip" zip-file
177 Default value: none. 182 Default value: none.
178 183
179summary-branches 184summary-branches
180 Specifies the number of branches to display in the repository "summary" 185 Specifies the number of branches to display in the repository "summary"
181 view. Default value: "10". 186 view. Default value: "10".
182 187
183summary-log 188summary-log
184 Specifies the number of log entries to display in the repository 189 Specifies the number of log entries to display in the repository
185 "summary" view. Default value: "10". 190 "summary" view. Default value: "10".
186 191
187summary-tags 192summary-tags
188 Specifies the number of tags to display in the repository "summary" 193 Specifies the number of tags to display in the repository "summary"
189 view. Default value: "10". 194 view. Default value: "10".
190 195
191virtual-root 196virtual-root
192 Url which, if specified, will be used as root for all cgit links. It 197 Url which, if specified, will be used as root for all cgit links. It
193 will also cause cgit to generate 'virtual urls', i.e. urls like 198 will also cause cgit to generate 'virtual urls', i.e. urls like
194 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 199 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
195 value: none. 200 value: none.
196 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 201 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
197 same kind of virtual urls, so this option will probably be deprecated. 202 same kind of virtual urls, so this option will probably be deprecated.
198 203
199REPOSITORY SETTINGS 204REPOSITORY SETTINGS
200------------------- 205-------------------
201repo.clone-url 206repo.clone-url
202 A list of space-separated urls which can be used to clone this repo. 207 A list of space-separated urls which can be used to clone this repo.
203 Default value: none. 208 Default value: none.
204 209
205repo.defbranch 210repo.defbranch
206 The name of the default branch for this repository. If no such branch 211 The name of the default branch for this repository. If no such branch
207 exists in the repository, the first branch name (when sorted) is used 212 exists in the repository, the first branch name (when sorted) is used
208 as default instead. Default value: "master". 213 as default instead. Default value: "master".
209 214
210repo.desc 215repo.desc
211 The value to show as repository description. Default value: none. 216 The value to show as repository description. Default value: none.
212 217
213repo.enable-log-filecount 218repo.enable-log-filecount
214 A flag which can be used to disable the global setting 219 A flag which can be used to disable the global setting
215 `enable-log-filecount'. Default value: none. 220 `enable-log-filecount'. Default value: none.
216 221
217repo.enable-log-linecount 222repo.enable-log-linecount
218 A flag which can be used to disable the global setting 223 A flag which can be used to disable the global setting
219 `enable-log-linecount'. Default value: none. 224 `enable-log-linecount'. Default value: none.
220 225
226repo.max-stats
227 Override the default maximum statistics period. Valid values are equal
228 to the values specified for the global "max-stats" setting. Default
229 value: none.
230
221repo.name 231repo.name
222 The value to show as repository name. Default value: <repo.url>. 232 The value to show as repository name. Default value: <repo.url>.
223 233
224repo.owner 234repo.owner
225 A value used to identify the owner of the repository. Default value: 235 A value used to identify the owner of the repository. Default value:
226 none. 236 none.
227 237
228repo.path 238repo.path
229 An absolute path to the repository directory. For non-bare repositories 239 An absolute path to the repository directory. For non-bare repositories
230 this is the .git-directory. Default value: none. 240 this is the .git-directory. Default value: none.
231 241
232repo.readme 242repo.readme
233 A path (relative to <repo.path>) which specifies a file to include 243 A path (relative to <repo.path>) which specifies a file to include
234 verbatim as the "About" page for this repo. Default value: none. 244 verbatim as the "About" page for this repo. Default value: none.
235 245
236repo.snapshots 246repo.snapshots
237 A mask of allowed snapshot-formats for this repo, restricted by the 247 A mask of allowed snapshot-formats for this repo, restricted by the
238 "snapshots" global setting. Default value: <snapshots>. 248 "snapshots" global setting. Default value: <snapshots>.
239 249
240repo.url 250repo.url
241 The relative url used to access the repository. This must be the first 251 The relative url used to access the repository. This must be the first
242 setting specified for each repo. Default value: none. 252 setting specified for each repo. Default value: none.
243 253
244 254
245EXAMPLE CGITRC FILE 255EXAMPLE CGITRC FILE
246------------------- 256-------------------
247 257
248# Enable caching of up to 1000 output entriess 258# Enable caching of up to 1000 output entriess
249cache-size=1000 259cache-size=1000
250 260
251 261
252# Specify some default clone prefixes 262# Specify some default clone prefixes
253clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 263clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
254 264
255# Specify the css url 265# Specify the css url
256css=/css/cgit.css 266css=/css/cgit.css
257 267
258 268
259# Show extra links for each repository on the index page 269# Show extra links for each repository on the index page
260enable-index-links=1 270enable-index-links=1
261 271
262 272
263# Show number of affected files per commit on the log pages 273# Show number of affected files per commit on the log pages
264enable-log-filecount=1 274enable-log-filecount=1
265 275
266 276
267# Show number of added/removed lines per commit on the log pages 277# Show number of added/removed lines per commit on the log pages
268enable-log-linecount=1 278enable-log-linecount=1
269 279
270 280
271# Add a cgit favicon 281# Add a cgit favicon
272favicon=/favicon.ico 282favicon=/favicon.ico
273 283
274 284
275# Use a custom logo 285# Use a custom logo
276logo=/img/mylogo.png 286logo=/img/mylogo.png
277 287
278 288
289# Enable statistics per week, month and quarter
290max-stats=quarter
291
292
279# Set the title and heading of the repository index page 293# Set the title and heading of the repository index page
280root-title=foobar.com git repositories 294root-title=foobar.com git repositories
281 295
282 296
283# Set a subheading for the repository index page 297# Set a subheading for the repository index page
284root-desc=tracking the foobar development 298root-desc=tracking the foobar development
285 299
286 300
287# Include some more info about foobar.com on the index page 301# Include some more info about foobar.com on the index page
288root-readme=/var/www/htdocs/about.html 302root-readme=/var/www/htdocs/about.html
289 303
290 304
291# Allow download of tar.gz, tar.bz2 and zip-files 305# Allow download of tar.gz, tar.bz2 and zip-files
292snapshots=tar.gz tar.bz2 zip 306snapshots=tar.gz tar.bz2 zip
293 307
294 308
295## 309##
296## List of repositories. 310## List of repositories.
297## PS: Any repositories listed when repo.group is unset will not be 311## PS: Any repositories listed when repo.group is unset will not be
298## displayed under a group heading 312## displayed under a group heading
299## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 313## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
300## and included like this: 314## and included like this:
301## include=/etc/cgitrepos 315## include=/etc/cgitrepos
302## 316##
303 317
304 318
305repo.url=foo 319repo.url=foo
306repo.path=/pub/git/foo.git 320repo.path=/pub/git/foo.git
307repo.desc=the master foo repository 321repo.desc=the master foo repository
308repo.owner=fooman@foobar.com 322repo.owner=fooman@foobar.com
309repo.readme=info/web/about.html 323repo.readme=info/web/about.html
310 324
311 325
312repo.url=bar 326repo.url=bar
313repo.path=/pub/git/bar.git 327repo.path=/pub/git/bar.git
314repo.desc=the bars for your foo 328repo.desc=the bars for your foo
315repo.owner=barman@foobar.com 329repo.owner=barman@foobar.com
316repo.readme=info/web/about.html 330repo.readme=info/web/about.html
317 331
318 332
319# The next repositories will be displayed under the 'extras' heading 333# The next repositories will be displayed under the 'extras' heading
320repo.group=extras 334repo.group=extras
321 335
322 336
323repo.url=baz 337repo.url=baz
324repo.path=/pub/git/baz.git 338repo.path=/pub/git/baz.git
325repo.desc=a set of extensions for bar users 339repo.desc=a set of extensions for bar users
326 340
327repo.url=wiz 341repo.url=wiz
328repo.path=/pub/git/wiz.git 342repo.path=/pub/git/wiz.git
329repo.desc=the wizard of foo 343repo.desc=the wizard of foo
330 344
331 345
332# Add some mirrored repositories 346# Add some mirrored repositories
333repo.group=mirrors 347repo.group=mirrors
334 348
335 349
336repo.url=git 350repo.url=git
337repo.path=/pub/git/git.git 351repo.path=/pub/git/git.git
338repo.desc=the dscm 352repo.desc=the dscm
339 353
340 354
341repo.url=linux 355repo.url=linux
342repo.path=/pub/git/linux.git 356repo.path=/pub/git/linux.git
343repo.desc=the kernel 357repo.desc=the kernel
344 358
345# Disable adhoc downloads of this repo 359# Disable adhoc downloads of this repo
346repo.snapshots=0 360repo.snapshots=0
347 361
348# Disable line-counts for this repo 362# Disable line-counts for this repo
349repo.enable-log-linecount=0 363repo.enable-log-linecount=0
350 364
365# Restrict the max statistics period for this repo
366repo.max-stats=month
367
351 368
352BUGS 369BUGS
353---- 370----
354Comments currently cannot appear on the same line as a setting; the comment 371Comments currently cannot appear on the same line as a setting; the comment
355will be included as part of the value. E.g. this line: 372will be included as part of the value. E.g. this line:
356 373
357 robots=index # allow indexing 374 robots=index # allow indexing
358 375
359will generate the following html element: 376will generate the following html element:
360 377
361 <meta name='robots' content='index # allow indexing'/> 378 <meta name='robots' content='index # allow indexing'/>
362 379
363 380
364 381
365AUTHOR 382AUTHOR
366------ 383------
367Lars Hjemli <hjemli@gmail.com> 384Lars Hjemli <hjemli@gmail.com>
diff --git a/cmd.c b/cmd.c
index 8914fa5..cf97da7 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,164 +1,171 @@
1/* cmd.c: the cgit command dispatcher 1/* cmd.c: the cgit command dispatcher
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 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 "cmd.h" 10#include "cmd.h"
11#include "cache.h" 11#include "cache.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13#include "ui-atom.h" 13#include "ui-atom.h"
14#include "ui-blob.h" 14#include "ui-blob.h"
15#include "ui-clone.h" 15#include "ui-clone.h"
16#include "ui-commit.h" 16#include "ui-commit.h"
17#include "ui-diff.h" 17#include "ui-diff.h"
18#include "ui-log.h" 18#include "ui-log.h"
19#include "ui-patch.h" 19#include "ui-patch.h"
20#include "ui-plain.h" 20#include "ui-plain.h"
21#include "ui-refs.h" 21#include "ui-refs.h"
22#include "ui-repolist.h" 22#include "ui-repolist.h"
23#include "ui-snapshot.h" 23#include "ui-snapshot.h"
24#include "ui-stats.h"
24#include "ui-summary.h" 25#include "ui-summary.h"
25#include "ui-tag.h" 26#include "ui-tag.h"
26#include "ui-tree.h" 27#include "ui-tree.h"
27 28
28static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
29{ 30{
30 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
31} 32}
32 33
33static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
34{ 35{
35 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10);
36} 37}
37 38
38static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
39{ 40{
40 if (ctx->repo) 41 if (ctx->repo)
41 cgit_print_repo_readme(); 42 cgit_print_repo_readme();
42 else 43 else
43 cgit_print_site_readme(); 44 cgit_print_site_readme();
44} 45}
45 46
46static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
47{ 48{
48 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
49} 50}
50 51
51static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
52{ 53{
53 cgit_print_commit(ctx->qry.sha1); 54 cgit_print_commit(ctx->qry.sha1);
54} 55}
55 56
56static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
57{ 58{
58 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
59} 60}
60 61
61static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
62{ 63{
63 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
64} 65}
65 66
66static void log_fn(struct cgit_context *ctx) 67static void log_fn(struct cgit_context *ctx)
67{ 68{
68 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, 69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
69 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1);
70} 71}
71 72
72static void ls_cache_fn(struct cgit_context *ctx) 73static void ls_cache_fn(struct cgit_context *ctx)
73{ 74{
74 ctx->page.mimetype = "text/plain"; 75 ctx->page.mimetype = "text/plain";
75 ctx->page.filename = "ls-cache.txt"; 76 ctx->page.filename = "ls-cache.txt";
76 cgit_print_http_headers(ctx); 77 cgit_print_http_headers(ctx);
77 cache_ls(ctx->cfg.cache_root); 78 cache_ls(ctx->cfg.cache_root);
78} 79}
79 80
80static void objects_fn(struct cgit_context *ctx) 81static void objects_fn(struct cgit_context *ctx)
81{ 82{
82 cgit_clone_objects(ctx); 83 cgit_clone_objects(ctx);
83} 84}
84 85
85static void repolist_fn(struct cgit_context *ctx) 86static void repolist_fn(struct cgit_context *ctx)
86{ 87{
87 cgit_print_repolist(); 88 cgit_print_repolist();
88} 89}
89 90
90static void patch_fn(struct cgit_context *ctx) 91static void patch_fn(struct cgit_context *ctx)
91{ 92{
92 cgit_print_patch(ctx->qry.sha1); 93 cgit_print_patch(ctx->qry.sha1);
93} 94}
94 95
95static void plain_fn(struct cgit_context *ctx) 96static void plain_fn(struct cgit_context *ctx)
96{ 97{
97 cgit_print_plain(ctx); 98 cgit_print_plain(ctx);
98} 99}
99 100
100static void refs_fn(struct cgit_context *ctx) 101static void refs_fn(struct cgit_context *ctx)
101{ 102{
102 cgit_print_refs(); 103 cgit_print_refs();
103} 104}
104 105
105static void snapshot_fn(struct cgit_context *ctx) 106static void snapshot_fn(struct cgit_context *ctx)
106{ 107{
107 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, 108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
108 ctx->repo->snapshots, ctx->qry.nohead); 109 ctx->repo->snapshots, ctx->qry.nohead);
109} 110}
110 111
112static void stats_fn(struct cgit_context *ctx)
113{
114 cgit_show_stats(ctx);
115}
116
111static void summary_fn(struct cgit_context *ctx) 117static void summary_fn(struct cgit_context *ctx)
112{ 118{
113 cgit_print_summary(); 119 cgit_print_summary();
114} 120}
115 121
116static void tag_fn(struct cgit_context *ctx) 122static void tag_fn(struct cgit_context *ctx)
117{ 123{
118 cgit_print_tag(ctx->qry.sha1); 124 cgit_print_tag(ctx->qry.sha1);
119} 125}
120 126
121static void tree_fn(struct cgit_context *ctx) 127static void tree_fn(struct cgit_context *ctx)
122{ 128{
123 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
124} 130}
125 131
126#define def_cmd(name, want_repo, want_layout) \ 132#define def_cmd(name, want_repo, want_layout) \
127 {#name, name##_fn, want_repo, want_layout} 133 {#name, name##_fn, want_repo, want_layout}
128 134
129struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
130{ 136{
131 static struct cgit_cmd cmds[] = { 137 static struct cgit_cmd cmds[] = {
132 def_cmd(HEAD, 1, 0), 138 def_cmd(HEAD, 1, 0),
133 def_cmd(atom, 1, 0), 139 def_cmd(atom, 1, 0),
134 def_cmd(about, 0, 1), 140 def_cmd(about, 0, 1),
135 def_cmd(blob, 1, 0), 141 def_cmd(blob, 1, 0),
136 def_cmd(commit, 1, 1), 142 def_cmd(commit, 1, 1),
137 def_cmd(diff, 1, 1), 143 def_cmd(diff, 1, 1),
138 def_cmd(info, 1, 0), 144 def_cmd(info, 1, 0),
139 def_cmd(log, 1, 1), 145 def_cmd(log, 1, 1),
140 def_cmd(ls_cache, 0, 0), 146 def_cmd(ls_cache, 0, 0),
141 def_cmd(objects, 1, 0), 147 def_cmd(objects, 1, 0),
142 def_cmd(patch, 1, 0), 148 def_cmd(patch, 1, 0),
143 def_cmd(plain, 1, 0), 149 def_cmd(plain, 1, 0),
144 def_cmd(refs, 1, 1), 150 def_cmd(refs, 1, 1),
145 def_cmd(repolist, 0, 0), 151 def_cmd(repolist, 0, 0),
146 def_cmd(snapshot, 1, 0), 152 def_cmd(snapshot, 1, 0),
153 def_cmd(stats, 1, 1),
147 def_cmd(summary, 1, 1), 154 def_cmd(summary, 1, 1),
148 def_cmd(tag, 1, 1), 155 def_cmd(tag, 1, 1),
149 def_cmd(tree, 1, 1), 156 def_cmd(tree, 1, 1),
150 }; 157 };
151 int i; 158 int i;
152 159
153 if (ctx->qry.page == NULL) { 160 if (ctx->qry.page == NULL) {
154 if (ctx->repo) 161 if (ctx->repo)
155 ctx->qry.page = "summary"; 162 ctx->qry.page = "summary";
156 else 163 else
157 ctx->qry.page = "repolist"; 164 ctx->qry.page = "repolist";
158 } 165 }
159 166
160 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
161 if (!strcmp(ctx->qry.page, cmds[i].name)) 168 if (!strcmp(ctx->qry.page, cmds[i].name))
162 return &cmds[i]; 169 return &cmds[i];
163 return NULL; 170 return NULL;
164} 171}
diff --git a/shared.c b/shared.c
index a764c4d..578a544 100644
--- a/shared.c
+++ b/shared.c
@@ -1,252 +1,253 @@
1/* shared.c: global vars + some callback functions 1/* shared.c: global vars + some callback functions
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 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd; 13int cgit_cmd;
14 14
15int chk_zero(int result, char *msg) 15int chk_zero(int result, char *msg)
16{ 16{
17 if (result != 0) 17 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 18 die("%s: %s", msg, strerror(errno));
19 return result; 19 return result;
20} 20}
21 21
22int chk_positive(int result, char *msg) 22int chk_positive(int result, char *msg)
23{ 23{
24 if (result <= 0) 24 if (result <= 0)
25 die("%s: %s", msg, strerror(errno)); 25 die("%s: %s", msg, strerror(errno));
26 return result; 26 return result;
27} 27}
28 28
29int chk_non_negative(int result, char *msg) 29int chk_non_negative(int result, char *msg)
30{ 30{
31 if (result < 0) 31 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 32 die("%s: %s",msg, strerror(errno));
33 return result; 33 return result;
34} 34}
35 35
36struct cgit_repo *cgit_add_repo(const char *url) 36struct cgit_repo *cgit_add_repo(const char *url)
37{ 37{
38 struct cgit_repo *ret; 38 struct cgit_repo *ret;
39 39
40 if (++cgit_repolist.count > cgit_repolist.length) { 40 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 41 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 42 cgit_repolist.length = 8;
43 else 43 else
44 cgit_repolist.length *= 2; 44 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 45 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 46 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 47 sizeof(struct cgit_repo));
48 } 48 }
49 49
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 52 ret->name = ret->url;
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->group = ctx.cfg.repo_group; 56 ret->group = ctx.cfg.repo_group;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->max_stats = ctx.cfg.max_stats;
61 ret->module_link = ctx.cfg.module_link; 62 ret->module_link = ctx.cfg.module_link;
62 ret->readme = NULL; 63 ret->readme = NULL;
63 ret->mtime = -1; 64 ret->mtime = -1;
64 return ret; 65 return ret;
65} 66}
66 67
67struct cgit_repo *cgit_get_repoinfo(const char *url) 68struct cgit_repo *cgit_get_repoinfo(const char *url)
68{ 69{
69 int i; 70 int i;
70 struct cgit_repo *repo; 71 struct cgit_repo *repo;
71 72
72 for (i=0; i<cgit_repolist.count; i++) { 73 for (i=0; i<cgit_repolist.count; i++) {
73 repo = &cgit_repolist.repos[i]; 74 repo = &cgit_repolist.repos[i];
74 if (!strcmp(repo->url, url)) 75 if (!strcmp(repo->url, url))
75 return repo; 76 return repo;
76 } 77 }
77 return NULL; 78 return NULL;
78} 79}
79 80
80void *cgit_free_commitinfo(struct commitinfo *info) 81void *cgit_free_commitinfo(struct commitinfo *info)
81{ 82{
82 free(info->author); 83 free(info->author);
83 free(info->author_email); 84 free(info->author_email);
84 free(info->committer); 85 free(info->committer);
85 free(info->committer_email); 86 free(info->committer_email);
86 free(info->subject); 87 free(info->subject);
87 free(info->msg); 88 free(info->msg);
88 free(info->msg_encoding); 89 free(info->msg_encoding);
89 free(info); 90 free(info);
90 return NULL; 91 return NULL;
91} 92}
92 93
93char *trim_end(const char *str, char c) 94char *trim_end(const char *str, char c)
94{ 95{
95 int len; 96 int len;
96 char *s, *t; 97 char *s, *t;
97 98
98 if (str == NULL) 99 if (str == NULL)
99 return NULL; 100 return NULL;
100 t = (char *)str; 101 t = (char *)str;
101 len = strlen(t); 102 len = strlen(t);
102 while(len > 0 && t[len - 1] == c) 103 while(len > 0 && t[len - 1] == c)
103 len--; 104 len--;
104 105
105 if (len == 0) 106 if (len == 0)
106 return NULL; 107 return NULL;
107 108
108 c = t[len]; 109 c = t[len];
109 t[len] = '\0'; 110 t[len] = '\0';
110 s = xstrdup(t); 111 s = xstrdup(t);
111 t[len] = c; 112 t[len] = c;
112 return s; 113 return s;
113} 114}
114 115
115char *strlpart(char *txt, int maxlen) 116char *strlpart(char *txt, int maxlen)
116{ 117{
117 char *result; 118 char *result;
118 119
119 if (!txt) 120 if (!txt)
120 return txt; 121 return txt;
121 122
122 if (strlen(txt) <= maxlen) 123 if (strlen(txt) <= maxlen)
123 return txt; 124 return txt;
124 result = xmalloc(maxlen + 1); 125 result = xmalloc(maxlen + 1);
125 memcpy(result, txt, maxlen - 3); 126 memcpy(result, txt, maxlen - 3);
126 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 127 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
127 result[maxlen] = '\0'; 128 result[maxlen] = '\0';
128 return result; 129 return result;
129} 130}
130 131
131char *strrpart(char *txt, int maxlen) 132char *strrpart(char *txt, int maxlen)
132{ 133{
133 char *result; 134 char *result;
134 135
135 if (!txt) 136 if (!txt)
136 return txt; 137 return txt;
137 138
138 if (strlen(txt) <= maxlen) 139 if (strlen(txt) <= maxlen)
139 return txt; 140 return txt;
140 result = xmalloc(maxlen + 1); 141 result = xmalloc(maxlen + 1);
141 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); 142 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
142 result[0] = result[1] = result[2] = '.'; 143 result[0] = result[1] = result[2] = '.';
143 return result; 144 return result;
144} 145}
145 146
146void cgit_add_ref(struct reflist *list, struct refinfo *ref) 147void cgit_add_ref(struct reflist *list, struct refinfo *ref)
147{ 148{
148 size_t size; 149 size_t size;
149 150
150 if (list->count >= list->alloc) { 151 if (list->count >= list->alloc) {
151 list->alloc += (list->alloc ? list->alloc : 4); 152 list->alloc += (list->alloc ? list->alloc : 4);
152 size = list->alloc * sizeof(struct refinfo *); 153 size = list->alloc * sizeof(struct refinfo *);
153 list->refs = xrealloc(list->refs, size); 154 list->refs = xrealloc(list->refs, size);
154 } 155 }
155 list->refs[list->count++] = ref; 156 list->refs[list->count++] = ref;
156} 157}
157 158
158struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) 159struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
159{ 160{
160 struct refinfo *ref; 161 struct refinfo *ref;
161 162
162 ref = xmalloc(sizeof (struct refinfo)); 163 ref = xmalloc(sizeof (struct refinfo));
163 ref->refname = xstrdup(refname); 164 ref->refname = xstrdup(refname);
164 ref->object = parse_object(sha1); 165 ref->object = parse_object(sha1);
165 switch (ref->object->type) { 166 switch (ref->object->type) {
166 case OBJ_TAG: 167 case OBJ_TAG:
167 ref->tag = cgit_parse_tag((struct tag *)ref->object); 168 ref->tag = cgit_parse_tag((struct tag *)ref->object);
168 break; 169 break;
169 case OBJ_COMMIT: 170 case OBJ_COMMIT:
170 ref->commit = cgit_parse_commit((struct commit *)ref->object); 171 ref->commit = cgit_parse_commit((struct commit *)ref->object);
171 break; 172 break;
172 } 173 }
173 return ref; 174 return ref;
174} 175}
175 176
176int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, 177int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
177 void *cb_data) 178 void *cb_data)
178{ 179{
179 struct reflist *list = (struct reflist *)cb_data; 180 struct reflist *list = (struct reflist *)cb_data;
180 struct refinfo *info = cgit_mk_refinfo(refname, sha1); 181 struct refinfo *info = cgit_mk_refinfo(refname, sha1);
181 182
182 if (info) 183 if (info)
183 cgit_add_ref(list, info); 184 cgit_add_ref(list, info);
184 return 0; 185 return 0;
185} 186}
186 187
187void cgit_diff_tree_cb(struct diff_queue_struct *q, 188void cgit_diff_tree_cb(struct diff_queue_struct *q,
188 struct diff_options *options, void *data) 189 struct diff_options *options, void *data)
189{ 190{
190 int i; 191 int i;
191 192
192 for (i = 0; i < q->nr; i++) { 193 for (i = 0; i < q->nr; i++) {
193 if (q->queue[i]->status == 'U') 194 if (q->queue[i]->status == 'U')
194 continue; 195 continue;
195 ((filepair_fn)data)(q->queue[i]); 196 ((filepair_fn)data)(q->queue[i]);
196 } 197 }
197} 198}
198 199
199static int load_mmfile(mmfile_t *file, const unsigned char *sha1) 200static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
200{ 201{
201 enum object_type type; 202 enum object_type type;
202 203
203 if (is_null_sha1(sha1)) { 204 if (is_null_sha1(sha1)) {
204 file->ptr = (char *)""; 205 file->ptr = (char *)"";
205 file->size = 0; 206 file->size = 0;
206 } else { 207 } else {
207 file->ptr = read_sha1_file(sha1, &type, 208 file->ptr = read_sha1_file(sha1, &type,
208 (unsigned long *)&file->size); 209 (unsigned long *)&file->size);
209 } 210 }
210 return 1; 211 return 1;
211} 212}
212 213
213/* 214/*
214 * Receive diff-buffers from xdiff and concatenate them as 215 * Receive diff-buffers from xdiff and concatenate them as
215 * needed across multiple callbacks. 216 * needed across multiple callbacks.
216 * 217 *
217 * This is basically a copy of xdiff-interface.c/xdiff_outf(), 218 * This is basically a copy of xdiff-interface.c/xdiff_outf(),
218 * ripped from git and modified to use globals instead of 219 * ripped from git and modified to use globals instead of
219 * a special callback-struct. 220 * a special callback-struct.
220 */ 221 */
221char *diffbuf = NULL; 222char *diffbuf = NULL;
222int buflen = 0; 223int buflen = 0;
223 224
224int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) 225int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
225{ 226{
226 int i; 227 int i;
227 228
228 for (i = 0; i < nbuf; i++) { 229 for (i = 0; i < nbuf; i++) {
229 if (mb[i].ptr[mb[i].size-1] != '\n') { 230 if (mb[i].ptr[mb[i].size-1] != '\n') {
230 /* Incomplete line */ 231 /* Incomplete line */
231 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 232 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
232 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 233 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
233 buflen += mb[i].size; 234 buflen += mb[i].size;
234 continue; 235 continue;
235 } 236 }
236 237
237 /* we have a complete line */ 238 /* we have a complete line */
238 if (!diffbuf) { 239 if (!diffbuf) {
239 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 240 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
240 continue; 241 continue;
241 } 242 }
242 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 243 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
243 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 244 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
244 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 245 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
245 free(diffbuf); 246 free(diffbuf);
246 diffbuf = NULL; 247 diffbuf = NULL;
247 buflen = 0; 248 buflen = 0;
248 } 249 }
249 if (diffbuf) { 250 if (diffbuf) {
250 ((linediff_fn)priv)(diffbuf, buflen); 251 ((linediff_fn)priv)(diffbuf, buflen);
251 free(diffbuf); 252 free(diffbuf);
252 diffbuf = NULL; 253 diffbuf = NULL;
diff --git a/ui-shared.c b/ui-shared.c
index fba1ba6..4f28512 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -180,549 +180,558 @@ static void site_link(char *page, char *name, char *title, char *class,
180 html("'>"); 180 html("'>");
181 html_txt(name); 181 html_txt(name);
182 html("</a>"); 182 html("</a>");
183} 183}
184 184
185void cgit_index_link(char *name, char *title, char *class, char *pattern, 185void cgit_index_link(char *name, char *title, char *class, char *pattern,
186 int ofs) 186 int ofs)
187{ 187{
188 site_link(NULL, name, title, class, pattern, ofs); 188 site_link(NULL, name, title, class, pattern, ofs);
189} 189}
190 190
191static char *repolink(char *title, char *class, char *page, char *head, 191static char *repolink(char *title, char *class, char *page, char *head,
192 char *path) 192 char *path)
193{ 193{
194 char *delim = "?"; 194 char *delim = "?";
195 195
196 html("<a"); 196 html("<a");
197 if (title) { 197 if (title) {
198 html(" title='"); 198 html(" title='");
199 html_attr(title); 199 html_attr(title);
200 html("'"); 200 html("'");
201 } 201 }
202 if (class) { 202 if (class) {
203 html(" class='"); 203 html(" class='");
204 html_attr(class); 204 html_attr(class);
205 html("'"); 205 html("'");
206 } 206 }
207 html(" href='"); 207 html(" href='");
208 if (ctx.cfg.virtual_root) { 208 if (ctx.cfg.virtual_root) {
209 html_url_path(ctx.cfg.virtual_root); 209 html_url_path(ctx.cfg.virtual_root);
210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
211 html("/"); 211 html("/");
212 html_url_path(ctx.repo->url); 212 html_url_path(ctx.repo->url);
213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
214 html("/"); 214 html("/");
215 if (page) { 215 if (page) {
216 html_url_path(page); 216 html_url_path(page);
217 html("/"); 217 html("/");
218 if (path) 218 if (path)
219 html_url_path(path); 219 html_url_path(path);
220 } 220 }
221 } else { 221 } else {
222 html(ctx.cfg.script_name); 222 html(ctx.cfg.script_name);
223 html("?url="); 223 html("?url=");
224 html_url_arg(ctx.repo->url); 224 html_url_arg(ctx.repo->url);
225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
226 html("/"); 226 html("/");
227 if (page) { 227 if (page) {
228 html_url_arg(page); 228 html_url_arg(page);
229 html("/"); 229 html("/");
230 if (path) 230 if (path)
231 html_url_arg(path); 231 html_url_arg(path);
232 } 232 }
233 delim = "&amp;"; 233 delim = "&amp;";
234 } 234 }
235 if (head && strcmp(head, ctx.repo->defbranch)) { 235 if (head && strcmp(head, ctx.repo->defbranch)) {
236 html(delim); 236 html(delim);
237 html("h="); 237 html("h=");
238 html_url_arg(head); 238 html_url_arg(head);
239 delim = "&amp;"; 239 delim = "&amp;";
240 } 240 }
241 return fmt("%s", delim); 241 return fmt("%s", delim);
242} 242}
243 243
244static void reporevlink(char *page, char *name, char *title, char *class, 244static void reporevlink(char *page, char *name, char *title, char *class,
245 char *head, char *rev, char *path) 245 char *head, char *rev, char *path)
246{ 246{
247 char *delim; 247 char *delim;
248 248
249 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
250 if (rev && strcmp(rev, ctx.qry.head)) { 250 if (rev && strcmp(rev, ctx.qry.head)) {
251 html(delim); 251 html(delim);
252 html("id="); 252 html("id=");
253 html_url_arg(rev); 253 html_url_arg(rev);
254 } 254 }
255 html("'>"); 255 html("'>");
256 html_txt(name); 256 html_txt(name);
257 html("</a>"); 257 html("</a>");
258} 258}
259 259
260void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(char *name, char *title, char *class, char *head)
261{ 261{
262 reporevlink(NULL, name, title, class, head, NULL, NULL); 262 reporevlink(NULL, name, title, class, head, NULL, NULL);
263} 263}
264 264
265void cgit_tag_link(char *name, char *title, char *class, char *head, 265void cgit_tag_link(char *name, char *title, char *class, char *head,
266 char *rev) 266 char *rev)
267{ 267{
268 reporevlink("tag", name, title, class, head, rev, NULL); 268 reporevlink("tag", name, title, class, head, rev, NULL);
269} 269}
270 270
271void cgit_tree_link(char *name, char *title, char *class, char *head, 271void cgit_tree_link(char *name, char *title, char *class, char *head,
272 char *rev, char *path) 272 char *rev, char *path)
273{ 273{
274 reporevlink("tree", name, title, class, head, rev, path); 274 reporevlink("tree", name, title, class, head, rev, path);
275} 275}
276 276
277void cgit_plain_link(char *name, char *title, char *class, char *head, 277void cgit_plain_link(char *name, char *title, char *class, char *head,
278 char *rev, char *path) 278 char *rev, char *path)
279{ 279{
280 reporevlink("plain", name, title, class, head, rev, path); 280 reporevlink("plain", name, title, class, head, rev, path);
281} 281}
282 282
283void cgit_log_link(char *name, char *title, char *class, char *head, 283void cgit_log_link(char *name, char *title, char *class, char *head,
284 char *rev, char *path, int ofs, char *grep, char *pattern, 284 char *rev, char *path, int ofs, char *grep, char *pattern,
285 int showmsg) 285 int showmsg)
286{ 286{
287 char *delim; 287 char *delim;
288 288
289 delim = repolink(title, class, "log", head, path); 289 delim = repolink(title, class, "log", head, path);
290 if (rev && strcmp(rev, ctx.qry.head)) { 290 if (rev && strcmp(rev, ctx.qry.head)) {
291 html(delim); 291 html(delim);
292 html("id="); 292 html("id=");
293 html_url_arg(rev); 293 html_url_arg(rev);
294 delim = "&"; 294 delim = "&";
295 } 295 }
296 if (grep && pattern) { 296 if (grep && pattern) {
297 html(delim); 297 html(delim);
298 html("qt="); 298 html("qt=");
299 html_url_arg(grep); 299 html_url_arg(grep);
300 delim = "&"; 300 delim = "&";
301 html(delim); 301 html(delim);
302 html("q="); 302 html("q=");
303 html_url_arg(pattern); 303 html_url_arg(pattern);
304 } 304 }
305 if (ofs > 0) { 305 if (ofs > 0) {
306 html(delim); 306 html(delim);
307 html("ofs="); 307 html("ofs=");
308 htmlf("%d", ofs); 308 htmlf("%d", ofs);
309 delim = "&"; 309 delim = "&";
310 } 310 }
311 if (showmsg) { 311 if (showmsg) {
312 html(delim); 312 html(delim);
313 html("showmsg=1"); 313 html("showmsg=1");
314 } 314 }
315 html("'>"); 315 html("'>");
316 html_txt(name); 316 html_txt(name);
317 html("</a>"); 317 html("</a>");
318} 318}
319 319
320void cgit_commit_link(char *name, char *title, char *class, char *head, 320void cgit_commit_link(char *name, char *title, char *class, char *head,
321 char *rev) 321 char *rev)
322{ 322{
323 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 323 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
324 name[ctx.cfg.max_msg_len] = '\0'; 324 name[ctx.cfg.max_msg_len] = '\0';
325 name[ctx.cfg.max_msg_len - 1] = '.'; 325 name[ctx.cfg.max_msg_len - 1] = '.';
326 name[ctx.cfg.max_msg_len - 2] = '.'; 326 name[ctx.cfg.max_msg_len - 2] = '.';
327 name[ctx.cfg.max_msg_len - 3] = '.'; 327 name[ctx.cfg.max_msg_len - 3] = '.';
328 } 328 }
329 reporevlink("commit", name, title, class, head, rev, NULL); 329 reporevlink("commit", name, title, class, head, rev, NULL);
330} 330}
331 331
332void cgit_refs_link(char *name, char *title, char *class, char *head, 332void cgit_refs_link(char *name, char *title, char *class, char *head,
333 char *rev, char *path) 333 char *rev, char *path)
334{ 334{
335 reporevlink("refs", name, title, class, head, rev, path); 335 reporevlink("refs", name, title, class, head, rev, path);
336} 336}
337 337
338void cgit_snapshot_link(char *name, char *title, char *class, char *head, 338void cgit_snapshot_link(char *name, char *title, char *class, char *head,
339 char *rev, char *archivename) 339 char *rev, char *archivename)
340{ 340{
341 reporevlink("snapshot", name, title, class, head, rev, archivename); 341 reporevlink("snapshot", name, title, class, head, rev, archivename);
342} 342}
343 343
344void cgit_diff_link(char *name, char *title, char *class, char *head, 344void cgit_diff_link(char *name, char *title, char *class, char *head,
345 char *new_rev, char *old_rev, char *path) 345 char *new_rev, char *old_rev, char *path)
346{ 346{
347 char *delim; 347 char *delim;
348 348
349 delim = repolink(title, class, "diff", head, path); 349 delim = repolink(title, class, "diff", head, path);
350 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 350 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
351 html(delim); 351 html(delim);
352 html("id="); 352 html("id=");
353 html_url_arg(new_rev); 353 html_url_arg(new_rev);
354 delim = "&amp;"; 354 delim = "&amp;";
355 } 355 }
356 if (old_rev) { 356 if (old_rev) {
357 html(delim); 357 html(delim);
358 html("id2="); 358 html("id2=");
359 html_url_arg(old_rev); 359 html_url_arg(old_rev);
360 } 360 }
361 html("'>"); 361 html("'>");
362 html_txt(name); 362 html_txt(name);
363 html("</a>"); 363 html("</a>");
364} 364}
365 365
366void cgit_patch_link(char *name, char *title, char *class, char *head, 366void cgit_patch_link(char *name, char *title, char *class, char *head,
367 char *rev) 367 char *rev)
368{ 368{
369 reporevlink("patch", name, title, class, head, rev, NULL); 369 reporevlink("patch", name, title, class, head, rev, NULL);
370} 370}
371 371
372void cgit_stats_link(char *name, char *title, char *class, char *head,
373 char *path)
374{
375 reporevlink("stats", name, title, class, head, NULL, path);
376}
377
372void cgit_object_link(struct object *obj) 378void cgit_object_link(struct object *obj)
373{ 379{
374 char *page, *shortrev, *fullrev, *name; 380 char *page, *shortrev, *fullrev, *name;
375 381
376 fullrev = sha1_to_hex(obj->sha1); 382 fullrev = sha1_to_hex(obj->sha1);
377 shortrev = xstrdup(fullrev); 383 shortrev = xstrdup(fullrev);
378 shortrev[10] = '\0'; 384 shortrev[10] = '\0';
379 if (obj->type == OBJ_COMMIT) { 385 if (obj->type == OBJ_COMMIT) {
380 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 386 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
381 ctx.qry.head, fullrev); 387 ctx.qry.head, fullrev);
382 return; 388 return;
383 } else if (obj->type == OBJ_TREE) 389 } else if (obj->type == OBJ_TREE)
384 page = "tree"; 390 page = "tree";
385 else if (obj->type == OBJ_TAG) 391 else if (obj->type == OBJ_TAG)
386 page = "tag"; 392 page = "tag";
387 else 393 else
388 page = "blob"; 394 page = "blob";
389 name = fmt("%s %s...", typename(obj->type), shortrev); 395 name = fmt("%s %s...", typename(obj->type), shortrev);
390 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 396 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
391} 397}
392 398
393void cgit_print_date(time_t secs, char *format, int local_time) 399void cgit_print_date(time_t secs, char *format, int local_time)
394{ 400{
395 char buf[64]; 401 char buf[64];
396 struct tm *time; 402 struct tm *time;
397 403
398 if (!secs) 404 if (!secs)
399 return; 405 return;
400 if(local_time) 406 if(local_time)
401 time = localtime(&secs); 407 time = localtime(&secs);
402 else 408 else
403 time = gmtime(&secs); 409 time = gmtime(&secs);
404 strftime(buf, sizeof(buf)-1, format, time); 410 strftime(buf, sizeof(buf)-1, format, time);
405 html_txt(buf); 411 html_txt(buf);
406} 412}
407 413
408void cgit_print_age(time_t t, time_t max_relative, char *format) 414void cgit_print_age(time_t t, time_t max_relative, char *format)
409{ 415{
410 time_t now, secs; 416 time_t now, secs;
411 417
412 if (!t) 418 if (!t)
413 return; 419 return;
414 time(&now); 420 time(&now);
415 secs = now - t; 421 secs = now - t;
416 422
417 if (secs > max_relative && max_relative >= 0) { 423 if (secs > max_relative && max_relative >= 0) {
418 cgit_print_date(t, format, ctx.cfg.local_time); 424 cgit_print_date(t, format, ctx.cfg.local_time);
419 return; 425 return;
420 } 426 }
421 427
422 if (secs < TM_HOUR * 2) { 428 if (secs < TM_HOUR * 2) {
423 htmlf("<span class='age-mins'>%.0f min.</span>", 429 htmlf("<span class='age-mins'>%.0f min.</span>",
424 secs * 1.0 / TM_MIN); 430 secs * 1.0 / TM_MIN);
425 return; 431 return;
426 } 432 }
427 if (secs < TM_DAY * 2) { 433 if (secs < TM_DAY * 2) {
428 htmlf("<span class='age-hours'>%.0f hours</span>", 434 htmlf("<span class='age-hours'>%.0f hours</span>",
429 secs * 1.0 / TM_HOUR); 435 secs * 1.0 / TM_HOUR);
430 return; 436 return;
431 } 437 }
432 if (secs < TM_WEEK * 2) { 438 if (secs < TM_WEEK * 2) {
433 htmlf("<span class='age-days'>%.0f days</span>", 439 htmlf("<span class='age-days'>%.0f days</span>",
434 secs * 1.0 / TM_DAY); 440 secs * 1.0 / TM_DAY);
435 return; 441 return;
436 } 442 }
437 if (secs < TM_MONTH * 2) { 443 if (secs < TM_MONTH * 2) {
438 htmlf("<span class='age-weeks'>%.0f weeks</span>", 444 htmlf("<span class='age-weeks'>%.0f weeks</span>",
439 secs * 1.0 / TM_WEEK); 445 secs * 1.0 / TM_WEEK);
440 return; 446 return;
441 } 447 }
442 if (secs < TM_YEAR * 2) { 448 if (secs < TM_YEAR * 2) {
443 htmlf("<span class='age-months'>%.0f months</span>", 449 htmlf("<span class='age-months'>%.0f months</span>",
444 secs * 1.0 / TM_MONTH); 450 secs * 1.0 / TM_MONTH);
445 return; 451 return;
446 } 452 }
447 htmlf("<span class='age-years'>%.0f years</span>", 453 htmlf("<span class='age-years'>%.0f years</span>",
448 secs * 1.0 / TM_YEAR); 454 secs * 1.0 / TM_YEAR);
449} 455}
450 456
451void cgit_print_http_headers(struct cgit_context *ctx) 457void cgit_print_http_headers(struct cgit_context *ctx)
452{ 458{
453 if (ctx->page.mimetype && ctx->page.charset) 459 if (ctx->page.mimetype && ctx->page.charset)
454 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 460 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
455 ctx->page.charset); 461 ctx->page.charset);
456 else if (ctx->page.mimetype) 462 else if (ctx->page.mimetype)
457 htmlf("Content-Type: %s\n", ctx->page.mimetype); 463 htmlf("Content-Type: %s\n", ctx->page.mimetype);
458 if (ctx->page.size) 464 if (ctx->page.size)
459 htmlf("Content-Length: %ld\n", ctx->page.size); 465 htmlf("Content-Length: %ld\n", ctx->page.size);
460 if (ctx->page.filename) 466 if (ctx->page.filename)
461 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 467 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
462 ctx->page.filename); 468 ctx->page.filename);
463 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 469 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
464 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 470 htmlf("Expires: %s\n", http_date(ctx->page.expires));
465 html("\n"); 471 html("\n");
466} 472}
467 473
468void cgit_print_docstart(struct cgit_context *ctx) 474void cgit_print_docstart(struct cgit_context *ctx)
469{ 475{
470 char *host = cgit_hosturl(); 476 char *host = cgit_hosturl();
471 html(cgit_doctype); 477 html(cgit_doctype);
472 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 478 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
473 html("<head>\n"); 479 html("<head>\n");
474 html("<title>"); 480 html("<title>");
475 html_txt(ctx->page.title); 481 html_txt(ctx->page.title);
476 html("</title>\n"); 482 html("</title>\n");
477 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 483 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
478 if (ctx->cfg.robots && *ctx->cfg.robots) 484 if (ctx->cfg.robots && *ctx->cfg.robots)
479 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 485 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
480 html("<link rel='stylesheet' type='text/css' href='"); 486 html("<link rel='stylesheet' type='text/css' href='");
481 html_attr(ctx->cfg.css); 487 html_attr(ctx->cfg.css);
482 html("'/>\n"); 488 html("'/>\n");
483 if (ctx->cfg.favicon) { 489 if (ctx->cfg.favicon) {
484 html("<link rel='shortcut icon' href='"); 490 html("<link rel='shortcut icon' href='");
485 html_attr(ctx->cfg.favicon); 491 html_attr(ctx->cfg.favicon);
486 html("'/>\n"); 492 html("'/>\n");
487 } 493 }
488 if (host && ctx->repo) { 494 if (host && ctx->repo) {
489 html("<link rel='alternate' title='Atom feed' href='http://"); 495 html("<link rel='alternate' title='Atom feed' href='http://");
490 html_attr(cgit_hosturl()); 496 html_attr(cgit_hosturl());
491 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 497 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
492 fmt("h=%s", ctx->qry.head))); 498 fmt("h=%s", ctx->qry.head)));
493 html("' type='application/atom+xml'/>"); 499 html("' type='application/atom+xml'/>");
494 } 500 }
495 html("</head>\n"); 501 html("</head>\n");
496 html("<body>\n"); 502 html("<body>\n");
497} 503}
498 504
499void cgit_print_docend() 505void cgit_print_docend()
500{ 506{
501 html("</div>"); 507 html("</div>");
502 if (ctx.cfg.footer) 508 if (ctx.cfg.footer)
503 html_include(ctx.cfg.footer); 509 html_include(ctx.cfg.footer);
504 else { 510 else {
505 htmlf("<div class='footer'>generated by cgit %s at ", 511 htmlf("<div class='footer'>generated by cgit %s at ",
506 cgit_version); 512 cgit_version);
507 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 513 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
508 html("</div>\n"); 514 html("</div>\n");
509 } 515 }
510 html("</body>\n</html>\n"); 516 html("</body>\n</html>\n");
511} 517}
512 518
513int print_branch_option(const char *refname, const unsigned char *sha1, 519int print_branch_option(const char *refname, const unsigned char *sha1,
514 int flags, void *cb_data) 520 int flags, void *cb_data)
515{ 521{
516 char *name = (char *)refname; 522 char *name = (char *)refname;
517 html_option(name, name, ctx.qry.head); 523 html_option(name, name, ctx.qry.head);
518 return 0; 524 return 0;
519} 525}
520 526
521int print_archive_ref(const char *refname, const unsigned char *sha1, 527int print_archive_ref(const char *refname, const unsigned char *sha1,
522 int flags, void *cb_data) 528 int flags, void *cb_data)
523{ 529{
524 struct tag *tag; 530 struct tag *tag;
525 struct taginfo *info; 531 struct taginfo *info;
526 struct object *obj; 532 struct object *obj;
527 char buf[256], *url; 533 char buf[256], *url;
528 unsigned char fileid[20]; 534 unsigned char fileid[20];
529 int *header = (int *)cb_data; 535 int *header = (int *)cb_data;
530 536
531 if (prefixcmp(refname, "refs/archives")) 537 if (prefixcmp(refname, "refs/archives"))
532 return 0; 538 return 0;
533 strncpy(buf, refname+14, sizeof(buf)); 539 strncpy(buf, refname+14, sizeof(buf));
534 obj = parse_object(sha1); 540 obj = parse_object(sha1);
535 if (!obj) 541 if (!obj)
536 return 1; 542 return 1;
537 if (obj->type == OBJ_TAG) { 543 if (obj->type == OBJ_TAG) {
538 tag = lookup_tag(sha1); 544 tag = lookup_tag(sha1);
539 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 545 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
540 return 0; 546 return 0;
541 hashcpy(fileid, tag->tagged->sha1); 547 hashcpy(fileid, tag->tagged->sha1);
542 } else if (obj->type != OBJ_BLOB) { 548 } else if (obj->type != OBJ_BLOB) {
543 return 0; 549 return 0;
544 } else { 550 } else {
545 hashcpy(fileid, sha1); 551 hashcpy(fileid, sha1);
546 } 552 }
547 if (!*header) { 553 if (!*header) {
548 html("<h1>download</h1>\n"); 554 html("<h1>download</h1>\n");
549 *header = 1; 555 *header = 1;
550 } 556 }
551 url = cgit_pageurl(ctx.qry.repo, "blob", 557 url = cgit_pageurl(ctx.qry.repo, "blob",
552 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 558 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
553 buf)); 559 buf));
554 html_link_open(url, NULL, "menu"); 560 html_link_open(url, NULL, "menu");
555 html_txt(strlpart(buf, 20)); 561 html_txt(strlpart(buf, 20));
556 html_link_close(); 562 html_link_close();
557 return 0; 563 return 0;
558} 564}
559 565
560void add_hidden_formfields(int incl_head, int incl_search, char *page) 566void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
561{ 567{
562 char *url; 568 char *url;
563 569
564 if (!ctx.cfg.virtual_root) { 570 if (!ctx.cfg.virtual_root) {
565 url = fmt("%s/%s", ctx.qry.repo, page); 571 url = fmt("%s/%s", ctx.qry.repo, page);
566 if (ctx.qry.path) 572 if (ctx.qry.path)
567 url = fmt("%s/%s", url, ctx.qry.path); 573 url = fmt("%s/%s", url, ctx.qry.path);
568 html_hidden("url", url); 574 html_hidden("url", url);
569 } 575 }
570 576
571 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 577 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
572 strcmp(ctx.qry.head, ctx.repo->defbranch)) 578 strcmp(ctx.qry.head, ctx.repo->defbranch))
573 html_hidden("h", ctx.qry.head); 579 html_hidden("h", ctx.qry.head);
574 580
575 if (ctx.qry.sha1) 581 if (ctx.qry.sha1)
576 html_hidden("id", ctx.qry.sha1); 582 html_hidden("id", ctx.qry.sha1);
577 if (ctx.qry.sha2) 583 if (ctx.qry.sha2)
578 html_hidden("id2", ctx.qry.sha2); 584 html_hidden("id2", ctx.qry.sha2);
579 if (ctx.qry.showmsg) 585 if (ctx.qry.showmsg)
580 html_hidden("showmsg", "1"); 586 html_hidden("showmsg", "1");
581 587
582 if (incl_search) { 588 if (incl_search) {
583 if (ctx.qry.grep) 589 if (ctx.qry.grep)
584 html_hidden("qt", ctx.qry.grep); 590 html_hidden("qt", ctx.qry.grep);
585 if (ctx.qry.search) 591 if (ctx.qry.search)
586 html_hidden("q", ctx.qry.search); 592 html_hidden("q", ctx.qry.search);
587 } 593 }
588} 594}
589 595
590const char *fallback_cmd = "repolist"; 596const char *fallback_cmd = "repolist";
591 597
592char *hc(struct cgit_cmd *cmd, const char *page) 598char *hc(struct cgit_cmd *cmd, const char *page)
593{ 599{
594 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 600 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active");
595} 601}
596 602
597void cgit_print_pageheader(struct cgit_context *ctx) 603void cgit_print_pageheader(struct cgit_context *ctx)
598{ 604{
599 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 605 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
600 606
601 if (!cmd && ctx->repo) 607 if (!cmd && ctx->repo)
602 fallback_cmd = "summary"; 608 fallback_cmd = "summary";
603 609
604 html("<table id='header'>\n"); 610 html("<table id='header'>\n");
605 html("<tr>\n"); 611 html("<tr>\n");
606 html("<td class='logo' rowspan='2'><a href='"); 612 html("<td class='logo' rowspan='2'><a href='");
607 if (ctx->cfg.logo_link) 613 if (ctx->cfg.logo_link)
608 html_attr(ctx->cfg.logo_link); 614 html_attr(ctx->cfg.logo_link);
609 else 615 else
610 html_attr(cgit_rooturl()); 616 html_attr(cgit_rooturl());
611 html("'><img src='"); 617 html("'><img src='");
612 html_attr(ctx->cfg.logo); 618 html_attr(ctx->cfg.logo);
613 html("' alt='cgit logo'/></a></td>\n"); 619 html("' alt='cgit logo'/></a></td>\n");
614 620
615 html("<td class='main'>"); 621 html("<td class='main'>");
616 if (ctx->repo) { 622 if (ctx->repo) {
617 cgit_index_link("index", NULL, NULL, NULL, 0); 623 cgit_index_link("index", NULL, NULL, NULL, 0);
618 html(" : "); 624 html(" : ");
619 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 625 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
620 html("</td><td class='form'>"); 626 html("</td><td class='form'>");
621 html("<form method='get' action=''>\n"); 627 html("<form method='get' action=''>\n");
622 add_hidden_formfields(0, 1, ctx->qry.page); 628 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
623 html("<select name='h' onchange='this.form.submit();'>\n"); 629 html("<select name='h' onchange='this.form.submit();'>\n");
624 for_each_branch_ref(print_branch_option, ctx->qry.head); 630 for_each_branch_ref(print_branch_option, ctx->qry.head);
625 html("</select> "); 631 html("</select> ");
626 html("<input type='submit' name='' value='switch'/>"); 632 html("<input type='submit' name='' value='switch'/>");
627 html("</form>"); 633 html("</form>");
628 } else 634 } else
629 html_txt(ctx->cfg.root_title); 635 html_txt(ctx->cfg.root_title);
630 html("</td></tr>\n"); 636 html("</td></tr>\n");
631 637
632 html("<tr><td class='sub'>"); 638 html("<tr><td class='sub'>");
633 if (ctx->repo) { 639 if (ctx->repo) {
634 html_txt(ctx->repo->desc); 640 html_txt(ctx->repo->desc);
635 html("</td><td class='sub right'>"); 641 html("</td><td class='sub right'>");
636 html_txt(ctx->repo->owner); 642 html_txt(ctx->repo->owner);
637 } else { 643 } else {
638 if (ctx->cfg.root_desc) 644 if (ctx->cfg.root_desc)
639 html_txt(ctx->cfg.root_desc); 645 html_txt(ctx->cfg.root_desc);
640 else if (ctx->cfg.index_info) 646 else if (ctx->cfg.index_info)
641 html_include(ctx->cfg.index_info); 647 html_include(ctx->cfg.index_info);
642 } 648 }
643 html("</td></tr></table>\n"); 649 html("</td></tr></table>\n");
644 650
645 html("<table class='tabs'><tr><td>\n"); 651 html("<table class='tabs'><tr><td>\n");
646 if (ctx->repo) { 652 if (ctx->repo) {
647 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 653 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
648 ctx->qry.head); 654 ctx->qry.head);
649 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 655 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
650 ctx->qry.sha1, NULL); 656 ctx->qry.sha1, NULL);
651 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 657 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
652 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 658 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
653 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 659 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
654 ctx->qry.sha1, NULL); 660 ctx->qry.sha1, NULL);
655 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 661 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
656 ctx->qry.head, ctx->qry.sha1); 662 ctx->qry.head, ctx->qry.sha1);
657 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 663 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
658 ctx->qry.sha1, ctx->qry.sha2, NULL); 664 ctx->qry.sha1, ctx->qry.sha2, NULL);
665 if (ctx->repo->max_stats)
666 cgit_stats_link("stats", NULL, hc(cmd, "stats"),
667 ctx->qry.head, NULL);
659 if (ctx->repo->readme) 668 if (ctx->repo->readme)
660 reporevlink("about", "about", NULL, 669 reporevlink("about", "about", NULL,
661 hc(cmd, "about"), ctx->qry.head, NULL, 670 hc(cmd, "about"), ctx->qry.head, NULL,
662 NULL); 671 NULL);
663 html("</td><td class='form'>"); 672 html("</td><td class='form'>");
664 html("<form class='right' method='get' action='"); 673 html("<form class='right' method='get' action='");
665 if (ctx->cfg.virtual_root) 674 if (ctx->cfg.virtual_root)
666 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 675 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
667 ctx->qry.path, NULL)); 676 ctx->qry.path, NULL));
668 html("'>\n"); 677 html("'>\n");
669 add_hidden_formfields(1, 0, "log"); 678 cgit_add_hidden_formfields(1, 0, "log");
670 html("<select name='qt'>\n"); 679 html("<select name='qt'>\n");
671 html_option("grep", "log msg", ctx->qry.grep); 680 html_option("grep", "log msg", ctx->qry.grep);
672 html_option("author", "author", ctx->qry.grep); 681 html_option("author", "author", ctx->qry.grep);
673 html_option("committer", "committer", ctx->qry.grep); 682 html_option("committer", "committer", ctx->qry.grep);
674 html("</select>\n"); 683 html("</select>\n");
675 html("<input class='txt' type='text' size='10' name='q' value='"); 684 html("<input class='txt' type='text' size='10' name='q' value='");
676 html_attr(ctx->qry.search); 685 html_attr(ctx->qry.search);
677 html("'/>\n"); 686 html("'/>\n");
678 html("<input type='submit' value='search'/>\n"); 687 html("<input type='submit' value='search'/>\n");
679 html("</form>\n"); 688 html("</form>\n");
680 } else { 689 } else {
681 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 690 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
682 if (ctx->cfg.root_readme) 691 if (ctx->cfg.root_readme)
683 site_link("about", "about", NULL, hc(cmd, "about"), 692 site_link("about", "about", NULL, hc(cmd, "about"),
684 NULL, 0); 693 NULL, 0);
685 html("</td><td class='form'>"); 694 html("</td><td class='form'>");
686 html("<form method='get' action='"); 695 html("<form method='get' action='");
687 html_attr(cgit_rooturl()); 696 html_attr(cgit_rooturl());
688 html("'>\n"); 697 html("'>\n");
689 html("<input type='text' name='q' size='10' value='"); 698 html("<input type='text' name='q' size='10' value='");
690 html_attr(ctx->qry.search); 699 html_attr(ctx->qry.search);
691 html("'/>\n"); 700 html("'/>\n");
692 html("<input type='submit' value='search'/>\n"); 701 html("<input type='submit' value='search'/>\n");
693 html("</form>"); 702 html("</form>");
694 } 703 }
695 html("</td></tr></table>\n"); 704 html("</td></tr></table>\n");
696 html("<div class='content'>"); 705 html("<div class='content'>");
697} 706}
698 707
699void cgit_print_filemode(unsigned short mode) 708void cgit_print_filemode(unsigned short mode)
700{ 709{
701 if (S_ISDIR(mode)) 710 if (S_ISDIR(mode))
702 html("d"); 711 html("d");
703 else if (S_ISLNK(mode)) 712 else if (S_ISLNK(mode))
704 html("l"); 713 html("l");
705 else if (S_ISGITLINK(mode)) 714 else if (S_ISGITLINK(mode))
706 html("m"); 715 html("m");
707 else 716 else
708 html("-"); 717 html("-");
709 html_fileperm(mode >> 6); 718 html_fileperm(mode >> 6);
710 html_fileperm(mode >> 3); 719 html_fileperm(mode >> 3);
711 html_fileperm(mode); 720 html_fileperm(mode);
712} 721}
713 722
714void cgit_print_snapshot_links(const char *repo, const char *head, 723void cgit_print_snapshot_links(const char *repo, const char *head,
715 const char *hex, int snapshots) 724 const char *hex, int snapshots)
716{ 725{
717 const struct cgit_snapshot_format* f; 726 const struct cgit_snapshot_format* f;
718 char *filename; 727 char *filename;
719 728
720 for (f = cgit_snapshot_formats; f->suffix; f++) { 729 for (f = cgit_snapshot_formats; f->suffix; f++) {
721 if (!(snapshots & f->bit)) 730 if (!(snapshots & f->bit))
722 continue; 731 continue;
723 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 732 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
724 f->suffix); 733 f->suffix);
725 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 734 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
726 html("<br/>"); 735 html("<br/>");
727 } 736 }
728} 737}
diff --git a/ui-shared.h b/ui-shared.h
index 2ab53ae..5a3821f 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,46 +1,49 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_hosturl(); 4extern char *cgit_hosturl();
5extern char *cgit_repourl(const char *reponame); 5extern char *cgit_repourl(const char *reponame);
6extern char *cgit_fileurl(const char *reponame, const char *pagename, 6extern char *cgit_fileurl(const char *reponame, const char *pagename,
7 const char *filename, const char *query); 7 const char *filename, const char *query);
8extern char *cgit_pageurl(const char *reponame, const char *pagename, 8extern char *cgit_pageurl(const char *reponame, const char *pagename,
9 const char *query); 9 const char *query);
10 10
11extern void cgit_index_link(char *name, char *title, char *class, 11extern void cgit_index_link(char *name, char *title, char *class,
12 char *pattern, int ofs); 12 char *pattern, int ofs);
13extern void cgit_summary_link(char *name, char *title, char *class, char *head); 13extern void cgit_summary_link(char *name, char *title, char *class, char *head);
14extern void cgit_tag_link(char *name, char *title, char *class, char *head, 14extern void cgit_tag_link(char *name, char *title, char *class, char *head,
15 char *rev); 15 char *rev);
16extern void cgit_tree_link(char *name, char *title, char *class, char *head, 16extern void cgit_tree_link(char *name, char *title, char *class, char *head,
17 char *rev, char *path); 17 char *rev, char *path);
18extern void cgit_plain_link(char *name, char *title, char *class, char *head, 18extern void cgit_plain_link(char *name, char *title, char *class, char *head,
19 char *rev, char *path); 19 char *rev, char *path);
20extern void cgit_log_link(char *name, char *title, char *class, char *head, 20extern void cgit_log_link(char *name, char *title, char *class, char *head,
21 char *rev, char *path, int ofs, char *grep, 21 char *rev, char *path, int ofs, char *grep,
22 char *pattern, int showmsg); 22 char *pattern, int showmsg);
23extern void cgit_commit_link(char *name, char *title, char *class, char *head, 23extern void cgit_commit_link(char *name, char *title, char *class, char *head,
24 char *rev); 24 char *rev);
25extern void cgit_patch_link(char *name, char *title, char *class, char *head, 25extern void cgit_patch_link(char *name, char *title, char *class, char *head,
26 char *rev); 26 char *rev);
27extern void cgit_refs_link(char *name, char *title, char *class, char *head, 27extern void cgit_refs_link(char *name, char *title, char *class, char *head,
28 char *rev, char *path); 28 char *rev, char *path);
29extern void cgit_snapshot_link(char *name, char *title, char *class, 29extern void cgit_snapshot_link(char *name, char *title, char *class,
30 char *head, char *rev, char *archivename); 30 char *head, char *rev, char *archivename);
31extern void cgit_diff_link(char *name, char *title, char *class, char *head, 31extern void cgit_diff_link(char *name, char *title, char *class, char *head,
32 char *new_rev, char *old_rev, char *path); 32 char *new_rev, char *old_rev, char *path);
33extern void cgit_stats_link(char *name, char *title, char *class, char *head,
34 char *path);
33extern void cgit_object_link(struct object *obj); 35extern void cgit_object_link(struct object *obj);
34 36
35extern void cgit_print_error(char *msg); 37extern void cgit_print_error(char *msg);
36extern void cgit_print_date(time_t secs, char *format, int local_time); 38extern void cgit_print_date(time_t secs, char *format, int local_time);
37extern void cgit_print_age(time_t t, time_t max_relative, char *format); 39extern void cgit_print_age(time_t t, time_t max_relative, char *format);
38extern void cgit_print_http_headers(struct cgit_context *ctx); 40extern void cgit_print_http_headers(struct cgit_context *ctx);
39extern void cgit_print_docstart(struct cgit_context *ctx); 41extern void cgit_print_docstart(struct cgit_context *ctx);
40extern void cgit_print_docend(); 42extern void cgit_print_docend();
41extern void cgit_print_pageheader(struct cgit_context *ctx); 43extern void cgit_print_pageheader(struct cgit_context *ctx);
42extern void cgit_print_filemode(unsigned short mode); 44extern void cgit_print_filemode(unsigned short mode);
43extern void cgit_print_snapshot_links(const char *repo, const char *head, 45extern void cgit_print_snapshot_links(const char *repo, const char *head,
44 const char *hex, int snapshots); 46 const char *hex, int snapshots);
45 47extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
48 char *page);
46#endif /* UI_SHARED_H */ 49#endif /* UI_SHARED_H */
diff --git a/ui-stats.c b/ui-stats.c
new file mode 100644
index 0000000..9fc06d3
--- a/dev/null
+++ b/ui-stats.c
@@ -0,0 +1,410 @@
1#include <string-list.h>
2
3#include "cgit.h"
4#include "html.h"
5#include "ui-shared.h"
6#include "ui-stats.h"
7
8#define MONTHS 6
9
10struct authorstat {
11 long total;
12 struct string_list list;
13};
14
15#define DAY_SECS (60 * 60 * 24)
16#define WEEK_SECS (DAY_SECS * 7)
17
18static void trunc_week(struct tm *tm)
19{
20 time_t t = timegm(tm);
21 t -= ((tm->tm_wday + 6) % 7) * DAY_SECS;
22 gmtime_r(&t, tm);
23}
24
25static void dec_week(struct tm *tm)
26{
27 time_t t = timegm(tm);
28 t -= WEEK_SECS;
29 gmtime_r(&t, tm);
30}
31
32static void inc_week(struct tm *tm)
33{
34 time_t t = timegm(tm);
35 t += WEEK_SECS;
36 gmtime_r(&t, tm);
37}
38
39static char *pretty_week(struct tm *tm)
40{
41 static char buf[10];
42
43 strftime(buf, sizeof(buf), "W%V %G", tm);
44 return buf;
45}
46
47static void trunc_month(struct tm *tm)
48{
49 tm->tm_mday = 1;
50}
51
52static void dec_month(struct tm *tm)
53{
54 tm->tm_mon--;
55 if (tm->tm_mon < 0) {
56 tm->tm_year--;
57 tm->tm_mon = 11;
58 }
59}
60
61static void inc_month(struct tm *tm)
62{
63 tm->tm_mon++;
64 if (tm->tm_mon > 11) {
65 tm->tm_year++;
66 tm->tm_mon = 0;
67 }
68}
69
70static char *pretty_month(struct tm *tm)
71{
72 static const char *months[] = {
73 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
74 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
75 };
76 return fmt("%s %d", months[tm->tm_mon], tm->tm_year + 1900);
77}
78
79static void trunc_quarter(struct tm *tm)
80{
81 trunc_month(tm);
82 while(tm->tm_mon % 3 != 0)
83 dec_month(tm);
84}
85
86static void dec_quarter(struct tm *tm)
87{
88 dec_month(tm);
89 dec_month(tm);
90 dec_month(tm);
91}
92
93static void inc_quarter(struct tm *tm)
94{
95 inc_month(tm);
96 inc_month(tm);
97 inc_month(tm);
98}
99
100static char *pretty_quarter(struct tm *tm)
101{
102 return fmt("Q%d %d", tm->tm_mon / 3 + 1, tm->tm_year + 1900);
103}
104
105static void trunc_year(struct tm *tm)
106{
107 trunc_month(tm);
108 tm->tm_mon = 0;
109}
110
111static void dec_year(struct tm *tm)
112{
113 tm->tm_year--;
114}
115
116static void inc_year(struct tm *tm)
117{
118 tm->tm_year++;
119}
120
121static char *pretty_year(struct tm *tm)
122{
123 return fmt("%d", tm->tm_year + 1900);
124}
125
126struct cgit_period periods[] = {
127 {'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},
129 {'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},
131};
132
133/* 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.
135 * If no matching code is found, return 0.
136 */
137int cgit_find_stats_period(const char *expr, struct cgit_period **period)
138{
139 int i;
140 char code = '\0';
141
142 if (!expr)
143 return 0;
144
145 if (strlen(expr) == 1)
146 code = expr[0];
147
148 for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++)
149 if (periods[i].code == code || !strcmp(periods[i].name, expr)) {
150 if (period)
151 *period = &periods[i];
152 return i+1;
153 }
154 return 0;
155}
156
157static void add_commit(struct string_list *authors, struct commit *commit,
158 struct cgit_period *period)
159{
160 struct commitinfo *info;
161 struct string_list_item *author, *item;
162 struct authorstat *authorstat;
163 struct string_list *items;
164 char *tmp;
165 struct tm *date;
166 time_t t;
167
168 info = cgit_parse_commit(commit);
169 tmp = xstrdup(info->author);
170 author = string_list_insert(tmp, authors);
171 if (!author->util)
172 author->util = xcalloc(1, sizeof(struct authorstat));
173 else
174 free(tmp);
175 authorstat = author->util;
176 items = &authorstat->list;
177 t = info->committer_date;
178 date = gmtime(&t);
179 period->trunc(date);
180 tmp = xstrdup(period->pretty(date));
181 item = string_list_insert(tmp, items);
182 if (item->util)
183 free(tmp);
184 item->util++;
185 authorstat->total++;
186 cgit_free_commitinfo(info);
187}
188
189static int cmp_total_commits(const void *a1, const void *a2)
190{
191 const struct string_list_item *i1 = a1;
192 const struct string_list_item *i2 = a2;
193 const struct authorstat *auth1 = i1->util;
194 const struct authorstat *auth2 = i2->util;
195
196 return auth2->total - auth1->total;
197}
198
199/* Walk the commit DAG and collect number of commits per author per
200 * timeperiod into a nested string_list collection.
201 */
202struct string_list collect_stats(struct cgit_context *ctx,
203 struct cgit_period *period)
204{
205 struct string_list authors;
206 struct rev_info rev;
207 struct commit *commit;
208 const char *argv[] = {NULL, ctx->qry.head, NULL, NULL, NULL, NULL};
209 int argc = 3;
210 time_t now;
211 long i;
212 struct tm *tm;
213 char tmp[11];
214
215 time(&now);
216 tm = gmtime(&now);
217 period->trunc(tm);
218 for (i = 1; i < period->count; i++)
219 period->dec(tm);
220 strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm);
221 argv[2] = xstrdup(fmt("--since=%s", tmp));
222 if (ctx->qry.path) {
223 argv[3] = "--";
224 argv[4] = ctx->qry.path;
225 argc += 2;
226 }
227 init_revisions(&rev, NULL);
228 rev.abbrev = DEFAULT_ABBREV;
229 rev.commit_format = CMIT_FMT_DEFAULT;
230 rev.no_merges = 1;
231 rev.verbose_header = 1;
232 rev.show_root_diff = 0;
233 setup_revisions(argc, argv, &rev, NULL);
234 prepare_revision_walk(&rev);
235 memset(&authors, 0, sizeof(authors));
236 while ((commit = get_revision(&rev)) != NULL) {
237 add_commit(&authors, commit, period);
238 free(commit->buffer);
239 free_commit_list(commit->parents);
240 }
241 return authors;
242}
243
244void print_combined_authorrow(struct string_list *authors, int from, int to,
245 const char *name, const char *leftclass, const char *centerclass,
246 const char *rightclass, struct cgit_period *period)
247{
248 struct string_list_item *author;
249 struct authorstat *authorstat;
250 struct string_list *items;
251 struct string_list_item *date;
252 time_t now;
253 long i, j, total, subtotal;
254 struct tm *tm;
255 char *tmp;
256
257 time(&now);
258 tm = gmtime(&now);
259 period->trunc(tm);
260 for (i = 1; i < period->count; i++)
261 period->dec(tm);
262
263 total = 0;
264 htmlf("<tr><td class='%s'>%s</td>", leftclass,
265 fmt(name, to - from + 1));
266 for (j = 0; j < period->count; j++) {
267 tmp = period->pretty(tm);
268 period->inc(tm);
269 subtotal = 0;
270 for (i = from; i <= to; i++) {
271 author = &authors->items[i];
272 authorstat = author->util;
273 items = &authorstat->list;
274 date = string_list_lookup(tmp, items);
275 if (date)
276 subtotal += (size_t)date->util;
277 }
278 htmlf("<td class='%s'>%d</td>", centerclass, subtotal);
279 total += subtotal;
280 }
281 htmlf("<td class='%s'>%d</td></tr>", rightclass, total);
282}
283
284void print_authors(struct string_list *authors, int top,
285 struct cgit_period *period)
286{
287 struct string_list_item *author;
288 struct authorstat *authorstat;
289 struct string_list *items;
290 struct string_list_item *date;
291 time_t now;
292 long i, j, total;
293 struct tm *tm;
294 char *tmp;
295
296 time(&now);
297 tm = gmtime(&now);
298 period->trunc(tm);
299 for (i = 1; i < period->count; i++)
300 period->dec(tm);
301
302 html("<table class='stats'><tr><th>Author</th>");
303 for (j = 0; j < period->count; j++) {
304 tmp = period->pretty(tm);
305 htmlf("<th>%s</th>", tmp);
306 period->inc(tm);
307 }
308 html("<th>Total</th></tr>\n");
309
310 if (top <= 0 || top > authors->nr)
311 top = authors->nr;
312
313 for (i = 0; i < top; i++) {
314 author = &authors->items[i];
315 html("<tr><td class='left'>");
316 html_txt(author->string);
317 html("</td>");
318 authorstat = author->util;
319 items = &authorstat->list;
320 total = 0;
321 for (j = 0; j < period->count; j++)
322 period->dec(tm);
323 for (j = 0; j < period->count; j++) {
324 tmp = period->pretty(tm);
325 period->inc(tm);
326 date = string_list_lookup(tmp, items);
327 if (!date)
328 html("<td>0</td>");
329 else {
330 htmlf("<td>%d</td>", date->util);
331 total += (size_t)date->util;
332 }
333 }
334 htmlf("<td class='sum'>%d</td></tr>", total);
335 }
336
337 if (top < authors->nr)
338 print_combined_authorrow(authors, top, authors->nr - 1,
339 "Others (%d)", "left", "", "sum", period);
340
341 print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
342 "total", "sum", "sum", period);
343 html("</table>");
344}
345
346/* Create a sorted string_list with one entry per author. The util-field
347 * for each author is another string_list which is used to calculate the
348 * number of commits per time-interval.
349 */
350void cgit_show_stats(struct cgit_context *ctx)
351{
352 struct string_list authors;
353 struct cgit_period *period;
354 int top, i;
355 const char *code = "w";
356
357 if (ctx->qry.period)
358 code = ctx->qry.period;
359
360 i = cgit_find_stats_period(code, &period);
361 if (!i) {
362 cgit_print_error(fmt("Unknown statistics type: %c", code));
363 return;
364 }
365 if (i > ctx->repo->max_stats) {
366 cgit_print_error(fmt("Statistics type disabled: %s",
367 period->name));
368 return;
369 }
370 authors = collect_stats(ctx, period);
371 qsort(authors.items, authors.nr, sizeof(struct string_list_item),
372 cmp_total_commits);
373
374 top = ctx->qry.ofs;
375 if (!top)
376 top = 10;
377 htmlf("<h2>Commits per author per %s", period->name);
378 if (ctx->qry.path) {
379 html(" (path '");
380 html_txt(ctx->qry.path);
381 html("')");
382 }
383 html("</h2>");
384
385 html("<form method='get' action='' style='float: right; text-align: right;'>");
386 cgit_add_hidden_formfields(1, 0, "stats");
387 if (ctx->repo->max_stats > 1) {
388 html("Period: ");
389 html("<select name='period' onchange='this.form.submit();'>");
390 for (i = 0; i < ctx->repo->max_stats; i++)
391 htmlf("<option value='%c'%s>%s</option>",
392 periods[i].code,
393 period == &periods[i] ? " selected" : "",
394 periods[i].name);
395 html("</select><br/><br/>");
396 }
397 html("Authors: ");
398 html("");
399 html("<select name='ofs' onchange='this.form.submit();'>");
400 htmlf("<option value='10'%s>10</option>", top == 10 ? " selected" : "");
401 htmlf("<option value='25'%s>25</option>", top == 25 ? " selected" : "");
402 htmlf("<option value='50'%s>50</option>", top == 50 ? " selected" : "");
403 htmlf("<option value='100'%s>100</option>", top == 100 ? " selected" : "");
404 htmlf("<option value='-1'%s>All</option>", top == -1 ? " selected" : "");
405 html("</select>");
406 html("<noscript>&nbsp;&nbsp;<input type='submit' value='Reload'/></noscript>");
407 html("</form>");
408 print_authors(&authors, top, period);
409}
410
diff --git a/ui-stats.h b/ui-stats.h
new file mode 100644
index 0000000..4f13dba
--- a/dev/null
+++ b/ui-stats.h
@@ -0,0 +1,27 @@
1#ifndef UI_STATS_H
2#define UI_STATS_H
3
4#include "cgit.h"
5
6struct cgit_period {
7 const char code;
8 const char *name;
9 int max_periods;
10 int count;
11
12 /* Convert a tm value to the first day in the period */
13 void (*trunc)(struct tm *tm);
14
15 /* Update tm value to start of next/previous period */
16 void (*dec)(struct tm *tm);
17 void (*inc)(struct tm *tm);
18
19 /* Pretty-print a tm value */
20 char *(*pretty)(struct tm *tm);
21};
22
23extern int cgit_find_stats_period(const char *expr, struct cgit_period **period);
24
25extern void cgit_show_stats(struct cgit_context *ctx);
26
27#endif /* UI_STATS_H */
diff --git a/ui-tree.c b/ui-tree.c
index 9876c99..4b8e7a0 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,225 +1,228 @@
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 "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 12
13char *curr_rev; 13char *curr_rev;
14char *match_path; 14char *match_path;
15int header = 0; 15int header = 0;
16 16
17static void print_object(const unsigned char *sha1, char *path) 17static void print_object(const unsigned char *sha1, char *path)
18{ 18{
19 enum object_type type; 19 enum object_type type;
20 char *buf; 20 char *buf;
21 unsigned long size, lineno, start, idx; 21 unsigned long size, lineno, start, idx;
22 const char *linefmt = "<tr><td class='no'><a id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a></td><td class='txt'>"; 22 const char *linefmt = "<tr><td class='no'><a id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a></td><td class='txt'>";
23 23
24 type = sha1_object_info(sha1, &size); 24 type = sha1_object_info(sha1, &size);
25 if (type == OBJ_BAD) { 25 if (type == OBJ_BAD) {
26 cgit_print_error(fmt("Bad object name: %s", 26 cgit_print_error(fmt("Bad object name: %s",
27 sha1_to_hex(sha1))); 27 sha1_to_hex(sha1)));
28 return; 28 return;
29 } 29 }
30 30
31 buf = read_sha1_file(sha1, &type, &size); 31 buf = read_sha1_file(sha1, &type, &size);
32 if (!buf) { 32 if (!buf) {
33 cgit_print_error(fmt("Error reading object %s", 33 cgit_print_error(fmt("Error reading object %s",
34 sha1_to_hex(sha1))); 34 sha1_to_hex(sha1)));
35 return; 35 return;
36 } 36 }
37 37
38 html(" ("); 38 html(" (");
39 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 39 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
40 curr_rev, path); 40 curr_rev, path);
41 htmlf(")<br/>blob: %s", sha1_to_hex(sha1)); 41 htmlf(")<br/>blob: %s", sha1_to_hex(sha1));
42 42
43 html("<table summary='blob content' class='blob'>\n"); 43 html("<table summary='blob content' class='blob'>\n");
44 idx = 0; 44 idx = 0;
45 start = 0; 45 start = 0;
46 lineno = 0; 46 lineno = 0;
47 while(idx < size) { 47 while(idx < size) {
48 if (buf[idx] == '\n') { 48 if (buf[idx] == '\n') {
49 buf[idx] = '\0'; 49 buf[idx] = '\0';
50 htmlf(linefmt, ++lineno); 50 htmlf(linefmt, ++lineno);
51 html_txt(buf + start); 51 html_txt(buf + start);
52 html("</td></tr>\n"); 52 html("</td></tr>\n");
53 start = idx + 1; 53 start = idx + 1;
54 } 54 }
55 idx++; 55 idx++;
56 } 56 }
57 if (start < idx) { 57 if (start < idx) {
58 htmlf(linefmt, ++lineno); 58 htmlf(linefmt, ++lineno);
59 html_txt(buf + start); 59 html_txt(buf + start);
60 } 60 }
61 html("</td></tr>\n"); 61 html("</td></tr>\n");
62 html("</table>\n"); 62 html("</table>\n");
63} 63}
64 64
65 65
66static int ls_item(const unsigned char *sha1, const char *base, int baselen, 66static int ls_item(const unsigned char *sha1, const char *base, int baselen,
67 const char *pathname, unsigned int mode, int stage, 67 const char *pathname, unsigned int mode, int stage,
68 void *cbdata) 68 void *cbdata)
69{ 69{
70 char *name; 70 char *name;
71 char *fullpath; 71 char *fullpath;
72 enum object_type type; 72 enum object_type type;
73 unsigned long size = 0; 73 unsigned long size = 0;
74 74
75 name = xstrdup(pathname); 75 name = xstrdup(pathname);
76 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 76 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
77 ctx.qry.path ? "/" : "", name); 77 ctx.qry.path ? "/" : "", name);
78 78
79 if (!S_ISGITLINK(mode)) { 79 if (!S_ISGITLINK(mode)) {
80 type = sha1_object_info(sha1, &size); 80 type = sha1_object_info(sha1, &size);
81 if (type == OBJ_BAD) { 81 if (type == OBJ_BAD) {
82 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 82 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
83 name, 83 name,
84 sha1_to_hex(sha1)); 84 sha1_to_hex(sha1));
85 return 0; 85 return 0;
86 } 86 }
87 } 87 }
88 88
89 html("<tr><td class='ls-mode'>"); 89 html("<tr><td class='ls-mode'>");
90 cgit_print_filemode(mode); 90 cgit_print_filemode(mode);
91 html("</td><td>"); 91 html("</td><td>");
92 if (S_ISGITLINK(mode)) { 92 if (S_ISGITLINK(mode)) {
93 htmlf("<a class='ls-mod' href='"); 93 htmlf("<a class='ls-mod' href='");
94 html_attr(fmt(ctx.repo->module_link, 94 html_attr(fmt(ctx.repo->module_link,
95 name, 95 name,
96 sha1_to_hex(sha1))); 96 sha1_to_hex(sha1)));
97 html("'>"); 97 html("'>");
98 html_txt(name); 98 html_txt(name);
99 html("</a>"); 99 html("</a>");
100 } else if (S_ISDIR(mode)) { 100 } else if (S_ISDIR(mode)) {
101 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 101 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
102 curr_rev, fullpath); 102 curr_rev, fullpath);
103 } else { 103 } else {
104 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head, 104 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head,
105 curr_rev, fullpath); 105 curr_rev, fullpath);
106 } 106 }
107 htmlf("</td><td class='ls-size'>%li</td>", size); 107 htmlf("</td><td class='ls-size'>%li</td>", size);
108 108
109 html("<td>"); 109 html("<td>");
110 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 110 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
111 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 111 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
112 if (ctx.repo->max_stats)
113 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
114 fullpath);
112 html("</td></tr>\n"); 115 html("</td></tr>\n");
113 free(name); 116 free(name);
114 return 0; 117 return 0;
115} 118}
116 119
117static void ls_head() 120static void ls_head()
118{ 121{
119 html("<table summary='tree listing' class='list'>\n"); 122 html("<table summary='tree listing' class='list'>\n");
120 html("<tr class='nohover'>"); 123 html("<tr class='nohover'>");
121 html("<th class='left'>Mode</th>"); 124 html("<th class='left'>Mode</th>");
122 html("<th class='left'>Name</th>"); 125 html("<th class='left'>Name</th>");
123 html("<th class='right'>Size</th>"); 126 html("<th class='right'>Size</th>");
124 html("<th/>"); 127 html("<th/>");
125 html("</tr>\n"); 128 html("</tr>\n");
126 header = 1; 129 header = 1;
127} 130}
128 131
129static void ls_tail() 132static void ls_tail()
130{ 133{
131 if (!header) 134 if (!header)
132 return; 135 return;
133 html("</table>\n"); 136 html("</table>\n");
134 header = 0; 137 header = 0;
135} 138}
136 139
137static void ls_tree(const unsigned char *sha1, char *path) 140static void ls_tree(const unsigned char *sha1, char *path)
138{ 141{
139 struct tree *tree; 142 struct tree *tree;
140 143
141 tree = parse_tree_indirect(sha1); 144 tree = parse_tree_indirect(sha1);
142 if (!tree) { 145 if (!tree) {
143 cgit_print_error(fmt("Not a tree object: %s", 146 cgit_print_error(fmt("Not a tree object: %s",
144 sha1_to_hex(sha1))); 147 sha1_to_hex(sha1)));
145 return; 148 return;
146 } 149 }
147 150
148 ls_head(); 151 ls_head();
149 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 152 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
150 ls_tail(); 153 ls_tail();
151} 154}
152 155
153 156
154static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 157static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
155 const char *pathname, unsigned mode, int stage, 158 const char *pathname, unsigned mode, int stage,
156 void *cbdata) 159 void *cbdata)
157{ 160{
158 static int state; 161 static int state;
159 static char buffer[PATH_MAX]; 162 static char buffer[PATH_MAX];
160 char *url; 163 char *url;
161 164
162 if (state == 0) { 165 if (state == 0) {
163 memcpy(buffer, base, baselen); 166 memcpy(buffer, base, baselen);
164 strcpy(buffer+baselen, pathname); 167 strcpy(buffer+baselen, pathname);
165 url = cgit_pageurl(ctx.qry.repo, "tree", 168 url = cgit_pageurl(ctx.qry.repo, "tree",
166 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 169 fmt("h=%s&amp;path=%s", curr_rev, buffer));
167 html("/"); 170 html("/");
168 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 171 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
169 curr_rev, buffer); 172 curr_rev, buffer);
170 173
171 if (strcmp(match_path, buffer)) 174 if (strcmp(match_path, buffer))
172 return READ_TREE_RECURSIVE; 175 return READ_TREE_RECURSIVE;
173 176
174 if (S_ISDIR(mode)) { 177 if (S_ISDIR(mode)) {
175 state = 1; 178 state = 1;
176 ls_head(); 179 ls_head();
177 return READ_TREE_RECURSIVE; 180 return READ_TREE_RECURSIVE;
178 } else { 181 } else {
179 print_object(sha1, buffer); 182 print_object(sha1, buffer);
180 return 0; 183 return 0;
181 } 184 }
182 } 185 }
183 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 186 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
184 return 0; 187 return 0;
185} 188}
186 189
187 190
188/* 191/*
189 * Show a tree or a blob 192 * Show a tree or a blob
190 * rev: the commit pointing at the root tree object 193 * rev: the commit pointing at the root tree object
191 * path: path to tree or blob 194 * path: path to tree or blob
192 */ 195 */
193void cgit_print_tree(const char *rev, char *path) 196void cgit_print_tree(const char *rev, char *path)
194{ 197{
195 unsigned char sha1[20]; 198 unsigned char sha1[20];
196 struct commit *commit; 199 struct commit *commit;
197 const char *paths[] = {path, NULL}; 200 const char *paths[] = {path, NULL};
198 201
199 if (!rev) 202 if (!rev)
200 rev = ctx.qry.head; 203 rev = ctx.qry.head;
201 204
202 curr_rev = xstrdup(rev); 205 curr_rev = xstrdup(rev);
203 if (get_sha1(rev, sha1)) { 206 if (get_sha1(rev, sha1)) {
204 cgit_print_error(fmt("Invalid revision name: %s", rev)); 207 cgit_print_error(fmt("Invalid revision name: %s", rev));
205 return; 208 return;
206 } 209 }
207 commit = lookup_commit_reference(sha1); 210 commit = lookup_commit_reference(sha1);
208 if (!commit || parse_commit(commit)) { 211 if (!commit || parse_commit(commit)) {
209 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 212 cgit_print_error(fmt("Invalid commit reference: %s", rev));
210 return; 213 return;
211 } 214 }
212 215
213 html("path: <a href='"); 216 html("path: <a href='");
214 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev))); 217 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
215 html("'>root</a>"); 218 html("'>root</a>");
216 219
217 if (path == NULL) { 220 if (path == NULL) {
218 ls_tree(commit->tree->object.sha1, NULL); 221 ls_tree(commit->tree->object.sha1, NULL);
219 return; 222 return;
220 } 223 }
221 224
222 match_path = path; 225 match_path = path;
223 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); 226 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
224 ls_tail(); 227 ls_tail();
225} 228}