summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--Makefile16
-rw-r--r--cgit.c14
-rw-r--r--cgit.css104
-rw-r--r--cgit.h5
-rw-r--r--cgitrc.5.txt17
-rwxr-xr-xfilters/syntax-highlighting.sh29
-rw-r--r--shared.c1
-rw-r--r--ui-commit.c11
-rw-r--r--ui-diff.c52
-rw-r--r--ui-log.c4
-rw-r--r--ui-refs.c4
-rw-r--r--ui-shared.c43
-rw-r--r--ui-shared.h5
-rw-r--r--ui-snapshot.c14
-rw-r--r--ui-ssdiff.c369
-rw-r--r--ui-ssdiff.h13
-rw-r--r--ui-tag.c24
-rw-r--r--ui-tree.c6
18 files changed, 679 insertions, 52 deletions
diff --git a/Makefile b/Makefile
index 7b9ac5f..d39a30e 100644
--- a/Makefile
+++ b/Makefile
@@ -1,171 +1,181 @@
1CGIT_VERSION = v0.8.3.1 1CGIT_VERSION = v0.8.3.1
2CGIT_SCRIPT_NAME = cgit.cgi 2CGIT_SCRIPT_NAME = cgit.cgi
3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit 3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) 4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
5CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
6CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
7SHA1_HEADER = <openssl/sha.h> 7SHA1_HEADER = <openssl/sha.h>
8GIT_VER = 1.6.4.3 8GIT_VER = 1.6.4.3
9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
10INSTALL = install 10INSTALL = install
11 11
12# Define NO_STRCASESTR if you don't have strcasestr. 12# Define NO_STRCASESTR if you don't have strcasestr.
13# 13#
14# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
15# implementation (slower).
16#
14# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 17# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
15# 18#
16 19
17#-include config.mak 20#-include config.mak
18 21
19# 22#
20# Platform specific tweaks 23# Platform specific tweaks
21# 24#
22 25
23uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 26uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
24uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') 27uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
25uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') 28uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
26 29
27ifeq ($(uname_O),Cygwin) 30ifeq ($(uname_O),Cygwin)
28 NO_STRCASESTR = YesPlease 31 NO_STRCASESTR = YesPlease
29 NEEDS_LIBICONV = YesPlease 32 NEEDS_LIBICONV = YesPlease
30endif 33endif
31 34
32# 35#
33# Let the user override the above settings. 36# Let the user override the above settings.
34# 37#
35-include cgit.conf 38-include cgit.conf
36 39
37# 40#
38# Define a way to invoke make in subdirs quietly, shamelessly ripped 41# Define a way to invoke make in subdirs quietly, shamelessly ripped
39# from git.git 42# from git.git
40# 43#
41QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir 44QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
42QUIET_SUBDIR1 = 45QUIET_SUBDIR1 =
43 46
44ifneq ($(findstring $(MAKEFLAGS),w),w) 47ifneq ($(findstring $(MAKEFLAGS),w),w)
45PRINT_DIR = --no-print-directory 48PRINT_DIR = --no-print-directory
46else # "make -w" 49else # "make -w"
47NO_SUBDIR = : 50NO_SUBDIR = :
48endif 51endif
49 52
50ifndef V 53ifndef V
51 QUIET_CC = @echo ' ' CC $@; 54 QUIET_CC = @echo ' ' CC $@;
52 QUIET_MM = @echo ' ' MM $@; 55 QUIET_MM = @echo ' ' MM $@;
53 QUIET_SUBDIR0 = +@subdir= 56 QUIET_SUBDIR0 = +@subdir=
54 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ 57 QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
55 $(MAKE) $(PRINT_DIR) -C $$subdir 58 $(MAKE) $(PRINT_DIR) -C $$subdir
56endif 59endif
57 60
58# 61#
59# Define a pattern rule for automatic dependency building 62# Define a pattern rule for automatic dependency building
60# 63#
61%.d: %.c 64%.d: %.c
62 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ 65 $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
63 66
64# 67#
65# Define a pattern rule for silent object building 68# Define a pattern rule for silent object building
66# 69#
67%.o: %.c 70%.o: %.c
68 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 71 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
69 72
70 73
71EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto 74EXTLIBS = git/libgit.a git/xdiff/lib.a -lz
72OBJECTS = 75OBJECTS =
73OBJECTS += cache.o 76OBJECTS += cache.o
74OBJECTS += cgit.o 77OBJECTS += cgit.o
75OBJECTS += cmd.o 78OBJECTS += cmd.o
76OBJECTS += configfile.o 79OBJECTS += configfile.o
77OBJECTS += html.o 80OBJECTS += html.o
78OBJECTS += parsing.o 81OBJECTS += parsing.o
79OBJECTS += scan-tree.o 82OBJECTS += scan-tree.o
80OBJECTS += shared.o 83OBJECTS += shared.o
81OBJECTS += ui-atom.o 84OBJECTS += ui-atom.o
82OBJECTS += ui-blob.o 85OBJECTS += ui-blob.o
83OBJECTS += ui-clone.o 86OBJECTS += ui-clone.o
84OBJECTS += ui-commit.o 87OBJECTS += ui-commit.o
85OBJECTS += ui-diff.o 88OBJECTS += ui-diff.o
86OBJECTS += ui-log.o 89OBJECTS += ui-log.o
87OBJECTS += ui-patch.o 90OBJECTS += ui-patch.o
88OBJECTS += ui-plain.o 91OBJECTS += ui-plain.o
89OBJECTS += ui-refs.o 92OBJECTS += ui-refs.o
90OBJECTS += ui-repolist.o 93OBJECTS += ui-repolist.o
91OBJECTS += ui-shared.o 94OBJECTS += ui-shared.o
92OBJECTS += ui-snapshot.o 95OBJECTS += ui-snapshot.o
96OBJECTS += ui-ssdiff.o
93OBJECTS += ui-stats.o 97OBJECTS += ui-stats.o
94OBJECTS += ui-summary.o 98OBJECTS += ui-summary.o
95OBJECTS += ui-tag.o 99OBJECTS += ui-tag.o
96OBJECTS += ui-tree.o 100OBJECTS += ui-tree.o
97 101
98ifdef NEEDS_LIBICONV 102ifdef NEEDS_LIBICONV
99 EXTLIBS += -liconv 103 EXTLIBS += -liconv
100endif 104endif
101 105
102 106
103.PHONY: all libgit test install uninstall clean force-version get-git \ 107.PHONY: all libgit test install uninstall clean force-version get-git \
104 doc man-doc html-doc clean-doc 108 doc man-doc html-doc clean-doc
105 109
106all: cgit 110all: cgit
107 111
108VERSION: force-version 112VERSION: force-version
109 @./gen-version.sh "$(CGIT_VERSION)" 113 @./gen-version.sh "$(CGIT_VERSION)"
110-include VERSION 114-include VERSION
111 115
112 116
113CFLAGS += -g -Wall -Igit 117CFLAGS += -g -Wall -Igit
114CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 118CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
115CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 119CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
116CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 120CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
117CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 121CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
118CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 122CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
119 123
120ifdef NO_ICONV 124ifdef NO_ICONV
121 CFLAGS += -DNO_ICONV 125 CFLAGS += -DNO_ICONV
122endif 126endif
123ifdef NO_STRCASESTR 127ifdef NO_STRCASESTR
124 CFLAGS += -DNO_STRCASESTR 128 CFLAGS += -DNO_STRCASESTR
125endif 129endif
130ifdef NO_OPENSSL
131 CFLAGS += -DNO_OPENSSL
132 GIT_OPTIONS += NO_OPENSSL=1
133else
134 EXTLIBS += -lcrypto
135endif
126 136
127cgit: $(OBJECTS) libgit 137cgit: $(OBJECTS) libgit
128 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 138 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
129 139
130cgit.o: VERSION 140cgit.o: VERSION
131 141
132-include $(OBJECTS:.o=.d) 142-include $(OBJECTS:.o=.d)
133 143
134libgit: 144libgit:
135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a 145 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a
136 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a 146 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a
137 147
138test: all 148test: all
139 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 149 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
140 150
141install: all 151install: all
142 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 152 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
143 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 153 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
144 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 154 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
145 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 155 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
146 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 156 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
147 157
148uninstall: 158uninstall:
149 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 159 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
150 rm -f $(CGIT_DATA_PATH)/cgit.css 160 rm -f $(CGIT_DATA_PATH)/cgit.css
151 rm -f $(CGIT_DATA_PATH)/cgit.png 161 rm -f $(CGIT_DATA_PATH)/cgit.png
152 162
153doc: man-doc html-doc pdf-doc 163doc: man-doc html-doc pdf-doc
154 164
155man-doc: cgitrc.5.txt 165man-doc: cgitrc.5.txt
156 a2x -f manpage cgitrc.5.txt 166 a2x -f manpage cgitrc.5.txt
157 167
158html-doc: cgitrc.5.txt 168html-doc: cgitrc.5.txt
159 a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt 169 a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt
160 170
161pdf-doc: cgitrc.5.txt 171pdf-doc: cgitrc.5.txt
162 a2x -f pdf cgitrc.5.txt 172 a2x -f pdf cgitrc.5.txt
163 173
164clean: clean-doc 174clean: clean-doc
165 rm -f cgit VERSION *.o *.d 175 rm -f cgit VERSION *.o *.d
166 176
167clean-doc: 177clean-doc:
168 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo 178 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
169 179
170get-git: 180get-git:
171 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 181 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cgit.c b/cgit.c
index 6bb712d..e46c00a 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,377 +1,391 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value) 20void add_mimetype(const char *name, const char *value)
21{ 21{
22 struct string_list_item *item; 22 struct string_list_item *item;
23 23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value); 25 item->util = xstrdup(value);
26} 26}
27 27
28struct cgit_filter *new_filter(const char *cmd, int extra_args) 28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{ 29{
30 struct cgit_filter *f; 30 struct cgit_filter *f;
31 31
32 if (!cmd || !cmd[0]) 32 if (!cmd || !cmd[0])
33 return NULL; 33 return NULL;
34 34
35 f = xmalloc(sizeof(struct cgit_filter)); 35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd); 36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd; 38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL; 39 f->argv[1] = NULL;
40 return f; 40 return f;
41} 41}
42 42
43static void process_cached_repolist(const char *path); 43static void process_cached_repolist(const char *path);
44 44
45void repo_config(struct cgit_repo *repo, const char *name, const char *value) 45void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{ 46{
47 if (!strcmp(name, "name")) 47 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value); 48 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url")) 49 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value); 50 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc")) 51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value); 52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner")) 53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "enable-remote-branches"))
64 repo->enable_remote_branches = atoi(value);
63 else if (!strcmp(name, "max-stats")) 65 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 66 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 67 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 68 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 69 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 70 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) { 71 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/') 72 if (*value == '/')
71 repo->readme = xstrdup(value); 73 repo->readme = xstrdup(value);
72 else 74 else
73 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 75 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) { 76 } else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter")) 77 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0); 78 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter")) 79 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0); 80 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter")) 81 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1); 82 repo->source_filter = new_filter(value, 1);
81 } 83 }
82} 84}
83 85
84void config_cb(const char *name, const char *value) 86void config_cb(const char *name, const char *value)
85{ 87{
86 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 88 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
87 ctx.cfg.section = xstrdup(value); 89 ctx.cfg.section = xstrdup(value);
88 else if (!strcmp(name, "repo.url")) 90 else if (!strcmp(name, "repo.url"))
89 ctx.repo = cgit_add_repo(value); 91 ctx.repo = cgit_add_repo(value);
90 else if (ctx.repo && !strcmp(name, "repo.path")) 92 else if (ctx.repo && !strcmp(name, "repo.path"))
91 ctx.repo->path = trim_end(value, '/'); 93 ctx.repo->path = trim_end(value, '/');
92 else if (ctx.repo && !prefixcmp(name, "repo.")) 94 else if (ctx.repo && !prefixcmp(name, "repo."))
93 repo_config(ctx.repo, name + 5, value); 95 repo_config(ctx.repo, name + 5, value);
94 else if (!strcmp(name, "root-title")) 96 else if (!strcmp(name, "root-title"))
95 ctx.cfg.root_title = xstrdup(value); 97 ctx.cfg.root_title = xstrdup(value);
96 else if (!strcmp(name, "root-desc")) 98 else if (!strcmp(name, "root-desc"))
97 ctx.cfg.root_desc = xstrdup(value); 99 ctx.cfg.root_desc = xstrdup(value);
98 else if (!strcmp(name, "root-readme")) 100 else if (!strcmp(name, "root-readme"))
99 ctx.cfg.root_readme = xstrdup(value); 101 ctx.cfg.root_readme = xstrdup(value);
100 else if (!strcmp(name, "css")) 102 else if (!strcmp(name, "css"))
101 ctx.cfg.css = xstrdup(value); 103 ctx.cfg.css = xstrdup(value);
102 else if (!strcmp(name, "favicon")) 104 else if (!strcmp(name, "favicon"))
103 ctx.cfg.favicon = xstrdup(value); 105 ctx.cfg.favicon = xstrdup(value);
104 else if (!strcmp(name, "footer")) 106 else if (!strcmp(name, "footer"))
105 ctx.cfg.footer = xstrdup(value); 107 ctx.cfg.footer = xstrdup(value);
106 else if (!strcmp(name, "head-include")) 108 else if (!strcmp(name, "head-include"))
107 ctx.cfg.head_include = xstrdup(value); 109 ctx.cfg.head_include = xstrdup(value);
108 else if (!strcmp(name, "header")) 110 else if (!strcmp(name, "header"))
109 ctx.cfg.header = xstrdup(value); 111 ctx.cfg.header = xstrdup(value);
110 else if (!strcmp(name, "logo")) 112 else if (!strcmp(name, "logo"))
111 ctx.cfg.logo = xstrdup(value); 113 ctx.cfg.logo = xstrdup(value);
112 else if (!strcmp(name, "index-header")) 114 else if (!strcmp(name, "index-header"))
113 ctx.cfg.index_header = xstrdup(value); 115 ctx.cfg.index_header = xstrdup(value);
114 else if (!strcmp(name, "index-info")) 116 else if (!strcmp(name, "index-info"))
115 ctx.cfg.index_info = xstrdup(value); 117 ctx.cfg.index_info = xstrdup(value);
116 else if (!strcmp(name, "logo-link")) 118 else if (!strcmp(name, "logo-link"))
117 ctx.cfg.logo_link = xstrdup(value); 119 ctx.cfg.logo_link = xstrdup(value);
118 else if (!strcmp(name, "module-link")) 120 else if (!strcmp(name, "module-link"))
119 ctx.cfg.module_link = xstrdup(value); 121 ctx.cfg.module_link = xstrdup(value);
120 else if (!strcmp(name, "virtual-root")) { 122 else if (!strcmp(name, "virtual-root")) {
121 ctx.cfg.virtual_root = trim_end(value, '/'); 123 ctx.cfg.virtual_root = trim_end(value, '/');
122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 124 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
123 ctx.cfg.virtual_root = ""; 125 ctx.cfg.virtual_root = "";
124 } else if (!strcmp(name, "nocache")) 126 } else if (!strcmp(name, "nocache"))
125 ctx.cfg.nocache = atoi(value); 127 ctx.cfg.nocache = atoi(value);
126 else if (!strcmp(name, "noplainemail")) 128 else if (!strcmp(name, "noplainemail"))
127 ctx.cfg.noplainemail = atoi(value); 129 ctx.cfg.noplainemail = atoi(value);
128 else if (!strcmp(name, "noheader")) 130 else if (!strcmp(name, "noheader"))
129 ctx.cfg.noheader = atoi(value); 131 ctx.cfg.noheader = atoi(value);
130 else if (!strcmp(name, "snapshots")) 132 else if (!strcmp(name, "snapshots"))
131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 133 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides")) 134 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value); 135 ctx.cfg.enable_filter_overrides = atoi(value);
134 else if (!strcmp(name, "enable-index-links")) 136 else if (!strcmp(name, "enable-index-links"))
135 ctx.cfg.enable_index_links = atoi(value); 137 ctx.cfg.enable_index_links = atoi(value);
136 else if (!strcmp(name, "enable-log-filecount")) 138 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 139 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 140 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 141 ctx.cfg.enable_log_linecount = atoi(value);
142 else if (!strcmp(name, "enable-remote-branches"))
143 ctx.cfg.enable_remote_branches = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 144 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 145 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 146 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 147 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 148 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 149 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 150 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 151 ctx.cfg.cache_root = xstrdup(value);
148 else if (!strcmp(name, "cache-root-ttl")) 152 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 153 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 154 else if (!strcmp(name, "cache-repo-ttl"))
151 ctx.cfg.cache_repo_ttl = atoi(value); 155 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl")) 156 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value); 157 ctx.cfg.cache_scanrc_ttl = atoi(value);
154 else if (!strcmp(name, "cache-static-ttl")) 158 else if (!strcmp(name, "cache-static-ttl"))
155 ctx.cfg.cache_static_ttl = atoi(value); 159 ctx.cfg.cache_static_ttl = atoi(value);
156 else if (!strcmp(name, "cache-dynamic-ttl")) 160 else if (!strcmp(name, "cache-dynamic-ttl"))
157 ctx.cfg.cache_dynamic_ttl = atoi(value); 161 ctx.cfg.cache_dynamic_ttl = atoi(value);
158 else if (!strcmp(name, "about-filter")) 162 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0); 163 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter")) 164 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0); 165 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 166 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 167 ctx.cfg.embedded = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 168 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 169 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 170 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 171 ctx.cfg.max_repodesc_len = atoi(value);
172 else if (!strcmp(name, "max-blob-size"))
173 ctx.cfg.max_blob_size = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 174 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 175 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 176 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 177 ctx.cfg.max_commit_count = atoi(value);
172 else if (!strcmp(name, "scan-path")) 178 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 179 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value); 180 process_cached_repolist(value);
175 else 181 else
176 scan_tree(value, repo_config); 182 scan_tree(value, repo_config);
177 else if (!strcmp(name, "source-filter")) 183 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1); 184 ctx.cfg.source_filter = new_filter(value, 1);
179 else if (!strcmp(name, "summary-log")) 185 else if (!strcmp(name, "summary-log"))
180 ctx.cfg.summary_log = atoi(value); 186 ctx.cfg.summary_log = atoi(value);
181 else if (!strcmp(name, "summary-branches")) 187 else if (!strcmp(name, "summary-branches"))
182 ctx.cfg.summary_branches = atoi(value); 188 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 189 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 190 ctx.cfg.summary_tags = atoi(value);
191 else if (!strcmp(name, "side-by-side-diffs"))
192 ctx.cfg.ssdiff = atoi(value);
185 else if (!strcmp(name, "agefile")) 193 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 194 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 195 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 196 ctx.cfg.renamelimit = atoi(value);
189 else if (!strcmp(name, "robots")) 197 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 198 ctx.cfg.robots = xstrdup(value);
191 else if (!strcmp(name, "clone-prefix")) 199 else if (!strcmp(name, "clone-prefix"))
192 ctx.cfg.clone_prefix = xstrdup(value); 200 ctx.cfg.clone_prefix = xstrdup(value);
193 else if (!strcmp(name, "local-time")) 201 else if (!strcmp(name, "local-time"))
194 ctx.cfg.local_time = atoi(value); 202 ctx.cfg.local_time = atoi(value);
195 else if (!prefixcmp(name, "mimetype.")) 203 else if (!prefixcmp(name, "mimetype."))
196 add_mimetype(name + 9, value); 204 add_mimetype(name + 9, value);
197 else if (!strcmp(name, "include")) 205 else if (!strcmp(name, "include"))
198 parse_configfile(value, config_cb); 206 parse_configfile(value, config_cb);
199} 207}
200 208
201static void querystring_cb(const char *name, const char *value) 209static void querystring_cb(const char *name, const char *value)
202{ 210{
203 if (!value) 211 if (!value)
204 value = ""; 212 value = "";
205 213
206 if (!strcmp(name,"r")) { 214 if (!strcmp(name,"r")) {
207 ctx.qry.repo = xstrdup(value); 215 ctx.qry.repo = xstrdup(value);
208 ctx.repo = cgit_get_repoinfo(value); 216 ctx.repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 217 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 218 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 219 } else if (!strcmp(name, "url")) {
220 if (*value == '/')
221 value++;
212 ctx.qry.url = xstrdup(value); 222 ctx.qry.url = xstrdup(value);
213 cgit_parse_url(value); 223 cgit_parse_url(value);
214 } else if (!strcmp(name, "qt")) { 224 } else if (!strcmp(name, "qt")) {
215 ctx.qry.grep = xstrdup(value); 225 ctx.qry.grep = xstrdup(value);
216 } else if (!strcmp(name, "q")) { 226 } else if (!strcmp(name, "q")) {
217 ctx.qry.search = xstrdup(value); 227 ctx.qry.search = xstrdup(value);
218 } else if (!strcmp(name, "h")) { 228 } else if (!strcmp(name, "h")) {
219 ctx.qry.head = xstrdup(value); 229 ctx.qry.head = xstrdup(value);
220 ctx.qry.has_symref = 1; 230 ctx.qry.has_symref = 1;
221 } else if (!strcmp(name, "id")) { 231 } else if (!strcmp(name, "id")) {
222 ctx.qry.sha1 = xstrdup(value); 232 ctx.qry.sha1 = xstrdup(value);
223 ctx.qry.has_sha1 = 1; 233 ctx.qry.has_sha1 = 1;
224 } else if (!strcmp(name, "id2")) { 234 } else if (!strcmp(name, "id2")) {
225 ctx.qry.sha2 = xstrdup(value); 235 ctx.qry.sha2 = xstrdup(value);
226 ctx.qry.has_sha1 = 1; 236 ctx.qry.has_sha1 = 1;
227 } else if (!strcmp(name, "ofs")) { 237 } else if (!strcmp(name, "ofs")) {
228 ctx.qry.ofs = atoi(value); 238 ctx.qry.ofs = atoi(value);
229 } else if (!strcmp(name, "path")) { 239 } else if (!strcmp(name, "path")) {
230 ctx.qry.path = trim_end(value, '/'); 240 ctx.qry.path = trim_end(value, '/');
231 } else if (!strcmp(name, "name")) { 241 } else if (!strcmp(name, "name")) {
232 ctx.qry.name = xstrdup(value); 242 ctx.qry.name = xstrdup(value);
233 } else if (!strcmp(name, "mimetype")) { 243 } else if (!strcmp(name, "mimetype")) {
234 ctx.qry.mimetype = xstrdup(value); 244 ctx.qry.mimetype = xstrdup(value);
235 } else if (!strcmp(name, "s")){ 245 } else if (!strcmp(name, "s")){
236 ctx.qry.sort = xstrdup(value); 246 ctx.qry.sort = xstrdup(value);
237 } else if (!strcmp(name, "showmsg")) { 247 } else if (!strcmp(name, "showmsg")) {
238 ctx.qry.showmsg = atoi(value); 248 ctx.qry.showmsg = atoi(value);
239 } else if (!strcmp(name, "period")) { 249 } else if (!strcmp(name, "period")) {
240 ctx.qry.period = xstrdup(value); 250 ctx.qry.period = xstrdup(value);
251 } else if (!strcmp(name, "ss")) {
252 ctx.qry.ssdiff = atoi(value);
241 } 253 }
242} 254}
243 255
244char *xstrdupn(const char *str) 256char *xstrdupn(const char *str)
245{ 257{
246 return (str ? xstrdup(str) : NULL); 258 return (str ? xstrdup(str) : NULL);
247} 259}
248 260
249static void prepare_context(struct cgit_context *ctx) 261static void prepare_context(struct cgit_context *ctx)
250{ 262{
251 memset(ctx, 0, sizeof(ctx)); 263 memset(ctx, 0, sizeof(ctx));
252 ctx->cfg.agefile = "info/web/last-modified"; 264 ctx->cfg.agefile = "info/web/last-modified";
253 ctx->cfg.nocache = 0; 265 ctx->cfg.nocache = 0;
254 ctx->cfg.cache_size = 0; 266 ctx->cfg.cache_size = 0;
255 ctx->cfg.cache_dynamic_ttl = 5; 267 ctx->cfg.cache_dynamic_ttl = 5;
256 ctx->cfg.cache_max_create_time = 5; 268 ctx->cfg.cache_max_create_time = 5;
257 ctx->cfg.cache_repo_ttl = 5; 269 ctx->cfg.cache_repo_ttl = 5;
258 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 270 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
259 ctx->cfg.cache_root_ttl = 5; 271 ctx->cfg.cache_root_ttl = 5;
260 ctx->cfg.cache_scanrc_ttl = 15; 272 ctx->cfg.cache_scanrc_ttl = 15;
261 ctx->cfg.cache_static_ttl = -1; 273 ctx->cfg.cache_static_ttl = -1;
262 ctx->cfg.css = "/cgit.css"; 274 ctx->cfg.css = "/cgit.css";
263 ctx->cfg.logo = "/cgit.png"; 275 ctx->cfg.logo = "/cgit.png";
264 ctx->cfg.local_time = 0; 276 ctx->cfg.local_time = 0;
265 ctx->cfg.enable_tree_linenumbers = 1; 277 ctx->cfg.enable_tree_linenumbers = 1;
266 ctx->cfg.max_repo_count = 50; 278 ctx->cfg.max_repo_count = 50;
267 ctx->cfg.max_commit_count = 50; 279 ctx->cfg.max_commit_count = 50;
268 ctx->cfg.max_lock_attempts = 5; 280 ctx->cfg.max_lock_attempts = 5;
269 ctx->cfg.max_msg_len = 80; 281 ctx->cfg.max_msg_len = 80;
270 ctx->cfg.max_repodesc_len = 80; 282 ctx->cfg.max_repodesc_len = 80;
283 ctx->cfg.max_blob_size = 0;
271 ctx->cfg.max_stats = 0; 284 ctx->cfg.max_stats = 0;
272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 285 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
273 ctx->cfg.renamelimit = -1; 286 ctx->cfg.renamelimit = -1;
274 ctx->cfg.robots = "index, nofollow"; 287 ctx->cfg.robots = "index, nofollow";
275 ctx->cfg.root_title = "Git repository browser"; 288 ctx->cfg.root_title = "Git repository browser";
276 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 289 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
277 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 290 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
278 ctx->cfg.section = ""; 291 ctx->cfg.section = "";
279 ctx->cfg.summary_branches = 10; 292 ctx->cfg.summary_branches = 10;
280 ctx->cfg.summary_log = 10; 293 ctx->cfg.summary_log = 10;
281 ctx->cfg.summary_tags = 10; 294 ctx->cfg.summary_tags = 10;
295 ctx->cfg.ssdiff = 0;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 296 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 297 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS")); 298 ctx->env.https = xstrdupn(getenv("HTTPS"));
285 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 299 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
286 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 300 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
287 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 301 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
288 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 302 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
289 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 303 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
290 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 304 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
291 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 305 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
292 ctx->page.mimetype = "text/html"; 306 ctx->page.mimetype = "text/html";
293 ctx->page.charset = PAGE_ENCODING; 307 ctx->page.charset = PAGE_ENCODING;
294 ctx->page.filename = NULL; 308 ctx->page.filename = NULL;
295 ctx->page.size = 0; 309 ctx->page.size = 0;
296 ctx->page.modified = time(NULL); 310 ctx->page.modified = time(NULL);
297 ctx->page.expires = ctx->page.modified; 311 ctx->page.expires = ctx->page.modified;
298 ctx->page.etag = NULL; 312 ctx->page.etag = NULL;
299 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 313 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
300 if (ctx->env.script_name) 314 if (ctx->env.script_name)
301 ctx->cfg.script_name = ctx->env.script_name; 315 ctx->cfg.script_name = ctx->env.script_name;
302 if (ctx->env.query_string) 316 if (ctx->env.query_string)
303 ctx->qry.raw = ctx->env.query_string; 317 ctx->qry.raw = ctx->env.query_string;
304 if (!ctx->env.cgit_config) 318 if (!ctx->env.cgit_config)
305 ctx->env.cgit_config = CGIT_CONFIG; 319 ctx->env.cgit_config = CGIT_CONFIG;
306} 320}
307 321
308struct refmatch { 322struct refmatch {
309 char *req_ref; 323 char *req_ref;
310 char *first_ref; 324 char *first_ref;
311 int match; 325 int match;
312}; 326};
313 327
314int find_current_ref(const char *refname, const unsigned char *sha1, 328int find_current_ref(const char *refname, const unsigned char *sha1,
315 int flags, void *cb_data) 329 int flags, void *cb_data)
316{ 330{
317 struct refmatch *info; 331 struct refmatch *info;
318 332
319 info = (struct refmatch *)cb_data; 333 info = (struct refmatch *)cb_data;
320 if (!strcmp(refname, info->req_ref)) 334 if (!strcmp(refname, info->req_ref))
321 info->match = 1; 335 info->match = 1;
322 if (!info->first_ref) 336 if (!info->first_ref)
323 info->first_ref = xstrdup(refname); 337 info->first_ref = xstrdup(refname);
324 return info->match; 338 return info->match;
325} 339}
326 340
327char *find_default_branch(struct cgit_repo *repo) 341char *find_default_branch(struct cgit_repo *repo)
328{ 342{
329 struct refmatch info; 343 struct refmatch info;
330 char *ref; 344 char *ref;
331 345
332 info.req_ref = repo->defbranch; 346 info.req_ref = repo->defbranch;
333 info.first_ref = NULL; 347 info.first_ref = NULL;
334 info.match = 0; 348 info.match = 0;
335 for_each_branch_ref(find_current_ref, &info); 349 for_each_branch_ref(find_current_ref, &info);
336 if (info.match) 350 if (info.match)
337 ref = info.req_ref; 351 ref = info.req_ref;
338 else 352 else
339 ref = info.first_ref; 353 ref = info.first_ref;
340 if (ref) 354 if (ref)
341 ref = xstrdup(ref); 355 ref = xstrdup(ref);
342 return ref; 356 return ref;
343} 357}
344 358
345static int prepare_repo_cmd(struct cgit_context *ctx) 359static int prepare_repo_cmd(struct cgit_context *ctx)
346{ 360{
347 char *tmp; 361 char *tmp;
348 unsigned char sha1[20]; 362 unsigned char sha1[20];
349 int nongit = 0; 363 int nongit = 0;
350 364
351 setenv("GIT_DIR", ctx->repo->path, 1); 365 setenv("GIT_DIR", ctx->repo->path, 1);
352 setup_git_directory_gently(&nongit); 366 setup_git_directory_gently(&nongit);
353 if (nongit) { 367 if (nongit) {
354 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 368 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
355 "config error"); 369 "config error");
356 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 370 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
357 ctx->repo = NULL; 371 ctx->repo = NULL;
358 cgit_print_http_headers(ctx); 372 cgit_print_http_headers(ctx);
359 cgit_print_docstart(ctx); 373 cgit_print_docstart(ctx);
360 cgit_print_pageheader(ctx); 374 cgit_print_pageheader(ctx);
361 cgit_print_error(tmp); 375 cgit_print_error(tmp);
362 cgit_print_docend(); 376 cgit_print_docend();
363 return 1; 377 return 1;
364 } 378 }
365 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 379 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
366 380
367 if (!ctx->qry.head) { 381 if (!ctx->qry.head) {
368 ctx->qry.nohead = 1; 382 ctx->qry.nohead = 1;
369 ctx->qry.head = find_default_branch(ctx->repo); 383 ctx->qry.head = find_default_branch(ctx->repo);
370 ctx->repo->defbranch = ctx->qry.head; 384 ctx->repo->defbranch = ctx->qry.head;
371 } 385 }
372 386
373 if (!ctx->qry.head) { 387 if (!ctx->qry.head) {
374 cgit_print_http_headers(ctx); 388 cgit_print_http_headers(ctx);
375 cgit_print_docstart(ctx); 389 cgit_print_docstart(ctx);
376 cgit_print_pageheader(ctx); 390 cgit_print_pageheader(ctx);
377 cgit_print_error("Repository seems to be empty"); 391 cgit_print_error("Repository seems to be empty");
diff --git a/cgit.css b/cgit.css
index c47ebc9..0cb894a 100644
--- a/cgit.css
+++ b/cgit.css
@@ -69,192 +69,197 @@ table.tabs {
69 margin-top: 2em; 69 margin-top: 2em;
70 margin-bottom: 0px; 70 margin-bottom: 0px;
71 width: 100%; 71 width: 100%;
72} 72}
73 73
74table.tabs td { 74table.tabs td {
75 padding: 0px 1em; 75 padding: 0px 1em;
76 vertical-align: bottom; 76 vertical-align: bottom;
77} 77}
78 78
79table.tabs td a { 79table.tabs td a {
80 padding: 2px 0.75em; 80 padding: 2px 0.75em;
81 color: #777; 81 color: #777;
82 font-size: 110%; 82 font-size: 110%;
83} 83}
84 84
85table.tabs td a.active { 85table.tabs td a.active {
86 color: #000; 86 color: #000;
87 background-color: #ccc; 87 background-color: #ccc;
88} 88}
89 89
90table.tabs td.form { 90table.tabs td.form {
91 text-align: right; 91 text-align: right;
92} 92}
93 93
94table.tabs td.form form { 94table.tabs td.form form {
95 padding-bottom: 2px; 95 padding-bottom: 2px;
96 font-size: 90%; 96 font-size: 90%;
97 white-space: nowrap; 97 white-space: nowrap;
98} 98}
99 99
100table.tabs td.form input, 100table.tabs td.form input,
101table.tabs td.form select { 101table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.content { 105div.content {
106 margin: 0px; 106 margin: 0px;
107 padding: 2em; 107 padding: 2em;
108 border-top: solid 3px #ccc; 108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 109 border-bottom: solid 3px #ccc;
110} 110}
111 111
112 112
113table.list { 113table.list {
114 width: 100%; 114 width: 100%;
115 border: none; 115 border: none;
116 border-collapse: collapse; 116 border-collapse: collapse;
117} 117}
118 118
119table.list tr { 119table.list tr {
120 background: white; 120 background: white;
121} 121}
122 122
123table.list tr.logheader { 123table.list tr.logheader {
124 background: #eee; 124 background: #eee;
125} 125}
126 126
127table.list tr:hover { 127table.list tr:hover {
128 background: #eee; 128 background: #eee;
129} 129}
130 130
131table.list tr.nohover:hover { 131table.list tr.nohover:hover {
132 background: white; 132 background: white;
133} 133}
134 134
135table.list th { 135table.list th {
136 font-weight: bold; 136 font-weight: bold;
137 /* color: #888; 137 /* color: #888;
138 border-top: dashed 1px #888; 138 border-top: dashed 1px #888;
139 border-bottom: dashed 1px #888; 139 border-bottom: dashed 1px #888;
140 */ 140 */
141 padding: 0.1em 0.5em 0.05em 0.5em; 141 padding: 0.1em 0.5em 0.05em 0.5em;
142 vertical-align: baseline; 142 vertical-align: baseline;
143} 143}
144 144
145table.list td { 145table.list td {
146 border: none; 146 border: none;
147 padding: 0.1em 0.5em 0.1em 0.5em; 147 padding: 0.1em 0.5em 0.1em 0.5em;
148} 148}
149 149
150table.list td.logsubject { 150table.list td.logsubject {
151 font-family: monospace; 151 font-family: monospace;
152 font-weight: bold; 152 font-weight: bold;
153} 153}
154 154
155table.list td.logmsg { 155table.list td.logmsg {
156 font-family: monospace; 156 font-family: monospace;
157 white-space: pre; 157 white-space: pre;
158 padding: 1em 0.5em 2em 0.5em; 158 padding: 1em 0.5em 2em 0.5em;
159} 159}
160 160
161table.list td a { 161table.list td a {
162 color: black; 162 color: black;
163} 163}
164 164
165table.list td a.ls-dir {
166 font-weight: bold;
167 color: #00f;
168}
169
165table.list td a:hover { 170table.list td a:hover {
166 color: #00f; 171 color: #00f;
167} 172}
168 173
169img { 174img {
170 border: none; 175 border: none;
171} 176}
172 177
173input#switch-btn { 178input#switch-btn {
174 margin: 2px 0px 0px 0px; 179 margin: 2px 0px 0px 0px;
175} 180}
176 181
177td#sidebar input.txt { 182td#sidebar input.txt {
178 width: 100%; 183 width: 100%;
179 margin: 2px 0px 0px 0px; 184 margin: 2px 0px 0px 0px;
180} 185}
181 186
182table#grid { 187table#grid {
183 margin: 0px; 188 margin: 0px;
184} 189}
185 190
186td#content { 191td#content {
187 vertical-align: top; 192 vertical-align: top;
188 padding: 1em 2em 1em 1em; 193 padding: 1em 2em 1em 1em;
189 border: none; 194 border: none;
190} 195}
191 196
192div#summary { 197div#summary {
193 vertical-align: top; 198 vertical-align: top;
194 margin-bottom: 1em; 199 margin-bottom: 1em;
195} 200}
196 201
197table#downloads { 202table#downloads {
198 float: right; 203 float: right;
199 border-collapse: collapse; 204 border-collapse: collapse;
200 border: solid 1px #777; 205 border: solid 1px #777;
201 margin-left: 0.5em; 206 margin-left: 0.5em;
202 margin-bottom: 0.5em; 207 margin-bottom: 0.5em;
203} 208}
204 209
205table#downloads th { 210table#downloads th {
206 background-color: #ccc; 211 background-color: #ccc;
207} 212}
208 213
209div#blob { 214div#blob {
210 border: solid 1px black; 215 border: solid 1px black;
211} 216}
212 217
213div.error { 218div.error {
214 color: red; 219 color: red;
215 font-weight: bold; 220 font-weight: bold;
216 margin: 1em 2em; 221 margin: 1em 2em;
217} 222}
218 223
219a.ls-blob, a.ls-dir, a.ls-mod { 224a.ls-blob, a.ls-dir, a.ls-mod {
220 font-family: monospace; 225 font-family: monospace;
221} 226}
222 227
223td.ls-size { 228td.ls-size {
224 text-align: right; 229 text-align: right;
225 font-family: monospace; 230 font-family: monospace;
226 width: 10em; 231 width: 10em;
227} 232}
228 233
229td.ls-mode { 234td.ls-mode {
230 font-family: monospace; 235 font-family: monospace;
231 width: 10em; 236 width: 10em;
232} 237}
233 238
234table.blob { 239table.blob {
235 margin-top: 0.5em; 240 margin-top: 0.5em;
236 border-top: solid 1px black; 241 border-top: solid 1px black;
237} 242}
238 243
239table.blob td.lines { 244table.blob td.lines {
240 margin: 0; padding: 0 0 0 0.5em; 245 margin: 0; padding: 0 0 0 0.5em;
241 vertical-align: top; 246 vertical-align: top;
242 color: black; 247 color: black;
243} 248}
244 249
245table.blob td.linenumbers { 250table.blob td.linenumbers {
246 margin: 0; padding: 0 0.5em 0 0.5em; 251 margin: 0; padding: 0 0.5em 0 0.5em;
247 vertical-align: top; 252 vertical-align: top;
248 text-align: right; 253 text-align: right;
249 border-right: 1px solid gray; 254 border-right: 1px solid gray;
250} 255}
251 256
252table.blob pre { 257table.blob pre {
253 padding: 0; margin: 0; 258 padding: 0; margin: 0;
254} 259}
255 260
256table.blob a.no { 261table.blob a.no {
257 color: gray; 262 color: gray;
258 text-align: right; 263 text-align: right;
259 text-decoration: none; 264 text-decoration: none;
260} 265}
@@ -508,96 +513,195 @@ a.tag-deco {
508 border: solid 1px #777700; 513 border: solid 1px #777700;
509} 514}
510a.remote-deco { 515a.remote-deco {
511 margin: 0px 0.5em; 516 margin: 0px 0.5em;
512 padding: 0px 0.25em; 517 padding: 0px 0.25em;
513 background-color: #ccccff; 518 background-color: #ccccff;
514 border: solid 1px #000077; 519 border: solid 1px #000077;
515} 520}
516a.deco { 521a.deco {
517 margin: 0px 0.5em; 522 margin: 0px 0.5em;
518 padding: 0px 0.25em; 523 padding: 0px 0.25em;
519 background-color: #ff8888; 524 background-color: #ff8888;
520 border: solid 1px #770000; 525 border: solid 1px #770000;
521} 526}
522 527
523div.commit-subject a { 528div.commit-subject a {
524 margin-left: 1em; 529 margin-left: 1em;
525 font-size: 75%; 530 font-size: 75%;
526} 531}
527 532
528table.stats { 533table.stats {
529 border: solid 1px black; 534 border: solid 1px black;
530 border-collapse: collapse; 535 border-collapse: collapse;
531} 536}
532 537
533table.stats th { 538table.stats th {
534 text-align: left; 539 text-align: left;
535 padding: 1px 0.5em; 540 padding: 1px 0.5em;
536 background-color: #eee; 541 background-color: #eee;
537 border: solid 1px black; 542 border: solid 1px black;
538} 543}
539 544
540table.stats td { 545table.stats td {
541 text-align: right; 546 text-align: right;
542 padding: 1px 0.5em; 547 padding: 1px 0.5em;
543 border: solid 1px black; 548 border: solid 1px black;
544} 549}
545 550
546table.stats td.total { 551table.stats td.total {
547 font-weight: bold; 552 font-weight: bold;
548 text-align: left; 553 text-align: left;
549} 554}
550 555
551table.stats td.sum { 556table.stats td.sum {
552 color: #c00; 557 color: #c00;
553 font-weight: bold; 558 font-weight: bold;
554 /*background-color: #eee; */ 559 /*background-color: #eee; */
555} 560}
556 561
557table.stats td.left { 562table.stats td.left {
558 text-align: left; 563 text-align: left;
559} 564}
560 565
561table.vgraph { 566table.vgraph {
562 border-collapse: separate; 567 border-collapse: separate;
563 border: solid 1px black; 568 border: solid 1px black;
564 height: 200px; 569 height: 200px;
565} 570}
566 571
567table.vgraph th { 572table.vgraph th {
568 background-color: #eee; 573 background-color: #eee;
569 font-weight: bold; 574 font-weight: bold;
570 border: solid 1px white; 575 border: solid 1px white;
571 padding: 1px 0.5em; 576 padding: 1px 0.5em;
572} 577}
573 578
574table.vgraph td { 579table.vgraph td {
575 vertical-align: bottom; 580 vertical-align: bottom;
576 padding: 0px 10px; 581 padding: 0px 10px;
577} 582}
578 583
579table.vgraph div.bar { 584table.vgraph div.bar {
580 background-color: #eee; 585 background-color: #eee;
581} 586}
582 587
583table.hgraph { 588table.hgraph {
584 border: solid 1px black; 589 border: solid 1px black;
585 width: 800px; 590 width: 800px;
586} 591}
587 592
588table.hgraph th { 593table.hgraph th {
589 background-color: #eee; 594 background-color: #eee;
590 font-weight: bold; 595 font-weight: bold;
591 border: solid 1px black; 596 border: solid 1px black;
592 padding: 1px 0.5em; 597 padding: 1px 0.5em;
593} 598}
594 599
595table.hgraph td { 600table.hgraph td {
596 vertical-align: center; 601 vertical-align: center;
597 padding: 2px 2px; 602 padding: 2px 2px;
598} 603}
599 604
600table.hgraph div.bar { 605table.hgraph div.bar {
601 background-color: #eee; 606 background-color: #eee;
602 height: 1em; 607 height: 1em;
603} 608}
609
610table.ssdiff {
611 width: 100%;
612}
613
614table.ssdiff td {
615 font-size: 75%;
616 font-family: monospace;
617 white-space: pre;
618 padding: 1px 4px 1px 4px;
619 border-left: solid 1px #aaa;
620 border-right: solid 1px #aaa;
621}
622
623table.ssdiff td.add {
624 color: black;
625 background: #cfc;
626 min-width: 50%;
627}
628
629table.ssdiff td.add_dark {
630 color: black;
631 background: #aca;
632 min-width: 50%;
633}
634
635table.ssdiff span.add {
636 background: #cfc;
637 font-weight: bold;
638}
639
640table.ssdiff td.del {
641 color: black;
642 background: #fcc;
643 min-width: 50%;
644}
645
646table.ssdiff td.del_dark {
647 color: black;
648 background: #caa;
649 min-width: 50%;
650}
651
652table.ssdiff span.del {
653 background: #fcc;
654 font-weight: bold;
655}
656
657table.ssdiff td.changed {
658 color: black;
659 background: #ffc;
660 min-width: 50%;
661}
662
663table.ssdiff td.changed_dark {
664 color: black;
665 background: #cca;
666 min-width: 50%;
667}
668
669table.ssdiff td.lineno {
670 color: black;
671 background: #eee;
672 text-align: right;
673 width: 3em;
674 min-width: 3em;
675}
676
677table.ssdiff td.hunk {
678 color: #black;
679 background: #ccf;
680 border-top: solid 1px #aaa;
681 border-bottom: solid 1px #aaa;
682}
683
684table.ssdiff td.head {
685 border-top: solid 1px #aaa;
686 border-bottom: solid 1px #aaa;
687}
688
689table.ssdiff td.head div.head {
690 font-weight: bold;
691 color: black;
692}
693
694table.ssdiff td.foot {
695 border-top: solid 1px #aaa;
696 border-left: none;
697 border-right: none;
698 border-bottom: none;
699}
700
701table.ssdiff td.space {
702 border: none;
703}
704
705table.ssdiff td.space div {
706 min-height: 3em;
707} \ No newline at end of file
diff --git a/cgit.h b/cgit.h
index 6c6c460..cd4af72 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,292 +1,297 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
52struct cgit_filter { 52struct cgit_filter {
53 char *cmd; 53 char *cmd;
54 char **argv; 54 char **argv;
55 int old_stdout; 55 int old_stdout;
56 int pipe_fh[2]; 56 int pipe_fh[2];
57 int pid; 57 int pid;
58 int exitstatus; 58 int exitstatus;
59}; 59};
60 60
61struct cgit_repo { 61struct cgit_repo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *module_link; 68 char *module_link;
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int enable_remote_branches;
75 int max_stats; 76 int max_stats;
76 time_t mtime; 77 time_t mtime;
77 struct cgit_filter *about_filter; 78 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 79 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 80 struct cgit_filter *source_filter;
80}; 81};
81 82
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 83typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value); 84 const char *value);
84 85
85struct cgit_repolist { 86struct cgit_repolist {
86 int length; 87 int length;
87 int count; 88 int count;
88 struct cgit_repo *repos; 89 struct cgit_repo *repos;
89}; 90};
90 91
91struct commitinfo { 92struct commitinfo {
92 struct commit *commit; 93 struct commit *commit;
93 char *author; 94 char *author;
94 char *author_email; 95 char *author_email;
95 unsigned long author_date; 96 unsigned long author_date;
96 char *committer; 97 char *committer;
97 char *committer_email; 98 char *committer_email;
98 unsigned long committer_date; 99 unsigned long committer_date;
99 char *subject; 100 char *subject;
100 char *msg; 101 char *msg;
101 char *msg_encoding; 102 char *msg_encoding;
102}; 103};
103 104
104struct taginfo { 105struct taginfo {
105 char *tagger; 106 char *tagger;
106 char *tagger_email; 107 char *tagger_email;
107 unsigned long tagger_date; 108 unsigned long tagger_date;
108 char *msg; 109 char *msg;
109}; 110};
110 111
111struct refinfo { 112struct refinfo {
112 const char *refname; 113 const char *refname;
113 struct object *object; 114 struct object *object;
114 union { 115 union {
115 struct taginfo *tag; 116 struct taginfo *tag;
116 struct commitinfo *commit; 117 struct commitinfo *commit;
117 }; 118 };
118}; 119};
119 120
120struct reflist { 121struct reflist {
121 struct refinfo **refs; 122 struct refinfo **refs;
122 int alloc; 123 int alloc;
123 int count; 124 int count;
124}; 125};
125 126
126struct cgit_query { 127struct cgit_query {
127 int has_symref; 128 int has_symref;
128 int has_sha1; 129 int has_sha1;
129 char *raw; 130 char *raw;
130 char *repo; 131 char *repo;
131 char *page; 132 char *page;
132 char *search; 133 char *search;
133 char *grep; 134 char *grep;
134 char *head; 135 char *head;
135 char *sha1; 136 char *sha1;
136 char *sha2; 137 char *sha2;
137 char *path; 138 char *path;
138 char *name; 139 char *name;
139 char *mimetype; 140 char *mimetype;
140 char *url; 141 char *url;
141 char *period; 142 char *period;
142 int ofs; 143 int ofs;
143 int nohead; 144 int nohead;
144 char *sort; 145 char *sort;
145 int showmsg; 146 int showmsg;
147 int ssdiff;
146}; 148};
147 149
148struct cgit_config { 150struct cgit_config {
149 char *agefile; 151 char *agefile;
150 char *cache_root; 152 char *cache_root;
151 char *clone_prefix; 153 char *clone_prefix;
152 char *css; 154 char *css;
153 char *favicon; 155 char *favicon;
154 char *footer; 156 char *footer;
155 char *head_include; 157 char *head_include;
156 char *header; 158 char *header;
157 char *index_header; 159 char *index_header;
158 char *index_info; 160 char *index_info;
159 char *logo; 161 char *logo;
160 char *logo_link; 162 char *logo_link;
161 char *module_link; 163 char *module_link;
162 char *robots; 164 char *robots;
163 char *root_title; 165 char *root_title;
164 char *root_desc; 166 char *root_desc;
165 char *root_readme; 167 char *root_readme;
166 char *script_name; 168 char *script_name;
167 char *section; 169 char *section;
168 char *virtual_root; 170 char *virtual_root;
169 int cache_size; 171 int cache_size;
170 int cache_dynamic_ttl; 172 int cache_dynamic_ttl;
171 int cache_max_create_time; 173 int cache_max_create_time;
172 int cache_repo_ttl; 174 int cache_repo_ttl;
173 int cache_root_ttl; 175 int cache_root_ttl;
174 int cache_scanrc_ttl; 176 int cache_scanrc_ttl;
175 int cache_static_ttl; 177 int cache_static_ttl;
176 int embedded; 178 int embedded;
177 int enable_filter_overrides; 179 int enable_filter_overrides;
178 int enable_index_links; 180 int enable_index_links;
179 int enable_log_filecount; 181 int enable_log_filecount;
180 int enable_log_linecount; 182 int enable_log_linecount;
183 int enable_remote_branches;
181 int enable_tree_linenumbers; 184 int enable_tree_linenumbers;
182 int local_time; 185 int local_time;
183 int max_repo_count; 186 int max_repo_count;
184 int max_commit_count; 187 int max_commit_count;
185 int max_lock_attempts; 188 int max_lock_attempts;
186 int max_msg_len; 189 int max_msg_len;
187 int max_repodesc_len; 190 int max_repodesc_len;
191 int max_blob_size;
188 int max_stats; 192 int max_stats;
189 int nocache; 193 int nocache;
190 int noplainemail; 194 int noplainemail;
191 int noheader; 195 int noheader;
192 int renamelimit; 196 int renamelimit;
193 int snapshots; 197 int snapshots;
194 int summary_branches; 198 int summary_branches;
195 int summary_log; 199 int summary_log;
196 int summary_tags; 200 int summary_tags;
201 int ssdiff;
197 struct string_list mimetypes; 202 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 203 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 204 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter; 205 struct cgit_filter *source_filter;
201}; 206};
202 207
203struct cgit_page { 208struct cgit_page {
204 time_t modified; 209 time_t modified;
205 time_t expires; 210 time_t expires;
206 size_t size; 211 size_t size;
207 char *mimetype; 212 char *mimetype;
208 char *charset; 213 char *charset;
209 char *filename; 214 char *filename;
210 char *etag; 215 char *etag;
211 char *title; 216 char *title;
212 int status; 217 int status;
213 char *statusmsg; 218 char *statusmsg;
214}; 219};
215 220
216struct cgit_environment { 221struct cgit_environment {
217 char *cgit_config; 222 char *cgit_config;
218 char *http_host; 223 char *http_host;
219 char *https; 224 char *https;
220 char *no_http; 225 char *no_http;
221 char *path_info; 226 char *path_info;
222 char *query_string; 227 char *query_string;
223 char *request_method; 228 char *request_method;
224 char *script_name; 229 char *script_name;
225 char *server_name; 230 char *server_name;
226 char *server_port; 231 char *server_port;
227}; 232};
228 233
229struct cgit_context { 234struct cgit_context {
230 struct cgit_environment env; 235 struct cgit_environment env;
231 struct cgit_query qry; 236 struct cgit_query qry;
232 struct cgit_config cfg; 237 struct cgit_config cfg;
233 struct cgit_repo *repo; 238 struct cgit_repo *repo;
234 struct cgit_page page; 239 struct cgit_page page;
235}; 240};
236 241
237struct cgit_snapshot_format { 242struct cgit_snapshot_format {
238 const char *suffix; 243 const char *suffix;
239 const char *mimetype; 244 const char *mimetype;
240 write_archive_fn_t write_func; 245 write_archive_fn_t write_func;
241 int bit; 246 int bit;
242}; 247};
243 248
244extern const char *cgit_version; 249extern const char *cgit_version;
245 250
246extern struct cgit_repolist cgit_repolist; 251extern struct cgit_repolist cgit_repolist;
247extern struct cgit_context ctx; 252extern struct cgit_context ctx;
248extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 253extern const struct cgit_snapshot_format cgit_snapshot_formats[];
249 254
250extern struct cgit_repo *cgit_add_repo(const char *url); 255extern struct cgit_repo *cgit_add_repo(const char *url);
251extern struct cgit_repo *cgit_get_repoinfo(const char *url); 256extern struct cgit_repo *cgit_get_repoinfo(const char *url);
252extern void cgit_repo_config_cb(const char *name, const char *value); 257extern void cgit_repo_config_cb(const char *name, const char *value);
253 258
254extern int chk_zero(int result, char *msg); 259extern int chk_zero(int result, char *msg);
255extern int chk_positive(int result, char *msg); 260extern int chk_positive(int result, char *msg);
256extern int chk_non_negative(int result, char *msg); 261extern int chk_non_negative(int result, char *msg);
257 262
258extern char *trim_end(const char *str, char c); 263extern char *trim_end(const char *str, char c);
259extern char *strlpart(char *txt, int maxlen); 264extern char *strlpart(char *txt, int maxlen);
260extern char *strrpart(char *txt, int maxlen); 265extern char *strrpart(char *txt, int maxlen);
261 266
262extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 267extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
263extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 268extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
264 int flags, void *cb_data); 269 int flags, void *cb_data);
265 270
266extern void *cgit_free_commitinfo(struct commitinfo *info); 271extern void *cgit_free_commitinfo(struct commitinfo *info);
267 272
268extern int cgit_diff_files(const unsigned char *old_sha1, 273extern int cgit_diff_files(const unsigned char *old_sha1,
269 const unsigned char *new_sha1, 274 const unsigned char *new_sha1,
270 unsigned long *old_size, unsigned long *new_size, 275 unsigned long *old_size, unsigned long *new_size,
271 int *binary, linediff_fn fn); 276 int *binary, linediff_fn fn);
272 277
273extern void cgit_diff_tree(const unsigned char *old_sha1, 278extern void cgit_diff_tree(const unsigned char *old_sha1,
274 const unsigned char *new_sha1, 279 const unsigned char *new_sha1,
275 filepair_fn fn, const char *prefix); 280 filepair_fn fn, const char *prefix);
276 281
277extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 282extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
278 283
279extern char *fmt(const char *format,...); 284extern char *fmt(const char *format,...);
280 285
281extern struct commitinfo *cgit_parse_commit(struct commit *commit); 286extern struct commitinfo *cgit_parse_commit(struct commit *commit);
282extern struct taginfo *cgit_parse_tag(struct tag *tag); 287extern struct taginfo *cgit_parse_tag(struct tag *tag);
283extern void cgit_parse_url(const char *url); 288extern void cgit_parse_url(const char *url);
284 289
285extern const char *cgit_repobasename(const char *reponame); 290extern const char *cgit_repobasename(const char *reponame);
286 291
287extern int cgit_parse_snapshots_mask(const char *str); 292extern int cgit_parse_snapshots_mask(const char *str);
288 293
289extern int cgit_open_filter(struct cgit_filter *filter); 294extern int cgit_open_filter(struct cgit_filter *filter);
290extern int cgit_close_filter(struct cgit_filter *filter); 295extern int cgit_close_filter(struct cgit_filter *filter);
291 296
292extern int readfile(const char *path, char **buf, size_t *size); 297extern int readfile(const char *path, char **buf, size_t *size);
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0c13485..d74d9e7 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -17,386 +17,403 @@ repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
17lines, and lines starting with '#', are ignored. 17lines, and lines starting with '#', are ignored.
18 18
19 19
20LOCATION 20LOCATION
21-------- 21--------
22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At 22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
23runtime, cgit will consult the environment variable CGIT_CONFIG and, if 23runtime, cgit will consult the environment variable CGIT_CONFIG and, if
24defined, use its value instead. 24defined, use its value instead.
25 25
26 26
27GLOBAL SETTINGS 27GLOBAL SETTINGS
28--------------- 28---------------
29about-filter:: 29about-filter::
30 Specifies a command which will be invoked to format the content of 30 Specifies a command which will be invoked to format the content of
31 about pages (both top-level and for each repository). The command will 31 about pages (both top-level and for each repository). The command will
32 get the content of the about-file on its STDIN, and the STDOUT from the 32 get the content of the about-file on its STDIN, and the STDOUT from the
33 command will be included verbatim on the about page. Default value: 33 command will be included verbatim on the about page. Default value:
34 none. 34 none.
35 35
36agefile:: 36agefile::
37 Specifies a path, relative to each repository path, which can be used 37 Specifies a path, relative to each repository path, which can be used
38 to specify the date and time of the youngest commit in the repository. 38 to specify the date and time of the youngest commit in the repository.
39 The first line in the file is used as input to the "parse_date" 39 The first line in the file is used as input to the "parse_date"
40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
41 hh:mm:ss". Default value: "info/web/last-modified". 41 hh:mm:ss". Default value: "info/web/last-modified".
42 42
43cache-root:: 43cache-root::
44 Path used to store the cgit cache entries. Default value: 44 Path used to store the cgit cache entries. Default value:
45 "/var/cache/cgit". 45 "/var/cache/cgit".
46 46
47cache-dynamic-ttl:: 47cache-dynamic-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed without a fixed SHA1. Default 49 version of repository pages accessed without a fixed SHA1. Default
50 value: "5". 50 value: "5".
51 51
52cache-repo-ttl:: 52cache-repo-ttl::
53 Number which specifies the time-to-live, in minutes, for the cached 53 Number which specifies the time-to-live, in minutes, for the cached
54 version of the repository summary page. Default value: "5". 54 version of the repository summary page. Default value: "5".
55 55
56cache-root-ttl:: 56cache-root-ttl::
57 Number which specifies the time-to-live, in minutes, for the cached 57 Number which specifies the time-to-live, in minutes, for the cached
58 version of the repository index page. Default value: "5". 58 version of the repository index page. Default value: "5".
59 59
60cache-scanrc-ttl:: 60cache-scanrc-ttl::
61 Number which specifies the time-to-live, in minutes, for the result 61 Number which specifies the time-to-live, in minutes, for the result
62 of scanning a path for git repositories. Default value: "15". 62 of scanning a path for git repositories. Default value: "15".
63 63
64cache-size:: 64cache-size::
65 The maximum number of entries in the cgit cache. Default value: "0" 65 The maximum number of entries in the cgit cache. Default value: "0"
66 (i.e. caching is disabled). 66 (i.e. caching is disabled).
67 67
68cache-static-ttl:: 68cache-static-ttl::
69 Number which specifies the time-to-live, in minutes, for the cached 69 Number which specifies the time-to-live, in minutes, for the cached
70 version of repository pages accessed with a fixed SHA1. Default value: 70 version of repository pages accessed with a fixed SHA1. Default value:
71 "5". 71 "5".
72 72
73clone-prefix:: 73clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
94enable-filter-overrides:: 94enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 95 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 96 overridden in repository-specific cgitrc files. Default value: none.
97 97
98enable-index-links:: 98enable-index-links::
99 Flag which, when set to "1", will make cgit generate extra links for 99 Flag which, when set to "1", will make cgit generate extra links for
100 each repo in the repository index (specifically, to the "summary", 100 each repo in the repository index (specifically, to the "summary",
101 "commit" and "tree" pages). Default value: "0". 101 "commit" and "tree" pages). Default value: "0".
102 102
103enable-log-filecount:: 103enable-log-filecount::
104 Flag which, when set to "1", will make cgit print the number of 104 Flag which, when set to "1", will make cgit print the number of
105 modified files for each commit on the repository log page. Default 105 modified files for each commit on the repository log page. Default
106 value: "0". 106 value: "0".
107 107
108enable-log-linecount:: 108enable-log-linecount::
109 Flag which, when set to "1", will make cgit print the number of added 109 Flag which, when set to "1", will make cgit print the number of added
110 and removed lines for each commit on the repository log page. Default 110 and removed lines for each commit on the repository log page. Default
111 value: "0". 111 value: "0".
112 112
113enable-remote-branches::
114 Flag which, when set to "1", will make cgit display remote branches
115 in the summary and refs views. Default value: "0". See also:
116 "repo.enable-remote-branches".
117
113enable-tree-linenumbers:: 118enable-tree-linenumbers::
114 Flag which, when set to "1", will make cgit generate linenumber links 119 Flag which, when set to "1", will make cgit generate linenumber links
115 for plaintext blobs printed in the tree view. Default value: "1". 120 for plaintext blobs printed in the tree view. Default value: "1".
116 121
117favicon:: 122favicon::
118 Url used as link to a shortcut icon for cgit. If specified, it is 123 Url used as link to a shortcut icon for cgit. If specified, it is
119 suggested to use the value "/favicon.ico" since certain browsers will 124 suggested to use the value "/favicon.ico" since certain browsers will
120 ignore other values. Default value: none. 125 ignore other values. Default value: none.
121 126
122footer:: 127footer::
123 The content of the file specified with this option will be included 128 The content of the file specified with this option will be included
124 verbatim at the bottom of all pages (i.e. it replaces the standard 129 verbatim at the bottom of all pages (i.e. it replaces the standard
125 "generated by..." message. Default value: none. 130 "generated by..." message. Default value: none.
126 131
127head-include:: 132head-include::
128 The content of the file specified with this option will be included 133 The content of the file specified with this option will be included
129 verbatim in the html HEAD section on all pages. Default value: none. 134 verbatim in the html HEAD section on all pages. Default value: none.
130 135
131header:: 136header::
132 The content of the file specified with this option will be included 137 The content of the file specified with this option will be included
133 verbatim at the top of all pages. Default value: none. 138 verbatim at the top of all pages. Default value: none.
134 139
135include:: 140include::
136 Name of a configfile to include before the rest of the current config- 141 Name of a configfile to include before the rest of the current config-
137 file is parsed. Default value: none. 142 file is parsed. Default value: none.
138 143
139index-header:: 144index-header::
140 The content of the file specified with this option will be included 145 The content of the file specified with this option will be included
141 verbatim above the repository index. This setting is deprecated, and 146 verbatim above the repository index. This setting is deprecated, and
142 will not be supported by cgit-1.0 (use root-readme instead). Default 147 will not be supported by cgit-1.0 (use root-readme instead). Default
143 value: none. 148 value: none.
144 149
145index-info:: 150index-info::
146 The content of the file specified with this option will be included 151 The content of the file specified with this option will be included
147 verbatim below the heading on the repository index page. This setting 152 verbatim below the heading on the repository index page. This setting
148 is deprecated, and will not be supported by cgit-1.0 (use root-desc 153 is deprecated, and will not be supported by cgit-1.0 (use root-desc
149 instead). Default value: none. 154 instead). Default value: none.
150 155
151local-time:: 156local-time::
152 Flag which, if set to "1", makes cgit print commit and tag times in the 157 Flag which, if set to "1", makes cgit print commit and tag times in the
153 servers timezone. Default value: "0". 158 servers timezone. Default value: "0".
154 159
155logo:: 160logo::
156 Url which specifies the source of an image which will be used as a logo 161 Url which specifies the source of an image which will be used as a logo
157 on all cgit pages. Default value: "/cgit.png". 162 on all cgit pages. Default value: "/cgit.png".
158 163
159logo-link:: 164logo-link::
160 Url loaded when clicking on the cgit logo image. If unspecified the 165 Url loaded when clicking on the cgit logo image. If unspecified the
161 calculated url of the repository index page will be used. Default 166 calculated url of the repository index page will be used. Default
162 value: none. 167 value: none.
163 168
164max-commit-count:: 169max-commit-count::
165 Specifies the number of entries to list per page in "log" view. Default 170 Specifies the number of entries to list per page in "log" view. Default
166 value: "50". 171 value: "50".
167 172
168max-message-length:: 173max-message-length::
169 Specifies the maximum number of commit message characters to display in 174 Specifies the maximum number of commit message characters to display in
170 "log" view. Default value: "80". 175 "log" view. Default value: "80".
171 176
172max-repo-count:: 177max-repo-count::
173 Specifies the number of entries to list per page on therepository 178 Specifies the number of entries to list per page on therepository
174 index page. Default value: "50". 179 index page. Default value: "50".
175 180
176max-repodesc-length:: 181max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 182 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 183 on the repository index page. Default value: "80".
179 184
185max-blob-size::
186 Specifies the maximum size of a blob to display HTML for in KBytes.
187 Default value: "0" (limit disabled).
188
180max-stats:: 189max-stats::
181 Set the default maximum statistics period. Valid values are "week", 190 Set the default maximum statistics period. Valid values are "week",
182 "month", "quarter" and "year". If unspecified, statistics are 191 "month", "quarter" and "year". If unspecified, statistics are
183 disabled. Default value: none. See also: "repo.max-stats". 192 disabled. Default value: none. See also: "repo.max-stats".
184 193
185mimetype.<ext>:: 194mimetype.<ext>::
186 Set the mimetype for the specified filename extension. This is used 195 Set the mimetype for the specified filename extension. This is used
187 by the `plain` command when returning blob content. 196 by the `plain` command when returning blob content.
188 197
189module-link:: 198module-link::
190 Text which will be used as the formatstring for a hyperlink when a 199 Text which will be used as the formatstring for a hyperlink when a
191 submodule is printed in a directory listing. The arguments for the 200 submodule is printed in a directory listing. The arguments for the
192 formatstring are the path and SHA1 of the submodule commit. Default 201 formatstring are the path and SHA1 of the submodule commit. Default
193 value: "./?repo=%s&page=commit&id=%s" 202 value: "./?repo=%s&page=commit&id=%s"
194 203
195nocache:: 204nocache::
196 If set to the value "1" caching will be disabled. This settings is 205 If set to the value "1" caching will be disabled. This settings is
197 deprecated, and will not be honored starting with cgit-1.0. Default 206 deprecated, and will not be honored starting with cgit-1.0. Default
198 value: "0". 207 value: "0".
199 208
200noplainemail:: 209noplainemail::
201 If set to "1" showing full author email adresses will be disabled. 210 If set to "1" showing full author email adresses will be disabled.
202 Default value: "0". 211 Default value: "0".
203 212
204noheader:: 213noheader::
205 Flag which, when set to "1", will make cgit omit the standard header 214 Flag which, when set to "1", will make cgit omit the standard header
206 on all pages. Default value: none. See also: "embedded". 215 on all pages. Default value: none. See also: "embedded".
207 216
208renamelimit:: 217renamelimit::
209 Maximum number of files to consider when detecting renames. The value 218 Maximum number of files to consider when detecting renames. The value
210 "-1" uses the compiletime value in git (for further info, look at 219 "-1" uses the compiletime value in git (for further info, look at
211 `man git-diff`). Default value: "-1". 220 `man git-diff`). Default value: "-1".
212 221
213repo.group:: 222repo.group::
214 Legacy alias for "section". This option is deprecated and will not be 223 Legacy alias for "section". This option is deprecated and will not be
215 supported in cgit-1.0. 224 supported in cgit-1.0.
216 225
217robots:: 226robots::
218 Text used as content for the "robots" meta-tag. Default value: 227 Text used as content for the "robots" meta-tag. Default value:
219 "index, nofollow". 228 "index, nofollow".
220 229
221root-desc:: 230root-desc::
222 Text printed below the heading on the repository index page. Default 231 Text printed below the heading on the repository index page. Default
223 value: "a fast webinterface for the git dscm". 232 value: "a fast webinterface for the git dscm".
224 233
225root-readme:: 234root-readme::
226 The content of the file specified with this option will be included 235 The content of the file specified with this option will be included
227 verbatim below the "about" link on the repository index page. Default 236 verbatim below the "about" link on the repository index page. Default
228 value: none. 237 value: none.
229 238
230root-title:: 239root-title::
231 Text printed as heading on the repository index page. Default value: 240 Text printed as heading on the repository index page. Default value:
232 "Git Repository Browser". 241 "Git Repository Browser".
233 242
234scan-path:: 243scan-path::
235 A path which will be scanned for repositories. If caching is enabled, 244 A path which will be scanned for repositories. If caching is enabled,
236 the result will be cached as a cgitrc include-file in the cache 245 the result will be cached as a cgitrc include-file in the cache
237 directory. Default value: none. See also: cache-scanrc-ttl. 246 directory. Default value: none. See also: cache-scanrc-ttl.
238 247
239section:: 248section::
240 The name of the current repository section - all repositories defined 249 The name of the current repository section - all repositories defined
241 after this option will inherit the current section name. Default value: 250 after this option will inherit the current section name. Default value:
242 none. 251 none.
243 252
253side-by-side-diffs::
254 If set to "1" shows side-by-side diffs instead of unidiffs per
255 default. Default value: "0".
256
244snapshots:: 257snapshots::
245 Text which specifies the default set of snapshot formats generated by 258 Text which specifies the default set of snapshot formats generated by
246 cgit. The value is a space-separated list of zero or more of the 259 cgit. The value is a space-separated list of zero or more of the
247 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 260 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
248 261
249source-filter:: 262source-filter::
250 Specifies a command which will be invoked to format plaintext blobs 263 Specifies a command which will be invoked to format plaintext blobs
251 in the tree view. The command will get the blob content on its STDIN 264 in the tree view. The command will get the blob content on its STDIN
252 and the name of the blob as its only command line argument. The STDOUT 265 and the name of the blob as its only command line argument. The STDOUT
253 from the command will be included verbatim as the blob contents, i.e. 266 from the command will be included verbatim as the blob contents, i.e.
254 this can be used to implement e.g. syntax highlighting. Default value: 267 this can be used to implement e.g. syntax highlighting. Default value:
255 none. 268 none.
256 269
257summary-branches:: 270summary-branches::
258 Specifies the number of branches to display in the repository "summary" 271 Specifies the number of branches to display in the repository "summary"
259 view. Default value: "10". 272 view. Default value: "10".
260 273
261summary-log:: 274summary-log::
262 Specifies the number of log entries to display in the repository 275 Specifies the number of log entries to display in the repository
263 "summary" view. Default value: "10". 276 "summary" view. Default value: "10".
264 277
265summary-tags:: 278summary-tags::
266 Specifies the number of tags to display in the repository "summary" 279 Specifies the number of tags to display in the repository "summary"
267 view. Default value: "10". 280 view. Default value: "10".
268 281
269virtual-root:: 282virtual-root::
270 Url which, if specified, will be used as root for all cgit links. It 283 Url which, if specified, will be used as root for all cgit links. It
271 will also cause cgit to generate 'virtual urls', i.e. urls like 284 will also cause cgit to generate 'virtual urls', i.e. urls like
272 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 285 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
273 value: none. 286 value: none.
274 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 287 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
275 same kind of virtual urls, so this option will probably be deprecated. 288 same kind of virtual urls, so this option will probably be deprecated.
276 289
277REPOSITORY SETTINGS 290REPOSITORY SETTINGS
278------------------- 291-------------------
279repo.about-filter:: 292repo.about-filter::
280 Override the default about-filter. Default value: none. See also: 293 Override the default about-filter. Default value: none. See also:
281 "enable-filter-overrides". 294 "enable-filter-overrides".
282 295
283repo.clone-url:: 296repo.clone-url::
284 A list of space-separated urls which can be used to clone this repo. 297 A list of space-separated urls which can be used to clone this repo.
285 Default value: none. 298 Default value: none.
286 299
287repo.commit-filter:: 300repo.commit-filter::
288 Override the default commit-filter. Default value: none. See also: 301 Override the default commit-filter. Default value: none. See also:
289 "enable-filter-overrides". 302 "enable-filter-overrides".
290 303
291repo.defbranch:: 304repo.defbranch::
292 The name of the default branch for this repository. If no such branch 305 The name of the default branch for this repository. If no such branch
293 exists in the repository, the first branch name (when sorted) is used 306 exists in the repository, the first branch name (when sorted) is used
294 as default instead. Default value: "master". 307 as default instead. Default value: "master".
295 308
296repo.desc:: 309repo.desc::
297 The value to show as repository description. Default value: none. 310 The value to show as repository description. Default value: none.
298 311
299repo.enable-log-filecount:: 312repo.enable-log-filecount::
300 A flag which can be used to disable the global setting 313 A flag which can be used to disable the global setting
301 `enable-log-filecount'. Default value: none. 314 `enable-log-filecount'. Default value: none.
302 315
303repo.enable-log-linecount:: 316repo.enable-log-linecount::
304 A flag which can be used to disable the global setting 317 A flag which can be used to disable the global setting
305 `enable-log-linecount'. Default value: none. 318 `enable-log-linecount'. Default value: none.
306 319
320repo.enable-remote-branches::
321 Flag which, when set to "1", will make cgit display remote branches
322 in the summary and refs views. Default value: <enable-remote-branches>.
323
307repo.max-stats:: 324repo.max-stats::
308 Override the default maximum statistics period. Valid values are equal 325 Override the default maximum statistics period. Valid values are equal
309 to the values specified for the global "max-stats" setting. Default 326 to the values specified for the global "max-stats" setting. Default
310 value: none. 327 value: none.
311 328
312repo.name:: 329repo.name::
313 The value to show as repository name. Default value: <repo.url>. 330 The value to show as repository name. Default value: <repo.url>.
314 331
315repo.owner:: 332repo.owner::
316 A value used to identify the owner of the repository. Default value: 333 A value used to identify the owner of the repository. Default value:
317 none. 334 none.
318 335
319repo.path:: 336repo.path::
320 An absolute path to the repository directory. For non-bare repositories 337 An absolute path to the repository directory. For non-bare repositories
321 this is the .git-directory. Default value: none. 338 this is the .git-directory. Default value: none.
322 339
323repo.readme:: 340repo.readme::
324 A path (relative to <repo.path>) which specifies a file to include 341 A path (relative to <repo.path>) which specifies a file to include
325 verbatim as the "About" page for this repo. Default value: none. 342 verbatim as the "About" page for this repo. Default value: none.
326 343
327repo.snapshots:: 344repo.snapshots::
328 A mask of allowed snapshot-formats for this repo, restricted by the 345 A mask of allowed snapshot-formats for this repo, restricted by the
329 "snapshots" global setting. Default value: <snapshots>. 346 "snapshots" global setting. Default value: <snapshots>.
330 347
331repo.section:: 348repo.section::
332 Override the current section name for this repository. Default value: 349 Override the current section name for this repository. Default value:
333 none. 350 none.
334 351
335repo.source-filter:: 352repo.source-filter::
336 Override the default source-filter. Default value: none. See also: 353 Override the default source-filter. Default value: none. See also:
337 "enable-filter-overrides". 354 "enable-filter-overrides".
338 355
339repo.url:: 356repo.url::
340 The relative url used to access the repository. This must be the first 357 The relative url used to access the repository. This must be the first
341 setting specified for each repo. Default value: none. 358 setting specified for each repo. Default value: none.
342 359
343 360
344REPOSITORY-SPECIFIC CGITRC FILE 361REPOSITORY-SPECIFIC CGITRC FILE
345------------------------------- 362-------------------------------
346When the option "scan-path" is used to auto-discover git repositories, cgit 363When the option "scan-path" is used to auto-discover git repositories, cgit
347will try to parse the file "cgitrc" within any found repository. Such a 364will try to parse the file "cgitrc" within any found repository. Such a
348repo-specific config file may contain any of the repo-specific options 365repo-specific config file may contain any of the repo-specific options
349described above, except "repo.url" and "repo.path". Additionally, the "filter" 366described above, except "repo.url" and "repo.path". Additionally, the "filter"
350options are only acknowledged in repo-specific config files when 367options are only acknowledged in repo-specific config files when
351"enable-filter-overrides" is set to "1". 368"enable-filter-overrides" is set to "1".
352 369
353Note: the "repo." prefix is dropped from the option names in repo-specific 370Note: the "repo." prefix is dropped from the option names in repo-specific
354config files, e.g. "repo.desc" becomes "desc". 371config files, e.g. "repo.desc" becomes "desc".
355 372
356 373
357EXAMPLE CGITRC FILE 374EXAMPLE CGITRC FILE
358------------------- 375-------------------
359 376
360.... 377....
361# Enable caching of up to 1000 output entriess 378# Enable caching of up to 1000 output entriess
362cache-size=1000 379cache-size=1000
363 380
364 381
365# Specify some default clone prefixes 382# Specify some default clone prefixes
366clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 383clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
367 384
368# Specify the css url 385# Specify the css url
369css=/css/cgit.css 386css=/css/cgit.css
370 387
371 388
372# Show extra links for each repository on the index page 389# Show extra links for each repository on the index page
373enable-index-links=1 390enable-index-links=1
374 391
375 392
376# Show number of affected files per commit on the log pages 393# Show number of affected files per commit on the log pages
377enable-log-filecount=1 394enable-log-filecount=1
378 395
379 396
380# Show number of added/removed lines per commit on the log pages 397# Show number of added/removed lines per commit on the log pages
381enable-log-linecount=1 398enable-log-linecount=1
382 399
383 400
384# Add a cgit favicon 401# Add a cgit favicon
385favicon=/favicon.ico 402favicon=/favicon.ico
386 403
387 404
388# Use a custom logo 405# Use a custom logo
389logo=/img/mylogo.png 406logo=/img/mylogo.png
390 407
391 408
392# Enable statistics per week, month and quarter 409# Enable statistics per week, month and quarter
393max-stats=quarter 410max-stats=quarter
394 411
395 412
396# Set the title and heading of the repository index page 413# Set the title and heading of the repository index page
397root-title=foobar.com git repositories 414root-title=foobar.com git repositories
398 415
399 416
400# Set a subheading for the repository index page 417# Set a subheading for the repository index page
401root-desc=tracking the foobar development 418root-desc=tracking the foobar development
402 419
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
index 999ad0c..6b1c576 100755
--- a/filters/syntax-highlighting.sh
+++ b/filters/syntax-highlighting.sh
@@ -1,39 +1,34 @@
1#!/bin/sh 1#!/bin/sh
2# This script can be used to implement syntax highlighting in the cgit 2# This script can be used to implement syntax highlighting in the cgit
3# tree-view by refering to this file with the source-filter or repo.source- 3# tree-view by refering to this file with the source-filter or repo.source-
4# filter options in cgitrc. 4# filter options in cgitrc.
5# 5#
6# This script requires a shell supporting the ${var##pattern} syntax.
7# It is supported by at least dash and bash, however busybox environments
8# might have to use an external call to sed instead.
9#
6# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax 10# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
7# highlighting, so you'll probably want something like the following included 11# highlighting, so you'll probably want something like the following included
8# in your css file (generated by highlight 2.4.8 and adapted for cgit): 12# in your css file (generated by highlight 2.4.8 and adapted for cgit):
9# 13#
10# table.blob .num { color:#2928ff; } 14# table.blob .num { color:#2928ff; }
11# table.blob .esc { color:#ff00ff; } 15# table.blob .esc { color:#ff00ff; }
12# table.blob .str { color:#ff0000; } 16# table.blob .str { color:#ff0000; }
13# table.blob .dstr { color:#818100; } 17# table.blob .dstr { color:#818100; }
14# table.blob .slc { color:#838183; font-style:italic; } 18# table.blob .slc { color:#838183; font-style:italic; }
15# table.blob .com { color:#838183; font-style:italic; } 19# table.blob .com { color:#838183; font-style:italic; }
16# table.blob .dir { color:#008200; } 20# table.blob .dir { color:#008200; }
17# table.blob .sym { color:#000000; } 21# table.blob .sym { color:#000000; }
18# table.blob .kwa { color:#000000; font-weight:bold; } 22# table.blob .kwa { color:#000000; font-weight:bold; }
19# table.blob .kwb { color:#830000; } 23# table.blob .kwb { color:#830000; }
20# table.blob .kwc { color:#000000; font-weight:bold; } 24# table.blob .kwc { color:#000000; font-weight:bold; }
21# table.blob .kwd { color:#010181; } 25# table.blob .kwd { color:#010181; }
22 26
23case "$1" in 27# store filename and extension in local vars
24 *.c) 28BASENAME="$1"
25 highlight -f -I -X -S c 29EXTENSION="${BASENAME##*.}"
26 ;; 30
27 *.h) 31# map Makefile and Makefile.* to .mk
28 highlight -f -I -X -S c 32[ "${BASENAME%%.*}" == "Makefile" ] && EXTENSION=mk
29 ;; 33
30 *.sh) 34exec highlight --force -f -I -X -S $EXTENSION 2>/dev/null
31 highlight -f -I -X -S sh
32 ;;
33 *.css)
34 highlight -f -I -X -S css
35 ;;
36 *)
37 highlight -f -I -X -S txt
38 ;;
39esac
diff --git a/shared.c b/shared.c
index 9362d21..5f46793 100644
--- a/shared.c
+++ b/shared.c
@@ -1,157 +1,158 @@
1/* shared.c: global vars + some callback functions 1/* shared.c: global vars + some callback functions
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd; 13int cgit_cmd;
14 14
15int chk_zero(int result, char *msg) 15int chk_zero(int result, char *msg)
16{ 16{
17 if (result != 0) 17 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 18 die("%s: %s", msg, strerror(errno));
19 return result; 19 return result;
20} 20}
21 21
22int chk_positive(int result, char *msg) 22int chk_positive(int result, char *msg)
23{ 23{
24 if (result <= 0) 24 if (result <= 0)
25 die("%s: %s", msg, strerror(errno)); 25 die("%s: %s", msg, strerror(errno));
26 return result; 26 return result;
27} 27}
28 28
29int chk_non_negative(int result, char *msg) 29int chk_non_negative(int result, char *msg)
30{ 30{
31 if (result < 0) 31 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 32 die("%s: %s",msg, strerror(errno));
33 return result; 33 return result;
34} 34}
35 35
36struct cgit_repo *cgit_add_repo(const char *url) 36struct cgit_repo *cgit_add_repo(const char *url)
37{ 37{
38 struct cgit_repo *ret; 38 struct cgit_repo *ret;
39 39
40 if (++cgit_repolist.count > cgit_repolist.length) { 40 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 41 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 42 cgit_repolist.length = 8;
43 else 43 else
44 cgit_repolist.length *= 2; 44 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 45 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 46 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 47 sizeof(struct cgit_repo));
48 } 48 }
49 49
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 memset(ret, 0, sizeof(struct cgit_repo)); 51 memset(ret, 0, sizeof(struct cgit_repo));
52 ret->url = trim_end(url, '/'); 52 ret->url = trim_end(url, '/');
53 ret->name = ret->url; 53 ret->name = ret->url;
54 ret->path = NULL; 54 ret->path = NULL;
55 ret->desc = "[no description]"; 55 ret->desc = "[no description]";
56 ret->owner = NULL; 56 ret->owner = NULL;
57 ret->section = ctx.cfg.section; 57 ret->section = ctx.cfg.section;
58 ret->defbranch = "master"; 58 ret->defbranch = "master";
59 ret->snapshots = ctx.cfg.snapshots; 59 ret->snapshots = ctx.cfg.snapshots;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->max_stats = ctx.cfg.max_stats; 63 ret->max_stats = ctx.cfg.max_stats;
63 ret->module_link = ctx.cfg.module_link; 64 ret->module_link = ctx.cfg.module_link;
64 ret->readme = NULL; 65 ret->readme = NULL;
65 ret->mtime = -1; 66 ret->mtime = -1;
66 ret->about_filter = ctx.cfg.about_filter; 67 ret->about_filter = ctx.cfg.about_filter;
67 ret->commit_filter = ctx.cfg.commit_filter; 68 ret->commit_filter = ctx.cfg.commit_filter;
68 ret->source_filter = ctx.cfg.source_filter; 69 ret->source_filter = ctx.cfg.source_filter;
69 return ret; 70 return ret;
70} 71}
71 72
72struct cgit_repo *cgit_get_repoinfo(const char *url) 73struct cgit_repo *cgit_get_repoinfo(const char *url)
73{ 74{
74 int i; 75 int i;
75 struct cgit_repo *repo; 76 struct cgit_repo *repo;
76 77
77 for (i=0; i<cgit_repolist.count; i++) { 78 for (i=0; i<cgit_repolist.count; i++) {
78 repo = &cgit_repolist.repos[i]; 79 repo = &cgit_repolist.repos[i];
79 if (!strcmp(repo->url, url)) 80 if (!strcmp(repo->url, url))
80 return repo; 81 return repo;
81 } 82 }
82 return NULL; 83 return NULL;
83} 84}
84 85
85void *cgit_free_commitinfo(struct commitinfo *info) 86void *cgit_free_commitinfo(struct commitinfo *info)
86{ 87{
87 free(info->author); 88 free(info->author);
88 free(info->author_email); 89 free(info->author_email);
89 free(info->committer); 90 free(info->committer);
90 free(info->committer_email); 91 free(info->committer_email);
91 free(info->subject); 92 free(info->subject);
92 free(info->msg); 93 free(info->msg);
93 free(info->msg_encoding); 94 free(info->msg_encoding);
94 free(info); 95 free(info);
95 return NULL; 96 return NULL;
96} 97}
97 98
98char *trim_end(const char *str, char c) 99char *trim_end(const char *str, char c)
99{ 100{
100 int len; 101 int len;
101 char *s, *t; 102 char *s, *t;
102 103
103 if (str == NULL) 104 if (str == NULL)
104 return NULL; 105 return NULL;
105 t = (char *)str; 106 t = (char *)str;
106 len = strlen(t); 107 len = strlen(t);
107 while(len > 0 && t[len - 1] == c) 108 while(len > 0 && t[len - 1] == c)
108 len--; 109 len--;
109 110
110 if (len == 0) 111 if (len == 0)
111 return NULL; 112 return NULL;
112 113
113 c = t[len]; 114 c = t[len];
114 t[len] = '\0'; 115 t[len] = '\0';
115 s = xstrdup(t); 116 s = xstrdup(t);
116 t[len] = c; 117 t[len] = c;
117 return s; 118 return s;
118} 119}
119 120
120char *strlpart(char *txt, int maxlen) 121char *strlpart(char *txt, int maxlen)
121{ 122{
122 char *result; 123 char *result;
123 124
124 if (!txt) 125 if (!txt)
125 return txt; 126 return txt;
126 127
127 if (strlen(txt) <= maxlen) 128 if (strlen(txt) <= maxlen)
128 return txt; 129 return txt;
129 result = xmalloc(maxlen + 1); 130 result = xmalloc(maxlen + 1);
130 memcpy(result, txt, maxlen - 3); 131 memcpy(result, txt, maxlen - 3);
131 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 132 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
132 result[maxlen] = '\0'; 133 result[maxlen] = '\0';
133 return result; 134 return result;
134} 135}
135 136
136char *strrpart(char *txt, int maxlen) 137char *strrpart(char *txt, int maxlen)
137{ 138{
138 char *result; 139 char *result;
139 140
140 if (!txt) 141 if (!txt)
141 return txt; 142 return txt;
142 143
143 if (strlen(txt) <= maxlen) 144 if (strlen(txt) <= maxlen)
144 return txt; 145 return txt;
145 result = xmalloc(maxlen + 1); 146 result = xmalloc(maxlen + 1);
146 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); 147 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
147 result[0] = result[1] = result[2] = '.'; 148 result[0] = result[1] = result[2] = '.';
148 return result; 149 return result;
149} 150}
150 151
151void cgit_add_ref(struct reflist *list, struct refinfo *ref) 152void cgit_add_ref(struct reflist *list, struct refinfo *ref)
152{ 153{
153 size_t size; 154 size_t size;
154 155
155 if (list->count >= list->alloc) { 156 if (list->count >= list->alloc) {
156 list->alloc += (list->alloc ? list->alloc : 4); 157 list->alloc += (list->alloc ? list->alloc : 4);
157 size = list->alloc * sizeof(struct refinfo *); 158 size = list->alloc * sizeof(struct refinfo *);
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..b5e3c01 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,118 +1,123 @@
1/* ui-commit.c: generate commit view 1/* ui-commit.c: generate commit view
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp; 21 char *tmp;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 34 return;
35 } 35 }
36 info = cgit_parse_commit(commit); 36 info = cgit_parse_commit(commit);
37 37
38 load_ref_decorations(DECORATE_FULL_REFS); 38 load_ref_decorations(DECORATE_FULL_REFS);
39 39
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 43 if (!ctx.cfg.noplainemail) {
44 html(" "); 44 html(" ");
45 html_txt(info->author_email); 45 html_txt(info->author_email);
46 } 46 }
47 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
49 html("</td></tr>\n"); 49 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 51 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 52 if (!ctx.cfg.noplainemail) {
53 html(" "); 53 html(" ");
54 html_txt(info->committer_email); 54 html_txt(info->committer_email);
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
64 html(") (");
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, 1);
67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, 1);
64 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
69 html("</td></tr>\n"); 74 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 75 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 76 parent = lookup_commit_reference(p->item->object.sha1);
72 if (!parent) { 77 if (!parent) {
73 html("<tr><td colspan='3'>"); 78 html("<tr><td colspan='3'>");
74 cgit_print_error("Error reading parent commit"); 79 cgit_print_error("Error reading parent commit");
75 html("</td></tr>"); 80 html("</td></tr>");
76 continue; 81 continue;
77 } 82 }
78 html("<tr><th>parent</th>" 83 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 84 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 85 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 86 ctx.qry.head, sha1_to_hex(p->item->object.sha1), 0);
82 html(" ("); 87 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 88 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 89 sha1_to_hex(p->item->object.sha1), NULL, 0);
85 html(")</td></tr>"); 90 html(")</td></tr>");
86 parents++; 91 parents++;
87 } 92 }
88 if (ctx.repo->snapshots) { 93 if (ctx.repo->snapshots) {
89 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 94 html("<tr><th>download</th><td colspan='2' class='sha1'>");
90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 95 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
91 hex, ctx.repo->snapshots); 96 hex, ctx.repo->snapshots);
92 html("</td></tr>"); 97 html("</td></tr>");
93 } 98 }
94 html("</table>\n"); 99 html("</table>\n");
95 html("<div class='commit-subject'>"); 100 html("<div class='commit-subject'>");
96 if (ctx.repo->commit_filter) 101 if (ctx.repo->commit_filter)
97 cgit_open_filter(ctx.repo->commit_filter); 102 cgit_open_filter(ctx.repo->commit_filter);
98 html_txt(info->subject); 103 html_txt(info->subject);
99 if (ctx.repo->commit_filter) 104 if (ctx.repo->commit_filter)
100 cgit_close_filter(ctx.repo->commit_filter); 105 cgit_close_filter(ctx.repo->commit_filter);
101 show_commit_decorations(commit); 106 show_commit_decorations(commit);
102 html("</div>"); 107 html("</div>");
103 html("<div class='commit-msg'>"); 108 html("<div class='commit-msg'>");
104 if (ctx.repo->commit_filter) 109 if (ctx.repo->commit_filter)
105 cgit_open_filter(ctx.repo->commit_filter); 110 cgit_open_filter(ctx.repo->commit_filter);
106 html_txt(info->msg); 111 html_txt(info->msg);
107 if (ctx.repo->commit_filter) 112 if (ctx.repo->commit_filter)
108 cgit_close_filter(ctx.repo->commit_filter); 113 cgit_close_filter(ctx.repo->commit_filter);
109 html("</div>"); 114 html("</div>");
110 if (parents < 3) { 115 if (parents < 3) {
111 if (parents) 116 if (parents)
112 tmp = sha1_to_hex(commit->parents->item->object.sha1); 117 tmp = sha1_to_hex(commit->parents->item->object.sha1);
113 else 118 else
114 tmp = NULL; 119 tmp = NULL;
115 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 120 cgit_print_diff(ctx.qry.sha1, tmp, NULL);
116 } 121 }
117 cgit_free_commitinfo(info); 122 cgit_free_commitinfo(info);
118} 123}
diff --git a/ui-diff.c b/ui-diff.c
index 2196745..a92a768 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,313 +1,353 @@
1/* ui-diff.c: show diff between two blobs 1/* ui-diff.c: show diff between two blobs
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-ssdiff.h"
12 13
13unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
14unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
15 16
16static int files, slots; 17static int files, slots;
17static int total_adds, total_rems, max_changes; 18static int total_adds, total_rems, max_changes;
18static int lines_added, lines_removed; 19static int lines_added, lines_removed;
19 20
20static struct fileinfo { 21static struct fileinfo {
21 char status; 22 char status;
22 unsigned char old_sha1[20]; 23 unsigned char old_sha1[20];
23 unsigned char new_sha1[20]; 24 unsigned char new_sha1[20];
24 unsigned short old_mode; 25 unsigned short old_mode;
25 unsigned short new_mode; 26 unsigned short new_mode;
26 char *old_path; 27 char *old_path;
27 char *new_path; 28 char *new_path;
28 unsigned int added; 29 unsigned int added;
29 unsigned int removed; 30 unsigned int removed;
30 unsigned long old_size; 31 unsigned long old_size;
31 unsigned long new_size; 32 unsigned long new_size;
32 int binary:1; 33 int binary:1;
33} *items; 34} *items;
34 35
36static int use_ssdiff = 0;
35 37
36static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
37{ 39{
38 char *class; 40 char *class;
39 41
40 switch (info->status) { 42 switch (info->status) {
41 case DIFF_STATUS_ADDED: 43 case DIFF_STATUS_ADDED:
42 class = "add"; 44 class = "add";
43 break; 45 break;
44 case DIFF_STATUS_COPIED: 46 case DIFF_STATUS_COPIED:
45 class = "cpy"; 47 class = "cpy";
46 break; 48 break;
47 case DIFF_STATUS_DELETED: 49 case DIFF_STATUS_DELETED:
48 class = "del"; 50 class = "del";
49 break; 51 break;
50 case DIFF_STATUS_MODIFIED: 52 case DIFF_STATUS_MODIFIED:
51 class = "upd"; 53 class = "upd";
52 break; 54 break;
53 case DIFF_STATUS_RENAMED: 55 case DIFF_STATUS_RENAMED:
54 class = "mov"; 56 class = "mov";
55 break; 57 break;
56 case DIFF_STATUS_TYPE_CHANGED: 58 case DIFF_STATUS_TYPE_CHANGED:
57 class = "typ"; 59 class = "typ";
58 break; 60 break;
59 case DIFF_STATUS_UNKNOWN: 61 case DIFF_STATUS_UNKNOWN:
60 class = "unk"; 62 class = "unk";
61 break; 63 break;
62 case DIFF_STATUS_UNMERGED: 64 case DIFF_STATUS_UNMERGED:
63 class = "stg"; 65 class = "stg";
64 break; 66 break;
65 default: 67 default:
66 die("bug: unhandled diff status %c", info->status); 68 die("bug: unhandled diff status %c", info->status);
67 } 69 }
68 70
69 html("<tr>"); 71 html("<tr>");
70 htmlf("<td class='mode'>"); 72 htmlf("<td class='mode'>");
71 if (is_null_sha1(info->new_sha1)) { 73 if (is_null_sha1(info->new_sha1)) {
72 cgit_print_filemode(info->old_mode); 74 cgit_print_filemode(info->old_mode);
73 } else { 75 } else {
74 cgit_print_filemode(info->new_mode); 76 cgit_print_filemode(info->new_mode);
75 } 77 }
76 78
77 if (info->old_mode != info->new_mode && 79 if (info->old_mode != info->new_mode &&
78 !is_null_sha1(info->old_sha1) && 80 !is_null_sha1(info->old_sha1) &&
79 !is_null_sha1(info->new_sha1)) { 81 !is_null_sha1(info->new_sha1)) {
80 html("<span class='modechange'>["); 82 html("<span class='modechange'>[");
81 cgit_print_filemode(info->old_mode); 83 cgit_print_filemode(info->old_mode);
82 html("]</span>"); 84 html("]</span>");
83 } 85 }
84 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
85 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
86 ctx.qry.sha2, info->new_path); 88 ctx.qry.sha2, info->new_path, 0);
87 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
88 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
89 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
90 info->old_path); 92 info->old_path);
91 html("</td><td class='right'>"); 93 html("</td><td class='right'>");
92 if (info->binary) { 94 if (info->binary) {
93 htmlf("bin</td><td class='graph'>%d -> %d bytes", 95 htmlf("bin</td><td class='graph'>%d -> %d bytes",
94 info->old_size, info->new_size); 96 info->old_size, info->new_size);
95 return; 97 return;
96 } 98 }
97 htmlf("%d", info->added + info->removed); 99 htmlf("%d", info->added + info->removed);
98 html("</td><td class='graph'>"); 100 html("</td><td class='graph'>");
99 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
100 htmlf("<td class='add' style='width: %.1f%%;'/>", 102 htmlf("<td class='add' style='width: %.1f%%;'/>",
101 info->added * 100.0 / max_changes); 103 info->added * 100.0 / max_changes);
102 htmlf("<td class='rem' style='width: %.1f%%;'/>", 104 htmlf("<td class='rem' style='width: %.1f%%;'/>",
103 info->removed * 100.0 / max_changes); 105 info->removed * 100.0 / max_changes);
104 htmlf("<td class='none' style='width: %.1f%%;'/>", 106 htmlf("<td class='none' style='width: %.1f%%;'/>",
105 (max_changes - info->removed - info->added) * 100.0 / max_changes); 107 (max_changes - info->removed - info->added) * 100.0 / max_changes);
106 html("</tr></table></td></tr>\n"); 108 html("</tr></table></td></tr>\n");
107} 109}
108 110
109static void count_diff_lines(char *line, int len) 111static void count_diff_lines(char *line, int len)
110{ 112{
111 if (line && (len > 0)) { 113 if (line && (len > 0)) {
112 if (line[0] == '+') 114 if (line[0] == '+')
113 lines_added++; 115 lines_added++;
114 else if (line[0] == '-') 116 else if (line[0] == '-')
115 lines_removed++; 117 lines_removed++;
116 } 118 }
117} 119}
118 120
119static void inspect_filepair(struct diff_filepair *pair) 121static void inspect_filepair(struct diff_filepair *pair)
120{ 122{
121 int binary = 0; 123 int binary = 0;
122 unsigned long old_size = 0; 124 unsigned long old_size = 0;
123 unsigned long new_size = 0; 125 unsigned long new_size = 0;
124 files++; 126 files++;
125 lines_added = 0; 127 lines_added = 0;
126 lines_removed = 0; 128 lines_removed = 0;
127 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
128 &binary, count_diff_lines); 130 &binary, count_diff_lines);
129 if (files >= slots) { 131 if (files >= slots) {
130 if (slots == 0) 132 if (slots == 0)
131 slots = 4; 133 slots = 4;
132 else 134 else
133 slots = slots * 2; 135 slots = slots * 2;
134 items = xrealloc(items, slots * sizeof(struct fileinfo)); 136 items = xrealloc(items, slots * sizeof(struct fileinfo));
135 } 137 }
136 items[files-1].status = pair->status; 138 items[files-1].status = pair->status;
137 hashcpy(items[files-1].old_sha1, pair->one->sha1); 139 hashcpy(items[files-1].old_sha1, pair->one->sha1);
138 hashcpy(items[files-1].new_sha1, pair->two->sha1); 140 hashcpy(items[files-1].new_sha1, pair->two->sha1);
139 items[files-1].old_mode = pair->one->mode; 141 items[files-1].old_mode = pair->one->mode;
140 items[files-1].new_mode = pair->two->mode; 142 items[files-1].new_mode = pair->two->mode;
141 items[files-1].old_path = xstrdup(pair->one->path); 143 items[files-1].old_path = xstrdup(pair->one->path);
142 items[files-1].new_path = xstrdup(pair->two->path); 144 items[files-1].new_path = xstrdup(pair->two->path);
143 items[files-1].added = lines_added; 145 items[files-1].added = lines_added;
144 items[files-1].removed = lines_removed; 146 items[files-1].removed = lines_removed;
145 items[files-1].old_size = old_size; 147 items[files-1].old_size = old_size;
146 items[files-1].new_size = new_size; 148 items[files-1].new_size = new_size;
147 items[files-1].binary = binary; 149 items[files-1].binary = binary;
148 if (lines_added + lines_removed > max_changes) 150 if (lines_added + lines_removed > max_changes)
149 max_changes = lines_added + lines_removed; 151 max_changes = lines_added + lines_removed;
150 total_adds += lines_added; 152 total_adds += lines_added;
151 total_rems += lines_removed; 153 total_rems += lines_removed;
152} 154}
153 155
154void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
155 const unsigned char *new_sha1) 157 const unsigned char *new_sha1)
156{ 158{
157 int i; 159 int i;
158 160
159 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
160 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
161 ctx.qry.sha2, NULL); 163 ctx.qry.sha2, NULL, 0);
162 html("</div>"); 164 html("</div>");
163 html("<table summary='diffstat' class='diffstat'>"); 165 html("<table summary='diffstat' class='diffstat'>");
164 max_changes = 0; 166 max_changes = 0;
165 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL);
166 for(i = 0; i<files; i++) 168 for(i = 0; i<files; i++)
167 print_fileinfo(&items[i]); 169 print_fileinfo(&items[i]);
168 html("</table>"); 170 html("</table>");
169 html("<div class='diffstat-summary'>"); 171 html("<div class='diffstat-summary'>");
170 htmlf("%d files changed, %d insertions, %d deletions", 172 htmlf("%d files changed, %d insertions, %d deletions",
171 files, total_adds, total_rems); 173 files, total_adds, total_rems);
172 html("</div>"); 174 html("</div>");
173} 175}
174 176
175 177
176/* 178/*
177 * print a single line returned from xdiff 179 * print a single line returned from xdiff
178 */ 180 */
179static void print_line(char *line, int len) 181static void print_line(char *line, int len)
180{ 182{
181 char *class = "ctx"; 183 char *class = "ctx";
182 char c = line[len-1]; 184 char c = line[len-1];
183 185
184 if (line[0] == '+') 186 if (line[0] == '+')
185 class = "add"; 187 class = "add";
186 else if (line[0] == '-') 188 else if (line[0] == '-')
187 class = "del"; 189 class = "del";
188 else if (line[0] == '@') 190 else if (line[0] == '@')
189 class = "hunk"; 191 class = "hunk";
190 192
191 htmlf("<div class='%s'>", class); 193 htmlf("<div class='%s'>", class);
192 line[len-1] = '\0'; 194 line[len-1] = '\0';
193 html_txt(line); 195 html_txt(line);
194 html("</div>"); 196 html("</div>");
195 line[len-1] = c; 197 line[len-1] = c;
196} 198}
197 199
198static void header(unsigned char *sha1, char *path1, int mode1, 200static void header(unsigned char *sha1, char *path1, int mode1,
199 unsigned char *sha2, char *path2, int mode2) 201 unsigned char *sha2, char *path2, int mode2)
200{ 202{
201 char *abbrev1, *abbrev2; 203 char *abbrev1, *abbrev2;
202 int subproject; 204 int subproject;
203 205
204 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 206 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
205 html("<div class='head'>"); 207 html("<div class='head'>");
206 html("diff --git a/"); 208 html("diff --git a/");
207 html_txt(path1); 209 html_txt(path1);
208 html(" b/"); 210 html(" b/");
209 html_txt(path2); 211 html_txt(path2);
210 212
211 if (is_null_sha1(sha1)) 213 if (is_null_sha1(sha1))
212 path1 = "dev/null"; 214 path1 = "dev/null";
213 if (is_null_sha1(sha2)) 215 if (is_null_sha1(sha2))
214 path2 = "dev/null"; 216 path2 = "dev/null";
215 217
216 if (mode1 == 0) 218 if (mode1 == 0)
217 htmlf("<br/>new file mode %.6o", mode2); 219 htmlf("<br/>new file mode %.6o", mode2);
218 220
219 if (mode2 == 0) 221 if (mode2 == 0)
220 htmlf("<br/>deleted file mode %.6o", mode1); 222 htmlf("<br/>deleted file mode %.6o", mode1);
221 223
222 if (!subproject) { 224 if (!subproject) {
223 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 225 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
224 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 226 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
225 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 227 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
226 free(abbrev1); 228 free(abbrev1);
227 free(abbrev2); 229 free(abbrev2);
228 if (mode1 != 0 && mode2 != 0) { 230 if (mode1 != 0 && mode2 != 0) {
229 htmlf(" %.6o", mode1); 231 htmlf(" %.6o", mode1);
230 if (mode2 != mode1) 232 if (mode2 != mode1)
231 htmlf("..%.6o", mode2); 233 htmlf("..%.6o", mode2);
232 } 234 }
233 html("<br/>--- a/"); 235 html("<br/>--- a/");
234 if (mode1 != 0) 236 if (mode1 != 0)
235 cgit_tree_link(path1, NULL, NULL, ctx.qry.head, 237 cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
236 sha1_to_hex(old_rev_sha1), path1); 238 sha1_to_hex(old_rev_sha1), path1);
237 else 239 else
238 html_txt(path1); 240 html_txt(path1);
239 html("<br/>+++ b/"); 241 html("<br/>+++ b/");
240 if (mode2 != 0) 242 if (mode2 != 0)
241 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 243 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
242 sha1_to_hex(new_rev_sha1), path2); 244 sha1_to_hex(new_rev_sha1), path2);
243 else 245 else
244 html_txt(path2); 246 html_txt(path2);
245 } 247 }
246 html("</div>"); 248 html("</div>");
247} 249}
248 250
251static void print_ssdiff_link()
252{
253 if (!strcmp(ctx.qry.page, "diff")) {
254 if (use_ssdiff)
255 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
256 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
257 else
258 cgit_diff_link("Side-by-side diff", NULL, NULL,
259 ctx.qry.head, ctx.qry.sha1,
260 ctx.qry.sha2, ctx.qry.path, 1);
261 }
262}
263
249static void filepair_cb(struct diff_filepair *pair) 264static void filepair_cb(struct diff_filepair *pair)
250{ 265{
251 unsigned long old_size = 0; 266 unsigned long old_size = 0;
252 unsigned long new_size = 0; 267 unsigned long new_size = 0;
253 int binary = 0; 268 int binary = 0;
269 linediff_fn print_line_fn = print_line;
254 270
271 if (use_ssdiff) {
272 cgit_ssdiff_header_begin();
273 print_line_fn = cgit_ssdiff_line_cb;
274 }
255 header(pair->one->sha1, pair->one->path, pair->one->mode, 275 header(pair->one->sha1, pair->one->path, pair->one->mode,
256 pair->two->sha1, pair->two->path, pair->two->mode); 276 pair->two->sha1, pair->two->path, pair->two->mode);
277 if (use_ssdiff)
278 cgit_ssdiff_header_end();
257 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 279 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
258 if (S_ISGITLINK(pair->one->mode)) 280 if (S_ISGITLINK(pair->one->mode))
259 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 281 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
260 if (S_ISGITLINK(pair->two->mode)) 282 if (S_ISGITLINK(pair->two->mode))
261 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 283 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
284 if (use_ssdiff)
285 cgit_ssdiff_footer();
262 return; 286 return;
263 } 287 }
264 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 288 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
265 &new_size, &binary, print_line)) 289 &new_size, &binary, print_line_fn))
266 cgit_print_error("Error running diff"); 290 cgit_print_error("Error running diff");
267 if (binary) 291 if (binary) {
292 if (use_ssdiff)
293 html("<tr><td colspan='4'>Binary files differ</td></tr>");
294 else
268 html("Binary files differ"); 295 html("Binary files differ");
269} 296}
297 if (use_ssdiff)
298 cgit_ssdiff_footer();
299}
270 300
271void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 301void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
272{ 302{
273 enum object_type type; 303 enum object_type type;
274 unsigned long size; 304 unsigned long size;
275 struct commit *commit, *commit2; 305 struct commit *commit, *commit2;
276 306
277 if (!new_rev) 307 if (!new_rev)
278 new_rev = ctx.qry.head; 308 new_rev = ctx.qry.head;
279 get_sha1(new_rev, new_rev_sha1); 309 get_sha1(new_rev, new_rev_sha1);
280 type = sha1_object_info(new_rev_sha1, &size); 310 type = sha1_object_info(new_rev_sha1, &size);
281 if (type == OBJ_BAD) { 311 if (type == OBJ_BAD) {
282 cgit_print_error(fmt("Bad object name: %s", new_rev)); 312 cgit_print_error(fmt("Bad object name: %s", new_rev));
283 return; 313 return;
284 } 314 }
285 commit = lookup_commit_reference(new_rev_sha1); 315 commit = lookup_commit_reference(new_rev_sha1);
286 if (!commit || parse_commit(commit)) 316 if (!commit || parse_commit(commit))
287 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); 317 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
288 318
289 if (old_rev) 319 if (old_rev)
290 get_sha1(old_rev, old_rev_sha1); 320 get_sha1(old_rev, old_rev_sha1);
291 else if (commit->parents && commit->parents->item) 321 else if (commit->parents && commit->parents->item)
292 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 322 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
293 else 323 else
294 hashclr(old_rev_sha1); 324 hashclr(old_rev_sha1);
295 325
296 if (!is_null_sha1(old_rev_sha1)) { 326 if (!is_null_sha1(old_rev_sha1)) {
297 type = sha1_object_info(old_rev_sha1, &size); 327 type = sha1_object_info(old_rev_sha1, &size);
298 if (type == OBJ_BAD) { 328 if (type == OBJ_BAD) {
299 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 329 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
300 return; 330 return;
301 } 331 }
302 commit2 = lookup_commit_reference(old_rev_sha1); 332 commit2 = lookup_commit_reference(old_rev_sha1);
303 if (!commit2 || parse_commit(commit2)) 333 if (!commit2 || parse_commit(commit2))
304 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 334 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
305 } 335 }
336
337 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
338 use_ssdiff = 1;
339
340 print_ssdiff_link();
306 cgit_print_diffstat(old_rev_sha1, new_rev_sha1); 341 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
307 342
343 if (use_ssdiff) {
344 html("<table summary='ssdiff' class='ssdiff'>");
345 } else {
308 html("<table summary='diff' class='diff'>"); 346 html("<table summary='diff' class='diff'>");
309 html("<tr><td>"); 347 html("<tr><td>");
348 }
310 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 349 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
350 if (!use_ssdiff)
311 html("</td></tr>"); 351 html("</td></tr>");
312 html("</table>"); 352 html("</table>");
313} 353}
diff --git a/ui-log.c b/ui-log.c
index f3132c9..0947604 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,188 +1,188 @@
1/* ui-log.c: functions for log output 1/* ui-log.c: functions for log output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13int files, add_lines, rem_lines; 13int files, add_lines, rem_lines;
14 14
15void count_lines(char *line, int size) 15void count_lines(char *line, int size)
16{ 16{
17 if (size <= 0) 17 if (size <= 0)
18 return; 18 return;
19 19
20 if (line[0] == '+') 20 if (line[0] == '+')
21 add_lines++; 21 add_lines++;
22 22
23 else if (line[0] == '-') 23 else if (line[0] == '-')
24 rem_lines++; 24 rem_lines++;
25} 25}
26 26
27void inspect_files(struct diff_filepair *pair) 27void inspect_files(struct diff_filepair *pair)
28{ 28{
29 unsigned long old_size = 0; 29 unsigned long old_size = 0;
30 unsigned long new_size = 0; 30 unsigned long new_size = 0;
31 int binary = 0; 31 int binary = 0;
32 32
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, count_lines);
37} 37}
38 38
39void show_commit_decorations(struct commit *commit) 39void show_commit_decorations(struct commit *commit)
40{ 40{
41 struct name_decoration *deco; 41 struct name_decoration *deco;
42 static char buf[1024]; 42 static char buf[1024];
43 43
44 buf[sizeof(buf) - 1] = 0; 44 buf[sizeof(buf) - 1] = 0;
45 deco = lookup_decoration(&name_decoration, &commit->object); 45 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 46 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 47 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 48 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 50 0, NULL, NULL, ctx.qry.showmsg);
51 } 51 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 53 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 55 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) { 56 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 57 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 59 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 60 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 61 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 62 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 63 sha1_to_hex(commit->object.sha1), NULL,
64 0, NULL, NULL, ctx.qry.showmsg); 64 0, NULL, NULL, ctx.qry.showmsg);
65 } 65 }
66 else { 66 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 67 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 69 sha1_to_hex(commit->object.sha1), 0);
70 } 70 }
71 deco = deco->next; 71 deco = deco->next;
72 } 72 }
73} 73}
74 74
75void print_commit(struct commit *commit) 75void print_commit(struct commit *commit)
76{ 76{
77 struct commitinfo *info; 77 struct commitinfo *info;
78 char *tmp; 78 char *tmp;
79 int cols = 2; 79 int cols = 2;
80 80
81 info = cgit_parse_commit(commit); 81 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 82 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 83 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
86 html_link_open(tmp, NULL, NULL); 86 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 88 html_link_close();
89 htmlf("</td><td%s>", 89 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 90 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 92 sha1_to_hex(commit->object.sha1), 0);
93 show_commit_decorations(commit); 93 show_commit_decorations(commit);
94 html("</td><td>"); 94 html("</td><td>");
95 html_txt(info->author); 95 html_txt(info->author);
96 if (ctx.repo->enable_log_filecount) { 96 if (ctx.repo->enable_log_filecount) {
97 files = 0; 97 files = 0;
98 add_lines = 0; 98 add_lines = 0;
99 rem_lines = 0; 99 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 100 cgit_diff_commit(commit, inspect_files);
101 html("</td><td>"); 101 html("</td><td>");
102 htmlf("%d", files); 102 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 103 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 104 html("</td><td>");
105 htmlf("-%d/+%d", rem_lines, add_lines); 105 htmlf("-%d/+%d", rem_lines, add_lines);
106 } 106 }
107 } 107 }
108 html("</td></tr>\n"); 108 html("</td></tr>\n");
109 if (ctx.qry.showmsg) { 109 if (ctx.qry.showmsg) {
110 if (ctx.repo->enable_log_filecount) { 110 if (ctx.repo->enable_log_filecount) {
111 cols++; 111 cols++;
112 if (ctx.repo->enable_log_linecount) 112 if (ctx.repo->enable_log_linecount)
113 cols++; 113 cols++;
114 } 114 }
115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
116 cols); 116 cols);
117 html_txt(info->msg); 117 html_txt(info->msg);
118 html("</td></tr>\n"); 118 html("</td></tr>\n");
119 } 119 }
120 cgit_free_commitinfo(info); 120 cgit_free_commitinfo(info);
121} 121}
122 122
123static const char *disambiguate_ref(const char *ref) 123static const char *disambiguate_ref(const char *ref)
124{ 124{
125 unsigned char sha1[20]; 125 unsigned char sha1[20];
126 const char *longref; 126 const char *longref;
127 127
128 longref = fmt("refs/heads/%s", ref); 128 longref = fmt("refs/heads/%s", ref);
129 if (get_sha1(longref, sha1) == 0) 129 if (get_sha1(longref, sha1) == 0)
130 return longref; 130 return longref;
131 131
132 return ref; 132 return ref;
133} 133}
134 134
135void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 135void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
136 char *path, int pager) 136 char *path, int pager)
137{ 137{
138 struct rev_info rev; 138 struct rev_info rev;
139 struct commit *commit; 139 struct commit *commit;
140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
141 int argc = 2; 141 int argc = 2;
142 int i, columns = 3; 142 int i, columns = 3;
143 143
144 if (!tip) 144 if (!tip)
145 tip = ctx.qry.head; 145 tip = ctx.qry.head;
146 146
147 argv[1] = disambiguate_ref(tip); 147 argv[1] = disambiguate_ref(tip);
148 148
149 if (grep && pattern && (!strcmp(grep, "grep") || 149 if (grep && pattern && (!strcmp(grep, "grep") ||
150 !strcmp(grep, "author") || 150 !strcmp(grep, "author") ||
151 !strcmp(grep, "committer"))) 151 !strcmp(grep, "committer")))
152 argv[argc++] = fmt("--%s=%s", grep, pattern); 152 argv[argc++] = fmt("--%s=%s", grep, pattern);
153 153
154 if (path) { 154 if (path) {
155 argv[argc++] = "--"; 155 argv[argc++] = "--";
156 argv[argc++] = path; 156 argv[argc++] = path;
157 } 157 }
158 init_revisions(&rev, NULL); 158 init_revisions(&rev, NULL);
159 rev.abbrev = DEFAULT_ABBREV; 159 rev.abbrev = DEFAULT_ABBREV;
160 rev.commit_format = CMIT_FMT_DEFAULT; 160 rev.commit_format = CMIT_FMT_DEFAULT;
161 rev.verbose_header = 1; 161 rev.verbose_header = 1;
162 rev.show_root_diff = 0; 162 rev.show_root_diff = 0;
163 setup_revisions(argc, argv, &rev, NULL); 163 setup_revisions(argc, argv, &rev, NULL);
164 load_ref_decorations(DECORATE_FULL_REFS); 164 load_ref_decorations(DECORATE_FULL_REFS);
165 rev.show_decorations = 1; 165 rev.show_decorations = 1;
166 rev.grep_filter.regflags |= REG_ICASE; 166 rev.grep_filter.regflags |= REG_ICASE;
167 compile_grep_patterns(&rev.grep_filter); 167 compile_grep_patterns(&rev.grep_filter);
168 prepare_revision_walk(&rev); 168 prepare_revision_walk(&rev);
169 169
170 if (pager) 170 if (pager)
171 html("<table class='list nowrap'>"); 171 html("<table class='list nowrap'>");
172 172
173 html("<tr class='nohover'><th class='left'>Age</th>" 173 html("<tr class='nohover'><th class='left'>Age</th>"
174 "<th class='left'>Commit message"); 174 "<th class='left'>Commit message");
175 if (pager) { 175 if (pager) {
176 html(" ("); 176 html(" (");
177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
178 NULL, ctx.qry.head, ctx.qry.sha1, 178 NULL, ctx.qry.head, ctx.qry.sha1,
179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep,
180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
181 html(")"); 181 html(")");
182 } 182 }
183 html("</th><th class='left'>Author</th>"); 183 html("</th><th class='left'>Author</th>");
184 if (ctx.repo->enable_log_filecount) { 184 if (ctx.repo->enable_log_filecount) {
185 html("<th class='left'>Files</th>"); 185 html("<th class='left'>Files</th>");
186 columns++; 186 columns++;
187 if (ctx.repo->enable_log_linecount) { 187 if (ctx.repo->enable_log_linecount) {
188 html("<th class='left'>Lines</th>"); 188 html("<th class='left'>Lines</th>");
diff --git a/ui-refs.c b/ui-refs.c
index d3b4f6e..98738db 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -1,245 +1,247 @@
1/* ui-refs.c: browse symbolic refs 1/* ui-refs.c: browse symbolic refs
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
13static int header; 13static int header;
14 14
15static int cmp_age(int age1, int age2) 15static int cmp_age(int age1, int age2)
16{ 16{
17 if (age1 != 0 && age2 != 0) 17 if (age1 != 0 && age2 != 0)
18 return age2 - age1; 18 return age2 - age1;
19 19
20 if (age1 == 0 && age2 == 0) 20 if (age1 == 0 && age2 == 0)
21 return 0; 21 return 0;
22 22
23 if (age1 == 0) 23 if (age1 == 0)
24 return +1; 24 return +1;
25 25
26 return -1; 26 return -1;
27} 27}
28 28
29static int cmp_ref_name(const void *a, const void *b) 29static int cmp_ref_name(const void *a, const void *b)
30{ 30{
31 struct refinfo *r1 = *(struct refinfo **)a; 31 struct refinfo *r1 = *(struct refinfo **)a;
32 struct refinfo *r2 = *(struct refinfo **)b; 32 struct refinfo *r2 = *(struct refinfo **)b;
33 33
34 return strcmp(r1->refname, r2->refname); 34 return strcmp(r1->refname, r2->refname);
35} 35}
36 36
37static int cmp_branch_age(const void *a, const void *b) 37static int cmp_branch_age(const void *a, const void *b)
38{ 38{
39 struct refinfo *r1 = *(struct refinfo **)a; 39 struct refinfo *r1 = *(struct refinfo **)a;
40 struct refinfo *r2 = *(struct refinfo **)b; 40 struct refinfo *r2 = *(struct refinfo **)b;
41 41
42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date); 42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
43} 43}
44 44
45static int cmp_tag_age(const void *a, const void *b) 45static int cmp_tag_age(const void *a, const void *b)
46{ 46{
47 struct refinfo *r1 = *(struct refinfo **)a; 47 struct refinfo *r1 = *(struct refinfo **)a;
48 struct refinfo *r2 = *(struct refinfo **)b; 48 struct refinfo *r2 = *(struct refinfo **)b;
49 int r1date, r2date; 49 int r1date, r2date;
50 50
51 if (r1->object->type != OBJ_COMMIT) 51 if (r1->object->type != OBJ_COMMIT)
52 r1date = r1->tag->tagger_date; 52 r1date = r1->tag->tagger_date;
53 else 53 else
54 r1date = r1->commit->committer_date; 54 r1date = r1->commit->committer_date;
55 55
56 if (r2->object->type != OBJ_COMMIT) 56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date; 57 r2date = r2->tag->tagger_date;
58 else 58 else
59 r2date = r2->commit->committer_date; 59 r2date = r2->commit->committer_date;
60 60
61 return cmp_age(r1date, r2date); 61 return cmp_age(r1date, r2date);
62} 62}
63 63
64static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
90static void print_tag_header() 90static void print_tag_header()
91{ 91{
92 html("<tr class='nohover'><th class='left'>Tag</th>" 92 html("<tr class='nohover'><th class='left'>Tag</th>"
93 "<th class='left'>Download</th>" 93 "<th class='left'>Download</th>"
94 "<th class='left'>Author</th>" 94 "<th class='left'>Author</th>"
95 "<th class='left' colspan='2'>Age</th></tr>\n"); 95 "<th class='left' colspan='2'>Age</th></tr>\n");
96 header = 1; 96 header = 1;
97} 97}
98 98
99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
100{ 100{
101 const struct cgit_snapshot_format* f; 101 const struct cgit_snapshot_format* f;
102 char *filename; 102 char *filename;
103 const char *basename; 103 const char *basename;
104 104
105 if (!ref || strlen(ref) < 2) 105 if (!ref || strlen(ref) < 2)
106 return; 106 return;
107 107
108 basename = cgit_repobasename(repo->url); 108 basename = cgit_repobasename(repo->url);
109 if (prefixcmp(ref, basename) != 0) { 109 if (prefixcmp(ref, basename) != 0) {
110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) 110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
111 ref++; 111 ref++;
112 if (isdigit(ref[0])) 112 if (isdigit(ref[0]))
113 ref = xstrdup(fmt("%s-%s", basename, ref)); 113 ref = xstrdup(fmt("%s-%s", basename, ref));
114 } 114 }
115 115
116 for (f = cgit_snapshot_formats; f->suffix; f++) { 116 for (f = cgit_snapshot_formats; f->suffix; f++) {
117 if (!(repo->snapshots & f->bit)) 117 if (!(repo->snapshots & f->bit))
118 continue; 118 continue;
119 filename = fmt("%s%s", ref, f->suffix); 119 filename = fmt("%s%s", ref, f->suffix);
120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
121 html("&nbsp;&nbsp;"); 121 html("&nbsp;&nbsp;");
122 } 122 }
123} 123}
124static int print_tag(struct refinfo *ref) 124static int print_tag(struct refinfo *ref)
125{ 125{
126 struct tag *tag; 126 struct tag *tag;
127 struct taginfo *info; 127 struct taginfo *info;
128 char *name = (char *)ref->refname; 128 char *name = (char *)ref->refname;
129 129
130 if (ref->object->type == OBJ_TAG) { 130 if (ref->object->type == OBJ_TAG) {
131 tag = (struct tag *)ref->object; 131 tag = (struct tag *)ref->object;
132 info = ref->tag; 132 info = ref->tag;
133 if (!tag || !info) 133 if (!tag || !info)
134 return 1; 134 return 1;
135 html("<tr><td>"); 135 html("<tr><td>");
136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
137 html("</td><td>"); 137 html("</td><td>");
138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) 138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
139 print_tag_downloads(ctx.repo, name); 139 print_tag_downloads(ctx.repo, name);
140 else 140 else
141 cgit_object_link(tag->tagged); 141 cgit_object_link(tag->tagged);
142 html("</td><td>"); 142 html("</td><td>");
143 if (info->tagger) 143 if (info->tagger)
144 html(info->tagger); 144 html(info->tagger);
145 html("</td><td colspan='2'>"); 145 html("</td><td colspan='2'>");
146 if (info->tagger_date > 0) 146 if (info->tagger_date > 0)
147 cgit_print_age(info->tagger_date, -1, NULL); 147 cgit_print_age(info->tagger_date, -1, NULL);
148 html("</td></tr>\n"); 148 html("</td></tr>\n");
149 } else { 149 } else {
150 if (!header) 150 if (!header)
151 print_tag_header(); 151 print_tag_header();
152 html("<tr><td>"); 152 html("<tr><td>");
153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
154 html("</td><td>"); 154 html("</td><td>");
155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) 155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT))
156 print_tag_downloads(ctx.repo, name); 156 print_tag_downloads(ctx.repo, name);
157 else 157 else
158 cgit_object_link(ref->object); 158 cgit_object_link(ref->object);
159 html("</td><td>"); 159 html("</td><td>");
160 if (ref->object->type == OBJ_COMMIT) 160 if (ref->object->type == OBJ_COMMIT)
161 html(ref->commit->author); 161 html(ref->commit->author);
162 html("</td><td colspan='2'>"); 162 html("</td><td colspan='2'>");
163 if (ref->object->type == OBJ_COMMIT) 163 if (ref->object->type == OBJ_COMMIT)
164 cgit_print_age(ref->commit->commit->date, -1, NULL); 164 cgit_print_age(ref->commit->commit->date, -1, NULL);
165 html("</td></tr>\n"); 165 html("</td></tr>\n");
166 } 166 }
167 return 0; 167 return 0;
168} 168}
169 169
170static void print_refs_link(char *path) 170static void print_refs_link(char *path)
171{ 171{
172 html("<tr class='nohover'><td colspan='4'>"); 172 html("<tr class='nohover'><td colspan='4'>");
173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
174 html("</td></tr>"); 174 html("</td></tr>");
175} 175}
176 176
177void cgit_print_branches(int maxcount) 177void cgit_print_branches(int maxcount)
178{ 178{
179 struct reflist list; 179 struct reflist list;
180 int i; 180 int i;
181 181
182 html("<tr class='nohover'><th class='left'>Branch</th>" 182 html("<tr class='nohover'><th class='left'>Branch</th>"
183 "<th class='left'>Commit message</th>" 183 "<th class='left'>Commit message</th>"
184 "<th class='left'>Author</th>" 184 "<th class='left'>Author</th>"
185 "<th class='left' colspan='2'>Age</th></tr>\n"); 185 "<th class='left' colspan='2'>Age</th></tr>\n");
186 186
187 list.refs = NULL; 187 list.refs = NULL;
188 list.alloc = list.count = 0; 188 list.alloc = list.count = 0;
189 for_each_branch_ref(cgit_refs_cb, &list); 189 for_each_branch_ref(cgit_refs_cb, &list);
190 if (ctx.repo->enable_remote_branches)
191 for_each_remote_ref(cgit_refs_cb, &list);
190 192
191 if (maxcount == 0 || maxcount > list.count) 193 if (maxcount == 0 || maxcount > list.count)
192 maxcount = list.count; 194 maxcount = list.count;
193 195
194 if (maxcount < list.count) { 196 if (maxcount < list.count) {
195 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
196 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 198 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
197 } 199 }
198 200
199 for(i=0; i<maxcount; i++) 201 for(i=0; i<maxcount; i++)
200 print_branch(list.refs[i]); 202 print_branch(list.refs[i]);
201 203
202 if (maxcount < list.count) 204 if (maxcount < list.count)
203 print_refs_link("heads"); 205 print_refs_link("heads");
204} 206}
205 207
206void cgit_print_tags(int maxcount) 208void cgit_print_tags(int maxcount)
207{ 209{
208 struct reflist list; 210 struct reflist list;
209 int i; 211 int i;
210 212
211 header = 0; 213 header = 0;
212 list.refs = NULL; 214 list.refs = NULL;
213 list.alloc = list.count = 0; 215 list.alloc = list.count = 0;
214 for_each_tag_ref(cgit_refs_cb, &list); 216 for_each_tag_ref(cgit_refs_cb, &list);
215 if (list.count == 0) 217 if (list.count == 0)
216 return; 218 return;
217 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); 219 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
218 if (!maxcount) 220 if (!maxcount)
219 maxcount = list.count; 221 maxcount = list.count;
220 else if (maxcount > list.count) 222 else if (maxcount > list.count)
221 maxcount = list.count; 223 maxcount = list.count;
222 print_tag_header(); 224 print_tag_header();
223 for(i=0; i<maxcount; i++) 225 for(i=0; i<maxcount; i++)
224 print_tag(list.refs[i]); 226 print_tag(list.refs[i]);
225 227
226 if (maxcount < list.count) 228 if (maxcount < list.count)
227 print_refs_link("tags"); 229 print_refs_link("tags");
228} 230}
229 231
230void cgit_print_refs() 232void cgit_print_refs()
231{ 233{
232 234
233 html("<table class='list nowrap'>"); 235 html("<table class='list nowrap'>");
234 236
235 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5)) 237 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5))
236 cgit_print_branches(0); 238 cgit_print_branches(0);
237 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4)) 239 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4))
238 cgit_print_tags(0); 240 cgit_print_tags(0);
239 else { 241 else {
240 cgit_print_branches(0); 242 cgit_print_branches(0);
241 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 243 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
242 cgit_print_tags(0); 244 cgit_print_tags(0);
243 } 245 }
244 html("</table>"); 246 html("</table>");
245} 247}
diff --git a/ui-shared.c b/ui-shared.c
index 4049a2b..08ea003 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -224,259 +224,281 @@ static char *repolink(char *title, char *class, char *page, char *head,
224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
225 html("/"); 225 html("/");
226 if (page) { 226 if (page) {
227 html_url_arg(page); 227 html_url_arg(page);
228 html("/"); 228 html("/");
229 if (path) 229 if (path)
230 html_url_arg(path); 230 html_url_arg(path);
231 } 231 }
232 delim = "&amp;"; 232 delim = "&amp;";
233 } 233 }
234 if (head && strcmp(head, ctx.repo->defbranch)) { 234 if (head && strcmp(head, ctx.repo->defbranch)) {
235 html(delim); 235 html(delim);
236 html("h="); 236 html("h=");
237 html_url_arg(head); 237 html_url_arg(head);
238 delim = "&amp;"; 238 delim = "&amp;";
239 } 239 }
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(char *page, char *name, char *title, char *class, 243static void reporevlink(char *page, char *name, char *title, char *class,
244 char *head, char *rev, char *path) 244 char *head, char *rev, char *path)
245{ 245{
246 char *delim; 246 char *delim;
247 247
248 delim = repolink(title, class, page, head, path); 248 delim = repolink(title, class, page, head, path);
249 if (rev && strcmp(rev, ctx.qry.head)) { 249 if (rev && strcmp(rev, ctx.qry.head)) {
250 html(delim); 250 html(delim);
251 html("id="); 251 html("id=");
252 html_url_arg(rev); 252 html_url_arg(rev);
253 } 253 }
254 html("'>"); 254 html("'>");
255 html_txt(name); 255 html_txt(name);
256 html("</a>"); 256 html("</a>");
257} 257}
258 258
259void cgit_summary_link(char *name, char *title, char *class, char *head) 259void cgit_summary_link(char *name, char *title, char *class, char *head)
260{ 260{
261 reporevlink(NULL, name, title, class, head, NULL, NULL); 261 reporevlink(NULL, name, title, class, head, NULL, NULL);
262} 262}
263 263
264void cgit_tag_link(char *name, char *title, char *class, char *head, 264void cgit_tag_link(char *name, char *title, char *class, char *head,
265 char *rev) 265 char *rev)
266{ 266{
267 reporevlink("tag", name, title, class, head, rev, NULL); 267 reporevlink("tag", name, title, class, head, rev, NULL);
268} 268}
269 269
270void cgit_tree_link(char *name, char *title, char *class, char *head, 270void cgit_tree_link(char *name, char *title, char *class, char *head,
271 char *rev, char *path) 271 char *rev, char *path)
272{ 272{
273 reporevlink("tree", name, title, class, head, rev, path); 273 reporevlink("tree", name, title, class, head, rev, path);
274} 274}
275 275
276void cgit_plain_link(char *name, char *title, char *class, char *head, 276void cgit_plain_link(char *name, char *title, char *class, char *head,
277 char *rev, char *path) 277 char *rev, char *path)
278{ 278{
279 reporevlink("plain", name, title, class, head, rev, path); 279 reporevlink("plain", name, title, class, head, rev, path);
280} 280}
281 281
282void cgit_log_link(char *name, char *title, char *class, char *head, 282void cgit_log_link(char *name, char *title, char *class, char *head,
283 char *rev, char *path, int ofs, char *grep, char *pattern, 283 char *rev, char *path, int ofs, char *grep, char *pattern,
284 int showmsg) 284 int showmsg)
285{ 285{
286 char *delim; 286 char *delim;
287 287
288 delim = repolink(title, class, "log", head, path); 288 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 289 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 290 html(delim);
291 html("id="); 291 html("id=");
292 html_url_arg(rev); 292 html_url_arg(rev);
293 delim = "&"; 293 delim = "&";
294 } 294 }
295 if (grep && pattern) { 295 if (grep && pattern) {
296 html(delim); 296 html(delim);
297 html("qt="); 297 html("qt=");
298 html_url_arg(grep); 298 html_url_arg(grep);
299 delim = "&"; 299 delim = "&";
300 html(delim); 300 html(delim);
301 html("q="); 301 html("q=");
302 html_url_arg(pattern); 302 html_url_arg(pattern);
303 } 303 }
304 if (ofs > 0) { 304 if (ofs > 0) {
305 html(delim); 305 html(delim);
306 html("ofs="); 306 html("ofs=");
307 htmlf("%d", ofs); 307 htmlf("%d", ofs);
308 delim = "&"; 308 delim = "&";
309 } 309 }
310 if (showmsg) { 310 if (showmsg) {
311 html(delim); 311 html(delim);
312 html("showmsg=1"); 312 html("showmsg=1");
313 } 313 }
314 html("'>"); 314 html("'>");
315 html_txt(name); 315 html_txt(name);
316 html("</a>"); 316 html("</a>");
317} 317}
318 318
319void cgit_commit_link(char *name, char *title, char *class, char *head, 319void cgit_commit_link(char *name, char *title, char *class, char *head,
320 char *rev) 320 char *rev, int toggle_ssdiff)
321{ 321{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 323 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 324 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 325 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 326 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 327 }
328 reporevlink("commit", name, title, class, head, rev, NULL); 328
329 char *delim;
330
331 delim = repolink(title, class, "commit", head, NULL);
332 if (rev && strcmp(rev, ctx.qry.head)) {
333 html(delim);
334 html("id=");
335 html_url_arg(rev);
336 delim = "&amp;";
337 }
338 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
339 html(delim);
340 html("ss=1");
341 }
342 html("'>");
343 html_txt(name);
344 html("</a>");
329} 345}
330 346
331void cgit_refs_link(char *name, char *title, char *class, char *head, 347void cgit_refs_link(char *name, char *title, char *class, char *head,
332 char *rev, char *path) 348 char *rev, char *path)
333{ 349{
334 reporevlink("refs", name, title, class, head, rev, path); 350 reporevlink("refs", name, title, class, head, rev, path);
335} 351}
336 352
337void cgit_snapshot_link(char *name, char *title, char *class, char *head, 353void cgit_snapshot_link(char *name, char *title, char *class, char *head,
338 char *rev, char *archivename) 354 char *rev, char *archivename)
339{ 355{
340 reporevlink("snapshot", name, title, class, head, rev, archivename); 356 reporevlink("snapshot", name, title, class, head, rev, archivename);
341} 357}
342 358
343void cgit_diff_link(char *name, char *title, char *class, char *head, 359void cgit_diff_link(char *name, char *title, char *class, char *head,
344 char *new_rev, char *old_rev, char *path) 360 char *new_rev, char *old_rev, char *path,
361 int toggle_ssdiff)
345{ 362{
346 char *delim; 363 char *delim;
347 364
348 delim = repolink(title, class, "diff", head, path); 365 delim = repolink(title, class, "diff", head, path);
349 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 366 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
350 html(delim); 367 html(delim);
351 html("id="); 368 html("id=");
352 html_url_arg(new_rev); 369 html_url_arg(new_rev);
353 delim = "&amp;"; 370 delim = "&amp;";
354 } 371 }
355 if (old_rev) { 372 if (old_rev) {
356 html(delim); 373 html(delim);
357 html("id2="); 374 html("id2=");
358 html_url_arg(old_rev); 375 html_url_arg(old_rev);
376 delim = "&amp;";
377 }
378 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
379 html(delim);
380 html("ss=1");
359 } 381 }
360 html("'>"); 382 html("'>");
361 html_txt(name); 383 html_txt(name);
362 html("</a>"); 384 html("</a>");
363} 385}
364 386
365void cgit_patch_link(char *name, char *title, char *class, char *head, 387void cgit_patch_link(char *name, char *title, char *class, char *head,
366 char *rev) 388 char *rev)
367{ 389{
368 reporevlink("patch", name, title, class, head, rev, NULL); 390 reporevlink("patch", name, title, class, head, rev, NULL);
369} 391}
370 392
371void cgit_stats_link(char *name, char *title, char *class, char *head, 393void cgit_stats_link(char *name, char *title, char *class, char *head,
372 char *path) 394 char *path)
373{ 395{
374 reporevlink("stats", name, title, class, head, NULL, path); 396 reporevlink("stats", name, title, class, head, NULL, path);
375} 397}
376 398
377void cgit_object_link(struct object *obj) 399void cgit_object_link(struct object *obj)
378{ 400{
379 char *page, *shortrev, *fullrev, *name; 401 char *page, *shortrev, *fullrev, *name;
380 402
381 fullrev = sha1_to_hex(obj->sha1); 403 fullrev = sha1_to_hex(obj->sha1);
382 shortrev = xstrdup(fullrev); 404 shortrev = xstrdup(fullrev);
383 shortrev[10] = '\0'; 405 shortrev[10] = '\0';
384 if (obj->type == OBJ_COMMIT) { 406 if (obj->type == OBJ_COMMIT) {
385 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 407 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
386 ctx.qry.head, fullrev); 408 ctx.qry.head, fullrev, 0);
387 return; 409 return;
388 } else if (obj->type == OBJ_TREE) 410 } else if (obj->type == OBJ_TREE)
389 page = "tree"; 411 page = "tree";
390 else if (obj->type == OBJ_TAG) 412 else if (obj->type == OBJ_TAG)
391 page = "tag"; 413 page = "tag";
392 else 414 else
393 page = "blob"; 415 page = "blob";
394 name = fmt("%s %s...", typename(obj->type), shortrev); 416 name = fmt("%s %s...", typename(obj->type), shortrev);
395 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 417 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
396} 418}
397 419
398void cgit_print_date(time_t secs, char *format, int local_time) 420void cgit_print_date(time_t secs, char *format, int local_time)
399{ 421{
400 char buf[64]; 422 char buf[64];
401 struct tm *time; 423 struct tm *time;
402 424
403 if (!secs) 425 if (!secs)
404 return; 426 return;
405 if(local_time) 427 if(local_time)
406 time = localtime(&secs); 428 time = localtime(&secs);
407 else 429 else
408 time = gmtime(&secs); 430 time = gmtime(&secs);
409 strftime(buf, sizeof(buf)-1, format, time); 431 strftime(buf, sizeof(buf)-1, format, time);
410 html_txt(buf); 432 html_txt(buf);
411} 433}
412 434
413void cgit_print_age(time_t t, time_t max_relative, char *format) 435void cgit_print_age(time_t t, time_t max_relative, char *format)
414{ 436{
415 time_t now, secs; 437 time_t now, secs;
416 438
417 if (!t) 439 if (!t)
418 return; 440 return;
419 time(&now); 441 time(&now);
420 secs = now - t; 442 secs = now - t;
421 443
422 if (secs > max_relative && max_relative >= 0) { 444 if (secs > max_relative && max_relative >= 0) {
423 cgit_print_date(t, format, ctx.cfg.local_time); 445 cgit_print_date(t, format, ctx.cfg.local_time);
424 return; 446 return;
425 } 447 }
426 448
427 if (secs < TM_HOUR * 2) { 449 if (secs < TM_HOUR * 2) {
428 htmlf("<span class='age-mins'>%.0f min.</span>", 450 htmlf("<span class='age-mins'>%.0f min.</span>",
429 secs * 1.0 / TM_MIN); 451 secs * 1.0 / TM_MIN);
430 return; 452 return;
431 } 453 }
432 if (secs < TM_DAY * 2) { 454 if (secs < TM_DAY * 2) {
433 htmlf("<span class='age-hours'>%.0f hours</span>", 455 htmlf("<span class='age-hours'>%.0f hours</span>",
434 secs * 1.0 / TM_HOUR); 456 secs * 1.0 / TM_HOUR);
435 return; 457 return;
436 } 458 }
437 if (secs < TM_WEEK * 2) { 459 if (secs < TM_WEEK * 2) {
438 htmlf("<span class='age-days'>%.0f days</span>", 460 htmlf("<span class='age-days'>%.0f days</span>",
439 secs * 1.0 / TM_DAY); 461 secs * 1.0 / TM_DAY);
440 return; 462 return;
441 } 463 }
442 if (secs < TM_MONTH * 2) { 464 if (secs < TM_MONTH * 2) {
443 htmlf("<span class='age-weeks'>%.0f weeks</span>", 465 htmlf("<span class='age-weeks'>%.0f weeks</span>",
444 secs * 1.0 / TM_WEEK); 466 secs * 1.0 / TM_WEEK);
445 return; 467 return;
446 } 468 }
447 if (secs < TM_YEAR * 2) { 469 if (secs < TM_YEAR * 2) {
448 htmlf("<span class='age-months'>%.0f months</span>", 470 htmlf("<span class='age-months'>%.0f months</span>",
449 secs * 1.0 / TM_MONTH); 471 secs * 1.0 / TM_MONTH);
450 return; 472 return;
451 } 473 }
452 htmlf("<span class='age-years'>%.0f years</span>", 474 htmlf("<span class='age-years'>%.0f years</span>",
453 secs * 1.0 / TM_YEAR); 475 secs * 1.0 / TM_YEAR);
454} 476}
455 477
456void cgit_print_http_headers(struct cgit_context *ctx) 478void cgit_print_http_headers(struct cgit_context *ctx)
457{ 479{
458 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1")) 480 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1"))
459 return; 481 return;
460 482
461 if (ctx->page.status) 483 if (ctx->page.status)
462 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg); 484 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
463 if (ctx->page.mimetype && ctx->page.charset) 485 if (ctx->page.mimetype && ctx->page.charset)
464 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 486 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
465 ctx->page.charset); 487 ctx->page.charset);
466 else if (ctx->page.mimetype) 488 else if (ctx->page.mimetype)
467 htmlf("Content-Type: %s\n", ctx->page.mimetype); 489 htmlf("Content-Type: %s\n", ctx->page.mimetype);
468 if (ctx->page.size) 490 if (ctx->page.size)
469 htmlf("Content-Length: %ld\n", ctx->page.size); 491 htmlf("Content-Length: %ld\n", ctx->page.size);
470 if (ctx->page.filename) 492 if (ctx->page.filename)
471 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 493 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
472 ctx->page.filename); 494 ctx->page.filename);
473 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 495 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
474 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 496 htmlf("Expires: %s\n", http_date(ctx->page.expires));
475 if (ctx->page.etag) 497 if (ctx->page.etag)
476 htmlf("ETag: \"%s\"\n", ctx->page.etag); 498 htmlf("ETag: \"%s\"\n", ctx->page.etag);
477 html("\n"); 499 html("\n");
478 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD")) 500 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD"))
479 exit(0); 501 exit(0);
480} 502}
481 503
482void cgit_print_docstart(struct cgit_context *ctx) 504void cgit_print_docstart(struct cgit_context *ctx)
@@ -602,172 +624,177 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
602 624
603 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 625 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
604 strcmp(ctx.qry.head, ctx.repo->defbranch)) 626 strcmp(ctx.qry.head, ctx.repo->defbranch))
605 html_hidden("h", ctx.qry.head); 627 html_hidden("h", ctx.qry.head);
606 628
607 if (ctx.qry.sha1) 629 if (ctx.qry.sha1)
608 html_hidden("id", ctx.qry.sha1); 630 html_hidden("id", ctx.qry.sha1);
609 if (ctx.qry.sha2) 631 if (ctx.qry.sha2)
610 html_hidden("id2", ctx.qry.sha2); 632 html_hidden("id2", ctx.qry.sha2);
611 if (ctx.qry.showmsg) 633 if (ctx.qry.showmsg)
612 html_hidden("showmsg", "1"); 634 html_hidden("showmsg", "1");
613 635
614 if (incl_search) { 636 if (incl_search) {
615 if (ctx.qry.grep) 637 if (ctx.qry.grep)
616 html_hidden("qt", ctx.qry.grep); 638 html_hidden("qt", ctx.qry.grep);
617 if (ctx.qry.search) 639 if (ctx.qry.search)
618 html_hidden("q", ctx.qry.search); 640 html_hidden("q", ctx.qry.search);
619 } 641 }
620} 642}
621 643
622const char *fallback_cmd = "repolist"; 644const char *fallback_cmd = "repolist";
623 645
624char *hc(struct cgit_cmd *cmd, const char *page) 646char *hc(struct cgit_cmd *cmd, const char *page)
625{ 647{
626 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 648 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active");
627} 649}
628 650
629static void print_header(struct cgit_context *ctx) 651static void print_header(struct cgit_context *ctx)
630{ 652{
631 html("<table id='header'>\n"); 653 html("<table id='header'>\n");
632 html("<tr>\n"); 654 html("<tr>\n");
633 655
634 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 656 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) {
635 html("<td class='logo' rowspan='2'><a href='"); 657 html("<td class='logo' rowspan='2'><a href='");
636 if (ctx->cfg.logo_link) 658 if (ctx->cfg.logo_link)
637 html_attr(ctx->cfg.logo_link); 659 html_attr(ctx->cfg.logo_link);
638 else 660 else
639 html_attr(cgit_rooturl()); 661 html_attr(cgit_rooturl());
640 html("'><img src='"); 662 html("'><img src='");
641 html_attr(ctx->cfg.logo); 663 html_attr(ctx->cfg.logo);
642 html("' alt='cgit logo'/></a></td>\n"); 664 html("' alt='cgit logo'/></a></td>\n");
643 } 665 }
644 666
645 html("<td class='main'>"); 667 html("<td class='main'>");
646 if (ctx->repo) { 668 if (ctx->repo) {
647 cgit_index_link("index", NULL, NULL, NULL, 0); 669 cgit_index_link("index", NULL, NULL, NULL, 0);
648 html(" : "); 670 html(" : ");
649 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 671 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
650 html("</td><td class='form'>"); 672 html("</td><td class='form'>");
651 html("<form method='get' action=''>\n"); 673 html("<form method='get' action=''>\n");
652 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 674 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
653 html("<select name='h' onchange='this.form.submit();'>\n"); 675 html("<select name='h' onchange='this.form.submit();'>\n");
654 for_each_branch_ref(print_branch_option, ctx->qry.head); 676 for_each_branch_ref(print_branch_option, ctx->qry.head);
655 html("</select> "); 677 html("</select> ");
656 html("<input type='submit' name='' value='switch'/>"); 678 html("<input type='submit' name='' value='switch'/>");
657 html("</form>"); 679 html("</form>");
658 } else 680 } else
659 html_txt(ctx->cfg.root_title); 681 html_txt(ctx->cfg.root_title);
660 html("</td></tr>\n"); 682 html("</td></tr>\n");
661 683
662 html("<tr><td class='sub'>"); 684 html("<tr><td class='sub'>");
663 if (ctx->repo) { 685 if (ctx->repo) {
664 html_txt(ctx->repo->desc); 686 html_txt(ctx->repo->desc);
665 html("</td><td class='sub right'>"); 687 html("</td><td class='sub right'>");
666 html_txt(ctx->repo->owner); 688 html_txt(ctx->repo->owner);
667 } else { 689 } else {
668 if (ctx->cfg.root_desc) 690 if (ctx->cfg.root_desc)
669 html_txt(ctx->cfg.root_desc); 691 html_txt(ctx->cfg.root_desc);
670 else if (ctx->cfg.index_info) 692 else if (ctx->cfg.index_info)
671 html_include(ctx->cfg.index_info); 693 html_include(ctx->cfg.index_info);
672 } 694 }
673 html("</td></tr></table>\n"); 695 html("</td></tr></table>\n");
674} 696}
675 697
676void cgit_print_pageheader(struct cgit_context *ctx) 698void cgit_print_pageheader(struct cgit_context *ctx)
677{ 699{
678 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 700 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
679 701
680 if (!cmd && ctx->repo) 702 if (!cmd && ctx->repo)
681 fallback_cmd = "summary"; 703 fallback_cmd = "summary";
682 704
683 html("<div id='cgit'>"); 705 html("<div id='cgit'>");
684 if (!ctx->cfg.noheader) 706 if (!ctx->cfg.noheader)
685 print_header(ctx); 707 print_header(ctx);
686 708
687 html("<table class='tabs'><tr><td>\n"); 709 html("<table class='tabs'><tr><td>\n");
688 if (ctx->repo) { 710 if (ctx->repo) {
689 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 711 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
690 ctx->qry.head); 712 ctx->qry.head);
691 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 713 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
692 ctx->qry.sha1, NULL); 714 ctx->qry.sha1, NULL);
693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 715 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 716 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 717 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
696 ctx->qry.sha1, NULL); 718 ctx->qry.sha1, NULL);
697 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 719 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
698 ctx->qry.head, ctx->qry.sha1); 720 ctx->qry.head, ctx->qry.sha1, 0);
699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 721 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
700 ctx->qry.sha1, ctx->qry.sha2, NULL); 722 ctx->qry.sha1, ctx->qry.sha2, NULL, 0);
701 if (ctx->repo->max_stats) 723 if (ctx->repo->max_stats)
702 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 724 cgit_stats_link("stats", NULL, hc(cmd, "stats"),
703 ctx->qry.head, NULL); 725 ctx->qry.head, NULL);
704 if (ctx->repo->readme) 726 if (ctx->repo->readme)
705 reporevlink("about", "about", NULL, 727 reporevlink("about", "about", NULL,
706 hc(cmd, "about"), ctx->qry.head, NULL, 728 hc(cmd, "about"), ctx->qry.head, NULL,
707 NULL); 729 NULL);
708 html("</td><td class='form'>"); 730 html("</td><td class='form'>");
709 html("<form class='right' method='get' action='"); 731 html("<form class='right' method='get' action='");
710 if (ctx->cfg.virtual_root) 732 if (ctx->cfg.virtual_root)
711 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 733 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
712 ctx->qry.path, NULL)); 734 ctx->qry.path, NULL));
713 html("'>\n"); 735 html("'>\n");
714 cgit_add_hidden_formfields(1, 0, "log"); 736 cgit_add_hidden_formfields(1, 0, "log");
715 html("<select name='qt'>\n"); 737 html("<select name='qt'>\n");
716 html_option("grep", "log msg", ctx->qry.grep); 738 html_option("grep", "log msg", ctx->qry.grep);
717 html_option("author", "author", ctx->qry.grep); 739 html_option("author", "author", ctx->qry.grep);
718 html_option("committer", "committer", ctx->qry.grep); 740 html_option("committer", "committer", ctx->qry.grep);
719 html("</select>\n"); 741 html("</select>\n");
720 html("<input class='txt' type='text' size='10' name='q' value='"); 742 html("<input class='txt' type='text' size='10' name='q' value='");
721 html_attr(ctx->qry.search); 743 html_attr(ctx->qry.search);
722 html("'/>\n"); 744 html("'/>\n");
723 html("<input type='submit' value='search'/>\n"); 745 html("<input type='submit' value='search'/>\n");
724 html("</form>\n"); 746 html("</form>\n");
725 } else { 747 } else {
726 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 748 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
727 if (ctx->cfg.root_readme) 749 if (ctx->cfg.root_readme)
728 site_link("about", "about", NULL, hc(cmd, "about"), 750 site_link("about", "about", NULL, hc(cmd, "about"),
729 NULL, 0); 751 NULL, 0);
730 html("</td><td class='form'>"); 752 html("</td><td class='form'>");
731 html("<form method='get' action='"); 753 html("<form method='get' action='");
732 html_attr(cgit_rooturl()); 754 html_attr(cgit_rooturl());
733 html("'>\n"); 755 html("'>\n");
734 html("<input type='text' name='q' size='10' value='"); 756 html("<input type='text' name='q' size='10' value='");
735 html_attr(ctx->qry.search); 757 html_attr(ctx->qry.search);
736 html("'/>\n"); 758 html("'/>\n");
737 html("<input type='submit' value='search'/>\n"); 759 html("<input type='submit' value='search'/>\n");
738 html("</form>"); 760 html("</form>");
739 } 761 }
740 html("</td></tr></table>\n"); 762 html("</td></tr></table>\n");
741 html("<div class='content'>"); 763 html("<div class='content'>");
742} 764}
743 765
744void cgit_print_filemode(unsigned short mode) 766void cgit_print_filemode(unsigned short mode)
745{ 767{
746 if (S_ISDIR(mode)) 768 if (S_ISDIR(mode))
747 html("d"); 769 html("d");
748 else if (S_ISLNK(mode)) 770 else if (S_ISLNK(mode))
749 html("l"); 771 html("l");
750 else if (S_ISGITLINK(mode)) 772 else if (S_ISGITLINK(mode))
751 html("m"); 773 html("m");
752 else 774 else
753 html("-"); 775 html("-");
754 html_fileperm(mode >> 6); 776 html_fileperm(mode >> 6);
755 html_fileperm(mode >> 3); 777 html_fileperm(mode >> 3);
756 html_fileperm(mode); 778 html_fileperm(mode);
757} 779}
758 780
759void cgit_print_snapshot_links(const char *repo, const char *head, 781void cgit_print_snapshot_links(const char *repo, const char *head,
760 const char *hex, int snapshots) 782 const char *hex, int snapshots)
761{ 783{
762 const struct cgit_snapshot_format* f; 784 const struct cgit_snapshot_format* f;
785 char *prefix;
763 char *filename; 786 char *filename;
787 unsigned char sha1[20];
764 788
789 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
790 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
791 hex++;
792 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
765 for (f = cgit_snapshot_formats; f->suffix; f++) { 793 for (f = cgit_snapshot_formats; f->suffix; f++) {
766 if (!(snapshots & f->bit)) 794 if (!(snapshots & f->bit))
767 continue; 795 continue;
768 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 796 filename = fmt("%s%s", prefix, f->suffix);
769 f->suffix);
770 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 797 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
771 html("<br/>"); 798 html("<br/>");
772 } 799 }
773} 800}
diff --git a/ui-shared.h b/ui-shared.h
index b12aa89..9ebc1f9 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,51 +1,52 @@
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_httpscheme(); 4extern char *cgit_httpscheme();
5extern char *cgit_hosturl(); 5extern char *cgit_hosturl();
6extern char *cgit_rooturl(); 6extern char *cgit_rooturl();
7extern char *cgit_repourl(const char *reponame); 7extern char *cgit_repourl(const char *reponame);
8extern char *cgit_fileurl(const char *reponame, const char *pagename, 8extern char *cgit_fileurl(const char *reponame, const char *pagename,
9 const char *filename, const char *query); 9 const char *filename, const char *query);
10extern char *cgit_pageurl(const char *reponame, const char *pagename, 10extern char *cgit_pageurl(const char *reponame, const char *pagename,
11 const char *query); 11 const char *query);
12 12
13extern void cgit_index_link(char *name, char *title, char *class, 13extern void cgit_index_link(char *name, char *title, char *class,
14 char *pattern, int ofs); 14 char *pattern, int ofs);
15extern void cgit_summary_link(char *name, char *title, char *class, char *head); 15extern void cgit_summary_link(char *name, char *title, char *class, char *head);
16extern void cgit_tag_link(char *name, char *title, char *class, char *head, 16extern void cgit_tag_link(char *name, char *title, char *class, char *head,
17 char *rev); 17 char *rev);
18extern void cgit_tree_link(char *name, char *title, char *class, char *head, 18extern void cgit_tree_link(char *name, char *title, char *class, char *head,
19 char *rev, char *path); 19 char *rev, char *path);
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_plain_link(char *name, char *title, char *class, char *head,
21 char *rev, char *path); 21 char *rev, char *path);
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22extern void cgit_log_link(char *name, char *title, char *class, char *head,
23 char *rev, char *path, int ofs, char *grep, 23 char *rev, char *path, int ofs, char *grep,
24 char *pattern, int showmsg); 24 char *pattern, int showmsg);
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25extern void cgit_commit_link(char *name, char *title, char *class, char *head,
26 char *rev); 26 char *rev, int toggle_ssdiff);
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27extern void cgit_patch_link(char *name, char *title, char *class, char *head,
28 char *rev); 28 char *rev);
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29extern void cgit_refs_link(char *name, char *title, char *class, char *head,
30 char *rev, char *path); 30 char *rev, char *path);
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31extern void cgit_snapshot_link(char *name, char *title, char *class,
32 char *head, char *rev, char *archivename); 32 char *head, char *rev, char *archivename);
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33extern void cgit_diff_link(char *name, char *title, char *class, char *head,
34 char *new_rev, char *old_rev, char *path); 34 char *new_rev, char *old_rev, char *path,
35 int toggle_ssdiff);
35extern void cgit_stats_link(char *name, char *title, char *class, char *head, 36extern void cgit_stats_link(char *name, char *title, char *class, char *head,
36 char *path); 37 char *path);
37extern void cgit_object_link(struct object *obj); 38extern void cgit_object_link(struct object *obj);
38 39
39extern void cgit_print_error(char *msg); 40extern void cgit_print_error(char *msg);
40extern void cgit_print_date(time_t secs, char *format, int local_time); 41extern void cgit_print_date(time_t secs, char *format, int local_time);
41extern void cgit_print_age(time_t t, time_t max_relative, char *format); 42extern void cgit_print_age(time_t t, time_t max_relative, char *format);
42extern void cgit_print_http_headers(struct cgit_context *ctx); 43extern void cgit_print_http_headers(struct cgit_context *ctx);
43extern void cgit_print_docstart(struct cgit_context *ctx); 44extern void cgit_print_docstart(struct cgit_context *ctx);
44extern void cgit_print_docend(); 45extern void cgit_print_docend();
45extern void cgit_print_pageheader(struct cgit_context *ctx); 46extern void cgit_print_pageheader(struct cgit_context *ctx);
46extern void cgit_print_filemode(unsigned short mode); 47extern void cgit_print_filemode(unsigned short mode);
47extern void cgit_print_snapshot_links(const char *repo, const char *head, 48extern void cgit_print_snapshot_links(const char *repo, const char *head,
48 const char *hex, int snapshots); 49 const char *hex, int snapshots);
49extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 50extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
50 char *page); 51 char *page);
51#endif /* UI_SHARED_H */ 52#endif /* UI_SHARED_H */
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 4136b3e..1b25dca 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -1,138 +1,144 @@
1/* ui-snapshot.c: generate snapshot of a commit 1/* ui-snapshot.c: generate snapshot of a commit
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
13static int write_compressed_tar_archive(struct archiver_args *args,const char *filter) 13static int write_compressed_tar_archive(struct archiver_args *args,const char *filter)
14{ 14{
15 int rv; 15 int rv;
16 struct cgit_filter f; 16 struct cgit_filter f;
17 17
18 f.cmd = xstrdup(filter); 18 f.cmd = xstrdup(filter);
19 f.argv = malloc(2 * sizeof(char *)); 19 f.argv = malloc(2 * sizeof(char *));
20 f.argv[0] = f.cmd; 20 f.argv[0] = f.cmd;
21 f.argv[1] = NULL; 21 f.argv[1] = NULL;
22 cgit_open_filter(&f); 22 cgit_open_filter(&f);
23 rv = write_tar_archive(args); 23 rv = write_tar_archive(args);
24 cgit_close_filter(&f); 24 cgit_close_filter(&f);
25 return rv; 25 return rv;
26} 26}
27 27
28static int write_tar_gzip_archive(struct archiver_args *args) 28static int write_tar_gzip_archive(struct archiver_args *args)
29{ 29{
30 return write_compressed_tar_archive(args,"gzip"); 30 return write_compressed_tar_archive(args,"gzip");
31} 31}
32 32
33static int write_tar_bzip2_archive(struct archiver_args *args) 33static int write_tar_bzip2_archive(struct archiver_args *args)
34{ 34{
35 return write_compressed_tar_archive(args,"bzip2"); 35 return write_compressed_tar_archive(args,"bzip2");
36} 36}
37 37
38static int write_tar_xz_archive(struct archiver_args *args)
39{
40 return write_compressed_tar_archive(args,"xz");
41}
42
38const struct cgit_snapshot_format cgit_snapshot_formats[] = { 43const struct cgit_snapshot_format cgit_snapshot_formats[] = {
39 { ".zip", "application/x-zip", write_zip_archive, 0x1 }, 44 { ".zip", "application/x-zip", write_zip_archive, 0x01 },
40 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x2 }, 45 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x02 },
41 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x4 }, 46 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x04 },
42 { ".tar", "application/x-tar", write_tar_archive, 0x8 }, 47 { ".tar", "application/x-tar", write_tar_archive, 0x08 },
48 { ".tar.xz", "application/x-xz", write_tar_xz_archive, 0x10 },
43 {} 49 {}
44}; 50};
45 51
46static const struct cgit_snapshot_format *get_format(const char *filename) 52static const struct cgit_snapshot_format *get_format(const char *filename)
47{ 53{
48 const struct cgit_snapshot_format *fmt; 54 const struct cgit_snapshot_format *fmt;
49 int fl, sl; 55 int fl, sl;
50 56
51 fl = strlen(filename); 57 fl = strlen(filename);
52 for(fmt = cgit_snapshot_formats; fmt->suffix; fmt++) { 58 for(fmt = cgit_snapshot_formats; fmt->suffix; fmt++) {
53 sl = strlen(fmt->suffix); 59 sl = strlen(fmt->suffix);
54 if (sl >= fl) 60 if (sl >= fl)
55 continue; 61 continue;
56 if (!strcmp(fmt->suffix, filename + fl - sl)) 62 if (!strcmp(fmt->suffix, filename + fl - sl))
57 return fmt; 63 return fmt;
58 } 64 }
59 return NULL; 65 return NULL;
60} 66}
61 67
62static int make_snapshot(const struct cgit_snapshot_format *format, 68static int make_snapshot(const struct cgit_snapshot_format *format,
63 const char *hex, const char *prefix, 69 const char *hex, const char *prefix,
64 const char *filename) 70 const char *filename)
65{ 71{
66 struct archiver_args args; 72 struct archiver_args args;
67 struct commit *commit; 73 struct commit *commit;
68 unsigned char sha1[20]; 74 unsigned char sha1[20];
69 75
70 if(get_sha1(hex, sha1)) { 76 if(get_sha1(hex, sha1)) {
71 cgit_print_error(fmt("Bad object id: %s", hex)); 77 cgit_print_error(fmt("Bad object id: %s", hex));
72 return 1; 78 return 1;
73 } 79 }
74 commit = lookup_commit_reference(sha1); 80 commit = lookup_commit_reference(sha1);
75 if(!commit) { 81 if(!commit) {
76 cgit_print_error(fmt("Not a commit reference: %s", hex)); 82 cgit_print_error(fmt("Not a commit reference: %s", hex));
77 return 1; 83 return 1;
78 } 84 }
79 memset(&args, 0, sizeof(args)); 85 memset(&args, 0, sizeof(args));
80 if (prefix) { 86 if (prefix) {
81 args.base = fmt("%s/", prefix); 87 args.base = fmt("%s/", prefix);
82 args.baselen = strlen(prefix) + 1; 88 args.baselen = strlen(prefix) + 1;
83 } else { 89 } else {
84 args.base = ""; 90 args.base = "";
85 args.baselen = 0; 91 args.baselen = 0;
86 } 92 }
87 args.tree = commit->tree; 93 args.tree = commit->tree;
88 args.time = commit->date; 94 args.time = commit->date;
89 ctx.page.mimetype = xstrdup(format->mimetype); 95 ctx.page.mimetype = xstrdup(format->mimetype);
90 ctx.page.filename = xstrdup(filename); 96 ctx.page.filename = xstrdup(filename);
91 cgit_print_http_headers(&ctx); 97 cgit_print_http_headers(&ctx);
92 format->write_func(&args); 98 format->write_func(&args);
93 return 0; 99 return 0;
94} 100}
95 101
96/* Try to guess the requested revision from the requested snapshot name. 102/* Try to guess the requested revision from the requested snapshot name.
97 * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become 103 * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become
98 * "cgit-0.7.2". If this is a valid commit object name we've got a winner. 104 * "cgit-0.7.2". If this is a valid commit object name we've got a winner.
99 * Otherwise, if the snapshot name has a prefix matching the result from 105 * Otherwise, if the snapshot name has a prefix matching the result from
100 * repo_basename(), we strip the basename and any following '-' and '_' 106 * repo_basename(), we strip the basename and any following '-' and '_'
101 * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once 107 * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once
102 * more. If this still isn't a valid commit object name, we check if pre- 108 * more. If this still isn't a valid commit object name, we check if pre-
103 * pending a 'v' to the remaining snapshot name ("0.7.2" -> "v0.7.2") gives 109 * pending a 'v' to the remaining snapshot name ("0.7.2" -> "v0.7.2") gives
104 * us something valid. 110 * us something valid.
105 */ 111 */
106static const char *get_ref_from_filename(const char *url, const char *filename, 112static const char *get_ref_from_filename(const char *url, const char *filename,
107 const struct cgit_snapshot_format *format) 113 const struct cgit_snapshot_format *format)
108{ 114{
109 const char *reponame; 115 const char *reponame;
110 unsigned char sha1[20]; 116 unsigned char sha1[20];
111 char *snapshot; 117 char *snapshot;
112 118
113 snapshot = xstrdup(filename); 119 snapshot = xstrdup(filename);
114 snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0'; 120 snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0';
115 fprintf(stderr, "snapshot=%s\n", snapshot); 121 fprintf(stderr, "snapshot=%s\n", snapshot);
116 122
117 if (get_sha1(snapshot, sha1) == 0) 123 if (get_sha1(snapshot, sha1) == 0)
118 return snapshot; 124 return snapshot;
119 125
120 reponame = cgit_repobasename(url); 126 reponame = cgit_repobasename(url);
121 fprintf(stderr, "reponame=%s\n", reponame); 127 fprintf(stderr, "reponame=%s\n", reponame);
122 if (prefixcmp(snapshot, reponame) == 0) { 128 if (prefixcmp(snapshot, reponame) == 0) {
123 snapshot += strlen(reponame); 129 snapshot += strlen(reponame);
124 while (snapshot && (*snapshot == '-' || *snapshot == '_')) 130 while (snapshot && (*snapshot == '-' || *snapshot == '_'))
125 snapshot++; 131 snapshot++;
126 } 132 }
127 133
128 if (get_sha1(snapshot, sha1) == 0) 134 if (get_sha1(snapshot, sha1) == 0)
129 return snapshot; 135 return snapshot;
130 136
131 snapshot = fmt("v%s", snapshot); 137 snapshot = fmt("v%s", snapshot);
132 if (get_sha1(snapshot, sha1) == 0) 138 if (get_sha1(snapshot, sha1) == 0)
133 return snapshot; 139 return snapshot;
134 140
135 return NULL; 141 return NULL;
136} 142}
137 143
138void show_error(char *msg) 144void show_error(char *msg)
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
new file mode 100644
index 0000000..408e620
--- a/dev/null
+++ b/ui-ssdiff.c
@@ -0,0 +1,369 @@
1#include "cgit.h"
2#include "html.h"
3#include "ui-shared.h"
4
5extern int use_ssdiff;
6
7static int current_old_line, current_new_line;
8
9struct deferred_lines {
10 int line_no;
11 char *line;
12 struct deferred_lines *next;
13};
14
15static struct deferred_lines *deferred_old, *deferred_old_last;
16static struct deferred_lines *deferred_new, *deferred_new_last;
17
18static char *longest_common_subsequence(char *A, char *B)
19{
20 int i, j, ri;
21 int m = strlen(A);
22 int n = strlen(B);
23 int L[m + 1][n + 1];
24 int tmp1, tmp2;
25 int lcs_length;
26 char *result;
27
28 for (i = m; i >= 0; i--) {
29 for (j = n; j >= 0; j--) {
30 if (A[i] == '\0' || B[j] == '\0') {
31 L[i][j] = 0;
32 } else if (A[i] == B[j]) {
33 L[i][j] = 1 + L[i + 1][j + 1];
34 } else {
35 tmp1 = L[i + 1][j];
36 tmp2 = L[i][j + 1];
37 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
38 }
39 }
40 }
41
42 lcs_length = L[0][0];
43 result = xmalloc(lcs_length + 2);
44 memset(result, 0, sizeof(*result) * (lcs_length + 2));
45
46 ri = 0;
47 i = 0;
48 j = 0;
49 while (i < m && j < n) {
50 if (A[i] == B[j]) {
51 result[ri] = A[i];
52 ri += 1;
53 i += 1;
54 j += 1;
55 } else if (L[i + 1][j] >= L[i][j + 1]) {
56 i += 1;
57 } else {
58 j += 1;
59 }
60 }
61 return result;
62}
63
64static int line_from_hunk(char *line, char type)
65{
66 char *buf1, *buf2;
67 int len;
68
69 buf1 = strchr(line, type);
70 if (buf1 == NULL)
71 return 0;
72 buf1 += 1;
73 buf2 = strchr(buf1, ',');
74 if (buf2 == NULL)
75 return 0;
76 len = buf2 - buf1;
77 buf2 = xmalloc(len + 1);
78 strncpy(buf2, buf1, len);
79 buf2[len] = '\0';
80 int res = atoi(buf2);
81 free(buf2);
82 return res;
83}
84
85static char *replace_tabs(char *line)
86{
87 char *prev_buf = line;
88 char *cur_buf;
89 int linelen = strlen(line);
90 int n_tabs = 0;
91 int i;
92 char *result;
93 char *spaces = " ";
94
95 if (linelen == 0) {
96 result = xmalloc(1);
97 result[0] = '\0';
98 return result;
99 }
100
101 for (i = 0; i < linelen; i++)
102 if (line[i] == '\t')
103 n_tabs += 1;
104 result = xmalloc(linelen + n_tabs * 8 + 1);
105 result[0] = '\0';
106
107 while (1) {
108 cur_buf = strchr(prev_buf, '\t');
109 if (!cur_buf) {
110 strcat(result, prev_buf);
111 break;
112 } else {
113 strcat(result, " ");
114 strncat(result, spaces, 8 - (strlen(result) % 8));
115 strncat(result, prev_buf, cur_buf - prev_buf);
116 }
117 prev_buf = cur_buf + 1;
118 }
119 return result;
120}
121
122static int calc_deferred_lines(struct deferred_lines *start)
123{
124 struct deferred_lines *item = start;
125 int result = 0;
126 while (item) {
127 result += 1;
128 item = item->next;
129 }
130 return result;
131}
132
133static void deferred_old_add(char *line, int line_no)
134{
135 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
136 item->line = xstrdup(line);
137 item->line_no = line_no;
138 item->next = NULL;
139 if (deferred_old) {
140 deferred_old_last->next = item;
141 deferred_old_last = item;
142 } else {
143 deferred_old = deferred_old_last = item;
144 }
145}
146
147static void deferred_new_add(char *line, int line_no)
148{
149 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
150 item->line = xstrdup(line);
151 item->line_no = line_no;
152 item->next = NULL;
153 if (deferred_new) {
154 deferred_new_last->next = item;
155 deferred_new_last = item;
156 } else {
157 deferred_new = deferred_new_last = item;
158 }
159}
160
161static void print_part_with_lcs(char *class, char *line, char *lcs)
162{
163 int line_len = strlen(line);
164 int i, j;
165 char c[2] = " ";
166 int same = 1;
167
168 j = 0;
169 for (i = 0; i < line_len; i++) {
170 c[0] = line[i];
171 if (same) {
172 if (line[i] == lcs[j])
173 j += 1;
174 else {
175 same = 0;
176 htmlf("<span class='%s'>", class);
177 }
178 } else if (line[i] == lcs[j]) {
179 same = 1;
180 htmlf("</span>");
181 j += 1;
182 }
183 html_txt(c);
184 }
185}
186
187static void print_ssdiff_line(char *class,
188 int old_line_no,
189 char *old_line,
190 int new_line_no,
191 char *new_line, int individual_chars)
192{
193 char *lcs = NULL;
194 if (old_line)
195 old_line = replace_tabs(old_line + 1);
196 if (new_line)
197 new_line = replace_tabs(new_line + 1);
198 if (individual_chars && old_line && new_line)
199 lcs = longest_common_subsequence(old_line, new_line);
200 html("<tr>");
201 if (old_line_no > 0)
202 htmlf("<td class='lineno'>%d</td><td class='%s'>",
203 old_line_no, class);
204 else if (old_line)
205 htmlf("<td class='lineno'></td><td class='%s'>", class);
206 else
207 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
208 if (old_line) {
209 if (lcs)
210 print_part_with_lcs("del", old_line, lcs);
211 else
212 html_txt(old_line);
213 }
214
215 html("</td>");
216 if (new_line_no > 0)
217 htmlf("<td class='lineno'>%d</td><td class='%s'>",
218 new_line_no, class);
219 else if (new_line)
220 htmlf("<td class='lineno'></td><td class='%s'>", class);
221 else
222 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
223 if (new_line) {
224 if (lcs)
225 print_part_with_lcs("add", new_line, lcs);
226 else
227 html_txt(new_line);
228 }
229
230 html("</td></tr>");
231 if (lcs)
232 free(lcs);
233 if (new_line)
234 free(new_line);
235 if (old_line)
236 free(old_line);
237}
238
239static void print_deferred_old_lines()
240{
241 struct deferred_lines *iter_old, *tmp;
242 iter_old = deferred_old;
243 while (iter_old) {
244 print_ssdiff_line("del", iter_old->line_no,
245 iter_old->line, -1, NULL, 0);
246 tmp = iter_old->next;
247 free(iter_old);
248 iter_old = tmp;
249 }
250}
251
252static void print_deferred_new_lines()
253{
254 struct deferred_lines *iter_new, *tmp;
255 iter_new = deferred_new;
256 while (iter_new) {
257 print_ssdiff_line("add", -1, NULL,
258 iter_new->line_no, iter_new->line, 0);
259 tmp = iter_new->next;
260 free(iter_new);
261 iter_new = tmp;
262 }
263}
264
265static void print_deferred_changed_lines()
266{
267 struct deferred_lines *iter_old, *iter_new, *tmp;
268 int n_old_lines = calc_deferred_lines(deferred_old);
269 int n_new_lines = calc_deferred_lines(deferred_new);
270 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
271
272 iter_old = deferred_old;
273 iter_new = deferred_new;
274 while (iter_old || iter_new) {
275 if (iter_old && iter_new)
276 print_ssdiff_line("changed", iter_old->line_no,
277 iter_old->line,
278 iter_new->line_no, iter_new->line,
279 individual_chars);
280 else if (iter_old)
281 print_ssdiff_line("changed", iter_old->line_no,
282 iter_old->line, -1, NULL, 0);
283 else if (iter_new)
284 print_ssdiff_line("changed", -1, NULL,
285 iter_new->line_no, iter_new->line, 0);
286 if (iter_old) {
287 tmp = iter_old->next;
288 free(iter_old);
289 iter_old = tmp;
290 }
291
292 if (iter_new) {
293 tmp = iter_new->next;
294 free(iter_new);
295 iter_new = tmp;
296 }
297 }
298}
299
300void cgit_ssdiff_print_deferred_lines()
301{
302 if (!deferred_old && !deferred_new)
303 return;
304 if (deferred_old && !deferred_new)
305 print_deferred_old_lines();
306 else if (!deferred_old && deferred_new)
307 print_deferred_new_lines();
308 else
309 print_deferred_changed_lines();
310 deferred_old = deferred_old_last = NULL;
311 deferred_new = deferred_new_last = NULL;
312}
313
314/*
315 * print a single line returned from xdiff
316 */
317void cgit_ssdiff_line_cb(char *line, int len)
318{
319 char c = line[len - 1];
320 line[len - 1] = '\0';
321 if (line[0] == '@') {
322 current_old_line = line_from_hunk(line, '-');
323 current_new_line = line_from_hunk(line, '+');
324 }
325
326 if (line[0] == ' ') {
327 if (deferred_old || deferred_new)
328 cgit_ssdiff_print_deferred_lines();
329 print_ssdiff_line("ctx", current_old_line, line,
330 current_new_line, line, 0);
331 current_old_line += 1;
332 current_new_line += 1;
333 } else if (line[0] == '+') {
334 deferred_new_add(line, current_new_line);
335 current_new_line += 1;
336 } else if (line[0] == '-') {
337 deferred_old_add(line, current_old_line);
338 current_old_line += 1;
339 } else if (line[0] == '@') {
340 html("<tr><td colspan='4' class='hunk'>");
341 html_txt(line);
342 html("</td></tr>");
343 } else {
344 html("<tr><td colspan='4' class='ctx'>");
345 html_txt(line);
346 html("</td></tr>");
347 }
348 line[len - 1] = c;
349}
350
351void cgit_ssdiff_header_begin()
352{
353 current_old_line = -1;
354 current_new_line = -1;
355 html("<tr><td class='space' colspan='4'><div></div></td></tr>");
356 html("<tr><td class='head' colspan='4'>");
357}
358
359void cgit_ssdiff_header_end()
360{
361 html("</td><tr>");
362}
363
364void cgit_ssdiff_footer()
365{
366 if (deferred_old || deferred_new)
367 cgit_ssdiff_print_deferred_lines();
368 html("<tr><td class='foot' colspan='4'></td></tr>");
369}
diff --git a/ui-ssdiff.h b/ui-ssdiff.h
new file mode 100644
index 0000000..64b4b12
--- a/dev/null
+++ b/ui-ssdiff.h
@@ -0,0 +1,13 @@
1#ifndef UI_SSDIFF_H
2#define UI_SSDIFF_H
3
4extern void cgit_ssdiff_print_deferred_lines();
5
6extern void cgit_ssdiff_line_cb(char *line, int len);
7
8extern void cgit_ssdiff_header_begin();
9extern void cgit_ssdiff_header_end();
10
11extern void cgit_ssdiff_footer();
12
13#endif /* UI_SSDIFF_H */
diff --git a/ui-tag.c b/ui-tag.c
index c2d72af..39e4cb8 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -1,92 +1,104 @@
1/* ui-tag.c: display a tag 1/* ui-tag.c: display a tag
2 * 2 *
3 * Copyright (C) 2007 Lars Hjemli 3 * Copyright (C) 2007 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
13static void print_tag_content(char *buf) 13static void print_tag_content(char *buf)
14{ 14{
15 char *p; 15 char *p;
16 16
17 if (!buf) 17 if (!buf)
18 return; 18 return;
19 19
20 html("<div class='commit-subject'>"); 20 html("<div class='commit-subject'>");
21 p = strchr(buf, '\n'); 21 p = strchr(buf, '\n');
22 if (p) 22 if (p)
23 *p = '\0'; 23 *p = '\0';
24 html_txt(buf); 24 html_txt(buf);
25 html("</div>"); 25 html("</div>");
26 if (p) { 26 if (p) {
27 html("<div class='commit-msg'>"); 27 html("<div class='commit-msg'>");
28 html_txt(++p); 28 html_txt(++p);
29 html("</div>"); 29 html("</div>");
30 } 30 }
31} 31}
32 32
33void print_download_links(char *revname)
34{
35 html("<tr><th>download</th><td class='sha1'>");
36 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
37 revname, ctx.repo->snapshots);
38 html("</td></tr>");
39}
40
33void cgit_print_tag(char *revname) 41void cgit_print_tag(char *revname)
34{ 42{
35 unsigned char sha1[20]; 43 unsigned char sha1[20];
36 struct object *obj; 44 struct object *obj;
37 struct tag *tag; 45 struct tag *tag;
38 struct taginfo *info; 46 struct taginfo *info;
39 47
40 if (!revname) 48 if (!revname)
41 revname = ctx.qry.head; 49 revname = ctx.qry.head;
42 50
43 if (get_sha1(fmt("refs/tags/%s", revname), sha1)) { 51 if (get_sha1(fmt("refs/tags/%s", revname), sha1)) {
44 cgit_print_error(fmt("Bad tag reference: %s", revname)); 52 cgit_print_error(fmt("Bad tag reference: %s", revname));
45 return; 53 return;
46 } 54 }
47 obj = parse_object(sha1); 55 obj = parse_object(sha1);
48 if (!obj) { 56 if (!obj) {
49 cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1))); 57 cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1)));
50 return; 58 return;
51 } 59 }
52 if (obj->type == OBJ_TAG) { 60 if (obj->type == OBJ_TAG) {
53 tag = lookup_tag(sha1); 61 tag = lookup_tag(sha1);
54 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) { 62 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
55 cgit_print_error(fmt("Bad tag object: %s", revname)); 63 cgit_print_error(fmt("Bad tag object: %s", revname));
56 return; 64 return;
57 } 65 }
58 html("<table class='commit-info'>\n"); 66 html("<table class='commit-info'>\n");
59 htmlf("<tr><td>Tag name</td><td>"); 67 htmlf("<tr><td>tag name</td><td>");
60 html_txt(revname); 68 html_txt(revname);
61 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1)); 69 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1));
62 if (info->tagger_date > 0) { 70 if (info->tagger_date > 0) {
63 html("<tr><td>Tag date</td><td>"); 71 html("<tr><td>tag date</td><td>");
64 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time); 72 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time);
65 html("</td></tr>\n"); 73 html("</td></tr>\n");
66 } 74 }
67 if (info->tagger) { 75 if (info->tagger) {
68 html("<tr><td>Tagged by</td><td>"); 76 html("<tr><td>tagged by</td><td>");
69 html_txt(info->tagger); 77 html_txt(info->tagger);
70 if (info->tagger_email && !ctx.cfg.noplainemail) { 78 if (info->tagger_email && !ctx.cfg.noplainemail) {
71 html(" "); 79 html(" ");
72 html_txt(info->tagger_email); 80 html_txt(info->tagger_email);
73 } 81 }
74 html("</td></tr>\n"); 82 html("</td></tr>\n");
75 } 83 }
76 html("<tr><td>Tagged object</td><td>"); 84 html("<tr><td>tagged object</td><td class='sha1'>");
77 cgit_object_link(tag->tagged); 85 cgit_object_link(tag->tagged);
78 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 if (ctx.repo->snapshots)
88 print_download_links(revname);
79 html("</table>\n"); 89 html("</table>\n");
80 print_tag_content(info->msg); 90 print_tag_content(info->msg);
81 } else { 91 } else {
82 html("<table class='commit-info'>\n"); 92 html("<table class='commit-info'>\n");
83 htmlf("<tr><td>Tag name</td><td>"); 93 htmlf("<tr><td>tag name</td><td>");
84 html_txt(revname); 94 html_txt(revname);
85 html("</td></tr>\n"); 95 html("</td></tr>\n");
86 html("<tr><td>Tagged object</td><td>"); 96 html("<tr><td>Tagged object</td><td class='sha1'>");
87 cgit_object_link(obj); 97 cgit_object_link(obj);
88 html("</td></tr>\n"); 98 html("</td></tr>\n");
99 if (ctx.repo->snapshots)
100 print_download_links(revname);
89 html("</table>\n"); 101 html("</table>\n");
90 } 102 }
91 return; 103 return;
92} 104}
diff --git a/ui-tree.c b/ui-tree.c
index a164767..94aff8f 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -14,192 +14,198 @@
14char *curr_rev; 14char *curr_rev;
15char *match_path; 15char *match_path;
16int header = 0; 16int header = 0;
17 17
18static void print_text_buffer(const char *name, char *buf, unsigned long size) 18static void print_text_buffer(const char *name, char *buf, unsigned long size)
19{ 19{
20 unsigned long lineno, idx; 20 unsigned long lineno, idx;
21 const char *numberfmt = 21 const char *numberfmt =
22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n"; 22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n";
23 23
24 html("<table summary='blob content' class='blob'>\n"); 24 html("<table summary='blob content' class='blob'>\n");
25 25
26 if (ctx.cfg.enable_tree_linenumbers) { 26 if (ctx.cfg.enable_tree_linenumbers) {
27 html("<tr><td class='linenumbers'><pre>"); 27 html("<tr><td class='linenumbers'><pre>");
28 idx = 0; 28 idx = 0;
29 lineno = 0; 29 lineno = 0;
30 30
31 if (size) { 31 if (size) {
32 htmlf(numberfmt, ++lineno); 32 htmlf(numberfmt, ++lineno);
33 while(idx < size - 1) { // skip absolute last newline 33 while(idx < size - 1) { // skip absolute last newline
34 if (buf[idx] == '\n') 34 if (buf[idx] == '\n')
35 htmlf(numberfmt, ++lineno); 35 htmlf(numberfmt, ++lineno);
36 idx++; 36 idx++;
37 } 37 }
38 } 38 }
39 html("</pre></td>\n"); 39 html("</pre></td>\n");
40 } 40 }
41 else { 41 else {
42 html("<tr>\n"); 42 html("<tr>\n");
43 } 43 }
44 44
45 if (ctx.repo->source_filter) { 45 if (ctx.repo->source_filter) {
46 html("<td class='lines'><pre><code>"); 46 html("<td class='lines'><pre><code>");
47 ctx.repo->source_filter->argv[1] = xstrdup(name); 47 ctx.repo->source_filter->argv[1] = xstrdup(name);
48 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
49 write(STDOUT_FILENO, buf, size); 49 write(STDOUT_FILENO, buf, size);
50 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
51 html("</code></pre></td></tr></table>\n"); 51 html("</code></pre></td></tr></table>\n");
52 return; 52 return;
53 } 53 }
54 54
55 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
56 html_txt(buf); 56 html_txt(buf);
57 html("</code></pre></td></tr></table>\n"); 57 html("</code></pre></td></tr></table>\n");
58} 58}
59 59
60#define ROWLEN 32 60#define ROWLEN 32
61 61
62static void print_binary_buffer(char *buf, unsigned long size) 62static void print_binary_buffer(char *buf, unsigned long size)
63{ 63{
64 unsigned long ofs, idx; 64 unsigned long ofs, idx;
65 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
66 66
67 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); 68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs);
71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
72 htmlf("%*s%02x", 72 htmlf("%*s%02x",
73 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
74 buf[idx] & 0xff); 74 buf[idx] & 0xff);
75 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
78 ascii[idx] = '\0'; 78 ascii[idx] = '\0';
79 html_txt(ascii); 79 html_txt(ascii);
80 html("</td></tr>\n"); 80 html("</td></tr>\n");
81 } 81 }
82 html("</table>\n"); 82 html("</table>\n");
83} 83}
84 84
85static void print_object(const unsigned char *sha1, char *path, const char *basename) 85static void print_object(const unsigned char *sha1, char *path, const char *basename)
86{ 86{
87 enum object_type type; 87 enum object_type type;
88 char *buf; 88 char *buf;
89 unsigned long size; 89 unsigned long size;
90 90
91 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
92 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
93 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 return; 95 return;
96 } 96 }
97 97
98 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
99 if (!buf) { 99 if (!buf) {
100 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
101 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
102 return; 102 return;
103 } 103 }
104 104
105 html(" ("); 105 html(" (");
106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
107 curr_rev, path); 107 curr_rev, path);
108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
109 109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size);
113 return;
114 }
115
110 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
111 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
112 else 118 else
113 print_text_buffer(basename, buf, size); 119 print_text_buffer(basename, buf, size);
114} 120}
115 121
116 122
117static int ls_item(const unsigned char *sha1, const char *base, int baselen, 123static int ls_item(const unsigned char *sha1, const char *base, int baselen,
118 const char *pathname, unsigned int mode, int stage, 124 const char *pathname, unsigned int mode, int stage,
119 void *cbdata) 125 void *cbdata)
120{ 126{
121 char *name; 127 char *name;
122 char *fullpath; 128 char *fullpath;
123 char *class; 129 char *class;
124 enum object_type type; 130 enum object_type type;
125 unsigned long size = 0; 131 unsigned long size = 0;
126 132
127 name = xstrdup(pathname); 133 name = xstrdup(pathname);
128 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 134 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
129 ctx.qry.path ? "/" : "", name); 135 ctx.qry.path ? "/" : "", name);
130 136
131 if (!S_ISGITLINK(mode)) { 137 if (!S_ISGITLINK(mode)) {
132 type = sha1_object_info(sha1, &size); 138 type = sha1_object_info(sha1, &size);
133 if (type == OBJ_BAD) { 139 if (type == OBJ_BAD) {
134 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 140 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
135 name, 141 name,
136 sha1_to_hex(sha1)); 142 sha1_to_hex(sha1));
137 return 0; 143 return 0;
138 } 144 }
139 } 145 }
140 146
141 html("<tr><td class='ls-mode'>"); 147 html("<tr><td class='ls-mode'>");
142 cgit_print_filemode(mode); 148 cgit_print_filemode(mode);
143 html("</td><td>"); 149 html("</td><td>");
144 if (S_ISGITLINK(mode)) { 150 if (S_ISGITLINK(mode)) {
145 htmlf("<a class='ls-mod' href='"); 151 htmlf("<a class='ls-mod' href='");
146 html_attr(fmt(ctx.repo->module_link, 152 html_attr(fmt(ctx.repo->module_link,
147 name, 153 name,
148 sha1_to_hex(sha1))); 154 sha1_to_hex(sha1)));
149 html("'>"); 155 html("'>");
150 html_txt(name); 156 html_txt(name);
151 html("</a>"); 157 html("</a>");
152 } else if (S_ISDIR(mode)) { 158 } else if (S_ISDIR(mode)) {
153 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 159 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
154 curr_rev, fullpath); 160 curr_rev, fullpath);
155 } else { 161 } else {
156 class = strrchr(name, '.'); 162 class = strrchr(name, '.');
157 if (class != NULL) { 163 if (class != NULL) {
158 class = fmt("ls-blob %s", class + 1); 164 class = fmt("ls-blob %s", class + 1);
159 } else 165 } else
160 class = "ls-blob"; 166 class = "ls-blob";
161 cgit_tree_link(name, NULL, class, ctx.qry.head, 167 cgit_tree_link(name, NULL, class, ctx.qry.head,
162 curr_rev, fullpath); 168 curr_rev, fullpath);
163 } 169 }
164 htmlf("</td><td class='ls-size'>%li</td>", size); 170 htmlf("</td><td class='ls-size'>%li</td>", size);
165 171
166 html("<td>"); 172 html("<td>");
167 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
168 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 174 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
169 if (ctx.repo->max_stats) 175 if (ctx.repo->max_stats)
170 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 176 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
171 fullpath); 177 fullpath);
172 html("</td></tr>\n"); 178 html("</td></tr>\n");
173 free(name); 179 free(name);
174 return 0; 180 return 0;
175} 181}
176 182
177static void ls_head() 183static void ls_head()
178{ 184{
179 html("<table summary='tree listing' class='list'>\n"); 185 html("<table summary='tree listing' class='list'>\n");
180 html("<tr class='nohover'>"); 186 html("<tr class='nohover'>");
181 html("<th class='left'>Mode</th>"); 187 html("<th class='left'>Mode</th>");
182 html("<th class='left'>Name</th>"); 188 html("<th class='left'>Name</th>");
183 html("<th class='right'>Size</th>"); 189 html("<th class='right'>Size</th>");
184 html("<th/>"); 190 html("<th/>");
185 html("</tr>\n"); 191 html("</tr>\n");
186 header = 1; 192 header = 1;
187} 193}
188 194
189static void ls_tail() 195static void ls_tail()
190{ 196{
191 if (!header) 197 if (!header)
192 return; 198 return;
193 html("</table>\n"); 199 html("</table>\n");
194 header = 0; 200 header = 0;
195} 201}
196 202
197static void ls_tree(const unsigned char *sha1, char *path) 203static void ls_tree(const unsigned char *sha1, char *path)
198{ 204{
199 struct tree *tree; 205 struct tree *tree;
200 206
201 tree = parse_tree_indirect(sha1); 207 tree = parse_tree_indirect(sha1);
202 if (!tree) { 208 if (!tree) {
203 cgit_print_error(fmt("Not a tree object: %s", 209 cgit_print_error(fmt("Not a tree object: %s",
204 sha1_to_hex(sha1))); 210 sha1_to_hex(sha1)));
205 return; 211 return;