summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c3
-rw-r--r--cgit.h7
-rw-r--r--shared.c2
-rw-r--r--ui-commit.c46
-rw-r--r--ui-diff.c64
-rw-r--r--ui-shared.c22
6 files changed, 75 insertions, 69 deletions
diff --git a/cgit.c b/cgit.c
index b936a70..3fc90bf 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,251 +1,250 @@
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 10
11const char cgit_version[] = CGIT_VERSION; 11const char cgit_version[] = CGIT_VERSION;
12 12
13 13
14static int cgit_prepare_cache(struct cacheitem *item) 14static int cgit_prepare_cache(struct cacheitem *item)
15{ 15{
16 if (!cgit_repo && cgit_query_repo) { 16 if (!cgit_repo && cgit_query_repo) {
17 char *title = fmt("%s - %s", cgit_root_title, "Bad request"); 17 char *title = fmt("%s - %s", cgit_root_title, "Bad request");
18 cgit_print_docstart(title, item); 18 cgit_print_docstart(title, item);
19 cgit_print_pageheader(title, 0); 19 cgit_print_pageheader(title, 0);
20 cgit_print_error(fmt("Unknown repo: %s", cgit_query_repo)); 20 cgit_print_error(fmt("Unknown repo: %s", cgit_query_repo));
21 cgit_print_docend(); 21 cgit_print_docend();
22 return 0; 22 return 0;
23 } 23 }
24 24
25 if (!cgit_repo) { 25 if (!cgit_repo) {
26 item->name = xstrdup(fmt("%s/index.html", cgit_cache_root)); 26 item->name = xstrdup(fmt("%s/index.html", cgit_cache_root));
27 item->ttl = cgit_cache_root_ttl; 27 item->ttl = cgit_cache_root_ttl;
28 return 1; 28 return 1;
29 } 29 }
30 30
31 if (!cgit_cmd) { 31 if (!cgit_cmd) {
32 item->name = xstrdup(fmt("%s/%s/index.html", cgit_cache_root, 32 item->name = xstrdup(fmt("%s/%s/index.html", cgit_cache_root,
33 cache_safe_filename(cgit_repo->url))); 33 cache_safe_filename(cgit_repo->url)));
34 item->ttl = cgit_cache_repo_ttl; 34 item->ttl = cgit_cache_repo_ttl;
35 } else { 35 } else {
36 item->name = xstrdup(fmt("%s/%s/%s/%s.html", cgit_cache_root, 36 item->name = xstrdup(fmt("%s/%s/%s/%s.html", cgit_cache_root,
37 cache_safe_filename(cgit_repo->url), cgit_query_page, 37 cache_safe_filename(cgit_repo->url), cgit_query_page,
38 cache_safe_filename(cgit_querystring))); 38 cache_safe_filename(cgit_querystring)));
39 if (cgit_query_has_symref) 39 if (cgit_query_has_symref)
40 item->ttl = cgit_cache_dynamic_ttl; 40 item->ttl = cgit_cache_dynamic_ttl;
41 else if (cgit_query_has_sha1) 41 else if (cgit_query_has_sha1)
42 item->ttl = cgit_cache_static_ttl; 42 item->ttl = cgit_cache_static_ttl;
43 else 43 else
44 item->ttl = cgit_cache_repo_ttl; 44 item->ttl = cgit_cache_repo_ttl;
45 } 45 }
46 return 1; 46 return 1;
47} 47}
48 48
49static void cgit_print_repo_page(struct cacheitem *item) 49static void cgit_print_repo_page(struct cacheitem *item)
50{ 50{
51 char *title; 51 char *title;
52 int show_search; 52 int show_search;
53 53
54 if (!cgit_query_head) 54 if (!cgit_query_head)
55 cgit_query_head = cgit_repo->defbranch; 55 cgit_query_head = cgit_repo->defbranch;
56 56
57 if (chdir(cgit_repo->path)) { 57 if (chdir(cgit_repo->path)) {
58 title = fmt("%s - %s", cgit_root_title, "Bad request"); 58 title = fmt("%s - %s", cgit_root_title, "Bad request");
59 cgit_print_docstart(title, item); 59 cgit_print_docstart(title, item);
60 cgit_print_pageheader(title, 0); 60 cgit_print_pageheader(title, 0);
61 cgit_print_error(fmt("Unable to scan repository: %s", 61 cgit_print_error(fmt("Unable to scan repository: %s",
62 strerror(errno))); 62 strerror(errno)));
63 cgit_print_docend(); 63 cgit_print_docend();
64 return; 64 return;
65 } 65 }
66 66
67 title = fmt("%s - %s", cgit_repo->name, cgit_repo->desc); 67 title = fmt("%s - %s", cgit_repo->name, cgit_repo->desc);
68 show_search = 0; 68 show_search = 0;
69 setenv("GIT_DIR", cgit_repo->path, 1); 69 setenv("GIT_DIR", cgit_repo->path, 1);
70 70
71 if ((cgit_cmd == CMD_SNAPSHOT) && cgit_repo->snapshots) { 71 if ((cgit_cmd == CMD_SNAPSHOT) && cgit_repo->snapshots) {
72 cgit_print_snapshot(item, cgit_query_sha1, "zip", 72 cgit_print_snapshot(item, cgit_query_sha1, "zip",
73 cgit_repo->url, cgit_query_name); 73 cgit_repo->url, cgit_query_name);
74 return; 74 return;
75 } 75 }
76 76
77 if (cgit_cmd == CMD_BLOB) { 77 if (cgit_cmd == CMD_BLOB) {
78 cgit_print_blob(item, cgit_query_sha1, cgit_query_path); 78 cgit_print_blob(item, cgit_query_sha1, cgit_query_path);
79 return; 79 return;
80 } 80 }
81 81
82 show_search = (cgit_cmd == CMD_LOG); 82 show_search = (cgit_cmd == CMD_LOG);
83 cgit_print_docstart(title, item); 83 cgit_print_docstart(title, item);
84 if (!cgit_cmd) { 84 if (!cgit_cmd) {
85 cgit_print_pageheader("summary", show_search); 85 cgit_print_pageheader("summary", show_search);
86 cgit_print_summary(); 86 cgit_print_summary();
87 cgit_print_docend(); 87 cgit_print_docend();
88 return; 88 return;
89 } 89 }
90 90
91 cgit_print_pageheader(cgit_query_page, show_search); 91 cgit_print_pageheader(cgit_query_page, show_search);
92 92
93 switch(cgit_cmd) { 93 switch(cgit_cmd) {
94 case CMD_LOG: 94 case CMD_LOG:
95 cgit_print_log(cgit_query_sha1, cgit_query_ofs, 95 cgit_print_log(cgit_query_sha1, cgit_query_ofs,
96 cgit_max_commit_count, cgit_query_search, 96 cgit_max_commit_count, cgit_query_search,
97 cgit_query_path, 1); 97 cgit_query_path, 1);
98 break; 98 break;
99 case CMD_TREE: 99 case CMD_TREE:
100 cgit_print_tree(cgit_query_sha1, cgit_query_path); 100 cgit_print_tree(cgit_query_sha1, cgit_query_path);
101 break; 101 break;
102 case CMD_COMMIT: 102 case CMD_COMMIT:
103 cgit_print_commit(cgit_query_sha1); 103 cgit_print_commit(cgit_query_sha1);
104 break; 104 break;
105 case CMD_DIFF: 105 case CMD_DIFF:
106 cgit_print_diff(cgit_query_head, cgit_query_sha1, cgit_query_sha2, 106 cgit_print_diff(cgit_query_sha1, cgit_query_sha2);
107 cgit_query_path);
108 break; 107 break;
109 default: 108 default:
110 cgit_print_error("Invalid request"); 109 cgit_print_error("Invalid request");
111 } 110 }
112 cgit_print_docend(); 111 cgit_print_docend();
113} 112}
114 113
115static void cgit_fill_cache(struct cacheitem *item, int use_cache) 114static void cgit_fill_cache(struct cacheitem *item, int use_cache)
116{ 115{
117 static char buf[PATH_MAX]; 116 static char buf[PATH_MAX];
118 int stdout2; 117 int stdout2;
119 118
120 getcwd(buf, sizeof(buf)); 119 getcwd(buf, sizeof(buf));
121 item->st.st_mtime = time(NULL); 120 item->st.st_mtime = time(NULL);
122 121
123 if (use_cache) { 122 if (use_cache) {
124 stdout2 = chk_positive(dup(STDOUT_FILENO), 123 stdout2 = chk_positive(dup(STDOUT_FILENO),
125 "Preserving STDOUT"); 124 "Preserving STDOUT");
126 chk_zero(close(STDOUT_FILENO), "Closing STDOUT"); 125 chk_zero(close(STDOUT_FILENO), "Closing STDOUT");
127 chk_positive(dup2(item->fd, STDOUT_FILENO), "Dup2(cachefile)"); 126 chk_positive(dup2(item->fd, STDOUT_FILENO), "Dup2(cachefile)");
128 } 127 }
129 128
130 if (cgit_repo) 129 if (cgit_repo)
131 cgit_print_repo_page(item); 130 cgit_print_repo_page(item);
132 else 131 else
133 cgit_print_repolist(item); 132 cgit_print_repolist(item);
134 133
135 if (use_cache) { 134 if (use_cache) {
136 chk_zero(close(STDOUT_FILENO), "Close redirected STDOUT"); 135 chk_zero(close(STDOUT_FILENO), "Close redirected STDOUT");
137 chk_positive(dup2(stdout2, STDOUT_FILENO), 136 chk_positive(dup2(stdout2, STDOUT_FILENO),
138 "Restoring original STDOUT"); 137 "Restoring original STDOUT");
139 chk_zero(close(stdout2), "Closing temporary STDOUT"); 138 chk_zero(close(stdout2), "Closing temporary STDOUT");
140 } 139 }
141 140
142 chdir(buf); 141 chdir(buf);
143} 142}
144 143
145static void cgit_check_cache(struct cacheitem *item) 144static void cgit_check_cache(struct cacheitem *item)
146{ 145{
147 int i = 0; 146 int i = 0;
148 147
149 top: 148 top:
150 if (++i > cgit_max_lock_attempts) { 149 if (++i > cgit_max_lock_attempts) {
151 die("cgit_refresh_cache: unable to lock %s: %s", 150 die("cgit_refresh_cache: unable to lock %s: %s",
152 item->name, strerror(errno)); 151 item->name, strerror(errno));
153 } 152 }
154 if (!cache_exist(item)) { 153 if (!cache_exist(item)) {
155 if (!cache_lock(item)) { 154 if (!cache_lock(item)) {
156 sleep(1); 155 sleep(1);
157 goto top; 156 goto top;
158 } 157 }
159 if (!cache_exist(item)) { 158 if (!cache_exist(item)) {
160 cgit_fill_cache(item, 1); 159 cgit_fill_cache(item, 1);
161 cache_unlock(item); 160 cache_unlock(item);
162 } else { 161 } else {
163 cache_cancel_lock(item); 162 cache_cancel_lock(item);
164 } 163 }
165 } else if (cache_expired(item) && cache_lock(item)) { 164 } else if (cache_expired(item) && cache_lock(item)) {
166 if (cache_expired(item)) { 165 if (cache_expired(item)) {
167 cgit_fill_cache(item, 1); 166 cgit_fill_cache(item, 1);
168 cache_unlock(item); 167 cache_unlock(item);
169 } else { 168 } else {
170 cache_cancel_lock(item); 169 cache_cancel_lock(item);
171 } 170 }
172 } 171 }
173} 172}
174 173
175static void cgit_print_cache(struct cacheitem *item) 174static void cgit_print_cache(struct cacheitem *item)
176{ 175{
177 static char buf[4096]; 176 static char buf[4096];
178 ssize_t i; 177 ssize_t i;
179 178
180 int fd = open(item->name, O_RDONLY); 179 int fd = open(item->name, O_RDONLY);
181 if (fd<0) 180 if (fd<0)
182 die("Unable to open cached file %s", item->name); 181 die("Unable to open cached file %s", item->name);
183 182
184 while((i=read(fd, buf, sizeof(buf))) > 0) 183 while((i=read(fd, buf, sizeof(buf))) > 0)
185 write(STDOUT_FILENO, buf, i); 184 write(STDOUT_FILENO, buf, i);
186 185
187 close(fd); 186 close(fd);
188} 187}
189 188
190static void cgit_parse_args(int argc, const char **argv) 189static void cgit_parse_args(int argc, const char **argv)
191{ 190{
192 int i; 191 int i;
193 192
194 for (i = 1; i < argc; i++) { 193 for (i = 1; i < argc; i++) {
195 if (!strncmp(argv[i], "--cache=", 8)) { 194 if (!strncmp(argv[i], "--cache=", 8)) {
196 cgit_cache_root = xstrdup(argv[i]+8); 195 cgit_cache_root = xstrdup(argv[i]+8);
197 } 196 }
198 if (!strcmp(argv[i], "--nocache")) { 197 if (!strcmp(argv[i], "--nocache")) {
199 cgit_nocache = 1; 198 cgit_nocache = 1;
200 } 199 }
201 if (!strncmp(argv[i], "--query=", 8)) { 200 if (!strncmp(argv[i], "--query=", 8)) {
202 cgit_querystring = xstrdup(argv[i]+8); 201 cgit_querystring = xstrdup(argv[i]+8);
203 } 202 }
204 if (!strncmp(argv[i], "--repo=", 7)) { 203 if (!strncmp(argv[i], "--repo=", 7)) {
205 cgit_query_repo = xstrdup(argv[i]+7); 204 cgit_query_repo = xstrdup(argv[i]+7);
206 } 205 }
207 if (!strncmp(argv[i], "--page=", 7)) { 206 if (!strncmp(argv[i], "--page=", 7)) {
208 cgit_query_page = xstrdup(argv[i]+7); 207 cgit_query_page = xstrdup(argv[i]+7);
209 } 208 }
210 if (!strncmp(argv[i], "--head=", 7)) { 209 if (!strncmp(argv[i], "--head=", 7)) {
211 cgit_query_head = xstrdup(argv[i]+7); 210 cgit_query_head = xstrdup(argv[i]+7);
212 cgit_query_has_symref = 1; 211 cgit_query_has_symref = 1;
213 } 212 }
214 if (!strncmp(argv[i], "--sha1=", 7)) { 213 if (!strncmp(argv[i], "--sha1=", 7)) {
215 cgit_query_sha1 = xstrdup(argv[i]+7); 214 cgit_query_sha1 = xstrdup(argv[i]+7);
216 cgit_query_has_sha1 = 1; 215 cgit_query_has_sha1 = 1;
217 } 216 }
218 if (!strncmp(argv[i], "--ofs=", 6)) { 217 if (!strncmp(argv[i], "--ofs=", 6)) {
219 cgit_query_ofs = atoi(argv[i]+6); 218 cgit_query_ofs = atoi(argv[i]+6);
220 } 219 }
221 } 220 }
222} 221}
223 222
224int main(int argc, const char **argv) 223int main(int argc, const char **argv)
225{ 224{
226 struct cacheitem item; 225 struct cacheitem item;
227 226
228 htmlfd = STDOUT_FILENO; 227 htmlfd = STDOUT_FILENO;
229 item.st.st_mtime = time(NULL); 228 item.st.st_mtime = time(NULL);
230 cgit_repolist.length = 0; 229 cgit_repolist.length = 0;
231 cgit_repolist.count = 0; 230 cgit_repolist.count = 0;
232 cgit_repolist.repos = NULL; 231 cgit_repolist.repos = NULL;
233 232
234 cgit_read_config(CGIT_CONFIG, cgit_global_config_cb); 233 cgit_read_config(CGIT_CONFIG, cgit_global_config_cb);
235 cgit_repo = NULL; 234 cgit_repo = NULL;
236 if (getenv("SCRIPT_NAME")) 235 if (getenv("SCRIPT_NAME"))
237 cgit_script_name = xstrdup(getenv("SCRIPT_NAME")); 236 cgit_script_name = xstrdup(getenv("SCRIPT_NAME"));
238 if (getenv("QUERY_STRING")) 237 if (getenv("QUERY_STRING"))
239 cgit_querystring = xstrdup(getenv("QUERY_STRING")); 238 cgit_querystring = xstrdup(getenv("QUERY_STRING"));
240 cgit_parse_args(argc, argv); 239 cgit_parse_args(argc, argv);
241 cgit_parse_query(cgit_querystring, cgit_querystring_cb); 240 cgit_parse_query(cgit_querystring, cgit_querystring_cb);
242 if (!cgit_prepare_cache(&item)) 241 if (!cgit_prepare_cache(&item))
243 return 0; 242 return 0;
244 if (cgit_nocache) { 243 if (cgit_nocache) {
245 cgit_fill_cache(&item, 0); 244 cgit_fill_cache(&item, 0);
246 } else { 245 } else {
247 cgit_check_cache(&item); 246 cgit_check_cache(&item);
248 cgit_print_cache(&item); 247 cgit_print_cache(&item);
249 } 248 }
250 return 0; 249 return 0;
251} 250}
diff --git a/cgit.h b/cgit.h
index c276a24..bd2dd0d 100644
--- a/cgit.h
+++ b/cgit.h
@@ -18,216 +18,217 @@
18#include <xdiff/xdiff.h> 18#include <xdiff/xdiff.h>
19 19
20 20
21/* 21/*
22 * The valid cgit repo-commands 22 * The valid cgit repo-commands
23 */ 23 */
24#define CMD_LOG 1 24#define CMD_LOG 1
25#define CMD_COMMIT 2 25#define CMD_COMMIT 2
26#define CMD_DIFF 3 26#define CMD_DIFF 3
27#define CMD_TREE 4 27#define CMD_TREE 4
28#define CMD_BLOB 5 28#define CMD_BLOB 5
29#define CMD_SNAPSHOT 6 29#define CMD_SNAPSHOT 6
30 30
31 31
32/* 32/*
33 * Dateformats used on misc. pages 33 * Dateformats used on misc. pages
34 */ 34 */
35#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S" 35#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S"
36#define FMT_SHORTDATE "%Y-%m-%d" 36#define FMT_SHORTDATE "%Y-%m-%d"
37 37
38 38
39/* 39/*
40 * Limits used for relative dates 40 * Limits used for relative dates
41 */ 41 */
42#define TM_MIN 60 42#define TM_MIN 60
43#define TM_HOUR (TM_MIN * 60) 43#define TM_HOUR (TM_MIN * 60)
44#define TM_DAY (TM_HOUR * 24) 44#define TM_DAY (TM_HOUR * 24)
45#define TM_WEEK (TM_DAY * 7) 45#define TM_WEEK (TM_DAY * 7)
46#define TM_YEAR (TM_DAY * 365) 46#define TM_YEAR (TM_DAY * 365)
47#define TM_MONTH (TM_YEAR / 12.0) 47#define TM_MONTH (TM_YEAR / 12.0)
48 48
49 49
50typedef void (*configfn)(const char *name, const char *value); 50typedef void (*configfn)(const char *name, const char *value);
51typedef void (*filepair_fn)(struct diff_filepair *pair); 51typedef void (*filepair_fn)(struct diff_filepair *pair);
52typedef void (*linediff_fn)(char *line, int len); 52typedef void (*linediff_fn)(char *line, int len);
53 53
54struct cacheitem { 54struct cacheitem {
55 char *name; 55 char *name;
56 struct stat st; 56 struct stat st;
57 int ttl; 57 int ttl;
58 int fd; 58 int fd;
59}; 59};
60 60
61struct repoinfo { 61struct repoinfo {
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 *group; 68 char *group;
69 char *module_link; 69 char *module_link;
70 char *readme; 70 char *readme;
71 int snapshots; 71 int snapshots;
72 int enable_log_filecount; 72 int enable_log_filecount;
73 int enable_log_linecount; 73 int enable_log_linecount;
74}; 74};
75 75
76struct repolist { 76struct repolist {
77 int length; 77 int length;
78 int count; 78 int count;
79 struct repoinfo *repos; 79 struct repoinfo *repos;
80}; 80};
81 81
82struct commitinfo { 82struct commitinfo {
83 struct commit *commit; 83 struct commit *commit;
84 char *author; 84 char *author;
85 char *author_email; 85 char *author_email;
86 unsigned long author_date; 86 unsigned long author_date;
87 char *committer; 87 char *committer;
88 char *committer_email; 88 char *committer_email;
89 unsigned long committer_date; 89 unsigned long committer_date;
90 char *subject; 90 char *subject;
91 char *msg; 91 char *msg;
92}; 92};
93 93
94struct taginfo { 94struct taginfo {
95 char *tagger; 95 char *tagger;
96 char *tagger_email; 96 char *tagger_email;
97 int tagger_date; 97 int tagger_date;
98 char *msg; 98 char *msg;
99}; 99};
100 100
101extern const char cgit_version[]; 101extern const char cgit_version[];
102 102
103extern struct repolist cgit_repolist; 103extern struct repolist cgit_repolist;
104extern struct repoinfo *cgit_repo; 104extern struct repoinfo *cgit_repo;
105extern int cgit_cmd; 105extern int cgit_cmd;
106 106
107extern char *cgit_root_title; 107extern char *cgit_root_title;
108extern char *cgit_css; 108extern char *cgit_css;
109extern char *cgit_logo; 109extern char *cgit_logo;
110extern char *cgit_index_header; 110extern char *cgit_index_header;
111extern char *cgit_logo_link; 111extern char *cgit_logo_link;
112extern char *cgit_module_link; 112extern char *cgit_module_link;
113extern char *cgit_agefile; 113extern char *cgit_agefile;
114extern char *cgit_virtual_root; 114extern char *cgit_virtual_root;
115extern char *cgit_script_name; 115extern char *cgit_script_name;
116extern char *cgit_cache_root; 116extern char *cgit_cache_root;
117extern char *cgit_repo_group; 117extern char *cgit_repo_group;
118 118
119extern int cgit_nocache; 119extern int cgit_nocache;
120extern int cgit_snapshots; 120extern int cgit_snapshots;
121extern int cgit_enable_log_filecount; 121extern int cgit_enable_log_filecount;
122extern int cgit_enable_log_linecount; 122extern int cgit_enable_log_linecount;
123extern int cgit_max_lock_attempts; 123extern int cgit_max_lock_attempts;
124extern int cgit_cache_root_ttl; 124extern int cgit_cache_root_ttl;
125extern int cgit_cache_repo_ttl; 125extern int cgit_cache_repo_ttl;
126extern int cgit_cache_dynamic_ttl; 126extern int cgit_cache_dynamic_ttl;
127extern int cgit_cache_static_ttl; 127extern int cgit_cache_static_ttl;
128extern int cgit_cache_max_create_time; 128extern int cgit_cache_max_create_time;
129extern int cgit_summary_log; 129extern int cgit_summary_log;
130 130
131extern int cgit_max_msg_len; 131extern int cgit_max_msg_len;
132extern int cgit_max_repodesc_len; 132extern int cgit_max_repodesc_len;
133extern int cgit_max_commit_count; 133extern int cgit_max_commit_count;
134 134
135extern int cgit_query_has_symref; 135extern int cgit_query_has_symref;
136extern int cgit_query_has_sha1; 136extern int cgit_query_has_sha1;
137 137
138extern char *cgit_querystring; 138extern char *cgit_querystring;
139extern char *cgit_query_repo; 139extern char *cgit_query_repo;
140extern char *cgit_query_page; 140extern char *cgit_query_page;
141extern char *cgit_query_search; 141extern char *cgit_query_search;
142extern char *cgit_query_head; 142extern char *cgit_query_head;
143extern char *cgit_query_sha1; 143extern char *cgit_query_sha1;
144extern char *cgit_query_sha2; 144extern char *cgit_query_sha2;
145extern char *cgit_query_path; 145extern char *cgit_query_path;
146extern char *cgit_query_name; 146extern char *cgit_query_name;
147extern int cgit_query_ofs; 147extern int cgit_query_ofs;
148 148
149extern int htmlfd; 149extern int htmlfd;
150 150
151extern int cgit_get_cmd_index(const char *cmd); 151extern int cgit_get_cmd_index(const char *cmd);
152extern struct repoinfo *cgit_get_repoinfo(const char *url); 152extern struct repoinfo *cgit_get_repoinfo(const char *url);
153extern void cgit_global_config_cb(const char *name, const char *value); 153extern void cgit_global_config_cb(const char *name, const char *value);
154extern void cgit_repo_config_cb(const char *name, const char *value); 154extern void cgit_repo_config_cb(const char *name, const char *value);
155extern void cgit_querystring_cb(const char *name, const char *value); 155extern void cgit_querystring_cb(const char *name, const char *value);
156 156
157extern int chk_zero(int result, char *msg); 157extern int chk_zero(int result, char *msg);
158extern int chk_positive(int result, char *msg); 158extern int chk_positive(int result, char *msg);
159 159
160extern int hextoint(char c); 160extern int hextoint(char c);
161 161
162extern void *cgit_free_commitinfo(struct commitinfo *info); 162extern void *cgit_free_commitinfo(struct commitinfo *info);
163 163
164extern int cgit_diff_files(const unsigned char *old_sha1, 164extern int cgit_diff_files(const unsigned char *old_sha1,
165 const unsigned char *new_sha1, 165 const unsigned char *new_sha1,
166 linediff_fn fn); 166 linediff_fn fn);
167 167
168extern void cgit_diff_tree(const unsigned char *old_sha1, 168extern void cgit_diff_tree(const unsigned char *old_sha1,
169 const unsigned char *new_sha1, 169 const unsigned char *new_sha1,
170 filepair_fn fn); 170 filepair_fn fn);
171 171
172extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 172extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
173 173
174extern char *fmt(const char *format,...); 174extern char *fmt(const char *format,...);
175 175
176extern void html(const char *txt); 176extern void html(const char *txt);
177extern void htmlf(const char *format,...); 177extern void htmlf(const char *format,...);
178extern void html_txt(char *txt); 178extern void html_txt(char *txt);
179extern void html_ntxt(int len, char *txt); 179extern void html_ntxt(int len, char *txt);
180extern void html_attr(char *txt); 180extern void html_attr(char *txt);
181extern void html_hidden(char *name, char *value); 181extern void html_hidden(char *name, char *value);
182extern void html_link_open(char *url, char *title, char *class); 182extern void html_link_open(char *url, char *title, char *class);
183extern void html_link_close(void); 183extern void html_link_close(void);
184extern void html_filemode(unsigned short mode); 184extern void html_filemode(unsigned short mode);
185extern int html_include(const char *filename); 185extern int html_include(const char *filename);
186 186
187extern int cgit_read_config(const char *filename, configfn fn); 187extern int cgit_read_config(const char *filename, configfn fn);
188extern int cgit_parse_query(char *txt, configfn fn); 188extern int cgit_parse_query(char *txt, configfn fn);
189extern struct commitinfo *cgit_parse_commit(struct commit *commit); 189extern struct commitinfo *cgit_parse_commit(struct commit *commit);
190extern struct taginfo *cgit_parse_tag(struct tag *tag); 190extern struct taginfo *cgit_parse_tag(struct tag *tag);
191extern void cgit_parse_url(const char *url); 191extern void cgit_parse_url(const char *url);
192 192
193extern char *cache_safe_filename(const char *unsafe); 193extern char *cache_safe_filename(const char *unsafe);
194extern int cache_lock(struct cacheitem *item); 194extern int cache_lock(struct cacheitem *item);
195extern int cache_unlock(struct cacheitem *item); 195extern int cache_unlock(struct cacheitem *item);
196extern int cache_cancel_lock(struct cacheitem *item); 196extern int cache_cancel_lock(struct cacheitem *item);
197extern int cache_exist(struct cacheitem *item); 197extern int cache_exist(struct cacheitem *item);
198extern int cache_expired(struct cacheitem *item); 198extern int cache_expired(struct cacheitem *item);
199 199
200extern char *cgit_repourl(const char *reponame); 200extern char *cgit_repourl(const char *reponame);
201extern char *cgit_pageurl(const char *reponame, const char *pagename, 201extern char *cgit_pageurl(const char *reponame, const char *pagename,
202 const char *query); 202 const char *query);
203 203
204extern void cgit_tree_link(char *name, char *title, char *class, char *head, 204extern void cgit_tree_link(char *name, char *title, char *class, char *head,
205 char *rev, char *path); 205 char *rev, char *path);
206extern void cgit_log_link(char *name, char *title, char *class, char *head, 206extern void cgit_log_link(char *name, char *title, char *class, char *head,
207 char *rev, char *path); 207 char *rev, char *path);
208extern void cgit_commit_link(char *name, char *title, char *class, char *head, 208extern void cgit_commit_link(char *name, char *title, char *class, char *head,
209 char *rev); 209 char *rev);
210extern void cgit_diff_link(char *name, char *title, char *class, char *head,
211 char *new_rev, char *old_rev, char *path);
210 212
211extern void cgit_print_error(char *msg); 213extern void cgit_print_error(char *msg);
212extern void cgit_print_date(time_t secs, char *format); 214extern void cgit_print_date(time_t secs, char *format);
213extern void cgit_print_age(time_t t, time_t max_relative, char *format); 215extern void cgit_print_age(time_t t, time_t max_relative, char *format);
214extern void cgit_print_docstart(char *title, struct cacheitem *item); 216extern void cgit_print_docstart(char *title, struct cacheitem *item);
215extern void cgit_print_docend(); 217extern void cgit_print_docend();
216extern void cgit_print_pageheader(char *title, int show_search); 218extern void cgit_print_pageheader(char *title, int show_search);
217extern void cgit_print_snapshot_start(const char *mimetype, 219extern void cgit_print_snapshot_start(const char *mimetype,
218 const char *filename, 220 const char *filename,
219 struct cacheitem *item); 221 struct cacheitem *item);
220 222
221extern void cgit_print_repolist(struct cacheitem *item); 223extern void cgit_print_repolist(struct cacheitem *item);
222extern void cgit_print_summary(); 224extern void cgit_print_summary();
223extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *path, int pager); 225extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *path, int pager);
224extern void cgit_print_blob(struct cacheitem *item, const char *hex, char *path); 226extern void cgit_print_blob(struct cacheitem *item, const char *hex, char *path);
225extern void cgit_print_tree(const char *rev, char *path); 227extern void cgit_print_tree(const char *rev, char *path);
226extern void cgit_print_commit(const char *hex); 228extern void cgit_print_commit(char *hex);
227extern void cgit_print_diff(const char *head, const char *old_hex, const char *new_hex, 229extern void cgit_print_diff(const char *new_hex, const char *old_hex);
228 char *path);
229extern void cgit_print_snapshot(struct cacheitem *item, const char *hex, 230extern void cgit_print_snapshot(struct cacheitem *item, const char *hex,
230 const char *format, const char *prefix, 231 const char *format, const char *prefix,
231 const char *filename); 232 const char *filename);
232 233
233#endif /* CGIT_H */ 234#endif /* CGIT_H */
diff --git a/shared.c b/shared.c
index c7cd8a5..f20fb5c 100644
--- a/shared.c
+++ b/shared.c
@@ -171,208 +171,208 @@ void cgit_global_config_cb(const char *name, const char *value)
171 else if (!strcmp(name, "agefile")) 171 else if (!strcmp(name, "agefile"))
172 cgit_agefile = xstrdup(value); 172 cgit_agefile = xstrdup(value);
173 else if (!strcmp(name, "repo.group")) 173 else if (!strcmp(name, "repo.group"))
174 cgit_repo_group = xstrdup(value); 174 cgit_repo_group = xstrdup(value);
175 else if (!strcmp(name, "repo.url")) 175 else if (!strcmp(name, "repo.url"))
176 cgit_repo = add_repo(value); 176 cgit_repo = add_repo(value);
177 else if (!strcmp(name, "repo.name")) 177 else if (!strcmp(name, "repo.name"))
178 cgit_repo->name = xstrdup(value); 178 cgit_repo->name = xstrdup(value);
179 else if (cgit_repo && !strcmp(name, "repo.path")) 179 else if (cgit_repo && !strcmp(name, "repo.path"))
180 cgit_repo->path = xstrdup(value); 180 cgit_repo->path = xstrdup(value);
181 else if (cgit_repo && !strcmp(name, "repo.desc")) 181 else if (cgit_repo && !strcmp(name, "repo.desc"))
182 cgit_repo->desc = xstrdup(value); 182 cgit_repo->desc = xstrdup(value);
183 else if (cgit_repo && !strcmp(name, "repo.owner")) 183 else if (cgit_repo && !strcmp(name, "repo.owner"))
184 cgit_repo->owner = xstrdup(value); 184 cgit_repo->owner = xstrdup(value);
185 else if (cgit_repo && !strcmp(name, "repo.defbranch")) 185 else if (cgit_repo && !strcmp(name, "repo.defbranch"))
186 cgit_repo->defbranch = xstrdup(value); 186 cgit_repo->defbranch = xstrdup(value);
187 else if (cgit_repo && !strcmp(name, "repo.snapshots")) 187 else if (cgit_repo && !strcmp(name, "repo.snapshots"))
188 cgit_repo->snapshots = cgit_snapshots * atoi(value); 188 cgit_repo->snapshots = cgit_snapshots * atoi(value);
189 else if (cgit_repo && !strcmp(name, "repo.enable-log-filecount")) 189 else if (cgit_repo && !strcmp(name, "repo.enable-log-filecount"))
190 cgit_repo->enable_log_filecount = cgit_enable_log_filecount * atoi(value); 190 cgit_repo->enable_log_filecount = cgit_enable_log_filecount * atoi(value);
191 else if (cgit_repo && !strcmp(name, "repo.enable-log-linecount")) 191 else if (cgit_repo && !strcmp(name, "repo.enable-log-linecount"))
192 cgit_repo->enable_log_linecount = cgit_enable_log_linecount * atoi(value); 192 cgit_repo->enable_log_linecount = cgit_enable_log_linecount * atoi(value);
193 else if (cgit_repo && !strcmp(name, "repo.module-link")) 193 else if (cgit_repo && !strcmp(name, "repo.module-link"))
194 cgit_repo->module_link= xstrdup(value); 194 cgit_repo->module_link= xstrdup(value);
195 else if (cgit_repo && !strcmp(name, "repo.readme") && value != NULL) { 195 else if (cgit_repo && !strcmp(name, "repo.readme") && value != NULL) {
196 if (*value == '/') 196 if (*value == '/')
197 cgit_repo->readme = xstrdup(value); 197 cgit_repo->readme = xstrdup(value);
198 else 198 else
199 cgit_repo->readme = xstrdup(fmt("%s/%s", cgit_repo->path, value)); 199 cgit_repo->readme = xstrdup(fmt("%s/%s", cgit_repo->path, value));
200 } else if (!strcmp(name, "include")) 200 } else if (!strcmp(name, "include"))
201 cgit_read_config(value, cgit_global_config_cb); 201 cgit_read_config(value, cgit_global_config_cb);
202} 202}
203 203
204void cgit_querystring_cb(const char *name, const char *value) 204void cgit_querystring_cb(const char *name, const char *value)
205{ 205{
206 if (!strcmp(name,"r")) { 206 if (!strcmp(name,"r")) {
207 cgit_query_repo = xstrdup(value); 207 cgit_query_repo = xstrdup(value);
208 cgit_repo = cgit_get_repoinfo(value); 208 cgit_repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 209 } else if (!strcmp(name, "p")) {
210 cgit_query_page = xstrdup(value); 210 cgit_query_page = xstrdup(value);
211 cgit_cmd = cgit_get_cmd_index(value); 211 cgit_cmd = cgit_get_cmd_index(value);
212 } else if (!strcmp(name, "url")) { 212 } else if (!strcmp(name, "url")) {
213 cgit_parse_url(value); 213 cgit_parse_url(value);
214 } else if (!strcmp(name, "q")) { 214 } else if (!strcmp(name, "q")) {
215 cgit_query_search = xstrdup(value); 215 cgit_query_search = xstrdup(value);
216 } else if (!strcmp(name, "h")) { 216 } else if (!strcmp(name, "h")) {
217 cgit_query_head = xstrdup(value); 217 cgit_query_head = xstrdup(value);
218 cgit_query_has_symref = 1; 218 cgit_query_has_symref = 1;
219 } else if (!strcmp(name, "id")) { 219 } else if (!strcmp(name, "id")) {
220 cgit_query_sha1 = xstrdup(value); 220 cgit_query_sha1 = xstrdup(value);
221 cgit_query_has_sha1 = 1; 221 cgit_query_has_sha1 = 1;
222 } else if (!strcmp(name, "id2")) { 222 } else if (!strcmp(name, "id2")) {
223 cgit_query_sha2 = xstrdup(value); 223 cgit_query_sha2 = xstrdup(value);
224 cgit_query_has_sha1 = 1; 224 cgit_query_has_sha1 = 1;
225 } else if (!strcmp(name, "ofs")) { 225 } else if (!strcmp(name, "ofs")) {
226 cgit_query_ofs = atoi(value); 226 cgit_query_ofs = atoi(value);
227 } else if (!strcmp(name, "path")) { 227 } else if (!strcmp(name, "path")) {
228 cgit_query_path = xstrdup(value); 228 cgit_query_path = xstrdup(value);
229 } else if (!strcmp(name, "name")) { 229 } else if (!strcmp(name, "name")) {
230 cgit_query_name = xstrdup(value); 230 cgit_query_name = xstrdup(value);
231 } 231 }
232} 232}
233 233
234void *cgit_free_commitinfo(struct commitinfo *info) 234void *cgit_free_commitinfo(struct commitinfo *info)
235{ 235{
236 free(info->author); 236 free(info->author);
237 free(info->author_email); 237 free(info->author_email);
238 free(info->committer); 238 free(info->committer);
239 free(info->committer_email); 239 free(info->committer_email);
240 free(info->subject); 240 free(info->subject);
241 free(info); 241 free(info);
242 return NULL; 242 return NULL;
243} 243}
244 244
245int hextoint(char c) 245int hextoint(char c)
246{ 246{
247 if (c >= 'a' && c <= 'f') 247 if (c >= 'a' && c <= 'f')
248 return 10 + c - 'a'; 248 return 10 + c - 'a';
249 else if (c >= 'A' && c <= 'F') 249 else if (c >= 'A' && c <= 'F')
250 return 10 + c - 'A'; 250 return 10 + c - 'A';
251 else if (c >= '0' && c <= '9') 251 else if (c >= '0' && c <= '9')
252 return c - '0'; 252 return c - '0';
253 else 253 else
254 return -1; 254 return -1;
255} 255}
256 256
257void cgit_diff_tree_cb(struct diff_queue_struct *q, 257void cgit_diff_tree_cb(struct diff_queue_struct *q,
258 struct diff_options *options, void *data) 258 struct diff_options *options, void *data)
259{ 259{
260 int i; 260 int i;
261 261
262 for (i = 0; i < q->nr; i++) { 262 for (i = 0; i < q->nr; i++) {
263 if (q->queue[i]->status == 'U') 263 if (q->queue[i]->status == 'U')
264 continue; 264 continue;
265 ((filepair_fn)data)(q->queue[i]); 265 ((filepair_fn)data)(q->queue[i]);
266 } 266 }
267} 267}
268 268
269static int load_mmfile(mmfile_t *file, const unsigned char *sha1) 269static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
270{ 270{
271 enum object_type type; 271 enum object_type type;
272 272
273 if (is_null_sha1(sha1)) { 273 if (is_null_sha1(sha1)) {
274 file->ptr = (char *)""; 274 file->ptr = (char *)"";
275 file->size = 0; 275 file->size = 0;
276 } else { 276 } else {
277 file->ptr = read_sha1_file(sha1, &type, &file->size); 277 file->ptr = read_sha1_file(sha1, &type, &file->size);
278 } 278 }
279 return 1; 279 return 1;
280} 280}
281 281
282/* 282/*
283 * Receive diff-buffers from xdiff and concatenate them as 283 * Receive diff-buffers from xdiff and concatenate them as
284 * needed across multiple callbacks. 284 * needed across multiple callbacks.
285 * 285 *
286 * This is basically a copy of xdiff-interface.c/xdiff_outf(), 286 * This is basically a copy of xdiff-interface.c/xdiff_outf(),
287 * ripped from git and modified to use globals instead of 287 * ripped from git and modified to use globals instead of
288 * a special callback-struct. 288 * a special callback-struct.
289 */ 289 */
290char *diffbuf = NULL; 290char *diffbuf = NULL;
291int buflen = 0; 291int buflen = 0;
292 292
293int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) 293int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
294{ 294{
295 int i; 295 int i;
296 296
297 for (i = 0; i < nbuf; i++) { 297 for (i = 0; i < nbuf; i++) {
298 if (mb[i].ptr[mb[i].size-1] != '\n') { 298 if (mb[i].ptr[mb[i].size-1] != '\n') {
299 /* Incomplete line */ 299 /* Incomplete line */
300 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 300 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
301 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 301 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
302 buflen += mb[i].size; 302 buflen += mb[i].size;
303 continue; 303 continue;
304 } 304 }
305 305
306 /* we have a complete line */ 306 /* we have a complete line */
307 if (!diffbuf) { 307 if (!diffbuf) {
308 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 308 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
309 continue; 309 continue;
310 } 310 }
311 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 311 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
312 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 312 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
313 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 313 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
314 free(diffbuf); 314 free(diffbuf);
315 diffbuf = NULL; 315 diffbuf = NULL;
316 buflen = 0; 316 buflen = 0;
317 } 317 }
318 if (diffbuf) { 318 if (diffbuf) {
319 ((linediff_fn)priv)(diffbuf, buflen); 319 ((linediff_fn)priv)(diffbuf, buflen);
320 free(diffbuf); 320 free(diffbuf);
321 diffbuf = NULL; 321 diffbuf = NULL;
322 buflen = 0; 322 buflen = 0;
323 } 323 }
324 return 0; 324 return 0;
325} 325}
326 326
327int cgit_diff_files(const unsigned char *old_sha1, 327int cgit_diff_files(const unsigned char *old_sha1,
328 const unsigned char *new_sha1, 328 const unsigned char *new_sha1,
329 linediff_fn fn) 329 linediff_fn fn)
330{ 330{
331 mmfile_t file1, file2; 331 mmfile_t file1, file2;
332 xpparam_t diff_params; 332 xpparam_t diff_params;
333 xdemitconf_t emit_params; 333 xdemitconf_t emit_params;
334 xdemitcb_t emit_cb; 334 xdemitcb_t emit_cb;
335 335
336 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 336 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
337 return 1; 337 return 1;
338 338
339 diff_params.flags = XDF_NEED_MINIMAL; 339 diff_params.flags = XDF_NEED_MINIMAL;
340 emit_params.ctxlen = 3; 340 emit_params.ctxlen = 3;
341 emit_params.flags = XDL_EMIT_FUNCNAMES; 341 emit_params.flags = XDL_EMIT_FUNCNAMES;
342 emit_cb.outf = filediff_cb; 342 emit_cb.outf = filediff_cb;
343 emit_cb.priv = fn; 343 emit_cb.priv = fn;
344 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 344 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
345 return 0; 345 return 0;
346} 346}
347 347
348void cgit_diff_tree(const unsigned char *old_sha1, 348void cgit_diff_tree(const unsigned char *old_sha1,
349 const unsigned char *new_sha1, 349 const unsigned char *new_sha1,
350 filepair_fn fn) 350 filepair_fn fn)
351{ 351{
352 struct diff_options opt; 352 struct diff_options opt;
353 int ret; 353 int ret;
354 354
355 diff_setup(&opt); 355 diff_setup(&opt);
356 opt.output_format = DIFF_FORMAT_CALLBACK; 356 opt.output_format = DIFF_FORMAT_CALLBACK;
357 opt.detect_rename = 1; 357 opt.detect_rename = 1;
358 opt.recursive = 1; 358 opt.recursive = 1;
359 opt.format_callback = cgit_diff_tree_cb; 359 opt.format_callback = cgit_diff_tree_cb;
360 opt.format_callback_data = fn; 360 opt.format_callback_data = fn;
361 diff_setup_done(&opt); 361 diff_setup_done(&opt);
362 362
363 if (old_sha1) 363 if (old_sha1 && !is_null_sha1(old_sha1))
364 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); 364 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
365 else 365 else
366 ret = diff_root_tree_sha1(new_sha1, "", &opt); 366 ret = diff_root_tree_sha1(new_sha1, "", &opt);
367 diffcore_std(&opt); 367 diffcore_std(&opt);
368 diff_flush(&opt); 368 diff_flush(&opt);
369} 369}
370 370
371void cgit_diff_commit(struct commit *commit, filepair_fn fn) 371void cgit_diff_commit(struct commit *commit, filepair_fn fn)
372{ 372{
373 unsigned char *old_sha1 = NULL; 373 unsigned char *old_sha1 = NULL;
374 374
375 if (commit->parents) 375 if (commit->parents)
376 old_sha1 = commit->parents->item->object.sha1; 376 old_sha1 = commit->parents->item->object.sha1;
377 cgit_diff_tree(old_sha1, commit->object.sha1, fn); 377 cgit_diff_tree(old_sha1, commit->object.sha1, fn);
378} 378}
diff --git a/ui-commit.c b/ui-commit.c
index d489d7c..2679b59 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,242 +1,228 @@
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 10
11static int files, slots; 11static int files, slots;
12static int total_adds, total_rems, max_changes; 12static int total_adds, total_rems, max_changes;
13static int lines_added, lines_removed; 13static int lines_added, lines_removed;
14static char *curr_rev;
14 15
15static struct fileinfo { 16static struct fileinfo {
16 char status; 17 char status;
17 unsigned char old_sha1[20]; 18 unsigned char old_sha1[20];
18 unsigned char new_sha1[20]; 19 unsigned char new_sha1[20];
19 unsigned short old_mode; 20 unsigned short old_mode;
20 unsigned short new_mode; 21 unsigned short new_mode;
21 char *old_path; 22 char *old_path;
22 char *new_path; 23 char *new_path;
23 unsigned int added; 24 unsigned int added;
24 unsigned int removed; 25 unsigned int removed;
25} *items; 26} *items;
26 27
27 28
28void print_fileinfo(struct fileinfo *info) 29void print_fileinfo(struct fileinfo *info)
29{ 30{
30 char *query, *query2;
31 char *class; 31 char *class;
32 32
33 switch (info->status) { 33 switch (info->status) {
34 case DIFF_STATUS_ADDED: 34 case DIFF_STATUS_ADDED:
35 class = "add"; 35 class = "add";
36 break; 36 break;
37 case DIFF_STATUS_COPIED: 37 case DIFF_STATUS_COPIED:
38 class = "cpy"; 38 class = "cpy";
39 break; 39 break;
40 case DIFF_STATUS_DELETED: 40 case DIFF_STATUS_DELETED:
41 class = "del"; 41 class = "del";
42 break; 42 break;
43 case DIFF_STATUS_MODIFIED: 43 case DIFF_STATUS_MODIFIED:
44 class = "upd"; 44 class = "upd";
45 break; 45 break;
46 case DIFF_STATUS_RENAMED: 46 case DIFF_STATUS_RENAMED:
47 class = "mov"; 47 class = "mov";
48 break; 48 break;
49 case DIFF_STATUS_TYPE_CHANGED: 49 case DIFF_STATUS_TYPE_CHANGED:
50 class = "typ"; 50 class = "typ";
51 break; 51 break;
52 case DIFF_STATUS_UNKNOWN: 52 case DIFF_STATUS_UNKNOWN:
53 class = "unk"; 53 class = "unk";
54 break; 54 break;
55 case DIFF_STATUS_UNMERGED: 55 case DIFF_STATUS_UNMERGED:
56 class = "stg"; 56 class = "stg";
57 break; 57 break;
58 default: 58 default:
59 die("bug: unhandled diff status %c", info->status); 59 die("bug: unhandled diff status %c", info->status);
60 } 60 }
61 61
62 html("<tr>"); 62 html("<tr>");
63 htmlf("<td class='mode'>"); 63 htmlf("<td class='mode'>");
64 if (is_null_sha1(info->new_sha1)) { 64 if (is_null_sha1(info->new_sha1)) {
65 html_filemode(info->old_mode); 65 html_filemode(info->old_mode);
66 } else { 66 } else {
67 html_filemode(info->new_mode); 67 html_filemode(info->new_mode);
68 } 68 }
69 69
70 if (info->old_mode != info->new_mode && 70 if (info->old_mode != info->new_mode &&
71 !is_null_sha1(info->old_sha1) && 71 !is_null_sha1(info->old_sha1) &&
72 !is_null_sha1(info->new_sha1)) { 72 !is_null_sha1(info->new_sha1)) {
73 html("<span class='modechange'>["); 73 html("<span class='modechange'>[");
74 html_filemode(info->old_mode); 74 html_filemode(info->old_mode);
75 html("]</span>"); 75 html("]</span>");
76 } 76 }
77 htmlf("</td><td class='%s'>", class); 77 htmlf("</td><td class='%s'>", class);
78 query = fmt("id=%s&amp;id2=%s&amp;path=%s", sha1_to_hex(info->old_sha1), 78 cgit_tree_link(info->new_path, NULL, NULL, cgit_query_head, curr_rev,
79 sha1_to_hex(info->new_sha1), info->new_path); 79 info->new_path);
80 html_link_open(cgit_pageurl(cgit_query_repo, "diff", query), 80 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
81 NULL, NULL); 81 htmlf(" (%s from %s)",
82 if (info->status == DIFF_STATUS_COPIED || 82 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
83 info->status == DIFF_STATUS_RENAMED) { 83 info->old_path);
84 html_txt(info->new_path);
85 htmlf("</a> (%s from ", info->status == DIFF_STATUS_COPIED ?
86 "copied" : "renamed");
87 query2 = fmt("id=%s", sha1_to_hex(info->old_sha1));
88 html_link_open(cgit_pageurl(cgit_query_repo, "view", query2),
89 NULL, NULL);
90 html_txt(info->old_path);
91 html("</a>)");
92 } else {
93 html_txt(info->new_path);
94 html("</a>");
95 }
96 html("</td><td class='right'>"); 84 html("</td><td class='right'>");
97 htmlf("%d", info->added + info->removed); 85 htmlf("%d", info->added + info->removed);
98 html("</td><td class='graph'>"); 86 html("</td><td class='graph'>");
99 htmlf("<table width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 87 htmlf("<table width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
100 htmlf("<td class='add' style='width: %.1f%%;'/>", 88 htmlf("<td class='add' style='width: %.1f%%;'/>",
101 info->added * 100.0 / max_changes); 89 info->added * 100.0 / max_changes);
102 htmlf("<td class='rem' style='width: %.1f%%;'/>", 90 htmlf("<td class='rem' style='width: %.1f%%;'/>",
103 info->removed * 100.0 / max_changes); 91 info->removed * 100.0 / max_changes);
104 htmlf("<td class='none' style='width: %.1f%%;'/>", 92 htmlf("<td class='none' style='width: %.1f%%;'/>",
105 (max_changes - info->removed - info->added) * 100.0 / max_changes); 93 (max_changes - info->removed - info->added) * 100.0 / max_changes);
106 html("</tr></table></td></tr>\n"); 94 html("</tr></table></td></tr>\n");
107} 95}
108 96
109void cgit_count_diff_lines(char *line, int len) 97void cgit_count_diff_lines(char *line, int len)
110{ 98{
111 if (line && (len > 0)) { 99 if (line && (len > 0)) {
112 if (line[0] == '+') 100 if (line[0] == '+')
113 lines_added++; 101 lines_added++;
114 else if (line[0] == '-') 102 else if (line[0] == '-')
115 lines_removed++; 103 lines_removed++;
116 } 104 }
117} 105}
118 106
119void inspect_filepair(struct diff_filepair *pair) 107void inspect_filepair(struct diff_filepair *pair)
120{ 108{
121 files++; 109 files++;
122 lines_added = 0; 110 lines_added = 0;
123 lines_removed = 0; 111 lines_removed = 0;
124 cgit_diff_files(pair->one->sha1, pair->two->sha1, cgit_count_diff_lines); 112 cgit_diff_files(pair->one->sha1, pair->two->sha1, cgit_count_diff_lines);
125 if (files >= slots) { 113 if (files >= slots) {
126 if (slots == 0) 114 if (slots == 0)
127 slots = 4; 115 slots = 4;
128 else 116 else
129 slots = slots * 2; 117 slots = slots * 2;
130 items = xrealloc(items, slots * sizeof(struct fileinfo)); 118 items = xrealloc(items, slots * sizeof(struct fileinfo));
131 } 119 }
132 items[files-1].status = pair->status; 120 items[files-1].status = pair->status;
133 hashcpy(items[files-1].old_sha1, pair->one->sha1); 121 hashcpy(items[files-1].old_sha1, pair->one->sha1);
134 hashcpy(items[files-1].new_sha1, pair->two->sha1); 122 hashcpy(items[files-1].new_sha1, pair->two->sha1);
135 items[files-1].old_mode = pair->one->mode; 123 items[files-1].old_mode = pair->one->mode;
136 items[files-1].new_mode = pair->two->mode; 124 items[files-1].new_mode = pair->two->mode;
137 items[files-1].old_path = xstrdup(pair->one->path); 125 items[files-1].old_path = xstrdup(pair->one->path);
138 items[files-1].new_path = xstrdup(pair->two->path); 126 items[files-1].new_path = xstrdup(pair->two->path);
139 items[files-1].added = lines_added; 127 items[files-1].added = lines_added;
140 items[files-1].removed = lines_removed; 128 items[files-1].removed = lines_removed;
141 if (lines_added + lines_removed > max_changes) 129 if (lines_added + lines_removed > max_changes)
142 max_changes = lines_added + lines_removed; 130 max_changes = lines_added + lines_removed;
143 total_adds += lines_added; 131 total_adds += lines_added;
144 total_rems += lines_removed; 132 total_rems += lines_removed;
145} 133}
146 134
147 135
148void cgit_print_commit(const char *hex) 136void cgit_print_commit(char *hex)
149{ 137{
150 struct commit *commit, *parent; 138 struct commit *commit, *parent;
151 struct commitinfo *info; 139 struct commitinfo *info;
152 struct commit_list *p; 140 struct commit_list *p;
153 unsigned char sha1[20]; 141 unsigned char sha1[20];
154 char *query;
155 char *filename; 142 char *filename;
156 char *tmp; 143 char *tmp;
157 int i; 144 int i;
158 145
159 if (!hex) 146 if (!hex)
160 hex = cgit_query_head; 147 hex = cgit_query_head;
148 curr_rev = hex;
161 149
162 if (get_sha1(hex, sha1)) { 150 if (get_sha1(hex, sha1)) {
163 cgit_print_error(fmt("Bad object id: %s", hex)); 151 cgit_print_error(fmt("Bad object id: %s", hex));
164 return; 152 return;
165 } 153 }
166 commit = lookup_commit_reference(sha1); 154 commit = lookup_commit_reference(sha1);
167 if (!commit) { 155 if (!commit) {
168 cgit_print_error(fmt("Bad commit reference: %s", hex)); 156 cgit_print_error(fmt("Bad commit reference: %s", hex));
169 return; 157 return;
170 } 158 }
171 info = cgit_parse_commit(commit); 159 info = cgit_parse_commit(commit);
172 160
173 html("<table class='commit-info'>\n"); 161 html("<table class='commit-info'>\n");
174 html("<tr><th>author</th><td>"); 162 html("<tr><th>author</th><td>");
175 html_txt(info->author); 163 html_txt(info->author);
176 html(" "); 164 html(" ");
177 html_txt(info->author_email); 165 html_txt(info->author_email);
178 html("</td><td class='right'>"); 166 html("</td><td class='right'>");
179 cgit_print_date(info->author_date, FMT_LONGDATE); 167 cgit_print_date(info->author_date, FMT_LONGDATE);
180 html("</td></tr>\n"); 168 html("</td></tr>\n");
181 html("<tr><th>committer</th><td>"); 169 html("<tr><th>committer</th><td>");
182 html_txt(info->committer); 170 html_txt(info->committer);
183 html(" "); 171 html(" ");
184 html_txt(info->committer_email); 172 html_txt(info->committer_email);
185 html("</td><td class='right'>"); 173 html("</td><td class='right'>");
186 cgit_print_date(info->committer_date, FMT_LONGDATE); 174 cgit_print_date(info->committer_date, FMT_LONGDATE);
187 html("</td></tr>\n"); 175 html("</td></tr>\n");
188 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 176 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
189 tmp = xstrdup(hex); 177 tmp = xstrdup(hex);
190 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 178 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
191 cgit_query_head, tmp, NULL); 179 cgit_query_head, tmp, NULL);
192 html("</td></tr>\n"); 180 html("</td></tr>\n");
193 for (p = commit->parents; p ; p = p->next) { 181 for (p = commit->parents; p ; p = p->next) {
194 parent = lookup_commit_reference(p->item->object.sha1); 182 parent = lookup_commit_reference(p->item->object.sha1);
195 if (!parent) { 183 if (!parent) {
196 html("<tr><td colspan='3'>"); 184 html("<tr><td colspan='3'>");
197 cgit_print_error("Error reading parent commit"); 185 cgit_print_error("Error reading parent commit");
198 html("</td></tr>"); 186 html("</td></tr>");
199 continue; 187 continue;
200 } 188 }
201 html("<tr><th>parent</th>" 189 html("<tr><th>parent</th>"
202 "<td colspan='2' class='sha1'>"); 190 "<td colspan='2' class='sha1'>");
203 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 191 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
204 cgit_query_head, sha1_to_hex(p->item->object.sha1)); 192 cgit_query_head, sha1_to_hex(p->item->object.sha1));
205 html(" (<a href='"); 193 html(" (");
206 query = fmt("id=%s&amp;id2=%s", sha1_to_hex(parent->tree->object.sha1), 194 cgit_diff_link("diff", NULL, NULL, cgit_query_head, hex,
207 sha1_to_hex(commit->tree->object.sha1)); 195 sha1_to_hex(p->item->object.sha1), NULL);
208 html_attr(cgit_pageurl(cgit_query_repo, "diff", query)); 196 html(")</td></tr>");
209 html("'>diff</a>)</td></tr>");
210 } 197 }
211 if (cgit_repo->snapshots) { 198 if (cgit_repo->snapshots) {
212 htmlf("<tr><th>download</th><td colspan='2' class='sha1'><a href='"); 199 htmlf("<tr><th>download</th><td colspan='2' class='sha1'><a href='");
213 filename = fmt("%s-%s.zip", cgit_query_repo, hex); 200 filename = fmt("%s-%s.zip", cgit_query_repo, hex);
214 html_attr(cgit_pageurl(cgit_query_repo, "snapshot", 201 html_attr(cgit_pageurl(cgit_query_repo, "snapshot",
215 fmt("id=%s&amp;name=%s", hex, filename))); 202 fmt("id=%s&amp;name=%s", hex, filename)));
216 htmlf("'>%s</a></td></tr>", filename); 203 htmlf("'>%s</a></td></tr>", filename);
217 } 204 }
218 html("</table>\n"); 205 html("</table>\n");
219 html("<div class='commit-subject'>"); 206 html("<div class='commit-subject'>");
220 html_txt(info->subject); 207 html_txt(info->subject);
221 html("</div>"); 208 html("</div>");
222 html("<div class='commit-msg'>"); 209 html("<div class='commit-msg'>");
223 html_txt(info->msg); 210 html_txt(info->msg);
224 html("</div>"); 211 html("</div>");
225 if (!(commit->parents && commit->parents->next && commit->parents->next->next)) { 212 if (!(commit->parents && commit->parents->next && commit->parents->next->next)) {
226 html("<div class='diffstat-header'>Diffstat</div>"); 213 html("<div class='diffstat-header'>Diffstat</div>");
227 html("<table class='diffstat'>"); 214 html("<table class='diffstat'>");
228 max_changes = 0; 215 max_changes = 0;
229 cgit_diff_commit(commit, inspect_filepair); 216 cgit_diff_commit(commit, inspect_filepair);
230 for(i = 0; i<files; i++) 217 for(i = 0; i<files; i++)
231 print_fileinfo(&items[i]); 218 print_fileinfo(&items[i]);
232 html("</table>"); 219 html("</table>");
233 html("<div class='diffstat-summary'>"); 220 html("<div class='diffstat-summary'>");
234 htmlf("%d files changed, %d insertions, %d deletions (", 221 htmlf("%d files changed, %d insertions, %d deletions (",
235 files, total_adds, total_rems); 222 files, total_adds, total_rems);
236 query = fmt("h=%s", hex); 223 cgit_diff_link("show diff", NULL, NULL, cgit_query_head, hex,
237 html_link_open(cgit_pageurl(cgit_query_repo, "diff", query), NULL, NULL); 224 NULL, NULL);
238 html("show diff</a>)"); 225 html(")</div>");
239 html("</div>");
240 } 226 }
241 cgit_free_commitinfo(info); 227 cgit_free_commitinfo(info);
242} 228}
diff --git a/ui-diff.c b/ui-diff.c
index 5c864d9..a76a234 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,142 +1,140 @@
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 10
11 11
12/* 12/*
13 * print a single line returned from xdiff 13 * print a single line returned from xdiff
14 */ 14 */
15static void print_line(char *line, int len) 15static void print_line(char *line, int len)
16{ 16{
17 char *class = "ctx"; 17 char *class = "ctx";
18 char c = line[len-1]; 18 char c = line[len-1];
19 19
20 if (line[0] == '+') 20 if (line[0] == '+')
21 class = "add"; 21 class = "add";
22 else if (line[0] == '-') 22 else if (line[0] == '-')
23 class = "del"; 23 class = "del";
24 else if (line[0] == '@') 24 else if (line[0] == '@')
25 class = "hunk"; 25 class = "hunk";
26 26
27 htmlf("<div class='%s'>", class); 27 htmlf("<div class='%s'>", class);
28 line[len-1] = '\0'; 28 line[len-1] = '\0';
29 html_txt(line); 29 html_txt(line);
30 html("</div>"); 30 html("</div>");
31 line[len-1] = c; 31 line[len-1] = c;
32} 32}
33 33
34static void header(unsigned char *sha1, char *path1, int mode1, 34static void header(unsigned char *sha1, char *path1, int mode1,
35 unsigned char *sha2, char *path2, int mode2) 35 unsigned char *sha2, char *path2, int mode2)
36{ 36{
37 char *abbrev1, *abbrev2; 37 char *abbrev1, *abbrev2;
38 int subproject; 38 int subproject;
39 39
40 subproject = (S_ISDIRLNK(mode1) || S_ISDIRLNK(mode2)); 40 subproject = (S_ISDIRLNK(mode1) || S_ISDIRLNK(mode2));
41 html("<div class='head'>"); 41 html("<div class='head'>");
42 html("diff --git a/"); 42 html("diff --git a/");
43 html_txt(path1); 43 html_txt(path1);
44 html(" b/"); 44 html(" b/");
45 html_txt(path2); 45 html_txt(path2);
46 46
47 if (is_null_sha1(sha1)) 47 if (is_null_sha1(sha1))
48 path1 = "dev/null"; 48 path1 = "dev/null";
49 if (is_null_sha1(sha2)) 49 if (is_null_sha1(sha2))
50 path2 = "dev/null"; 50 path2 = "dev/null";
51 51
52 if (mode1 == 0) 52 if (mode1 == 0)
53 htmlf("<br/>new file mode %.6o", mode2); 53 htmlf("<br/>new file mode %.6o", mode2);
54 54
55 if (mode2 == 0) 55 if (mode2 == 0)
56 htmlf("<br/>deleted file mode %.6o", mode1); 56 htmlf("<br/>deleted file mode %.6o", mode1);
57 57
58 if (!subproject) { 58 if (!subproject) {
59 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 59 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
60 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 60 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
61 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 61 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
62 free(abbrev1); 62 free(abbrev1);
63 free(abbrev2); 63 free(abbrev2);
64 if (mode1 != 0 && mode2 != 0) { 64 if (mode1 != 0 && mode2 != 0) {
65 htmlf(" %.6o", mode1); 65 htmlf(" %.6o", mode1);
66 if (mode2 != mode1) 66 if (mode2 != mode1)
67 htmlf("..%.6o", mode2); 67 htmlf("..%.6o", mode2);
68 } 68 }
69 html("<br/>--- a/"); 69 html("<br/>--- a/");
70 html_txt(path1); 70 html_txt(path1);
71 html("<br/>+++ b/"); 71 html("<br/>+++ b/");
72 html_txt(path2); 72 html_txt(path2);
73 } 73 }
74 html("</div>"); 74 html("</div>");
75} 75}
76 76
77static void filepair_cb(struct diff_filepair *pair) 77static void filepair_cb(struct diff_filepair *pair)
78{ 78{
79 header(pair->one->sha1, pair->one->path, pair->one->mode, 79 header(pair->one->sha1, pair->one->path, pair->one->mode,
80 pair->two->sha1, pair->two->path, pair->two->mode); 80 pair->two->sha1, pair->two->path, pair->two->mode);
81 if (S_ISDIRLNK(pair->one->mode) || S_ISDIRLNK(pair->two->mode)) { 81 if (S_ISDIRLNK(pair->one->mode) || S_ISDIRLNK(pair->two->mode)) {
82 if (S_ISDIRLNK(pair->one->mode)) 82 if (S_ISDIRLNK(pair->one->mode))
83 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 83 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
84 if (S_ISDIRLNK(pair->two->mode)) 84 if (S_ISDIRLNK(pair->two->mode))
85 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 85 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
86 return; 86 return;
87 } 87 }
88 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, print_line)) 88 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, print_line))
89 cgit_print_error("Error running diff"); 89 cgit_print_error("Error running diff");
90} 90}
91 91
92void cgit_print_diff(const char *head, const char *old_hex, const char *new_hex, char *path) 92void cgit_print_diff(const char *new_rev, const char *old_rev)
93{ 93{
94 unsigned char sha1[20], sha2[20]; 94 unsigned char sha1[20], sha2[20];
95 enum object_type type; 95 enum object_type type;
96 unsigned long size; 96 unsigned long size;
97 struct commit *commit; 97 struct commit *commit, *commit2;
98 98
99 html("<table class='diff'>"); 99 if (!new_rev)
100 html("<tr><td>"); 100 new_rev = cgit_query_head;
101 101 get_sha1(new_rev, sha1);
102 if (head && !old_hex && !new_hex) { 102 type = sha1_object_info(sha1, &size);
103 get_sha1(head, sha1); 103 if (type == OBJ_BAD) {
104 commit = lookup_commit_reference(sha1); 104 cgit_print_error(fmt("Bad object name: %s", new_rev));
105 if (commit && !parse_commit(commit)) 105 return;
106 cgit_diff_commit(commit, filepair_cb); 106 }
107 else 107 if (type != OBJ_COMMIT) {
108 cgit_print_error(fmt("Bad commit: %s", head)); 108 cgit_print_error(fmt("Unhandled object type: %s",
109 html("</td></tr>"); 109 typename(type)));
110 html("</table>");
111 return; 110 return;
112 } 111 }
113 112
114 get_sha1(old_hex, sha1); 113 commit = lookup_commit_reference(sha1);
115 get_sha1(new_hex, sha2); 114 if (!commit || parse_commit(commit))
115 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(sha1)));
116 116
117 type = sha1_object_info(sha1, &size); 117 if (old_rev)
118 if (type == OBJ_BAD) { 118 get_sha1(old_rev, sha2);
119 else if (commit->parents && commit->parents->item)
120 hashcpy(sha2, commit->parents->item->object.sha1);
121 else
122 hashclr(sha2);
123
124 if (!is_null_sha1(sha2)) {
119 type = sha1_object_info(sha2, &size); 125 type = sha1_object_info(sha2, &size);
120 if (type == OBJ_BAD) { 126 if (type == OBJ_BAD) {
121 cgit_print_error(fmt("Bad object names: %s, %s", old_hex, new_hex)); 127 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(sha2)));
122 return; 128 return;
123 } 129 }
130 commit2 = lookup_commit_reference(sha2);
131 if (!commit2 || parse_commit(commit2))
132 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(sha2)));
124 } 133 }
125 134
126 switch(type) { 135 html("<table class='diff'>");
127 case OBJ_BLOB: 136 html("<tr><td>");
128 header(sha1, path, 0644, sha2, path, 0644); 137 cgit_diff_tree(sha2, sha1, filepair_cb);
129 if (cgit_diff_files(sha1, sha2, print_line))
130 cgit_print_error("Error running diff");
131 break;
132 case OBJ_TREE:
133 cgit_diff_tree(sha1, sha2, filepair_cb);
134 break;
135 default:
136 cgit_print_error(fmt("Unhandled object type: %s",
137 typename(type)));
138 break;
139 }
140 html("</td></tr>"); 138 html("</td></tr>");
141 html("</table>"); 139 html("</table>");
142} 140}
diff --git a/ui-shared.c b/ui-shared.c
index 71c899a..15d8254 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,309 +1,331 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output functions
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10 10
11const char cgit_doctype[] = 11const char cgit_doctype[] =
12"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 12"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
13" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 13" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
14 14
15static char *http_date(time_t t) 15static char *http_date(time_t t)
16{ 16{
17 static char day[][4] = 17 static char day[][4] =
18 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 18 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
19 static char month[][4] = 19 static char month[][4] =
20 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 20 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
21 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; 21 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
22 struct tm *tm = gmtime(&t); 22 struct tm *tm = gmtime(&t);
23 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 23 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
24 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 24 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
25 tm->tm_hour, tm->tm_min, tm->tm_sec); 25 tm->tm_hour, tm->tm_min, tm->tm_sec);
26} 26}
27 27
28static long ttl_seconds(long ttl) 28static long ttl_seconds(long ttl)
29{ 29{
30 if (ttl<0) 30 if (ttl<0)
31 return 60 * 60 * 24 * 365; 31 return 60 * 60 * 24 * 365;
32 else 32 else
33 return ttl * 60; 33 return ttl * 60;
34} 34}
35 35
36void cgit_print_error(char *msg) 36void cgit_print_error(char *msg)
37{ 37{
38 html("<div class='error'>"); 38 html("<div class='error'>");
39 html_txt(msg); 39 html_txt(msg);
40 html("</div>\n"); 40 html("</div>\n");
41} 41}
42 42
43char *cgit_rooturl() 43char *cgit_rooturl()
44{ 44{
45 if (cgit_virtual_root) 45 if (cgit_virtual_root)
46 return fmt("%s/", cgit_virtual_root); 46 return fmt("%s/", cgit_virtual_root);
47 else 47 else
48 return cgit_script_name; 48 return cgit_script_name;
49} 49}
50 50
51char *cgit_repourl(const char *reponame) 51char *cgit_repourl(const char *reponame)
52{ 52{
53 if (cgit_virtual_root) { 53 if (cgit_virtual_root) {
54 return fmt("%s/%s/", cgit_virtual_root, reponame); 54 return fmt("%s/%s/", cgit_virtual_root, reponame);
55 } else { 55 } else {
56 return fmt("?r=%s", reponame); 56 return fmt("?r=%s", reponame);
57 } 57 }
58} 58}
59 59
60char *cgit_pageurl(const char *reponame, const char *pagename, 60char *cgit_pageurl(const char *reponame, const char *pagename,
61 const char *query) 61 const char *query)
62{ 62{
63 if (cgit_virtual_root) { 63 if (cgit_virtual_root) {
64 if (query) 64 if (query)
65 return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame, 65 return fmt("%s/%s/%s/?%s", cgit_virtual_root, reponame,
66 pagename, query); 66 pagename, query);
67 else 67 else
68 return fmt("%s/%s/%s/", cgit_virtual_root, reponame, 68 return fmt("%s/%s/%s/", cgit_virtual_root, reponame,
69 pagename); 69 pagename);
70 } else { 70 } else {
71 if (query) 71 if (query)
72 return fmt("?r=%s&amp;p=%s&amp;%s", reponame, pagename, query); 72 return fmt("?r=%s&amp;p=%s&amp;%s", reponame, pagename, query);
73 else 73 else
74 return fmt("?r=%s&amp;p=%s", reponame, pagename); 74 return fmt("?r=%s&amp;p=%s", reponame, pagename);
75 } 75 }
76} 76}
77 77
78char *cgit_currurl() 78char *cgit_currurl()
79{ 79{
80 if (!cgit_virtual_root) 80 if (!cgit_virtual_root)
81 return cgit_script_name; 81 return cgit_script_name;
82 else if (cgit_query_page) 82 else if (cgit_query_page)
83 return fmt("%s/%s/%s/", cgit_virtual_root, cgit_query_repo, cgit_query_page); 83 return fmt("%s/%s/%s/", cgit_virtual_root, cgit_query_repo, cgit_query_page);
84 else if (cgit_query_repo) 84 else if (cgit_query_repo)
85 return fmt("%s/%s/", cgit_virtual_root, cgit_query_repo); 85 return fmt("%s/%s/", cgit_virtual_root, cgit_query_repo);
86 else 86 else
87 return fmt("%s/", cgit_virtual_root); 87 return fmt("%s/", cgit_virtual_root);
88} 88}
89 89
90static char *repolink(char *title, char *class, char *page, char *head, 90static char *repolink(char *title, char *class, char *page, char *head,
91 char *path) 91 char *path)
92{ 92{
93 char *delim = "?"; 93 char *delim = "?";
94 94
95 html("<a"); 95 html("<a");
96 if (title) { 96 if (title) {
97 html(" title='"); 97 html(" title='");
98 html_attr(title); 98 html_attr(title);
99 html("'"); 99 html("'");
100 } 100 }
101 if (class) { 101 if (class) {
102 html(" class='"); 102 html(" class='");
103 html_attr(class); 103 html_attr(class);
104 html("'"); 104 html("'");
105 } 105 }
106 html(" href='"); 106 html(" href='");
107 if (cgit_virtual_root) { 107 if (cgit_virtual_root) {
108 html_attr(cgit_virtual_root); 108 html_attr(cgit_virtual_root);
109 if (cgit_virtual_root[strlen(cgit_virtual_root) - 1] != '/') 109 if (cgit_virtual_root[strlen(cgit_virtual_root) - 1] != '/')
110 html("/"); 110 html("/");
111 html_attr(cgit_repo->url); 111 html_attr(cgit_repo->url);
112 if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/') 112 if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/')
113 html("/"); 113 html("/");
114 html(page); 114 html(page);
115 html("/"); 115 html("/");
116 if (path) 116 if (path)
117 html_attr(path); 117 html_attr(path);
118 } else { 118 } else {
119 html(cgit_script_name); 119 html(cgit_script_name);
120 html("?url="); 120 html("?url=");
121 html_attr(cgit_repo->url); 121 html_attr(cgit_repo->url);
122 if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/') 122 if (cgit_repo->url[strlen(cgit_repo->url) - 1] != '/')
123 html("/"); 123 html("/");
124 html(page); 124 html(page);
125 html("/"); 125 html("/");
126 if (path) 126 if (path)
127 html_attr(path); 127 html_attr(path);
128 delim = "&amp;"; 128 delim = "&amp;";
129 } 129 }
130 if (head && strcmp(head, cgit_repo->defbranch)) { 130 if (head && strcmp(head, cgit_repo->defbranch)) {
131 html(delim); 131 html(delim);
132 html("h="); 132 html("h=");
133 html_attr(head); 133 html_attr(head);
134 delim = "&amp;"; 134 delim = "&amp;";
135 } 135 }
136 return fmt("%s", delim); 136 return fmt("%s", delim);
137} 137}
138 138
139static void reporevlink(char *page, char *name, char *title, char *class, 139static void reporevlink(char *page, char *name, char *title, char *class,
140 char *head, char *rev, char *path) 140 char *head, char *rev, char *path)
141{ 141{
142 char *delim; 142 char *delim;
143 143
144 delim = repolink(title, class, page, head, path); 144 delim = repolink(title, class, page, head, path);
145 if (rev && strcmp(rev, cgit_query_head)) { 145 if (rev && strcmp(rev, cgit_query_head)) {
146 html(delim); 146 html(delim);
147 html("id="); 147 html("id=");
148 html_attr(rev); 148 html_attr(rev);
149 } 149 }
150 html("'>"); 150 html("'>");
151 html_txt(name); 151 html_txt(name);
152 html("</a>"); 152 html("</a>");
153} 153}
154 154
155void cgit_tree_link(char *name, char *title, char *class, char *head, 155void cgit_tree_link(char *name, char *title, char *class, char *head,
156 char *rev, char *path) 156 char *rev, char *path)
157{ 157{
158 reporevlink("tree", name, title, class, head, rev, path); 158 reporevlink("tree", name, title, class, head, rev, path);
159} 159}
160 160
161void cgit_log_link(char *name, char *title, char *class, char *head, 161void cgit_log_link(char *name, char *title, char *class, char *head,
162 char *rev, char *path) 162 char *rev, char *path)
163{ 163{
164 reporevlink("log", name, title, class, head, rev, path); 164 reporevlink("log", name, title, class, head, rev, path);
165} 165}
166 166
167void cgit_commit_link(char *name, char *title, char *class, char *head, 167void cgit_commit_link(char *name, char *title, char *class, char *head,
168 char *rev) 168 char *rev)
169{ 169{
170 if (strlen(name) > cgit_max_msg_len && cgit_max_msg_len >= 15) { 170 if (strlen(name) > cgit_max_msg_len && cgit_max_msg_len >= 15) {
171 name[cgit_max_msg_len] = '\0'; 171 name[cgit_max_msg_len] = '\0';
172 name[cgit_max_msg_len - 1] = '.'; 172 name[cgit_max_msg_len - 1] = '.';
173 name[cgit_max_msg_len - 2] = '.'; 173 name[cgit_max_msg_len - 2] = '.';
174 name[cgit_max_msg_len - 3] = '.'; 174 name[cgit_max_msg_len - 3] = '.';
175 } 175 }
176 reporevlink("commit", name, title, class, head, rev, NULL); 176 reporevlink("commit", name, title, class, head, rev, NULL);
177} 177}
178 178
179void cgit_diff_link(char *name, char *title, char *class, char *head,
180 char *new_rev, char *old_rev, char *path)
181{
182 char *delim;
183
184 delim = repolink(title, class, "diff", head, path);
185 if (new_rev && strcmp(new_rev, cgit_query_head)) {
186 html(delim);
187 html("id=");
188 html_attr(new_rev);
189 delim = "&amp;";
190 }
191 if (old_rev) {
192 html(delim);
193 html("id2=");
194 html_attr(old_rev);
195 }
196 html("'>");
197 html_txt(name);
198 html("</a>");
199}
200
179void cgit_print_date(time_t secs, char *format) 201void cgit_print_date(time_t secs, char *format)
180{ 202{
181 char buf[64]; 203 char buf[64];
182 struct tm *time; 204 struct tm *time;
183 205
184 time = gmtime(&secs); 206 time = gmtime(&secs);
185 strftime(buf, sizeof(buf)-1, format, time); 207 strftime(buf, sizeof(buf)-1, format, time);
186 html_txt(buf); 208 html_txt(buf);
187} 209}
188 210
189void cgit_print_age(time_t t, time_t max_relative, char *format) 211void cgit_print_age(time_t t, time_t max_relative, char *format)
190{ 212{
191 time_t now, secs; 213 time_t now, secs;
192 214
193 time(&now); 215 time(&now);
194 secs = now - t; 216 secs = now - t;
195 217
196 if (secs > max_relative && max_relative >= 0) { 218 if (secs > max_relative && max_relative >= 0) {
197 cgit_print_date(t, format); 219 cgit_print_date(t, format);
198 return; 220 return;
199 } 221 }
200 222
201 if (secs < TM_HOUR * 2) { 223 if (secs < TM_HOUR * 2) {
202 htmlf("<span class='age-mins'>%.0f min.</span>", 224 htmlf("<span class='age-mins'>%.0f min.</span>",
203 secs * 1.0 / TM_MIN); 225 secs * 1.0 / TM_MIN);
204 return; 226 return;
205 } 227 }
206 if (secs < TM_DAY * 2) { 228 if (secs < TM_DAY * 2) {
207 htmlf("<span class='age-hours'>%.0f hours</span>", 229 htmlf("<span class='age-hours'>%.0f hours</span>",
208 secs * 1.0 / TM_HOUR); 230 secs * 1.0 / TM_HOUR);
209 return; 231 return;
210 } 232 }
211 if (secs < TM_WEEK * 2) { 233 if (secs < TM_WEEK * 2) {
212 htmlf("<span class='age-days'>%.0f days</span>", 234 htmlf("<span class='age-days'>%.0f days</span>",
213 secs * 1.0 / TM_DAY); 235 secs * 1.0 / TM_DAY);
214 return; 236 return;
215 } 237 }
216 if (secs < TM_MONTH * 2) { 238 if (secs < TM_MONTH * 2) {
217 htmlf("<span class='age-weeks'>%.0f weeks</span>", 239 htmlf("<span class='age-weeks'>%.0f weeks</span>",
218 secs * 1.0 / TM_WEEK); 240 secs * 1.0 / TM_WEEK);
219 return; 241 return;
220 } 242 }
221 if (secs < TM_YEAR * 2) { 243 if (secs < TM_YEAR * 2) {
222 htmlf("<span class='age-months'>%.0f months</span>", 244 htmlf("<span class='age-months'>%.0f months</span>",
223 secs * 1.0 / TM_MONTH); 245 secs * 1.0 / TM_MONTH);
224 return; 246 return;
225 } 247 }
226 htmlf("<span class='age-years'>%.0f years</span>", 248 htmlf("<span class='age-years'>%.0f years</span>",
227 secs * 1.0 / TM_YEAR); 249 secs * 1.0 / TM_YEAR);
228} 250}
229 251
230void cgit_print_docstart(char *title, struct cacheitem *item) 252void cgit_print_docstart(char *title, struct cacheitem *item)
231{ 253{
232 html("Content-Type: text/html; charset=utf-8\n"); 254 html("Content-Type: text/html; charset=utf-8\n");
233 htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime)); 255 htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime));
234 htmlf("Expires: %s\n", http_date(item->st.st_mtime + 256 htmlf("Expires: %s\n", http_date(item->st.st_mtime +
235 ttl_seconds(item->ttl))); 257 ttl_seconds(item->ttl)));
236 html("\n"); 258 html("\n");
237 html(cgit_doctype); 259 html(cgit_doctype);
238 html("<html>\n"); 260 html("<html>\n");
239 html("<head>\n"); 261 html("<head>\n");
240 html("<title>"); 262 html("<title>");
241 html_txt(title); 263 html_txt(title);
242 html("</title>\n"); 264 html("</title>\n");
243 htmlf("<meta name='generator' content='cgit v%s'/>\n", cgit_version); 265 htmlf("<meta name='generator' content='cgit v%s'/>\n", cgit_version);
244 html("<link rel='stylesheet' type='text/css' href='"); 266 html("<link rel='stylesheet' type='text/css' href='");
245 html_attr(cgit_css); 267 html_attr(cgit_css);
246 html("'/>\n"); 268 html("'/>\n");
247 html("</head>\n"); 269 html("</head>\n");
248 html("<body>\n"); 270 html("<body>\n");
249} 271}
250 272
251void cgit_print_docend() 273void cgit_print_docend()
252{ 274{
253 html("</td></tr></table>"); 275 html("</td></tr></table>");
254 html("</body>\n</html>\n"); 276 html("</body>\n</html>\n");
255} 277}
256 278
257void cgit_print_pageheader(char *title, int show_search) 279void cgit_print_pageheader(char *title, int show_search)
258{ 280{
259 html("<table id='layout'>"); 281 html("<table id='layout'>");
260 html("<tr><td id='header'>"); 282 html("<tr><td id='header'>");
261 html(cgit_root_title); 283 html(cgit_root_title);
262 html("</td><td id='logo'>"); 284 html("</td><td id='logo'>");
263 html("<a href='"); 285 html("<a href='");
264 html_attr(cgit_logo_link); 286 html_attr(cgit_logo_link);
265 htmlf("'><img src='%s' alt='logo'/></a>", cgit_logo); 287 htmlf("'><img src='%s' alt='logo'/></a>", cgit_logo);
266 html("</td></tr>"); 288 html("</td></tr>");
267 html("<tr><td id='crumb'>"); 289 html("<tr><td id='crumb'>");
268 htmlf("<a href='%s'>root</a>", cgit_rooturl()); 290 htmlf("<a href='%s'>root</a>", cgit_rooturl());
269 if (cgit_query_repo) { 291 if (cgit_query_repo) {
270 htmlf(" : <a href='%s'>", cgit_repourl(cgit_repo->url)); 292 htmlf(" : <a href='%s'>", cgit_repourl(cgit_repo->url));
271 html_txt(cgit_repo->name); 293 html_txt(cgit_repo->name);
272 htmlf("</a> : %s", title); 294 htmlf("</a> : %s", title);
273 } 295 }
274 html("</td>"); 296 html("</td>");
275 html("<td id='search'>"); 297 html("<td id='search'>");
276 if (show_search) { 298 if (show_search) {
277 html("<form method='get' action='"); 299 html("<form method='get' action='");
278 html_attr(cgit_currurl()); 300 html_attr(cgit_currurl());
279 html("'>"); 301 html("'>");
280 if (!cgit_virtual_root) { 302 if (!cgit_virtual_root) {
281 if (cgit_query_repo) 303 if (cgit_query_repo)
282 html_hidden("r", cgit_query_repo); 304 html_hidden("r", cgit_query_repo);
283 if (cgit_query_page) 305 if (cgit_query_page)
284 html_hidden("p", cgit_query_page); 306 html_hidden("p", cgit_query_page);
285 } 307 }
286 if (cgit_query_head) 308 if (cgit_query_head)
287 html_hidden("h", cgit_query_head); 309 html_hidden("h", cgit_query_head);
288 if (cgit_query_sha1) 310 if (cgit_query_sha1)
289 html_hidden("id", cgit_query_sha1); 311 html_hidden("id", cgit_query_sha1);
290 if (cgit_query_sha2) 312 if (cgit_query_sha2)
291 html_hidden("id2", cgit_query_sha2); 313 html_hidden("id2", cgit_query_sha2);
292 html("<input type='text' name='q' value='"); 314 html("<input type='text' name='q' value='");
293 html_attr(cgit_query_search); 315 html_attr(cgit_query_search);
294 html("'/></form>"); 316 html("'/></form>");
295 } 317 }
296 html("</td></tr>"); 318 html("</td></tr>");
297 html("<tr><td id='content' colspan='2'>"); 319 html("<tr><td id='content' colspan='2'>");
298} 320}
299 321
300void cgit_print_snapshot_start(const char *mimetype, const char *filename, 322void cgit_print_snapshot_start(const char *mimetype, const char *filename,
301 struct cacheitem *item) 323 struct cacheitem *item)
302{ 324{
303 htmlf("Content-Type: %s\n", mimetype); 325 htmlf("Content-Type: %s\n", mimetype);
304 htmlf("Content-Disposition: inline; filename=\"%s\"\n", filename); 326 htmlf("Content-Disposition: inline; filename=\"%s\"\n", filename);
305 htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime)); 327 htmlf("Last-Modified: %s\n", http_date(item->st.st_mtime));
306 htmlf("Expires: %s\n", http_date(item->st.st_mtime + 328 htmlf("Expires: %s\n", http_date(item->st.st_mtime +
307 ttl_seconds(item->ttl))); 329 ttl_seconds(item->ttl)));
308 html("\n"); 330 html("\n");
309} 331}