summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile1
-rw-r--r--cgit.c1
-rw-r--r--cgit.h1
-rw-r--r--cmd.c7
-rw-r--r--html.c9
-rw-r--r--html.h3
-rw-r--r--ui-clone.c10
-rw-r--r--ui-plain.c82
-rw-r--r--ui-plain.h6
-rw-r--r--ui-shared.c8
-rw-r--r--ui-shared.h2
-rw-r--r--ui-tree.c8
12 files changed, 125 insertions, 13 deletions
diff --git a/Makefile b/Makefile
index b002d44..e4265f7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,124 +1,125 @@
1CGIT_VERSION = v0.7.2 1CGIT_VERSION = v0.7.2
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_CONFIG = /etc/cgitrc 4CGIT_CONFIG = /etc/cgitrc
5CACHE_ROOT = /var/cache/cgit 5CACHE_ROOT = /var/cache/cgit
6SHA1_HEADER = <openssl/sha.h> 6SHA1_HEADER = <openssl/sha.h>
7GIT_VER = 1.6.0.rc1 7GIT_VER = 1.6.0.rc1
8GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 8GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
9 9
10# 10#
11# Let the user override the above settings. 11# Let the user override the above settings.
12# 12#
13-include cgit.conf 13-include cgit.conf
14 14
15# 15#
16# Define a way to invoke make in subdirs quietly, shamelessly ripped 16# Define a way to invoke make in subdirs quietly, shamelessly ripped
17# from git.git 17# from git.git
18# 18#
19QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir 19QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
20QUIET_SUBDIR1 = 20QUIET_SUBDIR1 =
21 21
22ifneq ($(findstring $(MAKEFLAGS),w),w) 22ifneq ($(findstring $(MAKEFLAGS),w),w)
23PRINT_DIR = --no-print-directory 23PRINT_DIR = --no-print-directory
24else # "make -w" 24else # "make -w"
25NO_SUBDIR = : 25NO_SUBDIR = :
26endif 26endif
27 27
28ifndef V 28ifndef V
29 QUIET_CC = @echo ' ' CC $@; 29 QUIET_CC = @echo ' ' CC $@;
30 QUIET_MM = @echo ' ' MM $@; 30 QUIET_MM = @echo ' ' MM $@;
31 QUIET_SUBDIR0 = +@subdir= 31 QUIET_SUBDIR0 = +@subdir=
32 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ 32 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
33 $(MAKE) $(PRINT_DIR) -C $$subdir 33 $(MAKE) $(PRINT_DIR) -C $$subdir
34endif 34endif
35 35
36# 36#
37# Define a pattern rule for automatic dependency building 37# Define a pattern rule for automatic dependency building
38# 38#
39%.d: %.c 39%.d: %.c
40 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 40 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
41 41
42# 42#
43# Define a pattern rule for silent object building 43# Define a pattern rule for silent object building
44# 44#
45%.o: %.c 45%.o: %.c
46 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 46 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
47 47
48 48
49EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto 49EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto
50OBJECTS = 50OBJECTS =
51OBJECTS += cache.o 51OBJECTS += cache.o
52OBJECTS += cgit.o 52OBJECTS += cgit.o
53OBJECTS += cmd.o 53OBJECTS += cmd.o
54OBJECTS += configfile.o 54OBJECTS += configfile.o
55OBJECTS += html.o 55OBJECTS += html.o
56OBJECTS += parsing.o 56OBJECTS += parsing.o
57OBJECTS += shared.o 57OBJECTS += shared.o
58OBJECTS += ui-atom.o 58OBJECTS += ui-atom.o
59OBJECTS += ui-blob.o 59OBJECTS += ui-blob.o
60OBJECTS += ui-clone.o 60OBJECTS += ui-clone.o
61OBJECTS += ui-commit.o 61OBJECTS += ui-commit.o
62OBJECTS += ui-diff.o 62OBJECTS += ui-diff.o
63OBJECTS += ui-log.o 63OBJECTS += ui-log.o
64OBJECTS += ui-patch.o 64OBJECTS += ui-patch.o
65OBJECTS += ui-plain.o
65OBJECTS += ui-refs.o 66OBJECTS += ui-refs.o
66OBJECTS += ui-repolist.o 67OBJECTS += ui-repolist.o
67OBJECTS += ui-shared.o 68OBJECTS += ui-shared.o
68OBJECTS += ui-snapshot.o 69OBJECTS += ui-snapshot.o
69OBJECTS += ui-summary.o 70OBJECTS += ui-summary.o
70OBJECTS += ui-tag.o 71OBJECTS += ui-tag.o
71OBJECTS += ui-tree.o 72OBJECTS += ui-tree.o
72 73
73ifdef NEEDS_LIBICONV 74ifdef NEEDS_LIBICONV
74 EXTLIBS += -liconv 75 EXTLIBS += -liconv
75endif 76endif
76 77
77 78
78.PHONY: all libgit test install uninstall clean force-version get-git 79.PHONY: all libgit test install uninstall clean force-version get-git
79 80
80all: cgit 81all: cgit
81 82
82VERSION: force-version 83VERSION: force-version
83 @./gen-version.sh "$(CGIT_VERSION)" 84 @./gen-version.sh "$(CGIT_VERSION)"
84-include VERSION 85-include VERSION
85 86
86 87
87CFLAGS += -g -Wall -Igit 88CFLAGS += -g -Wall -Igit
88CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 89CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
89CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 90CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
90CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 91CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
91CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 92CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
92CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 93CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
93 94
94 95
95cgit: $(OBJECTS) libgit 96cgit: $(OBJECTS) libgit
96 $(QUIET_CC)$(CC) $(CFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 97 $(QUIET_CC)$(CC) $(CFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
97 98
98cgit.o: VERSION 99cgit.o: VERSION
99 100
100-include $(OBJECTS:.o=.d) 101-include $(OBJECTS:.o=.d)
101 102
102libgit: 103libgit:
103 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a 104 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a
104 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a 105 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a
105 106
106test: all 107test: all
107 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 108 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
108 109
109install: all 110install: all
110 mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH) 111 mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH)
111 install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 112 install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
112 install cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css 113 install cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css
113 install cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png 114 install cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png
114 115
115uninstall: 116uninstall:
116 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 117 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
117 rm -f $(CGIT_SCRIPT_PATH)/cgit.css 118 rm -f $(CGIT_SCRIPT_PATH)/cgit.css
118 rm -f $(CGIT_SCRIPT_PATH)/cgit.png 119 rm -f $(CGIT_SCRIPT_PATH)/cgit.png
119 120
120clean: 121clean:
121 rm -f cgit VERSION *.o *.d 122 rm -f cgit VERSION *.o *.d
122 123
123get-git: 124get-git:
124 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 125 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cgit.c b/cgit.c
index f49fffa..497337b 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,402 +1,403 @@
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 15
16const char *cgit_version = CGIT_VERSION; 16const char *cgit_version = CGIT_VERSION;
17 17
18void config_cb(const char *name, const char *value) 18void config_cb(const char *name, const char *value)
19{ 19{
20 if (!strcmp(name, "root-title")) 20 if (!strcmp(name, "root-title"))
21 ctx.cfg.root_title = xstrdup(value); 21 ctx.cfg.root_title = xstrdup(value);
22 else if (!strcmp(name, "root-desc")) 22 else if (!strcmp(name, "root-desc"))
23 ctx.cfg.root_desc = xstrdup(value); 23 ctx.cfg.root_desc = xstrdup(value);
24 else if (!strcmp(name, "root-readme")) 24 else if (!strcmp(name, "root-readme"))
25 ctx.cfg.root_readme = xstrdup(value); 25 ctx.cfg.root_readme = xstrdup(value);
26 else if (!strcmp(name, "css")) 26 else if (!strcmp(name, "css"))
27 ctx.cfg.css = xstrdup(value); 27 ctx.cfg.css = xstrdup(value);
28 else if (!strcmp(name, "favicon")) 28 else if (!strcmp(name, "favicon"))
29 ctx.cfg.favicon = xstrdup(value); 29 ctx.cfg.favicon = xstrdup(value);
30 else if (!strcmp(name, "footer")) 30 else if (!strcmp(name, "footer"))
31 ctx.cfg.footer = xstrdup(value); 31 ctx.cfg.footer = xstrdup(value);
32 else if (!strcmp(name, "logo")) 32 else if (!strcmp(name, "logo"))
33 ctx.cfg.logo = xstrdup(value); 33 ctx.cfg.logo = xstrdup(value);
34 else if (!strcmp(name, "index-header")) 34 else if (!strcmp(name, "index-header"))
35 ctx.cfg.index_header = xstrdup(value); 35 ctx.cfg.index_header = xstrdup(value);
36 else if (!strcmp(name, "index-info")) 36 else if (!strcmp(name, "index-info"))
37 ctx.cfg.index_info = xstrdup(value); 37 ctx.cfg.index_info = xstrdup(value);
38 else if (!strcmp(name, "logo-link")) 38 else if (!strcmp(name, "logo-link"))
39 ctx.cfg.logo_link = xstrdup(value); 39 ctx.cfg.logo_link = xstrdup(value);
40 else if (!strcmp(name, "module-link")) 40 else if (!strcmp(name, "module-link"))
41 ctx.cfg.module_link = xstrdup(value); 41 ctx.cfg.module_link = xstrdup(value);
42 else if (!strcmp(name, "virtual-root")) { 42 else if (!strcmp(name, "virtual-root")) {
43 ctx.cfg.virtual_root = trim_end(value, '/'); 43 ctx.cfg.virtual_root = trim_end(value, '/');
44 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 44 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
45 ctx.cfg.virtual_root = ""; 45 ctx.cfg.virtual_root = "";
46 } else if (!strcmp(name, "nocache")) 46 } else if (!strcmp(name, "nocache"))
47 ctx.cfg.nocache = atoi(value); 47 ctx.cfg.nocache = atoi(value);
48 else if (!strcmp(name, "snapshots")) 48 else if (!strcmp(name, "snapshots"))
49 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 49 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
50 else if (!strcmp(name, "enable-index-links")) 50 else if (!strcmp(name, "enable-index-links"))
51 ctx.cfg.enable_index_links = atoi(value); 51 ctx.cfg.enable_index_links = atoi(value);
52 else if (!strcmp(name, "enable-log-filecount")) 52 else if (!strcmp(name, "enable-log-filecount"))
53 ctx.cfg.enable_log_filecount = atoi(value); 53 ctx.cfg.enable_log_filecount = atoi(value);
54 else if (!strcmp(name, "enable-log-linecount")) 54 else if (!strcmp(name, "enable-log-linecount"))
55 ctx.cfg.enable_log_linecount = atoi(value); 55 ctx.cfg.enable_log_linecount = atoi(value);
56 else if (!strcmp(name, "cache-size")) 56 else if (!strcmp(name, "cache-size"))
57 ctx.cfg.cache_size = atoi(value); 57 ctx.cfg.cache_size = atoi(value);
58 else if (!strcmp(name, "cache-root")) 58 else if (!strcmp(name, "cache-root"))
59 ctx.cfg.cache_root = xstrdup(value); 59 ctx.cfg.cache_root = xstrdup(value);
60 else if (!strcmp(name, "cache-root-ttl")) 60 else if (!strcmp(name, "cache-root-ttl"))
61 ctx.cfg.cache_root_ttl = atoi(value); 61 ctx.cfg.cache_root_ttl = atoi(value);
62 else if (!strcmp(name, "cache-repo-ttl")) 62 else if (!strcmp(name, "cache-repo-ttl"))
63 ctx.cfg.cache_repo_ttl = atoi(value); 63 ctx.cfg.cache_repo_ttl = atoi(value);
64 else if (!strcmp(name, "cache-static-ttl")) 64 else if (!strcmp(name, "cache-static-ttl"))
65 ctx.cfg.cache_static_ttl = atoi(value); 65 ctx.cfg.cache_static_ttl = atoi(value);
66 else if (!strcmp(name, "cache-dynamic-ttl")) 66 else if (!strcmp(name, "cache-dynamic-ttl"))
67 ctx.cfg.cache_dynamic_ttl = atoi(value); 67 ctx.cfg.cache_dynamic_ttl = atoi(value);
68 else if (!strcmp(name, "max-message-length")) 68 else if (!strcmp(name, "max-message-length"))
69 ctx.cfg.max_msg_len = atoi(value); 69 ctx.cfg.max_msg_len = atoi(value);
70 else if (!strcmp(name, "max-repodesc-length")) 70 else if (!strcmp(name, "max-repodesc-length"))
71 ctx.cfg.max_repodesc_len = atoi(value); 71 ctx.cfg.max_repodesc_len = atoi(value);
72 else if (!strcmp(name, "max-repo-count")) 72 else if (!strcmp(name, "max-repo-count"))
73 ctx.cfg.max_repo_count = atoi(value); 73 ctx.cfg.max_repo_count = atoi(value);
74 else if (!strcmp(name, "max-commit-count")) 74 else if (!strcmp(name, "max-commit-count"))
75 ctx.cfg.max_commit_count = atoi(value); 75 ctx.cfg.max_commit_count = atoi(value);
76 else if (!strcmp(name, "summary-log")) 76 else if (!strcmp(name, "summary-log"))
77 ctx.cfg.summary_log = atoi(value); 77 ctx.cfg.summary_log = atoi(value);
78 else if (!strcmp(name, "summary-branches")) 78 else if (!strcmp(name, "summary-branches"))
79 ctx.cfg.summary_branches = atoi(value); 79 ctx.cfg.summary_branches = atoi(value);
80 else if (!strcmp(name, "summary-tags")) 80 else if (!strcmp(name, "summary-tags"))
81 ctx.cfg.summary_tags = atoi(value); 81 ctx.cfg.summary_tags = atoi(value);
82 else if (!strcmp(name, "agefile")) 82 else if (!strcmp(name, "agefile"))
83 ctx.cfg.agefile = xstrdup(value); 83 ctx.cfg.agefile = xstrdup(value);
84 else if (!strcmp(name, "renamelimit")) 84 else if (!strcmp(name, "renamelimit"))
85 ctx.cfg.renamelimit = atoi(value); 85 ctx.cfg.renamelimit = atoi(value);
86 else if (!strcmp(name, "robots")) 86 else if (!strcmp(name, "robots"))
87 ctx.cfg.robots = xstrdup(value); 87 ctx.cfg.robots = xstrdup(value);
88 else if (!strcmp(name, "clone-prefix")) 88 else if (!strcmp(name, "clone-prefix"))
89 ctx.cfg.clone_prefix = xstrdup(value); 89 ctx.cfg.clone_prefix = xstrdup(value);
90 else if (!strcmp(name, "local-time")) 90 else if (!strcmp(name, "local-time"))
91 ctx.cfg.local_time = atoi(value); 91 ctx.cfg.local_time = atoi(value);
92 else if (!strcmp(name, "repo.group")) 92 else if (!strcmp(name, "repo.group"))
93 ctx.cfg.repo_group = xstrdup(value); 93 ctx.cfg.repo_group = xstrdup(value);
94 else if (!strcmp(name, "repo.url")) 94 else if (!strcmp(name, "repo.url"))
95 ctx.repo = cgit_add_repo(value); 95 ctx.repo = cgit_add_repo(value);
96 else if (!strcmp(name, "repo.name")) 96 else if (!strcmp(name, "repo.name"))
97 ctx.repo->name = xstrdup(value); 97 ctx.repo->name = xstrdup(value);
98 else if (ctx.repo && !strcmp(name, "repo.path")) 98 else if (ctx.repo && !strcmp(name, "repo.path"))
99 ctx.repo->path = trim_end(value, '/'); 99 ctx.repo->path = trim_end(value, '/');
100 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 100 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
101 ctx.repo->clone_url = xstrdup(value); 101 ctx.repo->clone_url = xstrdup(value);
102 else if (ctx.repo && !strcmp(name, "repo.desc")) 102 else if (ctx.repo && !strcmp(name, "repo.desc"))
103 ctx.repo->desc = xstrdup(value); 103 ctx.repo->desc = xstrdup(value);
104 else if (ctx.repo && !strcmp(name, "repo.owner")) 104 else if (ctx.repo && !strcmp(name, "repo.owner"))
105 ctx.repo->owner = xstrdup(value); 105 ctx.repo->owner = xstrdup(value);
106 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 106 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
107 ctx.repo->defbranch = xstrdup(value); 107 ctx.repo->defbranch = xstrdup(value);
108 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 108 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
109 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 109 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
110 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 110 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
111 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 111 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
112 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 112 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
113 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 113 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
114 else if (ctx.repo && !strcmp(name, "repo.module-link")) 114 else if (ctx.repo && !strcmp(name, "repo.module-link"))
115 ctx.repo->module_link= xstrdup(value); 115 ctx.repo->module_link= xstrdup(value);
116 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 116 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
117 if (*value == '/') 117 if (*value == '/')
118 ctx.repo->readme = xstrdup(value); 118 ctx.repo->readme = xstrdup(value);
119 else 119 else
120 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 120 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
121 } else if (!strcmp(name, "include")) 121 } else if (!strcmp(name, "include"))
122 parse_configfile(value, config_cb); 122 parse_configfile(value, config_cb);
123} 123}
124 124
125static void querystring_cb(const char *name, const char *value) 125static void querystring_cb(const char *name, const char *value)
126{ 126{
127 if (!strcmp(name,"r")) { 127 if (!strcmp(name,"r")) {
128 ctx.qry.repo = xstrdup(value); 128 ctx.qry.repo = xstrdup(value);
129 ctx.repo = cgit_get_repoinfo(value); 129 ctx.repo = cgit_get_repoinfo(value);
130 } else if (!strcmp(name, "p")) { 130 } else if (!strcmp(name, "p")) {
131 ctx.qry.page = xstrdup(value); 131 ctx.qry.page = xstrdup(value);
132 } else if (!strcmp(name, "url")) { 132 } else if (!strcmp(name, "url")) {
133 cgit_parse_url(value); 133 cgit_parse_url(value);
134 } else if (!strcmp(name, "qt")) { 134 } else if (!strcmp(name, "qt")) {
135 ctx.qry.grep = xstrdup(value); 135 ctx.qry.grep = xstrdup(value);
136 } else if (!strcmp(name, "q")) { 136 } else if (!strcmp(name, "q")) {
137 ctx.qry.search = xstrdup(value); 137 ctx.qry.search = xstrdup(value);
138 } else if (!strcmp(name, "h")) { 138 } else if (!strcmp(name, "h")) {
139 ctx.qry.head = xstrdup(value); 139 ctx.qry.head = xstrdup(value);
140 ctx.qry.has_symref = 1; 140 ctx.qry.has_symref = 1;
141 } else if (!strcmp(name, "id")) { 141 } else if (!strcmp(name, "id")) {
142 ctx.qry.sha1 = xstrdup(value); 142 ctx.qry.sha1 = xstrdup(value);
143 ctx.qry.has_sha1 = 1; 143 ctx.qry.has_sha1 = 1;
144 } else if (!strcmp(name, "id2")) { 144 } else if (!strcmp(name, "id2")) {
145 ctx.qry.sha2 = xstrdup(value); 145 ctx.qry.sha2 = xstrdup(value);
146 ctx.qry.has_sha1 = 1; 146 ctx.qry.has_sha1 = 1;
147 } else if (!strcmp(name, "ofs")) { 147 } else if (!strcmp(name, "ofs")) {
148 ctx.qry.ofs = atoi(value); 148 ctx.qry.ofs = atoi(value);
149 } else if (!strcmp(name, "path")) { 149 } else if (!strcmp(name, "path")) {
150 ctx.qry.path = trim_end(value, '/'); 150 ctx.qry.path = trim_end(value, '/');
151 } else if (!strcmp(name, "name")) { 151 } else if (!strcmp(name, "name")) {
152 ctx.qry.name = xstrdup(value); 152 ctx.qry.name = xstrdup(value);
153 } else if (!strcmp(name, "mimetype")) { 153 } else if (!strcmp(name, "mimetype")) {
154 ctx.qry.mimetype = xstrdup(value); 154 ctx.qry.mimetype = xstrdup(value);
155 } 155 }
156} 156}
157 157
158static void prepare_context(struct cgit_context *ctx) 158static void prepare_context(struct cgit_context *ctx)
159{ 159{
160 memset(ctx, 0, sizeof(ctx)); 160 memset(ctx, 0, sizeof(ctx));
161 ctx->cfg.agefile = "info/web/last-modified"; 161 ctx->cfg.agefile = "info/web/last-modified";
162 ctx->cfg.nocache = 0; 162 ctx->cfg.nocache = 0;
163 ctx->cfg.cache_size = 0; 163 ctx->cfg.cache_size = 0;
164 ctx->cfg.cache_dynamic_ttl = 5; 164 ctx->cfg.cache_dynamic_ttl = 5;
165 ctx->cfg.cache_max_create_time = 5; 165 ctx->cfg.cache_max_create_time = 5;
166 ctx->cfg.cache_repo_ttl = 5; 166 ctx->cfg.cache_repo_ttl = 5;
167 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 167 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
168 ctx->cfg.cache_root_ttl = 5; 168 ctx->cfg.cache_root_ttl = 5;
169 ctx->cfg.cache_static_ttl = -1; 169 ctx->cfg.cache_static_ttl = -1;
170 ctx->cfg.css = "/cgit.css"; 170 ctx->cfg.css = "/cgit.css";
171 ctx->cfg.logo = "/git-logo.png"; 171 ctx->cfg.logo = "/git-logo.png";
172 ctx->cfg.local_time = 0; 172 ctx->cfg.local_time = 0;
173 ctx->cfg.max_repo_count = 50; 173 ctx->cfg.max_repo_count = 50;
174 ctx->cfg.max_commit_count = 50; 174 ctx->cfg.max_commit_count = 50;
175 ctx->cfg.max_lock_attempts = 5; 175 ctx->cfg.max_lock_attempts = 5;
176 ctx->cfg.max_msg_len = 80; 176 ctx->cfg.max_msg_len = 80;
177 ctx->cfg.max_repodesc_len = 80; 177 ctx->cfg.max_repodesc_len = 80;
178 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 178 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
179 ctx->cfg.renamelimit = -1; 179 ctx->cfg.renamelimit = -1;
180 ctx->cfg.robots = "index, nofollow"; 180 ctx->cfg.robots = "index, nofollow";
181 ctx->cfg.root_title = "Git repository browser"; 181 ctx->cfg.root_title = "Git repository browser";
182 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 182 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
183 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 183 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
184 ctx->cfg.summary_branches = 10; 184 ctx->cfg.summary_branches = 10;
185 ctx->cfg.summary_log = 10; 185 ctx->cfg.summary_log = 10;
186 ctx->cfg.summary_tags = 10; 186 ctx->cfg.summary_tags = 10;
187 ctx->page.mimetype = "text/html"; 187 ctx->page.mimetype = "text/html";
188 ctx->page.charset = PAGE_ENCODING; 188 ctx->page.charset = PAGE_ENCODING;
189 ctx->page.filename = NULL; 189 ctx->page.filename = NULL;
190 ctx->page.size = 0;
190 ctx->page.modified = time(NULL); 191 ctx->page.modified = time(NULL);
191 ctx->page.expires = ctx->page.modified; 192 ctx->page.expires = ctx->page.modified;
192} 193}
193 194
194struct refmatch { 195struct refmatch {
195 char *req_ref; 196 char *req_ref;
196 char *first_ref; 197 char *first_ref;
197 int match; 198 int match;
198}; 199};
199 200
200int find_current_ref(const char *refname, const unsigned char *sha1, 201int find_current_ref(const char *refname, const unsigned char *sha1,
201 int flags, void *cb_data) 202 int flags, void *cb_data)
202{ 203{
203 struct refmatch *info; 204 struct refmatch *info;
204 205
205 info = (struct refmatch *)cb_data; 206 info = (struct refmatch *)cb_data;
206 if (!strcmp(refname, info->req_ref)) 207 if (!strcmp(refname, info->req_ref))
207 info->match = 1; 208 info->match = 1;
208 if (!info->first_ref) 209 if (!info->first_ref)
209 info->first_ref = xstrdup(refname); 210 info->first_ref = xstrdup(refname);
210 return info->match; 211 return info->match;
211} 212}
212 213
213char *find_default_branch(struct cgit_repo *repo) 214char *find_default_branch(struct cgit_repo *repo)
214{ 215{
215 struct refmatch info; 216 struct refmatch info;
216 char *ref; 217 char *ref;
217 218
218 info.req_ref = repo->defbranch; 219 info.req_ref = repo->defbranch;
219 info.first_ref = NULL; 220 info.first_ref = NULL;
220 info.match = 0; 221 info.match = 0;
221 for_each_branch_ref(find_current_ref, &info); 222 for_each_branch_ref(find_current_ref, &info);
222 if (info.match) 223 if (info.match)
223 ref = info.req_ref; 224 ref = info.req_ref;
224 else 225 else
225 ref = info.first_ref; 226 ref = info.first_ref;
226 if (ref) 227 if (ref)
227 ref = xstrdup(ref); 228 ref = xstrdup(ref);
228 return ref; 229 return ref;
229} 230}
230 231
231static int prepare_repo_cmd(struct cgit_context *ctx) 232static int prepare_repo_cmd(struct cgit_context *ctx)
232{ 233{
233 char *tmp; 234 char *tmp;
234 unsigned char sha1[20]; 235 unsigned char sha1[20];
235 int nongit = 0; 236 int nongit = 0;
236 237
237 setenv("GIT_DIR", ctx->repo->path, 1); 238 setenv("GIT_DIR", ctx->repo->path, 1);
238 setup_git_directory_gently(&nongit); 239 setup_git_directory_gently(&nongit);
239 if (nongit) { 240 if (nongit) {
240 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 241 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
241 "config error"); 242 "config error");
242 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 243 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
243 ctx->repo = NULL; 244 ctx->repo = NULL;
244 cgit_print_http_headers(ctx); 245 cgit_print_http_headers(ctx);
245 cgit_print_docstart(ctx); 246 cgit_print_docstart(ctx);
246 cgit_print_pageheader(ctx); 247 cgit_print_pageheader(ctx);
247 cgit_print_error(tmp); 248 cgit_print_error(tmp);
248 cgit_print_docend(); 249 cgit_print_docend();
249 return 1; 250 return 1;
250 } 251 }
251 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 252 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
252 253
253 if (!ctx->qry.head) { 254 if (!ctx->qry.head) {
254 ctx->qry.head = find_default_branch(ctx->repo); 255 ctx->qry.head = find_default_branch(ctx->repo);
255 ctx->repo->defbranch = ctx->qry.head; 256 ctx->repo->defbranch = ctx->qry.head;
256 } 257 }
257 258
258 if (!ctx->qry.head) { 259 if (!ctx->qry.head) {
259 cgit_print_http_headers(ctx); 260 cgit_print_http_headers(ctx);
260 cgit_print_docstart(ctx); 261 cgit_print_docstart(ctx);
261 cgit_print_pageheader(ctx); 262 cgit_print_pageheader(ctx);
262 cgit_print_error("Repository seems to be empty"); 263 cgit_print_error("Repository seems to be empty");
263 cgit_print_docend(); 264 cgit_print_docend();
264 return 1; 265 return 1;
265 } 266 }
266 267
267 if (get_sha1(ctx->qry.head, sha1)) { 268 if (get_sha1(ctx->qry.head, sha1)) {
268 tmp = xstrdup(ctx->qry.head); 269 tmp = xstrdup(ctx->qry.head);
269 ctx->qry.head = ctx->repo->defbranch; 270 ctx->qry.head = ctx->repo->defbranch;
270 cgit_print_http_headers(ctx); 271 cgit_print_http_headers(ctx);
271 cgit_print_docstart(ctx); 272 cgit_print_docstart(ctx);
272 cgit_print_pageheader(ctx); 273 cgit_print_pageheader(ctx);
273 cgit_print_error(fmt("Invalid branch: %s", tmp)); 274 cgit_print_error(fmt("Invalid branch: %s", tmp));
274 cgit_print_docend(); 275 cgit_print_docend();
275 return 1; 276 return 1;
276 } 277 }
277 return 0; 278 return 0;
278} 279}
279 280
280static void process_request(void *cbdata) 281static void process_request(void *cbdata)
281{ 282{
282 struct cgit_context *ctx = cbdata; 283 struct cgit_context *ctx = cbdata;
283 struct cgit_cmd *cmd; 284 struct cgit_cmd *cmd;
284 285
285 cmd = cgit_get_cmd(ctx); 286 cmd = cgit_get_cmd(ctx);
286 if (!cmd) { 287 if (!cmd) {
287 ctx->page.title = "cgit error"; 288 ctx->page.title = "cgit error";
288 ctx->repo = NULL; 289 ctx->repo = NULL;
289 cgit_print_http_headers(ctx); 290 cgit_print_http_headers(ctx);
290 cgit_print_docstart(ctx); 291 cgit_print_docstart(ctx);
291 cgit_print_pageheader(ctx); 292 cgit_print_pageheader(ctx);
292 cgit_print_error("Invalid request"); 293 cgit_print_error("Invalid request");
293 cgit_print_docend(); 294 cgit_print_docend();
294 return; 295 return;
295 } 296 }
296 297
297 if (cmd->want_repo && !ctx->repo) { 298 if (cmd->want_repo && !ctx->repo) {
298 cgit_print_http_headers(ctx); 299 cgit_print_http_headers(ctx);
299 cgit_print_docstart(ctx); 300 cgit_print_docstart(ctx);
300 cgit_print_pageheader(ctx); 301 cgit_print_pageheader(ctx);
301 cgit_print_error(fmt("No repository selected")); 302 cgit_print_error(fmt("No repository selected"));
302 cgit_print_docend(); 303 cgit_print_docend();
303 return; 304 return;
304 } 305 }
305 306
306 if (ctx->repo && prepare_repo_cmd(ctx)) 307 if (ctx->repo && prepare_repo_cmd(ctx))
307 return; 308 return;
308 309
309 if (cmd->want_layout) { 310 if (cmd->want_layout) {
310 cgit_print_http_headers(ctx); 311 cgit_print_http_headers(ctx);
311 cgit_print_docstart(ctx); 312 cgit_print_docstart(ctx);
312 cgit_print_pageheader(ctx); 313 cgit_print_pageheader(ctx);
313 } 314 }
314 315
315 cmd->fn(ctx); 316 cmd->fn(ctx);
316 317
317 if (cmd->want_layout) 318 if (cmd->want_layout)
318 cgit_print_docend(); 319 cgit_print_docend();
319} 320}
320 321
321static void cgit_parse_args(int argc, const char **argv) 322static void cgit_parse_args(int argc, const char **argv)
322{ 323{
323 int i; 324 int i;
324 325
325 for (i = 1; i < argc; i++) { 326 for (i = 1; i < argc; i++) {
326 if (!strncmp(argv[i], "--cache=", 8)) { 327 if (!strncmp(argv[i], "--cache=", 8)) {
327 ctx.cfg.cache_root = xstrdup(argv[i]+8); 328 ctx.cfg.cache_root = xstrdup(argv[i]+8);
328 } 329 }
329 if (!strcmp(argv[i], "--nocache")) { 330 if (!strcmp(argv[i], "--nocache")) {
330 ctx.cfg.nocache = 1; 331 ctx.cfg.nocache = 1;
331 } 332 }
332 if (!strncmp(argv[i], "--query=", 8)) { 333 if (!strncmp(argv[i], "--query=", 8)) {
333 ctx.qry.raw = xstrdup(argv[i]+8); 334 ctx.qry.raw = xstrdup(argv[i]+8);
334 } 335 }
335 if (!strncmp(argv[i], "--repo=", 7)) { 336 if (!strncmp(argv[i], "--repo=", 7)) {
336 ctx.qry.repo = xstrdup(argv[i]+7); 337 ctx.qry.repo = xstrdup(argv[i]+7);
337 } 338 }
338 if (!strncmp(argv[i], "--page=", 7)) { 339 if (!strncmp(argv[i], "--page=", 7)) {
339 ctx.qry.page = xstrdup(argv[i]+7); 340 ctx.qry.page = xstrdup(argv[i]+7);
340 } 341 }
341 if (!strncmp(argv[i], "--head=", 7)) { 342 if (!strncmp(argv[i], "--head=", 7)) {
342 ctx.qry.head = xstrdup(argv[i]+7); 343 ctx.qry.head = xstrdup(argv[i]+7);
343 ctx.qry.has_symref = 1; 344 ctx.qry.has_symref = 1;
344 } 345 }
345 if (!strncmp(argv[i], "--sha1=", 7)) { 346 if (!strncmp(argv[i], "--sha1=", 7)) {
346 ctx.qry.sha1 = xstrdup(argv[i]+7); 347 ctx.qry.sha1 = xstrdup(argv[i]+7);
347 ctx.qry.has_sha1 = 1; 348 ctx.qry.has_sha1 = 1;
348 } 349 }
349 if (!strncmp(argv[i], "--ofs=", 6)) { 350 if (!strncmp(argv[i], "--ofs=", 6)) {
350 ctx.qry.ofs = atoi(argv[i]+6); 351 ctx.qry.ofs = atoi(argv[i]+6);
351 } 352 }
352 } 353 }
353} 354}
354 355
355static int calc_ttl() 356static int calc_ttl()
356{ 357{
357 if (!ctx.repo) 358 if (!ctx.repo)
358 return ctx.cfg.cache_root_ttl; 359 return ctx.cfg.cache_root_ttl;
359 360
360 if (!ctx.qry.page) 361 if (!ctx.qry.page)
361 return ctx.cfg.cache_repo_ttl; 362 return ctx.cfg.cache_repo_ttl;
362 363
363 if (ctx.qry.has_symref) 364 if (ctx.qry.has_symref)
364 return ctx.cfg.cache_dynamic_ttl; 365 return ctx.cfg.cache_dynamic_ttl;
365 366
366 if (ctx.qry.has_sha1) 367 if (ctx.qry.has_sha1)
367 return ctx.cfg.cache_static_ttl; 368 return ctx.cfg.cache_static_ttl;
368 369
369 return ctx.cfg.cache_repo_ttl; 370 return ctx.cfg.cache_repo_ttl;
370} 371}
371 372
372int main(int argc, const char **argv) 373int main(int argc, const char **argv)
373{ 374{
374 const char *cgit_config_env = getenv("CGIT_CONFIG"); 375 const char *cgit_config_env = getenv("CGIT_CONFIG");
375 int err, ttl; 376 int err, ttl;
376 377
377 prepare_context(&ctx); 378 prepare_context(&ctx);
378 cgit_repolist.length = 0; 379 cgit_repolist.length = 0;
379 cgit_repolist.count = 0; 380 cgit_repolist.count = 0;
380 cgit_repolist.repos = NULL; 381 cgit_repolist.repos = NULL;
381 382
382 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 383 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
383 config_cb); 384 config_cb);
384 ctx.repo = NULL; 385 ctx.repo = NULL;
385 if (getenv("SCRIPT_NAME")) 386 if (getenv("SCRIPT_NAME"))
386 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME")); 387 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
387 if (getenv("QUERY_STRING")) 388 if (getenv("QUERY_STRING"))
388 ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); 389 ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
389 cgit_parse_args(argc, argv); 390 cgit_parse_args(argc, argv);
390 http_parse_querystring(ctx.qry.raw, querystring_cb); 391 http_parse_querystring(ctx.qry.raw, querystring_cb);
391 392
392 ttl = calc_ttl(); 393 ttl = calc_ttl();
393 ctx.page.expires += ttl*60; 394 ctx.page.expires += ttl*60;
394 if (ctx.cfg.nocache) 395 if (ctx.cfg.nocache)
395 ctx.cfg.cache_size = 0; 396 ctx.cfg.cache_size = 0;
396 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 397 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
397 ctx.qry.raw, ttl, process_request, &ctx); 398 ctx.qry.raw, ttl, process_request, &ctx);
398 if (err) 399 if (err)
399 cgit_print_error(fmt("Error processing page: %s (%d)", 400 cgit_print_error(fmt("Error processing page: %s (%d)",
400 strerror(err), err)); 401 strerror(err), err));
401 return err; 402 return err;
402} 403}
diff --git a/cgit.h b/cgit.h
index a1fa841..1615616 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,240 +1,241 @@
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}; 64};
65 65
66struct cgit_repolist { 66struct cgit_repolist {
67 int length; 67 int length;
68 int count; 68 int count;
69 struct cgit_repo *repos; 69 struct cgit_repo *repos;
70}; 70};
71 71
72struct commitinfo { 72struct commitinfo {
73 struct commit *commit; 73 struct commit *commit;
74 char *author; 74 char *author;
75 char *author_email; 75 char *author_email;
76 unsigned long author_date; 76 unsigned long author_date;
77 char *committer; 77 char *committer;
78 char *committer_email; 78 char *committer_email;
79 unsigned long committer_date; 79 unsigned long committer_date;
80 char *subject; 80 char *subject;
81 char *msg; 81 char *msg;
82 char *msg_encoding; 82 char *msg_encoding;
83}; 83};
84 84
85struct taginfo { 85struct taginfo {
86 char *tagger; 86 char *tagger;
87 char *tagger_email; 87 char *tagger_email;
88 int tagger_date; 88 int tagger_date;
89 char *msg; 89 char *msg;
90}; 90};
91 91
92struct refinfo { 92struct refinfo {
93 const char *refname; 93 const char *refname;
94 struct object *object; 94 struct object *object;
95 union { 95 union {
96 struct taginfo *tag; 96 struct taginfo *tag;
97 struct commitinfo *commit; 97 struct commitinfo *commit;
98 }; 98 };
99}; 99};
100 100
101struct reflist { 101struct reflist {
102 struct refinfo **refs; 102 struct refinfo **refs;
103 int alloc; 103 int alloc;
104 int count; 104 int count;
105}; 105};
106 106
107struct cgit_query { 107struct cgit_query {
108 int has_symref; 108 int has_symref;
109 int has_sha1; 109 int has_sha1;
110 char *raw; 110 char *raw;
111 char *repo; 111 char *repo;
112 char *page; 112 char *page;
113 char *search; 113 char *search;
114 char *grep; 114 char *grep;
115 char *head; 115 char *head;
116 char *sha1; 116 char *sha1;
117 char *sha2; 117 char *sha2;
118 char *path; 118 char *path;
119 char *name; 119 char *name;
120 char *mimetype; 120 char *mimetype;
121 int ofs; 121 int ofs;
122}; 122};
123 123
124struct cgit_config { 124struct cgit_config {
125 char *agefile; 125 char *agefile;
126 char *cache_root; 126 char *cache_root;
127 char *clone_prefix; 127 char *clone_prefix;
128 char *css; 128 char *css;
129 char *favicon; 129 char *favicon;
130 char *footer; 130 char *footer;
131 char *index_header; 131 char *index_header;
132 char *index_info; 132 char *index_info;
133 char *logo; 133 char *logo;
134 char *logo_link; 134 char *logo_link;
135 char *module_link; 135 char *module_link;
136 char *repo_group; 136 char *repo_group;
137 char *robots; 137 char *robots;
138 char *root_title; 138 char *root_title;
139 char *root_desc; 139 char *root_desc;
140 char *root_readme; 140 char *root_readme;
141 char *script_name; 141 char *script_name;
142 char *virtual_root; 142 char *virtual_root;
143 int cache_size; 143 int cache_size;
144 int cache_dynamic_ttl; 144 int cache_dynamic_ttl;
145 int cache_max_create_time; 145 int cache_max_create_time;
146 int cache_repo_ttl; 146 int cache_repo_ttl;
147 int cache_root_ttl; 147 int cache_root_ttl;
148 int cache_static_ttl; 148 int cache_static_ttl;
149 int enable_index_links; 149 int enable_index_links;
150 int enable_log_filecount; 150 int enable_log_filecount;
151 int enable_log_linecount; 151 int enable_log_linecount;
152 int local_time; 152 int local_time;
153 int max_repo_count; 153 int max_repo_count;
154 int max_commit_count; 154 int max_commit_count;
155 int max_lock_attempts; 155 int max_lock_attempts;
156 int max_msg_len; 156 int max_msg_len;
157 int max_repodesc_len; 157 int max_repodesc_len;
158 int nocache; 158 int nocache;
159 int renamelimit; 159 int renamelimit;
160 int snapshots; 160 int snapshots;
161 int summary_branches; 161 int summary_branches;
162 int summary_log; 162 int summary_log;
163 int summary_tags; 163 int summary_tags;
164}; 164};
165 165
166struct cgit_page { 166struct cgit_page {
167 time_t modified; 167 time_t modified;
168 time_t expires; 168 time_t expires;
169 size_t size;
169 char *mimetype; 170 char *mimetype;
170 char *charset; 171 char *charset;
171 char *filename; 172 char *filename;
172 char *title; 173 char *title;
173}; 174};
174 175
175struct cgit_context { 176struct cgit_context {
176 struct cgit_query qry; 177 struct cgit_query qry;
177 struct cgit_config cfg; 178 struct cgit_config cfg;
178 struct cgit_repo *repo; 179 struct cgit_repo *repo;
179 struct cgit_page page; 180 struct cgit_page page;
180}; 181};
181 182
182struct cgit_snapshot_format { 183struct cgit_snapshot_format {
183 const char *suffix; 184 const char *suffix;
184 const char *mimetype; 185 const char *mimetype;
185 write_archive_fn_t write_func; 186 write_archive_fn_t write_func;
186 int bit; 187 int bit;
187}; 188};
188 189
189extern const char *cgit_version; 190extern const char *cgit_version;
190 191
191extern struct cgit_repolist cgit_repolist; 192extern struct cgit_repolist cgit_repolist;
192extern struct cgit_context ctx; 193extern struct cgit_context ctx;
193extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 194extern const struct cgit_snapshot_format cgit_snapshot_formats[];
194 195
195extern struct cgit_repo *cgit_add_repo(const char *url); 196extern struct cgit_repo *cgit_add_repo(const char *url);
196extern struct cgit_repo *cgit_get_repoinfo(const char *url); 197extern struct cgit_repo *cgit_get_repoinfo(const char *url);
197extern void cgit_repo_config_cb(const char *name, const char *value); 198extern void cgit_repo_config_cb(const char *name, const char *value);
198 199
199extern int chk_zero(int result, char *msg); 200extern int chk_zero(int result, char *msg);
200extern int chk_positive(int result, char *msg); 201extern int chk_positive(int result, char *msg);
201extern int chk_non_negative(int result, char *msg); 202extern int chk_non_negative(int result, char *msg);
202 203
203extern char *trim_end(const char *str, char c); 204extern char *trim_end(const char *str, char c);
204extern char *strlpart(char *txt, int maxlen); 205extern char *strlpart(char *txt, int maxlen);
205extern char *strrpart(char *txt, int maxlen); 206extern char *strrpart(char *txt, int maxlen);
206 207
207extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 208extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
208extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 209extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
209 int flags, void *cb_data); 210 int flags, void *cb_data);
210 211
211extern void *cgit_free_commitinfo(struct commitinfo *info); 212extern void *cgit_free_commitinfo(struct commitinfo *info);
212 213
213extern int cgit_diff_files(const unsigned char *old_sha1, 214extern int cgit_diff_files(const unsigned char *old_sha1,
214 const unsigned char *new_sha1, 215 const unsigned char *new_sha1,
215 linediff_fn fn); 216 linediff_fn fn);
216 217
217extern void cgit_diff_tree(const unsigned char *old_sha1, 218extern void cgit_diff_tree(const unsigned char *old_sha1,
218 const unsigned char *new_sha1, 219 const unsigned char *new_sha1,
219 filepair_fn fn, const char *prefix); 220 filepair_fn fn, const char *prefix);
220 221
221extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 222extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
222 223
223extern char *fmt(const char *format,...); 224extern char *fmt(const char *format,...);
224 225
225extern struct commitinfo *cgit_parse_commit(struct commit *commit); 226extern struct commitinfo *cgit_parse_commit(struct commit *commit);
226extern struct taginfo *cgit_parse_tag(struct tag *tag); 227extern struct taginfo *cgit_parse_tag(struct tag *tag);
227extern void cgit_parse_url(const char *url); 228extern void cgit_parse_url(const char *url);
228 229
229extern const char *cgit_repobasename(const char *reponame); 230extern const char *cgit_repobasename(const char *reponame);
230 231
231extern int cgit_parse_snapshots_mask(const char *str); 232extern int cgit_parse_snapshots_mask(const char *str);
232 233
233/* libgit.a either links against or compiles its own implementation of 234/* libgit.a either links against or compiles its own implementation of
234 * strcasestr(), and we'd like to reuse it. Simply re-declaring it 235 * strcasestr(), and we'd like to reuse it. Simply re-declaring it
235 * seems to do the trick. 236 * seems to do the trick.
236 */ 237 */
237extern char *strcasestr(const char *haystack, const char *needle); 238extern char *strcasestr(const char *haystack, const char *needle);
238 239
239 240
240#endif /* CGIT_H */ 241#endif /* CGIT_H */
diff --git a/cmd.c b/cmd.c
index 40ac53e..a989220 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,158 +1,165 @@
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-refs.h" 21#include "ui-refs.h"
21#include "ui-repolist.h" 22#include "ui-repolist.h"
22#include "ui-snapshot.h" 23#include "ui-snapshot.h"
23#include "ui-summary.h" 24#include "ui-summary.h"
24#include "ui-tag.h" 25#include "ui-tag.h"
25#include "ui-tree.h" 26#include "ui-tree.h"
26 27
27static void HEAD_fn(struct cgit_context *ctx) 28static void HEAD_fn(struct cgit_context *ctx)
28{ 29{
29 cgit_clone_head(ctx); 30 cgit_clone_head(ctx);
30} 31}
31 32
32static void atom_fn(struct cgit_context *ctx) 33static void atom_fn(struct cgit_context *ctx)
33{ 34{
34 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); 35 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10);
35} 36}
36 37
37static void about_fn(struct cgit_context *ctx) 38static void about_fn(struct cgit_context *ctx)
38{ 39{
39 if (ctx->repo) 40 if (ctx->repo)
40 cgit_print_repo_readme(); 41 cgit_print_repo_readme();
41 else 42 else
42 cgit_print_site_readme(); 43 cgit_print_site_readme();
43} 44}
44 45
45static void blob_fn(struct cgit_context *ctx) 46static void blob_fn(struct cgit_context *ctx)
46{ 47{
47 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 48 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
48} 49}
49 50
50static void commit_fn(struct cgit_context *ctx) 51static void commit_fn(struct cgit_context *ctx)
51{ 52{
52 cgit_print_commit(ctx->qry.sha1); 53 cgit_print_commit(ctx->qry.sha1);
53} 54}
54 55
55static void diff_fn(struct cgit_context *ctx) 56static void diff_fn(struct cgit_context *ctx)
56{ 57{
57 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 58 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
58} 59}
59 60
60static void info_fn(struct cgit_context *ctx) 61static void info_fn(struct cgit_context *ctx)
61{ 62{
62 cgit_clone_info(ctx); 63 cgit_clone_info(ctx);
63} 64}
64 65
65static void log_fn(struct cgit_context *ctx) 66static void log_fn(struct cgit_context *ctx)
66{ 67{
67 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, 68 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
68 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 69 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1);
69} 70}
70 71
71static void ls_cache_fn(struct cgit_context *ctx) 72static void ls_cache_fn(struct cgit_context *ctx)
72{ 73{
73 ctx->page.mimetype = "text/plain"; 74 ctx->page.mimetype = "text/plain";
74 ctx->page.filename = "ls-cache.txt"; 75 ctx->page.filename = "ls-cache.txt";
75 cgit_print_http_headers(ctx); 76 cgit_print_http_headers(ctx);
76 cache_ls(ctx->cfg.cache_root); 77 cache_ls(ctx->cfg.cache_root);
77} 78}
78 79
79static void objects_fn(struct cgit_context *ctx) 80static void objects_fn(struct cgit_context *ctx)
80{ 81{
81 cgit_clone_objects(ctx); 82 cgit_clone_objects(ctx);
82} 83}
83 84
84static void repolist_fn(struct cgit_context *ctx) 85static void repolist_fn(struct cgit_context *ctx)
85{ 86{
86 cgit_print_repolist(); 87 cgit_print_repolist();
87} 88}
88 89
89static void patch_fn(struct cgit_context *ctx) 90static void patch_fn(struct cgit_context *ctx)
90{ 91{
91 cgit_print_patch(ctx->qry.sha1); 92 cgit_print_patch(ctx->qry.sha1);
92} 93}
93 94
95static void plain_fn(struct cgit_context *ctx)
96{
97 cgit_print_plain(ctx);
98}
99
94static void refs_fn(struct cgit_context *ctx) 100static void refs_fn(struct cgit_context *ctx)
95{ 101{
96 cgit_print_refs(); 102 cgit_print_refs();
97} 103}
98 104
99static void snapshot_fn(struct cgit_context *ctx) 105static void snapshot_fn(struct cgit_context *ctx)
100{ 106{
101 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, 107 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1,
102 cgit_repobasename(ctx->repo->url), ctx->qry.path, 108 cgit_repobasename(ctx->repo->url), ctx->qry.path,
103 ctx->repo->snapshots); 109 ctx->repo->snapshots);
104} 110}
105 111
106static void summary_fn(struct cgit_context *ctx) 112static void summary_fn(struct cgit_context *ctx)
107{ 113{
108 cgit_print_summary(); 114 cgit_print_summary();
109} 115}
110 116
111static void tag_fn(struct cgit_context *ctx) 117static void tag_fn(struct cgit_context *ctx)
112{ 118{
113 cgit_print_tag(ctx->qry.sha1); 119 cgit_print_tag(ctx->qry.sha1);
114} 120}
115 121
116static void tree_fn(struct cgit_context *ctx) 122static void tree_fn(struct cgit_context *ctx)
117{ 123{
118 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 124 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
119} 125}
120 126
121#define def_cmd(name, want_repo, want_layout) \ 127#define def_cmd(name, want_repo, want_layout) \
122 {#name, name##_fn, want_repo, want_layout} 128 {#name, name##_fn, want_repo, want_layout}
123 129
124struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 130struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
125{ 131{
126 static struct cgit_cmd cmds[] = { 132 static struct cgit_cmd cmds[] = {
127 def_cmd(HEAD, 1, 0), 133 def_cmd(HEAD, 1, 0),
128 def_cmd(atom, 1, 0), 134 def_cmd(atom, 1, 0),
129 def_cmd(about, 0, 1), 135 def_cmd(about, 0, 1),
130 def_cmd(blob, 1, 0), 136 def_cmd(blob, 1, 0),
131 def_cmd(commit, 1, 1), 137 def_cmd(commit, 1, 1),
132 def_cmd(diff, 1, 1), 138 def_cmd(diff, 1, 1),
133 def_cmd(info, 1, 0), 139 def_cmd(info, 1, 0),
134 def_cmd(log, 1, 1), 140 def_cmd(log, 1, 1),
135 def_cmd(ls_cache, 0, 0), 141 def_cmd(ls_cache, 0, 0),
136 def_cmd(objects, 1, 0), 142 def_cmd(objects, 1, 0),
137 def_cmd(patch, 1, 0), 143 def_cmd(patch, 1, 0),
144 def_cmd(plain, 1, 0),
138 def_cmd(refs, 1, 1), 145 def_cmd(refs, 1, 1),
139 def_cmd(repolist, 0, 0), 146 def_cmd(repolist, 0, 0),
140 def_cmd(snapshot, 1, 0), 147 def_cmd(snapshot, 1, 0),
141 def_cmd(summary, 1, 1), 148 def_cmd(summary, 1, 1),
142 def_cmd(tag, 1, 1), 149 def_cmd(tag, 1, 1),
143 def_cmd(tree, 1, 1), 150 def_cmd(tree, 1, 1),
144 }; 151 };
145 int i; 152 int i;
146 153
147 if (ctx->qry.page == NULL) { 154 if (ctx->qry.page == NULL) {
148 if (ctx->repo) 155 if (ctx->repo)
149 ctx->qry.page = "summary"; 156 ctx->qry.page = "summary";
150 else 157 else
151 ctx->qry.page = "repolist"; 158 ctx->qry.page = "repolist";
152 } 159 }
153 160
154 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 161 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
155 if (!strcmp(ctx->qry.page, cmds[i].name)) 162 if (!strcmp(ctx->qry.page, cmds[i].name))
156 return &cmds[i]; 163 return &cmds[i];
157 return NULL; 164 return NULL;
158} 165}
diff --git a/html.c b/html.c
index 1237076..36e9a2f 100644
--- a/html.c
+++ b/html.c
@@ -1,252 +1,257 @@
1/* html.c: helper functions for html output 1/* html.c: helper functions for html output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <unistd.h> 9#include <unistd.h>
10#include <stdio.h> 10#include <stdio.h>
11#include <stdlib.h> 11#include <stdlib.h>
12#include <stdarg.h> 12#include <stdarg.h>
13#include <string.h> 13#include <string.h>
14#include <errno.h> 14#include <errno.h>
15 15
16int htmlfd = STDOUT_FILENO; 16int htmlfd = STDOUT_FILENO;
17 17
18char *fmt(const char *format, ...) 18char *fmt(const char *format, ...)
19{ 19{
20 static char buf[8][1024]; 20 static char buf[8][1024];
21 static int bufidx; 21 static int bufidx;
22 int len; 22 int len;
23 va_list args; 23 va_list args;
24 24
25 bufidx++; 25 bufidx++;
26 bufidx &= 7; 26 bufidx &= 7;
27 27
28 va_start(args, format); 28 va_start(args, format);
29 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); 29 len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args);
30 va_end(args); 30 va_end(args);
31 if (len>sizeof(buf[bufidx])) { 31 if (len>sizeof(buf[bufidx])) {
32 fprintf(stderr, "[html.c] string truncated: %s\n", format); 32 fprintf(stderr, "[html.c] string truncated: %s\n", format);
33 exit(1); 33 exit(1);
34 } 34 }
35 return buf[bufidx]; 35 return buf[bufidx];
36} 36}
37 37
38void html_raw(const char *data, size_t size)
39{
40 write(htmlfd, data, size);
41}
42
38void html(const char *txt) 43void html(const char *txt)
39{ 44{
40 write(htmlfd, txt, strlen(txt)); 45 write(htmlfd, txt, strlen(txt));
41} 46}
42 47
43void htmlf(const char *format, ...) 48void htmlf(const char *format, ...)
44{ 49{
45 static char buf[65536]; 50 static char buf[65536];
46 va_list args; 51 va_list args;
47 52
48 va_start(args, format); 53 va_start(args, format);
49 vsnprintf(buf, sizeof(buf), format, args); 54 vsnprintf(buf, sizeof(buf), format, args);
50 va_end(args); 55 va_end(args);
51 html(buf); 56 html(buf);
52} 57}
53 58
54void html_status(int code, int more_headers) 59void html_status(int code, const char *msg, int more_headers)
55{ 60{
56 htmlf("Status: %d\n", code); 61 htmlf("Status: %d %s\n", code, msg);
57 if (!more_headers) 62 if (!more_headers)
58 html("\n"); 63 html("\n");
59} 64}
60 65
61void html_txt(char *txt) 66void html_txt(char *txt)
62{ 67{
63 char *t = txt; 68 char *t = txt;
64 while(t && *t){ 69 while(t && *t){
65 int c = *t; 70 int c = *t;
66 if (c=='<' || c=='>' || c=='&') { 71 if (c=='<' || c=='>' || c=='&') {
67 write(htmlfd, txt, t - txt); 72 write(htmlfd, txt, t - txt);
68 if (c=='>') 73 if (c=='>')
69 html("&gt;"); 74 html("&gt;");
70 else if (c=='<') 75 else if (c=='<')
71 html("&lt;"); 76 html("&lt;");
72 else if (c=='&') 77 else if (c=='&')
73 html("&amp;"); 78 html("&amp;");
74 txt = t+1; 79 txt = t+1;
75 } 80 }
76 t++; 81 t++;
77 } 82 }
78 if (t!=txt) 83 if (t!=txt)
79 html(txt); 84 html(txt);
80} 85}
81 86
82void html_ntxt(int len, char *txt) 87void html_ntxt(int len, char *txt)
83{ 88{
84 char *t = txt; 89 char *t = txt;
85 while(t && *t && len--){ 90 while(t && *t && len--){
86 int c = *t; 91 int c = *t;
87 if (c=='<' || c=='>' || c=='&') { 92 if (c=='<' || c=='>' || c=='&') {
88 write(htmlfd, txt, t - txt); 93 write(htmlfd, txt, t - txt);
89 if (c=='>') 94 if (c=='>')
90 html("&gt;"); 95 html("&gt;");
91 else if (c=='<') 96 else if (c=='<')
92 html("&lt;"); 97 html("&lt;");
93 else if (c=='&') 98 else if (c=='&')
94 html("&amp;"); 99 html("&amp;");
95 txt = t+1; 100 txt = t+1;
96 } 101 }
97 t++; 102 t++;
98 } 103 }
99 if (t!=txt) 104 if (t!=txt)
100 write(htmlfd, txt, t - txt); 105 write(htmlfd, txt, t - txt);
101 if (len<0) 106 if (len<0)
102 html("..."); 107 html("...");
103} 108}
104 109
105void html_attr(char *txt) 110void html_attr(char *txt)
106{ 111{
107 char *t = txt; 112 char *t = txt;
108 while(t && *t){ 113 while(t && *t){
109 int c = *t; 114 int c = *t;
110 if (c=='<' || c=='>' || c=='\'') { 115 if (c=='<' || c=='>' || c=='\'') {
111 write(htmlfd, txt, t - txt); 116 write(htmlfd, txt, t - txt);
112 if (c=='>') 117 if (c=='>')
113 html("&gt;"); 118 html("&gt;");
114 else if (c=='<') 119 else if (c=='<')
115 html("&lt;"); 120 html("&lt;");
116 else if (c=='\'') 121 else if (c=='\'')
117 html("&quote;"); 122 html("&quote;");
118 txt = t+1; 123 txt = t+1;
119 } 124 }
120 t++; 125 t++;
121 } 126 }
122 if (t!=txt) 127 if (t!=txt)
123 html(txt); 128 html(txt);
124} 129}
125 130
126void html_hidden(char *name, char *value) 131void html_hidden(char *name, char *value)
127{ 132{
128 html("<input type='hidden' name='"); 133 html("<input type='hidden' name='");
129 html_attr(name); 134 html_attr(name);
130 html("' value='"); 135 html("' value='");
131 html_attr(value); 136 html_attr(value);
132 html("'/>"); 137 html("'/>");
133} 138}
134 139
135void html_option(char *value, char *text, char *selected_value) 140void html_option(char *value, char *text, char *selected_value)
136{ 141{
137 html("<option value='"); 142 html("<option value='");
138 html_attr(value); 143 html_attr(value);
139 html("'"); 144 html("'");
140 if (selected_value && !strcmp(selected_value, value)) 145 if (selected_value && !strcmp(selected_value, value))
141 html(" selected='selected'"); 146 html(" selected='selected'");
142 html(">"); 147 html(">");
143 html_txt(text); 148 html_txt(text);
144 html("</option>\n"); 149 html("</option>\n");
145} 150}
146 151
147void html_link_open(char *url, char *title, char *class) 152void html_link_open(char *url, char *title, char *class)
148{ 153{
149 html("<a href='"); 154 html("<a href='");
150 html_attr(url); 155 html_attr(url);
151 if (title) { 156 if (title) {
152 html("' title='"); 157 html("' title='");
153 html_attr(title); 158 html_attr(title);
154 } 159 }
155 if (class) { 160 if (class) {
156 html("' class='"); 161 html("' class='");
157 html_attr(class); 162 html_attr(class);
158 } 163 }
159 html("'>"); 164 html("'>");
160} 165}
161 166
162void html_link_close(void) 167void html_link_close(void)
163{ 168{
164 html("</a>"); 169 html("</a>");
165} 170}
166 171
167void html_fileperm(unsigned short mode) 172void html_fileperm(unsigned short mode)
168{ 173{
169 htmlf("%c%c%c", (mode & 4 ? 'r' : '-'), 174 htmlf("%c%c%c", (mode & 4 ? 'r' : '-'),
170 (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-')); 175 (mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-'));
171} 176}
172 177
173int html_include(const char *filename) 178int html_include(const char *filename)
174{ 179{
175 FILE *f; 180 FILE *f;
176 char buf[4096]; 181 char buf[4096];
177 size_t len; 182 size_t len;
178 183
179 if (!(f = fopen(filename, "r"))) { 184 if (!(f = fopen(filename, "r"))) {
180 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n", 185 fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n",
181 filename, strerror(errno), errno); 186 filename, strerror(errno), errno);
182 return -1; 187 return -1;
183 } 188 }
184 while((len = fread(buf, 1, 4096, f)) > 0) 189 while((len = fread(buf, 1, 4096, f)) > 0)
185 write(htmlfd, buf, len); 190 write(htmlfd, buf, len);
186 fclose(f); 191 fclose(f);
187 return 0; 192 return 0;
188} 193}
189 194
190int hextoint(char c) 195int hextoint(char c)
191{ 196{
192 if (c >= 'a' && c <= 'f') 197 if (c >= 'a' && c <= 'f')
193 return 10 + c - 'a'; 198 return 10 + c - 'a';
194 else if (c >= 'A' && c <= 'F') 199 else if (c >= 'A' && c <= 'F')
195 return 10 + c - 'A'; 200 return 10 + c - 'A';
196 else if (c >= '0' && c <= '9') 201 else if (c >= '0' && c <= '9')
197 return c - '0'; 202 return c - '0';
198 else 203 else
199 return -1; 204 return -1;
200} 205}
201 206
202char *convert_query_hexchar(char *txt) 207char *convert_query_hexchar(char *txt)
203{ 208{
204 int d1, d2; 209 int d1, d2;
205 if (strlen(txt) < 3) { 210 if (strlen(txt) < 3) {
206 *txt = '\0'; 211 *txt = '\0';
207 return txt-1; 212 return txt-1;
208 } 213 }
209 d1 = hextoint(*(txt+1)); 214 d1 = hextoint(*(txt+1));
210 d2 = hextoint(*(txt+2)); 215 d2 = hextoint(*(txt+2));
211 if (d1<0 || d2<0) { 216 if (d1<0 || d2<0) {
212 strcpy(txt, txt+3); 217 strcpy(txt, txt+3);
213 return txt-1; 218 return txt-1;
214 } else { 219 } else {
215 *txt = d1 * 16 + d2; 220 *txt = d1 * 16 + d2;
216 strcpy(txt+1, txt+3); 221 strcpy(txt+1, txt+3);
217 return txt; 222 return txt;
218 } 223 }
219} 224}
220 225
221int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)) 226int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value))
222{ 227{
223 char *t, *value = NULL, c; 228 char *t, *value = NULL, c;
224 229
225 if (!txt) 230 if (!txt)
226 return 0; 231 return 0;
227 232
228 t = txt = strdup(txt); 233 t = txt = strdup(txt);
229 if (t == NULL) { 234 if (t == NULL) {
230 printf("Out of memory\n"); 235 printf("Out of memory\n");
231 exit(1); 236 exit(1);
232 } 237 }
233 while((c=*t) != '\0') { 238 while((c=*t) != '\0') {
234 if (c=='=') { 239 if (c=='=') {
235 *t = '\0'; 240 *t = '\0';
236 value = t+1; 241 value = t+1;
237 } else if (c=='+') { 242 } else if (c=='+') {
238 *t = ' '; 243 *t = ' ';
239 } else if (c=='%') { 244 } else if (c=='%') {
240 t = convert_query_hexchar(t); 245 t = convert_query_hexchar(t);
241 } else if (c=='&') { 246 } else if (c=='&') {
242 *t = '\0'; 247 *t = '\0';
243 (*fn)(txt, value); 248 (*fn)(txt, value);
244 txt = t+1; 249 txt = t+1;
245 value = NULL; 250 value = NULL;
246 } 251 }
247 t++; 252 t++;
248 } 253 }
249 if (t!=txt) 254 if (t!=txt)
250 (*fn)(txt, value); 255 (*fn)(txt, value);
251 return 0; 256 return 0;
252} 257}
diff --git a/html.h b/html.h
index 2bde28d..3c32935 100644
--- a/html.h
+++ b/html.h
@@ -1,21 +1,22 @@
1#ifndef HTML_H 1#ifndef HTML_H
2#define HTML_H 2#define HTML_H
3 3
4extern int htmlfd; 4extern int htmlfd;
5 5
6extern void html_raw(const char *txt, size_t size);
6extern void html(const char *txt); 7extern void html(const char *txt);
7extern void htmlf(const char *format,...); 8extern void htmlf(const char *format,...);
8extern void html_status(int code, int more_headers); 9extern void html_status(int code, const char *msg, int more_headers);
9extern void html_txt(char *txt); 10extern void html_txt(char *txt);
10extern void html_ntxt(int len, char *txt); 11extern void html_ntxt(int len, char *txt);
11extern void html_attr(char *txt); 12extern void html_attr(char *txt);
12extern void html_hidden(char *name, char *value); 13extern void html_hidden(char *name, char *value);
13extern void html_option(char *value, char *text, char *selected_value); 14extern void html_option(char *value, char *text, char *selected_value);
14extern void html_link_open(char *url, char *title, char *class); 15extern void html_link_open(char *url, char *title, char *class);
15extern void html_link_close(void); 16extern void html_link_close(void);
16extern void html_fileperm(unsigned short mode); 17extern void html_fileperm(unsigned short mode);
17extern int html_include(const char *filename); 18extern int html_include(const char *filename);
18 19
19extern int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)); 20extern int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value));
20 21
21#endif /* HTML_H */ 22#endif /* HTML_H */
diff --git a/ui-clone.c b/ui-clone.c
index 3a037ad..81e7a4e 100644
--- a/ui-clone.c
+++ b/ui-clone.c
@@ -1,104 +1,102 @@
1/* ui-clone.c: functions for http cloning, based on 1/* ui-clone.c: functions for http cloning, based on
2 * git's http-backend.c by Shawn O. Pearce 2 * git's http-backend.c by Shawn O. Pearce
3 * 3 *
4 * Copyright (C) 2008 Lars Hjemli 4 * Copyright (C) 2008 Lars Hjemli
5 * 5 *
6 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text) 7 * (see COPYING for full license text)
8 */ 8 */
9 9
10#include "cgit.h" 10#include "cgit.h"
11#include "html.h" 11#include "html.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13 13
14static int print_ref_info(const char *refname, const unsigned char *sha1, 14static int print_ref_info(const char *refname, const unsigned char *sha1,
15 int flags, void *cb_data) 15 int flags, void *cb_data)
16{ 16{
17 struct object *obj; 17 struct object *obj;
18 18
19 if (!(obj = parse_object(sha1))) 19 if (!(obj = parse_object(sha1)))
20 return 0; 20 return 0;
21 21
22 if (!strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/")) 22 if (!strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/"))
23 htmlf("%s\t%s\n", sha1_to_hex(sha1), refname); 23 htmlf("%s\t%s\n", sha1_to_hex(sha1), refname);
24 else if (!prefixcmp(refname, "refs/tags") && obj->type == OBJ_TAG) { 24 else if (!prefixcmp(refname, "refs/tags") && obj->type == OBJ_TAG) {
25 if (!(obj = deref_tag(obj, refname, 0))) 25 if (!(obj = deref_tag(obj, refname, 0)))
26 return 0; 26 return 0;
27 htmlf("%s\t%s\n", sha1_to_hex(sha1), refname); 27 htmlf("%s\t%s\n", sha1_to_hex(sha1), refname);
28 htmlf("%s\t%s^{}\n", sha1_to_hex(obj->sha1), refname); 28 htmlf("%s\t%s^{}\n", sha1_to_hex(obj->sha1), refname);
29 } 29 }
30 return 0; 30 return 0;
31} 31}
32 32
33static void print_pack_info(struct cgit_context *ctx) 33static void print_pack_info(struct cgit_context *ctx)
34{ 34{
35 struct packed_git *pack; 35 struct packed_git *pack;
36 int ofs; 36 int ofs;
37 37
38 ctx->page.mimetype = "text/plain"; 38 ctx->page.mimetype = "text/plain";
39 ctx->page.filename = "objects/info/packs"; 39 ctx->page.filename = "objects/info/packs";
40 cgit_print_http_headers(ctx); 40 cgit_print_http_headers(ctx);
41 ofs = strlen(ctx->repo->path) + strlen("/objects/pack/"); 41 ofs = strlen(ctx->repo->path) + strlen("/objects/pack/");
42 prepare_packed_git(); 42 prepare_packed_git();
43 for (pack = packed_git; pack; pack = pack->next) 43 for (pack = packed_git; pack; pack = pack->next)
44 if (pack->pack_local) 44 if (pack->pack_local)
45 htmlf("P %s\n", pack->pack_name + ofs); 45 htmlf("P %s\n", pack->pack_name + ofs);
46} 46}
47 47
48static void send_file(struct cgit_context *ctx, char *path) 48static void send_file(struct cgit_context *ctx, char *path)
49{ 49{
50 struct stat st; 50 struct stat st;
51 int err;
52 51
53 if (stat(path, &st)) { 52 if (stat(path, &st)) {
54 switch (errno) { 53 switch (errno) {
55 case ENOENT: 54 case ENOENT:
56 err = 404; 55 html_status(404, "Not found", 0);
57 break; 56 break;
58 case EACCES: 57 case EACCES:
59 err = 403; 58 html_status(403, "Forbidden", 0);
60 break; 59 break;
61 default: 60 default:
62 err = 400; 61 html_status(400, "Bad request", 0);
63 } 62 }
64 html_status(err, 0);
65 return; 63 return;
66 } 64 }
67 ctx->page.mimetype = "application/octet-stream"; 65 ctx->page.mimetype = "application/octet-stream";
68 ctx->page.filename = path; 66 ctx->page.filename = path;
69 if (prefixcmp(ctx->repo->path, path)) 67 if (prefixcmp(ctx->repo->path, path))
70 ctx->page.filename += strlen(ctx->repo->path) + 1; 68 ctx->page.filename += strlen(ctx->repo->path) + 1;
71 cgit_print_http_headers(ctx); 69 cgit_print_http_headers(ctx);
72 html_include(path); 70 html_include(path);
73} 71}
74 72
75void cgit_clone_info(struct cgit_context *ctx) 73void cgit_clone_info(struct cgit_context *ctx)
76{ 74{
77 if (!ctx->qry.path || strcmp(ctx->qry.path, "refs")) 75 if (!ctx->qry.path || strcmp(ctx->qry.path, "refs"))
78 return; 76 return;
79 77
80 ctx->page.mimetype = "text/plain"; 78 ctx->page.mimetype = "text/plain";
81 ctx->page.filename = "info/refs"; 79 ctx->page.filename = "info/refs";
82 cgit_print_http_headers(ctx); 80 cgit_print_http_headers(ctx);
83 for_each_ref(print_ref_info, ctx); 81 for_each_ref(print_ref_info, ctx);
84} 82}
85 83
86void cgit_clone_objects(struct cgit_context *ctx) 84void cgit_clone_objects(struct cgit_context *ctx)
87{ 85{
88 if (!ctx->qry.path) { 86 if (!ctx->qry.path) {
89 html_status(400, 0); 87 html_status(400, "Bad request", 0);
90 return; 88 return;
91 } 89 }
92 90
93 if (!strcmp(ctx->qry.path, "info/packs")) { 91 if (!strcmp(ctx->qry.path, "info/packs")) {
94 print_pack_info(ctx); 92 print_pack_info(ctx);
95 return; 93 return;
96 } 94 }
97 95
98 send_file(ctx, git_path("objects/%s", ctx->qry.path)); 96 send_file(ctx, git_path("objects/%s", ctx->qry.path));
99} 97}
100 98
101void cgit_clone_head(struct cgit_context *ctx) 99void cgit_clone_head(struct cgit_context *ctx)
102{ 100{
103 send_file(ctx, git_path("%s", "HEAD")); 101 send_file(ctx, git_path("%s", "HEAD"));
104} 102}
diff --git a/ui-plain.c b/ui-plain.c
new file mode 100644
index 0000000..35888a0
--- a/dev/null
+++ b/ui-plain.c
@@ -0,0 +1,82 @@
1/* ui-plain.c: functions for output of plain blobs by path
2 *
3 * Copyright (C) 2008 Lars Hjemli
4 *
5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text)
7 */
8
9#include "cgit.h"
10#include "html.h"
11#include "ui-shared.h"
12
13char *curr_rev;
14char *match_path;
15int match;
16
17static void print_object(const unsigned char *sha1, const char *path)
18{
19 enum object_type type;
20 char *buf;
21 size_t size;
22
23 type = sha1_object_info(sha1, &size);
24 if (type == OBJ_BAD) {
25 html_status(404, "Not found", 0);
26 return;
27 }
28
29 buf = read_sha1_file(sha1, &type, &size);
30 if (!buf) {
31 html_status(404, "Not found", 0);
32 return;
33 }
34 ctx.page.mimetype = "text/plain";
35 ctx.page.filename = fmt("%s", path);
36 ctx.page.size = size;
37 cgit_print_http_headers(&ctx);
38 html_raw(buf, size);
39 match = 1;
40}
41
42static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
43 const char *pathname, unsigned mode, int stage,
44 void *cbdata)
45{
46 fprintf(stderr, "[cgit] walk_tree.pathname=%s", pathname);
47
48 if (!pathname || strcmp(match_path, pathname))
49 return READ_TREE_RECURSIVE;
50
51 if (S_ISREG(mode))
52 print_object(sha1, pathname);
53
54 return 0;
55}
56
57void cgit_print_plain(struct cgit_context *ctx)
58{
59 const char *rev = ctx->qry.sha1;
60 unsigned char sha1[20];
61 struct commit *commit;
62 const char *paths[] = {ctx->qry.path, NULL};
63
64 if (!rev)
65 rev = ctx->qry.head;
66
67 curr_rev = xstrdup(rev);
68 if (get_sha1(rev, sha1)) {
69 html_status(404, "Not found", 0);
70 return;
71 }
72 commit = lookup_commit_reference(sha1);
73 if (!commit || parse_commit(commit)) {
74 html_status(404, "Not found", 0);
75 return;
76 }
77 match_path = ctx->qry.path;
78 fprintf(stderr, "[cgit] match_path=%s", match_path);
79 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
80 if (!match)
81 html_status(404, "Not found", 0);
82}
diff --git a/ui-plain.h b/ui-plain.h
new file mode 100644
index 0000000..4373118
--- a/dev/null
+++ b/ui-plain.h
@@ -0,0 +1,6 @@
1#ifndef UI_PLAIN_H
2#define UI_PLAIN_H
3
4extern void cgit_print_plain(struct cgit_context *ctx);
5
6#endif /* UI_PLAIN_H */
diff --git a/ui-shared.c b/ui-shared.c
index 37c60b2..4818e70 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,699 +1,707 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output 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#include "cmd.h" 10#include "cmd.h"
11#include "html.h" 11#include "html.h"
12 12
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_hosturl() 37char *cgit_hosturl()
38{ 38{
39 char *host, *port; 39 char *host, *port;
40 40
41 host = getenv("SERVER_NAME"); 41 host = getenv("SERVER_NAME");
42 if (!host) 42 if (!host)
43 return NULL; 43 return NULL;
44 port = getenv("SERVER_PORT"); 44 port = getenv("SERVER_PORT");
45 if (port && atoi(port) != 80) 45 if (port && atoi(port) != 80)
46 host = xstrdup(fmt("%s:%d", host, atoi(port))); 46 host = xstrdup(fmt("%s:%d", host, atoi(port)));
47 else 47 else
48 host = xstrdup(host); 48 host = xstrdup(host);
49 return host; 49 return host;
50} 50}
51 51
52char *cgit_rooturl() 52char *cgit_rooturl()
53{ 53{
54 if (ctx.cfg.virtual_root) 54 if (ctx.cfg.virtual_root)
55 return fmt("%s/", ctx.cfg.virtual_root); 55 return fmt("%s/", ctx.cfg.virtual_root);
56 else 56 else
57 return ctx.cfg.script_name; 57 return ctx.cfg.script_name;
58} 58}
59 59
60char *cgit_repourl(const char *reponame) 60char *cgit_repourl(const char *reponame)
61{ 61{
62 if (ctx.cfg.virtual_root) { 62 if (ctx.cfg.virtual_root) {
63 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 63 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
64 } else { 64 } else {
65 return fmt("?r=%s", reponame); 65 return fmt("?r=%s", reponame);
66 } 66 }
67} 67}
68 68
69char *cgit_fileurl(const char *reponame, const char *pagename, 69char *cgit_fileurl(const char *reponame, const char *pagename,
70 const char *filename, const char *query) 70 const char *filename, const char *query)
71{ 71{
72 char *tmp; 72 char *tmp;
73 char *delim; 73 char *delim;
74 74
75 if (ctx.cfg.virtual_root) { 75 if (ctx.cfg.virtual_root) {
76 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 76 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
77 pagename, (filename ? filename:"")); 77 pagename, (filename ? filename:""));
78 delim = "?"; 78 delim = "?";
79 } else { 79 } else {
80 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 80 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
81 (filename ? filename : "")); 81 (filename ? filename : ""));
82 delim = "&"; 82 delim = "&";
83 } 83 }
84 if (query) 84 if (query)
85 tmp = fmt("%s%s%s", tmp, delim, query); 85 tmp = fmt("%s%s%s", tmp, delim, query);
86 return tmp; 86 return tmp;
87} 87}
88 88
89char *cgit_pageurl(const char *reponame, const char *pagename, 89char *cgit_pageurl(const char *reponame, const char *pagename,
90 const char *query) 90 const char *query)
91{ 91{
92 return cgit_fileurl(reponame,pagename,0,query); 92 return cgit_fileurl(reponame,pagename,0,query);
93} 93}
94 94
95const char *cgit_repobasename(const char *reponame) 95const char *cgit_repobasename(const char *reponame)
96{ 96{
97 /* I assume we don't need to store more than one repo basename */ 97 /* I assume we don't need to store more than one repo basename */
98 static char rvbuf[1024]; 98 static char rvbuf[1024];
99 int p; 99 int p;
100 const char *rv; 100 const char *rv;
101 strncpy(rvbuf,reponame,sizeof(rvbuf)); 101 strncpy(rvbuf,reponame,sizeof(rvbuf));
102 if(rvbuf[sizeof(rvbuf)-1]) 102 if(rvbuf[sizeof(rvbuf)-1])
103 die("cgit_repobasename: truncated repository name '%s'", reponame); 103 die("cgit_repobasename: truncated repository name '%s'", reponame);
104 p = strlen(rvbuf)-1; 104 p = strlen(rvbuf)-1;
105 /* strip trailing slashes */ 105 /* strip trailing slashes */
106 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 106 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
107 /* strip trailing .git */ 107 /* strip trailing .git */
108 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 108 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
109 p -= 3; rvbuf[p--] = 0; 109 p -= 3; rvbuf[p--] = 0;
110 } 110 }
111 /* strip more trailing slashes if any */ 111 /* strip more trailing slashes if any */
112 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 112 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
113 /* find last slash in the remaining string */ 113 /* find last slash in the remaining string */
114 rv = strrchr(rvbuf,'/'); 114 rv = strrchr(rvbuf,'/');
115 if(rv) 115 if(rv)
116 return ++rv; 116 return ++rv;
117 return rvbuf; 117 return rvbuf;
118} 118}
119 119
120char *cgit_currurl() 120char *cgit_currurl()
121{ 121{
122 if (!ctx.cfg.virtual_root) 122 if (!ctx.cfg.virtual_root)
123 return ctx.cfg.script_name; 123 return ctx.cfg.script_name;
124 else if (ctx.qry.page) 124 else if (ctx.qry.page)
125 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 125 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
126 else if (ctx.qry.repo) 126 else if (ctx.qry.repo)
127 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 127 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
128 else 128 else
129 return fmt("%s/", ctx.cfg.virtual_root); 129 return fmt("%s/", ctx.cfg.virtual_root);
130} 130}
131 131
132static void site_url(char *page, char *search, int ofs) 132static void site_url(char *page, char *search, int ofs)
133{ 133{
134 char *delim = "?"; 134 char *delim = "?";
135 135
136 if (ctx.cfg.virtual_root) { 136 if (ctx.cfg.virtual_root) {
137 html_attr(ctx.cfg.virtual_root); 137 html_attr(ctx.cfg.virtual_root);
138 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 138 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
139 html("/"); 139 html("/");
140 } else 140 } else
141 html(ctx.cfg.script_name); 141 html(ctx.cfg.script_name);
142 142
143 if (page) { 143 if (page) {
144 htmlf("?p=%s", page); 144 htmlf("?p=%s", page);
145 delim = "&"; 145 delim = "&";
146 } 146 }
147 if (search) { 147 if (search) {
148 html(delim); 148 html(delim);
149 html("q="); 149 html("q=");
150 html_attr(search); 150 html_attr(search);
151 delim = "&"; 151 delim = "&";
152 } 152 }
153 if (ofs) { 153 if (ofs) {
154 html(delim); 154 html(delim);
155 htmlf("ofs=%d", ofs); 155 htmlf("ofs=%d", ofs);
156 } 156 }
157} 157}
158 158
159static void site_link(char *page, char *name, char *title, char *class, 159static void site_link(char *page, char *name, char *title, char *class,
160 char *search, int ofs) 160 char *search, int ofs)
161{ 161{
162 html("<a"); 162 html("<a");
163 if (title) { 163 if (title) {
164 html(" title='"); 164 html(" title='");
165 html_attr(title); 165 html_attr(title);
166 html("'"); 166 html("'");
167 } 167 }
168 if (class) { 168 if (class) {
169 html(" class='"); 169 html(" class='");
170 html_attr(class); 170 html_attr(class);
171 html("'"); 171 html("'");
172 } 172 }
173 html(" href='"); 173 html(" href='");
174 site_url(page, search, ofs); 174 site_url(page, search, ofs);
175 html("'>"); 175 html("'>");
176 html_txt(name); 176 html_txt(name);
177 html("</a>"); 177 html("</a>");
178} 178}
179 179
180void cgit_index_link(char *name, char *title, char *class, char *pattern, 180void cgit_index_link(char *name, char *title, char *class, char *pattern,
181 int ofs) 181 int ofs)
182{ 182{
183 site_link(NULL, name, title, class, pattern, ofs); 183 site_link(NULL, name, title, class, pattern, ofs);
184} 184}
185 185
186static char *repolink(char *title, char *class, char *page, char *head, 186static char *repolink(char *title, char *class, char *page, char *head,
187 char *path) 187 char *path)
188{ 188{
189 char *delim = "?"; 189 char *delim = "?";
190 190
191 html("<a"); 191 html("<a");
192 if (title) { 192 if (title) {
193 html(" title='"); 193 html(" title='");
194 html_attr(title); 194 html_attr(title);
195 html("'"); 195 html("'");
196 } 196 }
197 if (class) { 197 if (class) {
198 html(" class='"); 198 html(" class='");
199 html_attr(class); 199 html_attr(class);
200 html("'"); 200 html("'");
201 } 201 }
202 html(" href='"); 202 html(" href='");
203 if (ctx.cfg.virtual_root) { 203 if (ctx.cfg.virtual_root) {
204 html_attr(ctx.cfg.virtual_root); 204 html_attr(ctx.cfg.virtual_root);
205 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 205 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
206 html("/"); 206 html("/");
207 html_attr(ctx.repo->url); 207 html_attr(ctx.repo->url);
208 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 208 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
209 html("/"); 209 html("/");
210 if (page) { 210 if (page) {
211 html(page); 211 html(page);
212 html("/"); 212 html("/");
213 if (path) 213 if (path)
214 html_attr(path); 214 html_attr(path);
215 } 215 }
216 } else { 216 } else {
217 html(ctx.cfg.script_name); 217 html(ctx.cfg.script_name);
218 html("?url="); 218 html("?url=");
219 html_attr(ctx.repo->url); 219 html_attr(ctx.repo->url);
220 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 220 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
221 html("/"); 221 html("/");
222 if (page) { 222 if (page) {
223 html(page); 223 html(page);
224 html("/"); 224 html("/");
225 if (path) 225 if (path)
226 html_attr(path); 226 html_attr(path);
227 } 227 }
228 delim = "&amp;"; 228 delim = "&amp;";
229 } 229 }
230 if (head && strcmp(head, ctx.repo->defbranch)) { 230 if (head && strcmp(head, ctx.repo->defbranch)) {
231 html(delim); 231 html(delim);
232 html("h="); 232 html("h=");
233 html_attr(head); 233 html_attr(head);
234 delim = "&amp;"; 234 delim = "&amp;";
235 } 235 }
236 return fmt("%s", delim); 236 return fmt("%s", delim);
237} 237}
238 238
239static void reporevlink(char *page, char *name, char *title, char *class, 239static void reporevlink(char *page, char *name, char *title, char *class,
240 char *head, char *rev, char *path) 240 char *head, char *rev, char *path)
241{ 241{
242 char *delim; 242 char *delim;
243 243
244 delim = repolink(title, class, page, head, path); 244 delim = repolink(title, class, page, head, path);
245 if (rev && strcmp(rev, ctx.qry.head)) { 245 if (rev && strcmp(rev, ctx.qry.head)) {
246 html(delim); 246 html(delim);
247 html("id="); 247 html("id=");
248 html_attr(rev); 248 html_attr(rev);
249 } 249 }
250 html("'>"); 250 html("'>");
251 html_txt(name); 251 html_txt(name);
252 html("</a>"); 252 html("</a>");
253} 253}
254 254
255void cgit_tree_link(char *name, char *title, char *class, char *head, 255void cgit_tree_link(char *name, char *title, char *class, char *head,
256 char *rev, char *path) 256 char *rev, char *path)
257{ 257{
258 reporevlink("tree", name, title, class, head, rev, path); 258 reporevlink("tree", name, title, class, head, rev, path);
259} 259}
260 260
261void cgit_plain_link(char *name, char *title, char *class, char *head,
262 char *rev, char *path)
263{
264 reporevlink("plain", name, title, class, head, rev, path);
265}
266
261void cgit_log_link(char *name, char *title, char *class, char *head, 267void cgit_log_link(char *name, char *title, char *class, char *head,
262 char *rev, char *path, int ofs, char *grep, char *pattern) 268 char *rev, char *path, int ofs, char *grep, char *pattern)
263{ 269{
264 char *delim; 270 char *delim;
265 271
266 delim = repolink(title, class, "log", head, path); 272 delim = repolink(title, class, "log", head, path);
267 if (rev && strcmp(rev, ctx.qry.head)) { 273 if (rev && strcmp(rev, ctx.qry.head)) {
268 html(delim); 274 html(delim);
269 html("id="); 275 html("id=");
270 html_attr(rev); 276 html_attr(rev);
271 delim = "&"; 277 delim = "&";
272 } 278 }
273 if (grep && pattern) { 279 if (grep && pattern) {
274 html(delim); 280 html(delim);
275 html("qt="); 281 html("qt=");
276 html_attr(grep); 282 html_attr(grep);
277 delim = "&"; 283 delim = "&";
278 html(delim); 284 html(delim);
279 html("q="); 285 html("q=");
280 html_attr(pattern); 286 html_attr(pattern);
281 } 287 }
282 if (ofs > 0) { 288 if (ofs > 0) {
283 html(delim); 289 html(delim);
284 html("ofs="); 290 html("ofs=");
285 htmlf("%d", ofs); 291 htmlf("%d", ofs);
286 } 292 }
287 html("'>"); 293 html("'>");
288 html_txt(name); 294 html_txt(name);
289 html("</a>"); 295 html("</a>");
290} 296}
291 297
292void cgit_commit_link(char *name, char *title, char *class, char *head, 298void cgit_commit_link(char *name, char *title, char *class, char *head,
293 char *rev) 299 char *rev)
294{ 300{
295 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 301 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
296 name[ctx.cfg.max_msg_len] = '\0'; 302 name[ctx.cfg.max_msg_len] = '\0';
297 name[ctx.cfg.max_msg_len - 1] = '.'; 303 name[ctx.cfg.max_msg_len - 1] = '.';
298 name[ctx.cfg.max_msg_len - 2] = '.'; 304 name[ctx.cfg.max_msg_len - 2] = '.';
299 name[ctx.cfg.max_msg_len - 3] = '.'; 305 name[ctx.cfg.max_msg_len - 3] = '.';
300 } 306 }
301 reporevlink("commit", name, title, class, head, rev, NULL); 307 reporevlink("commit", name, title, class, head, rev, NULL);
302} 308}
303 309
304void cgit_refs_link(char *name, char *title, char *class, char *head, 310void cgit_refs_link(char *name, char *title, char *class, char *head,
305 char *rev, char *path) 311 char *rev, char *path)
306{ 312{
307 reporevlink("refs", name, title, class, head, rev, path); 313 reporevlink("refs", name, title, class, head, rev, path);
308} 314}
309 315
310void cgit_snapshot_link(char *name, char *title, char *class, char *head, 316void cgit_snapshot_link(char *name, char *title, char *class, char *head,
311 char *rev, char *archivename) 317 char *rev, char *archivename)
312{ 318{
313 reporevlink("snapshot", name, title, class, head, rev, archivename); 319 reporevlink("snapshot", name, title, class, head, rev, archivename);
314} 320}
315 321
316void cgit_diff_link(char *name, char *title, char *class, char *head, 322void cgit_diff_link(char *name, char *title, char *class, char *head,
317 char *new_rev, char *old_rev, char *path) 323 char *new_rev, char *old_rev, char *path)
318{ 324{
319 char *delim; 325 char *delim;
320 326
321 delim = repolink(title, class, "diff", head, path); 327 delim = repolink(title, class, "diff", head, path);
322 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 328 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
323 html(delim); 329 html(delim);
324 html("id="); 330 html("id=");
325 html_attr(new_rev); 331 html_attr(new_rev);
326 delim = "&amp;"; 332 delim = "&amp;";
327 } 333 }
328 if (old_rev) { 334 if (old_rev) {
329 html(delim); 335 html(delim);
330 html("id2="); 336 html("id2=");
331 html_attr(old_rev); 337 html_attr(old_rev);
332 } 338 }
333 html("'>"); 339 html("'>");
334 html_txt(name); 340 html_txt(name);
335 html("</a>"); 341 html("</a>");
336} 342}
337 343
338void cgit_patch_link(char *name, char *title, char *class, char *head, 344void cgit_patch_link(char *name, char *title, char *class, char *head,
339 char *rev) 345 char *rev)
340{ 346{
341 reporevlink("patch", name, title, class, head, rev, NULL); 347 reporevlink("patch", name, title, class, head, rev, NULL);
342} 348}
343 349
344void cgit_object_link(struct object *obj) 350void cgit_object_link(struct object *obj)
345{ 351{
346 char *page, *arg, *url; 352 char *page, *arg, *url;
347 353
348 if (obj->type == OBJ_COMMIT) { 354 if (obj->type == OBJ_COMMIT) {
349 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL, 355 cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
350 ctx.qry.head, sha1_to_hex(obj->sha1)); 356 ctx.qry.head, sha1_to_hex(obj->sha1));
351 return; 357 return;
352 } else if (obj->type == OBJ_TREE) { 358 } else if (obj->type == OBJ_TREE) {
353 page = "tree"; 359 page = "tree";
354 arg = "id"; 360 arg = "id";
355 } else if (obj->type == OBJ_TAG) { 361 } else if (obj->type == OBJ_TAG) {
356 page = "tag"; 362 page = "tag";
357 arg = "id"; 363 arg = "id";
358 } else { 364 } else {
359 page = "blob"; 365 page = "blob";
360 arg = "id"; 366 arg = "id";
361 } 367 }
362 368
363 url = cgit_pageurl(ctx.qry.repo, page, 369 url = cgit_pageurl(ctx.qry.repo, page,
364 fmt("%s=%s", arg, sha1_to_hex(obj->sha1))); 370 fmt("%s=%s", arg, sha1_to_hex(obj->sha1)));
365 html_link_open(url, NULL, NULL); 371 html_link_open(url, NULL, NULL);
366 htmlf("%s %s", typename(obj->type), 372 htmlf("%s %s", typename(obj->type),
367 sha1_to_hex(obj->sha1)); 373 sha1_to_hex(obj->sha1));
368 html_link_close(); 374 html_link_close();
369} 375}
370 376
371void cgit_print_date(time_t secs, char *format, int local_time) 377void cgit_print_date(time_t secs, char *format, int local_time)
372{ 378{
373 char buf[64]; 379 char buf[64];
374 struct tm *time; 380 struct tm *time;
375 381
376 if (!secs) 382 if (!secs)
377 return; 383 return;
378 if(local_time) 384 if(local_time)
379 time = localtime(&secs); 385 time = localtime(&secs);
380 else 386 else
381 time = gmtime(&secs); 387 time = gmtime(&secs);
382 strftime(buf, sizeof(buf)-1, format, time); 388 strftime(buf, sizeof(buf)-1, format, time);
383 html_txt(buf); 389 html_txt(buf);
384} 390}
385 391
386void cgit_print_age(time_t t, time_t max_relative, char *format) 392void cgit_print_age(time_t t, time_t max_relative, char *format)
387{ 393{
388 time_t now, secs; 394 time_t now, secs;
389 395
390 if (!t) 396 if (!t)
391 return; 397 return;
392 time(&now); 398 time(&now);
393 secs = now - t; 399 secs = now - t;
394 400
395 if (secs > max_relative && max_relative >= 0) { 401 if (secs > max_relative && max_relative >= 0) {
396 cgit_print_date(t, format, ctx.cfg.local_time); 402 cgit_print_date(t, format, ctx.cfg.local_time);
397 return; 403 return;
398 } 404 }
399 405
400 if (secs < TM_HOUR * 2) { 406 if (secs < TM_HOUR * 2) {
401 htmlf("<span class='age-mins'>%.0f min.</span>", 407 htmlf("<span class='age-mins'>%.0f min.</span>",
402 secs * 1.0 / TM_MIN); 408 secs * 1.0 / TM_MIN);
403 return; 409 return;
404 } 410 }
405 if (secs < TM_DAY * 2) { 411 if (secs < TM_DAY * 2) {
406 htmlf("<span class='age-hours'>%.0f hours</span>", 412 htmlf("<span class='age-hours'>%.0f hours</span>",
407 secs * 1.0 / TM_HOUR); 413 secs * 1.0 / TM_HOUR);
408 return; 414 return;
409 } 415 }
410 if (secs < TM_WEEK * 2) { 416 if (secs < TM_WEEK * 2) {
411 htmlf("<span class='age-days'>%.0f days</span>", 417 htmlf("<span class='age-days'>%.0f days</span>",
412 secs * 1.0 / TM_DAY); 418 secs * 1.0 / TM_DAY);
413 return; 419 return;
414 } 420 }
415 if (secs < TM_MONTH * 2) { 421 if (secs < TM_MONTH * 2) {
416 htmlf("<span class='age-weeks'>%.0f weeks</span>", 422 htmlf("<span class='age-weeks'>%.0f weeks</span>",
417 secs * 1.0 / TM_WEEK); 423 secs * 1.0 / TM_WEEK);
418 return; 424 return;
419 } 425 }
420 if (secs < TM_YEAR * 2) { 426 if (secs < TM_YEAR * 2) {
421 htmlf("<span class='age-months'>%.0f months</span>", 427 htmlf("<span class='age-months'>%.0f months</span>",
422 secs * 1.0 / TM_MONTH); 428 secs * 1.0 / TM_MONTH);
423 return; 429 return;
424 } 430 }
425 htmlf("<span class='age-years'>%.0f years</span>", 431 htmlf("<span class='age-years'>%.0f years</span>",
426 secs * 1.0 / TM_YEAR); 432 secs * 1.0 / TM_YEAR);
427} 433}
428 434
429void cgit_print_http_headers(struct cgit_context *ctx) 435void cgit_print_http_headers(struct cgit_context *ctx)
430{ 436{
431 if (ctx->page.mimetype && ctx->page.charset) 437 if (ctx->page.mimetype && ctx->page.charset)
432 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 438 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
433 ctx->page.charset); 439 ctx->page.charset);
434 else if (ctx->page.mimetype) 440 else if (ctx->page.mimetype)
435 htmlf("Content-Type: %s\n", ctx->page.mimetype); 441 htmlf("Content-Type: %s\n", ctx->page.mimetype);
442 if (ctx->page.size)
443 htmlf("Content-Length: %ld\n", ctx->page.size);
436 if (ctx->page.filename) 444 if (ctx->page.filename)
437 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 445 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
438 ctx->page.filename); 446 ctx->page.filename);
439 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 447 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
440 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 448 htmlf("Expires: %s\n", http_date(ctx->page.expires));
441 html("\n"); 449 html("\n");
442} 450}
443 451
444void cgit_print_docstart(struct cgit_context *ctx) 452void cgit_print_docstart(struct cgit_context *ctx)
445{ 453{
446 char *host = cgit_hosturl(); 454 char *host = cgit_hosturl();
447 html(cgit_doctype); 455 html(cgit_doctype);
448 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 456 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
449 html("<head>\n"); 457 html("<head>\n");
450 html("<title>"); 458 html("<title>");
451 html_txt(ctx->page.title); 459 html_txt(ctx->page.title);
452 html("</title>\n"); 460 html("</title>\n");
453 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 461 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
454 if (ctx->cfg.robots && *ctx->cfg.robots) 462 if (ctx->cfg.robots && *ctx->cfg.robots)
455 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 463 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
456 html("<link rel='stylesheet' type='text/css' href='"); 464 html("<link rel='stylesheet' type='text/css' href='");
457 html_attr(ctx->cfg.css); 465 html_attr(ctx->cfg.css);
458 html("'/>\n"); 466 html("'/>\n");
459 if (ctx->cfg.favicon) { 467 if (ctx->cfg.favicon) {
460 html("<link rel='shortcut icon' href='"); 468 html("<link rel='shortcut icon' href='");
461 html_attr(ctx->cfg.favicon); 469 html_attr(ctx->cfg.favicon);
462 html("'/>\n"); 470 html("'/>\n");
463 } 471 }
464 if (host && ctx->repo) { 472 if (host && ctx->repo) {
465 html("<link rel='alternate' title='Atom feed' href='http://"); 473 html("<link rel='alternate' title='Atom feed' href='http://");
466 html_attr(cgit_hosturl()); 474 html_attr(cgit_hosturl());
467 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 475 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
468 fmt("h=%s", ctx->qry.head))); 476 fmt("h=%s", ctx->qry.head)));
469 html("' type='application/atom+xml'/>"); 477 html("' type='application/atom+xml'/>");
470 } 478 }
471 html("</head>\n"); 479 html("</head>\n");
472 html("<body>\n"); 480 html("<body>\n");
473} 481}
474 482
475void cgit_print_docend() 483void cgit_print_docend()
476{ 484{
477 html("</div>"); 485 html("</div>");
478 if (ctx.cfg.footer) 486 if (ctx.cfg.footer)
479 html_include(ctx.cfg.footer); 487 html_include(ctx.cfg.footer);
480 else { 488 else {
481 html("<div class='footer'>generated "); 489 html("<div class='footer'>generated ");
482 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 490 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
483 htmlf(" by cgit %s", cgit_version); 491 htmlf(" by cgit %s", cgit_version);
484 html("</div>\n"); 492 html("</div>\n");
485 } 493 }
486 html("</body>\n</html>\n"); 494 html("</body>\n</html>\n");
487} 495}
488 496
489int print_branch_option(const char *refname, const unsigned char *sha1, 497int print_branch_option(const char *refname, const unsigned char *sha1,
490 int flags, void *cb_data) 498 int flags, void *cb_data)
491{ 499{
492 char *name = (char *)refname; 500 char *name = (char *)refname;
493 html_option(name, name, ctx.qry.head); 501 html_option(name, name, ctx.qry.head);
494 return 0; 502 return 0;
495} 503}
496 504
497int print_archive_ref(const char *refname, const unsigned char *sha1, 505int print_archive_ref(const char *refname, const unsigned char *sha1,
498 int flags, void *cb_data) 506 int flags, void *cb_data)
499{ 507{
500 struct tag *tag; 508 struct tag *tag;
501 struct taginfo *info; 509 struct taginfo *info;
502 struct object *obj; 510 struct object *obj;
503 char buf[256], *url; 511 char buf[256], *url;
504 unsigned char fileid[20]; 512 unsigned char fileid[20];
505 int *header = (int *)cb_data; 513 int *header = (int *)cb_data;
506 514
507 if (prefixcmp(refname, "refs/archives")) 515 if (prefixcmp(refname, "refs/archives"))
508 return 0; 516 return 0;
509 strncpy(buf, refname+14, sizeof(buf)); 517 strncpy(buf, refname+14, sizeof(buf));
510 obj = parse_object(sha1); 518 obj = parse_object(sha1);
511 if (!obj) 519 if (!obj)
512 return 1; 520 return 1;
513 if (obj->type == OBJ_TAG) { 521 if (obj->type == OBJ_TAG) {
514 tag = lookup_tag(sha1); 522 tag = lookup_tag(sha1);
515 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 523 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
516 return 0; 524 return 0;
517 hashcpy(fileid, tag->tagged->sha1); 525 hashcpy(fileid, tag->tagged->sha1);
518 } else if (obj->type != OBJ_BLOB) { 526 } else if (obj->type != OBJ_BLOB) {
519 return 0; 527 return 0;
520 } else { 528 } else {
521 hashcpy(fileid, sha1); 529 hashcpy(fileid, sha1);
522 } 530 }
523 if (!*header) { 531 if (!*header) {
524 html("<h1>download</h1>\n"); 532 html("<h1>download</h1>\n");
525 *header = 1; 533 *header = 1;
526 } 534 }
527 url = cgit_pageurl(ctx.qry.repo, "blob", 535 url = cgit_pageurl(ctx.qry.repo, "blob",
528 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 536 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
529 buf)); 537 buf));
530 html_link_open(url, NULL, "menu"); 538 html_link_open(url, NULL, "menu");
531 html_txt(strlpart(buf, 20)); 539 html_txt(strlpart(buf, 20));
532 html_link_close(); 540 html_link_close();
533 return 0; 541 return 0;
534} 542}
535 543
536void add_hidden_formfields(int incl_head, int incl_search, char *page) 544void add_hidden_formfields(int incl_head, int incl_search, char *page)
537{ 545{
538 char *url; 546 char *url;
539 547
540 if (!ctx.cfg.virtual_root) { 548 if (!ctx.cfg.virtual_root) {
541 url = fmt("%s/%s", ctx.qry.repo, page); 549 url = fmt("%s/%s", ctx.qry.repo, page);
542 if (ctx.qry.path) 550 if (ctx.qry.path)
543 url = fmt("%s/%s", url, ctx.qry.path); 551 url = fmt("%s/%s", url, ctx.qry.path);
544 html_hidden("url", url); 552 html_hidden("url", url);
545 } 553 }
546 554
547 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 555 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
548 strcmp(ctx.qry.head, ctx.repo->defbranch)) 556 strcmp(ctx.qry.head, ctx.repo->defbranch))
549 html_hidden("h", ctx.qry.head); 557 html_hidden("h", ctx.qry.head);
550 558
551 if (ctx.qry.sha1) 559 if (ctx.qry.sha1)
552 html_hidden("id", ctx.qry.sha1); 560 html_hidden("id", ctx.qry.sha1);
553 if (ctx.qry.sha2) 561 if (ctx.qry.sha2)
554 html_hidden("id2", ctx.qry.sha2); 562 html_hidden("id2", ctx.qry.sha2);
555 563
556 if (incl_search) { 564 if (incl_search) {
557 if (ctx.qry.grep) 565 if (ctx.qry.grep)
558 html_hidden("qt", ctx.qry.grep); 566 html_hidden("qt", ctx.qry.grep);
559 if (ctx.qry.search) 567 if (ctx.qry.search)
560 html_hidden("q", ctx.qry.search); 568 html_hidden("q", ctx.qry.search);
561 } 569 }
562} 570}
563 571
564char *hc(struct cgit_cmd *cmd, const char *page) 572char *hc(struct cgit_cmd *cmd, const char *page)
565{ 573{
566 return (strcmp(cmd->name, page) ? NULL : "active"); 574 return (strcmp(cmd->name, page) ? NULL : "active");
567} 575}
568 576
569void cgit_print_pageheader(struct cgit_context *ctx) 577void cgit_print_pageheader(struct cgit_context *ctx)
570{ 578{
571 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 579 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
572 580
573 html("<table id='header'>\n"); 581 html("<table id='header'>\n");
574 html("<tr>\n"); 582 html("<tr>\n");
575 html("<td class='logo' rowspan='2'><a href='"); 583 html("<td class='logo' rowspan='2'><a href='");
576 if (ctx->cfg.logo_link) 584 if (ctx->cfg.logo_link)
577 html_attr(ctx->cfg.logo_link); 585 html_attr(ctx->cfg.logo_link);
578 else 586 else
579 html_attr(cgit_rooturl()); 587 html_attr(cgit_rooturl());
580 html("'><img src='"); 588 html("'><img src='");
581 html_attr(ctx->cfg.logo); 589 html_attr(ctx->cfg.logo);
582 html("' alt='cgit logo'/></a></td>\n"); 590 html("' alt='cgit logo'/></a></td>\n");
583 591
584 html("<td class='main'>"); 592 html("<td class='main'>");
585 if (ctx->repo) { 593 if (ctx->repo) {
586 cgit_index_link("index", NULL, NULL, NULL, 0); 594 cgit_index_link("index", NULL, NULL, NULL, 0);
587 html(" : "); 595 html(" : ");
588 reporevlink(NULL, ctx->repo->name, NULL, hc(cmd, "summary"), 596 reporevlink(NULL, ctx->repo->name, NULL, hc(cmd, "summary"),
589 ctx->qry.head, NULL, NULL); 597 ctx->qry.head, NULL, NULL);
590 html("</td><td class='form'>"); 598 html("</td><td class='form'>");
591 html("<form method='get' action=''>\n"); 599 html("<form method='get' action=''>\n");
592 add_hidden_formfields(0, 1, ctx->qry.page); 600 add_hidden_formfields(0, 1, ctx->qry.page);
593 html("<select name='h' onchange='this.form.submit();'>\n"); 601 html("<select name='h' onchange='this.form.submit();'>\n");
594 for_each_branch_ref(print_branch_option, ctx->qry.head); 602 for_each_branch_ref(print_branch_option, ctx->qry.head);
595 html("</select> "); 603 html("</select> ");
596 html("<input type='submit' name='' value='switch'/>"); 604 html("<input type='submit' name='' value='switch'/>");
597 html("</form>"); 605 html("</form>");
598 } else 606 } else
599 html_txt(ctx->cfg.root_title); 607 html_txt(ctx->cfg.root_title);
600 html("</td></tr>\n"); 608 html("</td></tr>\n");
601 609
602 html("<tr><td class='sub'>"); 610 html("<tr><td class='sub'>");
603 if (ctx->repo) { 611 if (ctx->repo) {
604 html_txt(ctx->repo->desc); 612 html_txt(ctx->repo->desc);
605 html("</td><td class='sub right'>"); 613 html("</td><td class='sub right'>");
606 html_txt(ctx->repo->owner); 614 html_txt(ctx->repo->owner);
607 } else { 615 } else {
608 if (ctx->cfg.root_desc) 616 if (ctx->cfg.root_desc)
609 html_txt(ctx->cfg.root_desc); 617 html_txt(ctx->cfg.root_desc);
610 else if (ctx->cfg.index_info) 618 else if (ctx->cfg.index_info)
611 html_include(ctx->cfg.index_info); 619 html_include(ctx->cfg.index_info);
612 } 620 }
613 html("</td></tr></table>\n"); 621 html("</td></tr></table>\n");
614 622
615 html("<table class='tabs'><tr><td>\n"); 623 html("<table class='tabs'><tr><td>\n");
616 if (ctx->repo) { 624 if (ctx->repo) {
617 reporevlink(NULL, "summary", NULL, hc(cmd, "summary"), 625 reporevlink(NULL, "summary", NULL, hc(cmd, "summary"),
618 ctx->qry.head, NULL, NULL); 626 ctx->qry.head, NULL, NULL);
619 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 627 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
620 ctx->qry.sha1, NULL); 628 ctx->qry.sha1, NULL);
621 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 629 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
622 NULL, NULL, 0, NULL, NULL); 630 NULL, NULL, 0, NULL, NULL);
623 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 631 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
624 ctx->qry.sha1, NULL); 632 ctx->qry.sha1, NULL);
625 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 633 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
626 ctx->qry.head, ctx->qry.sha1); 634 ctx->qry.head, ctx->qry.sha1);
627 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 635 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
628 ctx->qry.sha1, ctx->qry.sha2, NULL); 636 ctx->qry.sha1, ctx->qry.sha2, NULL);
629 if (ctx->repo->readme) 637 if (ctx->repo->readme)
630 reporevlink("about", "about", NULL, 638 reporevlink("about", "about", NULL,
631 hc(cmd, "about"), ctx->qry.head, NULL, 639 hc(cmd, "about"), ctx->qry.head, NULL,
632 NULL); 640 NULL);
633 html("</td><td class='form'>"); 641 html("</td><td class='form'>");
634 html("<form class='right' method='get' action='"); 642 html("<form class='right' method='get' action='");
635 if (ctx->cfg.virtual_root) 643 if (ctx->cfg.virtual_root)
636 html_attr(cgit_fileurl(ctx->qry.repo, "log", 644 html_attr(cgit_fileurl(ctx->qry.repo, "log",
637 ctx->qry.path, NULL)); 645 ctx->qry.path, NULL));
638 html("'>\n"); 646 html("'>\n");
639 add_hidden_formfields(1, 0, "log"); 647 add_hidden_formfields(1, 0, "log");
640 html("<select name='qt'>\n"); 648 html("<select name='qt'>\n");
641 html_option("grep", "log msg", ctx->qry.grep); 649 html_option("grep", "log msg", ctx->qry.grep);
642 html_option("author", "author", ctx->qry.grep); 650 html_option("author", "author", ctx->qry.grep);
643 html_option("committer", "committer", ctx->qry.grep); 651 html_option("committer", "committer", ctx->qry.grep);
644 html("</select>\n"); 652 html("</select>\n");
645 html("<input class='txt' type='text' size='10' name='q' value='"); 653 html("<input class='txt' type='text' size='10' name='q' value='");
646 html_attr(ctx->qry.search); 654 html_attr(ctx->qry.search);
647 html("'/>\n"); 655 html("'/>\n");
648 html("<input type='submit' value='search'/>\n"); 656 html("<input type='submit' value='search'/>\n");
649 html("</form>\n"); 657 html("</form>\n");
650 } else { 658 } else {
651 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 659 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
652 if (ctx->cfg.root_readme) 660 if (ctx->cfg.root_readme)
653 site_link("about", "about", NULL, hc(cmd, "about"), 661 site_link("about", "about", NULL, hc(cmd, "about"),
654 NULL, 0); 662 NULL, 0);
655 html("</td><td class='form'>"); 663 html("</td><td class='form'>");
656 html("<form method='get' action='"); 664 html("<form method='get' action='");
657 html_attr(cgit_rooturl()); 665 html_attr(cgit_rooturl());
658 html("'>\n"); 666 html("'>\n");
659 html("<input type='text' name='q' size='10' value='"); 667 html("<input type='text' name='q' size='10' value='");
660 html_attr(ctx->qry.search); 668 html_attr(ctx->qry.search);
661 html("'/>\n"); 669 html("'/>\n");
662 html("<input type='submit' value='search'/>\n"); 670 html("<input type='submit' value='search'/>\n");
663 html("</form>"); 671 html("</form>");
664 } 672 }
665 html("</td></tr></table>\n"); 673 html("</td></tr></table>\n");
666 html("<div class='content'>"); 674 html("<div class='content'>");
667} 675}
668 676
669void cgit_print_filemode(unsigned short mode) 677void cgit_print_filemode(unsigned short mode)
670{ 678{
671 if (S_ISDIR(mode)) 679 if (S_ISDIR(mode))
672 html("d"); 680 html("d");
673 else if (S_ISLNK(mode)) 681 else if (S_ISLNK(mode))
674 html("l"); 682 html("l");
675 else if (S_ISGITLINK(mode)) 683 else if (S_ISGITLINK(mode))
676 html("m"); 684 html("m");
677 else 685 else
678 html("-"); 686 html("-");
679 html_fileperm(mode >> 6); 687 html_fileperm(mode >> 6);
680 html_fileperm(mode >> 3); 688 html_fileperm(mode >> 3);
681 html_fileperm(mode); 689 html_fileperm(mode);
682} 690}
683 691
684void cgit_print_snapshot_links(const char *repo, const char *head, 692void cgit_print_snapshot_links(const char *repo, const char *head,
685 const char *hex, int snapshots) 693 const char *hex, int snapshots)
686{ 694{
687 const struct cgit_snapshot_format* f; 695 const struct cgit_snapshot_format* f;
688 char *filename; 696 char *filename;
689 697
690 for (f = cgit_snapshot_formats; f->suffix; f++) { 698 for (f = cgit_snapshot_formats; f->suffix; f++) {
691 if (!(snapshots & f->bit)) 699 if (!(snapshots & f->bit))
692 continue; 700 continue;
693 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 701 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
694 f->suffix); 702 f->suffix);
695 cgit_snapshot_link(filename, NULL, NULL, (char *)head, 703 cgit_snapshot_link(filename, NULL, NULL, (char *)head,
696 (char *)hex, filename); 704 (char *)hex, filename);
697 html("<br/>"); 705 html("<br/>");
698 } 706 }
699} 707}
diff --git a/ui-shared.h b/ui-shared.h
index f4123d3..747f092 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,41 +1,43 @@
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_tree_link(char *name, char *title, char *class, char *head, 13extern void cgit_tree_link(char *name, char *title, char *class, char *head,
14 char *rev, char *path); 14 char *rev, char *path);
15extern void cgit_plain_link(char *name, char *title, char *class, char *head,
16 char *rev, char *path);
15extern void cgit_log_link(char *name, char *title, char *class, char *head, 17extern void cgit_log_link(char *name, char *title, char *class, char *head,
16 char *rev, char *path, int ofs, char *grep, 18 char *rev, char *path, int ofs, char *grep,
17 char *pattern); 19 char *pattern);
18extern void cgit_commit_link(char *name, char *title, char *class, char *head, 20extern void cgit_commit_link(char *name, char *title, char *class, char *head,
19 char *rev); 21 char *rev);
20extern void cgit_patch_link(char *name, char *title, char *class, char *head, 22extern void cgit_patch_link(char *name, char *title, char *class, char *head,
21 char *rev); 23 char *rev);
22extern void cgit_refs_link(char *name, char *title, char *class, char *head, 24extern void cgit_refs_link(char *name, char *title, char *class, char *head,
23 char *rev, char *path); 25 char *rev, char *path);
24extern void cgit_snapshot_link(char *name, char *title, char *class, 26extern void cgit_snapshot_link(char *name, char *title, char *class,
25 char *head, char *rev, char *archivename); 27 char *head, char *rev, char *archivename);
26extern void cgit_diff_link(char *name, char *title, char *class, char *head, 28extern void cgit_diff_link(char *name, char *title, char *class, char *head,
27 char *new_rev, char *old_rev, char *path); 29 char *new_rev, char *old_rev, char *path);
28extern void cgit_object_link(struct object *obj); 30extern void cgit_object_link(struct object *obj);
29 31
30extern void cgit_print_error(char *msg); 32extern void cgit_print_error(char *msg);
31extern void cgit_print_date(time_t secs, char *format, int local_time); 33extern void cgit_print_date(time_t secs, char *format, int local_time);
32extern void cgit_print_age(time_t t, time_t max_relative, char *format); 34extern void cgit_print_age(time_t t, time_t max_relative, char *format);
33extern void cgit_print_http_headers(struct cgit_context *ctx); 35extern void cgit_print_http_headers(struct cgit_context *ctx);
34extern void cgit_print_docstart(struct cgit_context *ctx); 36extern void cgit_print_docstart(struct cgit_context *ctx);
35extern void cgit_print_docend(); 37extern void cgit_print_docend();
36extern void cgit_print_pageheader(struct cgit_context *ctx); 38extern void cgit_print_pageheader(struct cgit_context *ctx);
37extern void cgit_print_filemode(unsigned short mode); 39extern void cgit_print_filemode(unsigned short mode);
38extern void cgit_print_snapshot_links(const char *repo, const char *head, 40extern void cgit_print_snapshot_links(const char *repo, const char *head,
39 const char *hex, int snapshots); 41 const char *hex, int snapshots);
40 42
41#endif /* UI_SHARED_H */ 43#endif /* UI_SHARED_H */
diff --git a/ui-tree.c b/ui-tree.c
index 9a837e2..79332fc 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,223 +1,223 @@
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(" blob: <a href='"); 38 html(" (");
39 html_attr(cgit_pageurl(ctx.qry.repo, "blob", 39 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
40 fmt("id=%s&path=%s", sha1_to_hex(sha1), path))); 40 curr_rev, path);
41 htmlf("'>%s</a>",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 htmlf(linefmt, ++lineno); 57 htmlf(linefmt, ++lineno);
58 html_txt(buf + start); 58 html_txt(buf + start);
59 html("</td></tr>\n"); 59 html("</td></tr>\n");
60 html("</table>\n"); 60 html("</table>\n");
61} 61}
62 62
63 63
64static int ls_item(const unsigned char *sha1, const char *base, int baselen, 64static int ls_item(const unsigned char *sha1, const char *base, int baselen,
65 const char *pathname, unsigned int mode, int stage, 65 const char *pathname, unsigned int mode, int stage,
66 void *cbdata) 66 void *cbdata)
67{ 67{
68 char *name; 68 char *name;
69 char *fullpath; 69 char *fullpath;
70 enum object_type type; 70 enum object_type type;
71 unsigned long size = 0; 71 unsigned long size = 0;
72 72
73 name = xstrdup(pathname); 73 name = xstrdup(pathname);
74 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 74 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
75 ctx.qry.path ? "/" : "", name); 75 ctx.qry.path ? "/" : "", name);
76 76
77 if (!S_ISGITLINK(mode)) { 77 if (!S_ISGITLINK(mode)) {
78 type = sha1_object_info(sha1, &size); 78 type = sha1_object_info(sha1, &size);
79 if (type == OBJ_BAD) { 79 if (type == OBJ_BAD) {
80 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 80 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
81 name, 81 name,
82 sha1_to_hex(sha1)); 82 sha1_to_hex(sha1));
83 return 0; 83 return 0;
84 } 84 }
85 } 85 }
86 86
87 html("<tr><td class='ls-mode'>"); 87 html("<tr><td class='ls-mode'>");
88 cgit_print_filemode(mode); 88 cgit_print_filemode(mode);
89 html("</td><td>"); 89 html("</td><td>");
90 if (S_ISGITLINK(mode)) { 90 if (S_ISGITLINK(mode)) {
91 htmlf("<a class='ls-mod' href='"); 91 htmlf("<a class='ls-mod' href='");
92 html_attr(fmt(ctx.repo->module_link, 92 html_attr(fmt(ctx.repo->module_link,
93 name, 93 name,
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 html("'>"); 95 html("'>");
96 html_txt(name); 96 html_txt(name);
97 html("</a>"); 97 html("</a>");
98 } else if (S_ISDIR(mode)) { 98 } else if (S_ISDIR(mode)) {
99 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 99 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
100 curr_rev, fullpath); 100 curr_rev, fullpath);
101 } else { 101 } else {
102 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head, 102 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head,
103 curr_rev, fullpath); 103 curr_rev, fullpath);
104 } 104 }
105 htmlf("</td><td class='ls-size'>%li</td>", size); 105 htmlf("</td><td class='ls-size'>%li</td>", size);
106 106
107 html("<td>"); 107 html("<td>");
108 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 108 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
109 fullpath, 0, NULL, NULL); 109 fullpath, 0, NULL, NULL);
110 html("</td></tr>\n"); 110 html("</td></tr>\n");
111 free(name); 111 free(name);
112 return 0; 112 return 0;
113} 113}
114 114
115static void ls_head() 115static void ls_head()
116{ 116{
117 html("<table summary='tree listing' class='list'>\n"); 117 html("<table summary='tree listing' class='list'>\n");
118 html("<tr class='nohover'>"); 118 html("<tr class='nohover'>");
119 html("<th class='left'>Mode</th>"); 119 html("<th class='left'>Mode</th>");
120 html("<th class='left'>Name</th>"); 120 html("<th class='left'>Name</th>");
121 html("<th class='right'>Size</th>"); 121 html("<th class='right'>Size</th>");
122 html("<th/>"); 122 html("<th/>");
123 html("</tr>\n"); 123 html("</tr>\n");
124 header = 1; 124 header = 1;
125} 125}
126 126
127static void ls_tail() 127static void ls_tail()
128{ 128{
129 if (!header) 129 if (!header)
130 return; 130 return;
131 html("</table>\n"); 131 html("</table>\n");
132 header = 0; 132 header = 0;
133} 133}
134 134
135static void ls_tree(const unsigned char *sha1, char *path) 135static void ls_tree(const unsigned char *sha1, char *path)
136{ 136{
137 struct tree *tree; 137 struct tree *tree;
138 138
139 tree = parse_tree_indirect(sha1); 139 tree = parse_tree_indirect(sha1);
140 if (!tree) { 140 if (!tree) {
141 cgit_print_error(fmt("Not a tree object: %s", 141 cgit_print_error(fmt("Not a tree object: %s",
142 sha1_to_hex(sha1))); 142 sha1_to_hex(sha1)));
143 return; 143 return;
144 } 144 }
145 145
146 ls_head(); 146 ls_head();
147 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 147 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
148 ls_tail(); 148 ls_tail();
149} 149}
150 150
151 151
152static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 152static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
153 const char *pathname, unsigned mode, int stage, 153 const char *pathname, unsigned mode, int stage,
154 void *cbdata) 154 void *cbdata)
155{ 155{
156 static int state; 156 static int state;
157 static char buffer[PATH_MAX]; 157 static char buffer[PATH_MAX];
158 char *url; 158 char *url;
159 159
160 if (state == 0) { 160 if (state == 0) {
161 memcpy(buffer, base, baselen); 161 memcpy(buffer, base, baselen);
162 strcpy(buffer+baselen, pathname); 162 strcpy(buffer+baselen, pathname);
163 url = cgit_pageurl(ctx.qry.repo, "tree", 163 url = cgit_pageurl(ctx.qry.repo, "tree",
164 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 164 fmt("h=%s&amp;path=%s", curr_rev, buffer));
165 html("/"); 165 html("/");
166 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 166 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
167 curr_rev, buffer); 167 curr_rev, buffer);
168 168
169 if (strcmp(match_path, buffer)) 169 if (strcmp(match_path, buffer))
170 return READ_TREE_RECURSIVE; 170 return READ_TREE_RECURSIVE;
171 171
172 if (S_ISDIR(mode)) { 172 if (S_ISDIR(mode)) {
173 state = 1; 173 state = 1;
174 ls_head(); 174 ls_head();
175 return READ_TREE_RECURSIVE; 175 return READ_TREE_RECURSIVE;
176 } else { 176 } else {
177 print_object(sha1, buffer); 177 print_object(sha1, buffer);
178 return 0; 178 return 0;
179 } 179 }
180 } 180 }
181 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 181 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
182 return 0; 182 return 0;
183} 183}
184 184
185 185
186/* 186/*
187 * Show a tree or a blob 187 * Show a tree or a blob
188 * rev: the commit pointing at the root tree object 188 * rev: the commit pointing at the root tree object
189 * path: path to tree or blob 189 * path: path to tree or blob
190 */ 190 */
191void cgit_print_tree(const char *rev, char *path) 191void cgit_print_tree(const char *rev, char *path)
192{ 192{
193 unsigned char sha1[20]; 193 unsigned char sha1[20];
194 struct commit *commit; 194 struct commit *commit;
195 const char *paths[] = {path, NULL}; 195 const char *paths[] = {path, NULL};
196 196
197 if (!rev) 197 if (!rev)
198 rev = ctx.qry.head; 198 rev = ctx.qry.head;
199 199
200 curr_rev = xstrdup(rev); 200 curr_rev = xstrdup(rev);
201 if (get_sha1(rev, sha1)) { 201 if (get_sha1(rev, sha1)) {
202 cgit_print_error(fmt("Invalid revision name: %s", rev)); 202 cgit_print_error(fmt("Invalid revision name: %s", rev));
203 return; 203 return;
204 } 204 }
205 commit = lookup_commit_reference(sha1); 205 commit = lookup_commit_reference(sha1);
206 if (!commit || parse_commit(commit)) { 206 if (!commit || parse_commit(commit)) {
207 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 207 cgit_print_error(fmt("Invalid commit reference: %s", rev));
208 return; 208 return;
209 } 209 }
210 210
211 html("path: <a href='"); 211 html("path: <a href='");
212 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev))); 212 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
213 html("'>root</a>"); 213 html("'>root</a>");
214 214
215 if (path == NULL) { 215 if (path == NULL) {
216 ls_tree(commit->tree->object.sha1, NULL); 216 ls_tree(commit->tree->object.sha1, NULL);
217 return; 217 return;
218 } 218 }
219 219
220 match_path = path; 220 match_path = path;
221 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); 221 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
222 ls_tail(); 222 ls_tail();
223} 223}