summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2008-12-06 10:37:37 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2008-12-06 10:37:37 (UTC)
commit3c32fe07717f27fc891b66ccd06057fb810d03ad (patch) (unidiff)
tree04897b3eac6432c43284e7da79161e47856d078b
parent9c8be943f72b6f1bda5a31ce401899c3dd734e98 (diff)
parentab67164d6217fcda5ef3e07aefa3dd6eee6b65a3 (diff)
downloadcgit-3c32fe07717f27fc891b66ccd06057fb810d03ad.zip
cgit-3c32fe07717f27fc891b66ccd06057fb810d03ad.tar.gz
cgit-3c32fe07717f27fc891b66ccd06057fb810d03ad.tar.bz2
Merge branch 'full-log'
Conflicts: cgit.c cgit.h
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c2
-rw-r--r--cgit.css15
-rw-r--r--cgit.h1
-rw-r--r--ui-log.c35
-rw-r--r--ui-refs.c3
-rw-r--r--ui-repolist.c2
-rw-r--r--ui-shared.c12
-rw-r--r--ui-shared.h2
-rw-r--r--ui-tree.c2
9 files changed, 61 insertions, 13 deletions
diff --git a/cgit.c b/cgit.c
index e09c86e..166fbc6 100644
--- a/cgit.c
+++ b/cgit.c
@@ -63,192 +63,194 @@ void config_cb(const char *name, const char *value)
63 else if (!strcmp(name, "cache-repo-ttl")) 63 else if (!strcmp(name, "cache-repo-ttl"))
64 ctx.cfg.cache_repo_ttl = atoi(value); 64 ctx.cfg.cache_repo_ttl = atoi(value);
65 else if (!strcmp(name, "cache-static-ttl")) 65 else if (!strcmp(name, "cache-static-ttl"))
66 ctx.cfg.cache_static_ttl = atoi(value); 66 ctx.cfg.cache_static_ttl = atoi(value);
67 else if (!strcmp(name, "cache-dynamic-ttl")) 67 else if (!strcmp(name, "cache-dynamic-ttl"))
68 ctx.cfg.cache_dynamic_ttl = atoi(value); 68 ctx.cfg.cache_dynamic_ttl = atoi(value);
69 else if (!strcmp(name, "max-message-length")) 69 else if (!strcmp(name, "max-message-length"))
70 ctx.cfg.max_msg_len = atoi(value); 70 ctx.cfg.max_msg_len = atoi(value);
71 else if (!strcmp(name, "max-repodesc-length")) 71 else if (!strcmp(name, "max-repodesc-length"))
72 ctx.cfg.max_repodesc_len = atoi(value); 72 ctx.cfg.max_repodesc_len = atoi(value);
73 else if (!strcmp(name, "max-repo-count")) 73 else if (!strcmp(name, "max-repo-count"))
74 ctx.cfg.max_repo_count = atoi(value); 74 ctx.cfg.max_repo_count = atoi(value);
75 else if (!strcmp(name, "max-commit-count")) 75 else if (!strcmp(name, "max-commit-count"))
76 ctx.cfg.max_commit_count = atoi(value); 76 ctx.cfg.max_commit_count = atoi(value);
77 else if (!strcmp(name, "summary-log")) 77 else if (!strcmp(name, "summary-log"))
78 ctx.cfg.summary_log = atoi(value); 78 ctx.cfg.summary_log = atoi(value);
79 else if (!strcmp(name, "summary-branches")) 79 else if (!strcmp(name, "summary-branches"))
80 ctx.cfg.summary_branches = atoi(value); 80 ctx.cfg.summary_branches = atoi(value);
81 else if (!strcmp(name, "summary-tags")) 81 else if (!strcmp(name, "summary-tags"))
82 ctx.cfg.summary_tags = atoi(value); 82 ctx.cfg.summary_tags = atoi(value);
83 else if (!strcmp(name, "agefile")) 83 else if (!strcmp(name, "agefile"))
84 ctx.cfg.agefile = xstrdup(value); 84 ctx.cfg.agefile = xstrdup(value);
85 else if (!strcmp(name, "renamelimit")) 85 else if (!strcmp(name, "renamelimit"))
86 ctx.cfg.renamelimit = atoi(value); 86 ctx.cfg.renamelimit = atoi(value);
87 else if (!strcmp(name, "robots")) 87 else if (!strcmp(name, "robots"))
88 ctx.cfg.robots = xstrdup(value); 88 ctx.cfg.robots = xstrdup(value);
89 else if (!strcmp(name, "clone-prefix")) 89 else if (!strcmp(name, "clone-prefix"))
90 ctx.cfg.clone_prefix = xstrdup(value); 90 ctx.cfg.clone_prefix = xstrdup(value);
91 else if (!strcmp(name, "local-time")) 91 else if (!strcmp(name, "local-time"))
92 ctx.cfg.local_time = atoi(value); 92 ctx.cfg.local_time = atoi(value);
93 else if (!strcmp(name, "repo.group")) 93 else if (!strcmp(name, "repo.group"))
94 ctx.cfg.repo_group = xstrdup(value); 94 ctx.cfg.repo_group = xstrdup(value);
95 else if (!strcmp(name, "repo.url")) 95 else if (!strcmp(name, "repo.url"))
96 ctx.repo = cgit_add_repo(value); 96 ctx.repo = cgit_add_repo(value);
97 else if (!strcmp(name, "repo.name")) 97 else if (!strcmp(name, "repo.name"))
98 ctx.repo->name = xstrdup(value); 98 ctx.repo->name = xstrdup(value);
99 else if (ctx.repo && !strcmp(name, "repo.path")) 99 else if (ctx.repo && !strcmp(name, "repo.path"))
100 ctx.repo->path = trim_end(value, '/'); 100 ctx.repo->path = trim_end(value, '/');
101 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 101 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
102 ctx.repo->clone_url = xstrdup(value); 102 ctx.repo->clone_url = xstrdup(value);
103 else if (ctx.repo && !strcmp(name, "repo.desc")) 103 else if (ctx.repo && !strcmp(name, "repo.desc"))
104 ctx.repo->desc = xstrdup(value); 104 ctx.repo->desc = xstrdup(value);
105 else if (ctx.repo && !strcmp(name, "repo.owner")) 105 else if (ctx.repo && !strcmp(name, "repo.owner"))
106 ctx.repo->owner = xstrdup(value); 106 ctx.repo->owner = xstrdup(value);
107 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 107 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
108 ctx.repo->defbranch = xstrdup(value); 108 ctx.repo->defbranch = xstrdup(value);
109 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 109 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
110 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 110 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
111 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 111 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
112 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 112 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
113 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 113 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
114 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 114 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
115 else if (ctx.repo && !strcmp(name, "repo.module-link")) 115 else if (ctx.repo && !strcmp(name, "repo.module-link"))
116 ctx.repo->module_link= xstrdup(value); 116 ctx.repo->module_link= xstrdup(value);
117 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 117 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
118 if (*value == '/') 118 if (*value == '/')
119 ctx.repo->readme = xstrdup(value); 119 ctx.repo->readme = xstrdup(value);
120 else 120 else
121 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 121 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
122 } else if (!strcmp(name, "include")) 122 } else if (!strcmp(name, "include"))
123 parse_configfile(value, config_cb); 123 parse_configfile(value, config_cb);
124} 124}
125 125
126static void querystring_cb(const char *name, const char *value) 126static void querystring_cb(const char *name, const char *value)
127{ 127{
128 if (!strcmp(name,"r")) { 128 if (!strcmp(name,"r")) {
129 ctx.qry.repo = xstrdup(value); 129 ctx.qry.repo = xstrdup(value);
130 ctx.repo = cgit_get_repoinfo(value); 130 ctx.repo = cgit_get_repoinfo(value);
131 } else if (!strcmp(name, "p")) { 131 } else if (!strcmp(name, "p")) {
132 ctx.qry.page = xstrdup(value); 132 ctx.qry.page = xstrdup(value);
133 } else if (!strcmp(name, "url")) { 133 } else if (!strcmp(name, "url")) {
134 ctx.qry.url = xstrdup(value); 134 ctx.qry.url = xstrdup(value);
135 cgit_parse_url(value); 135 cgit_parse_url(value);
136 } else if (!strcmp(name, "qt")) { 136 } else if (!strcmp(name, "qt")) {
137 ctx.qry.grep = xstrdup(value); 137 ctx.qry.grep = xstrdup(value);
138 } else if (!strcmp(name, "q")) { 138 } else if (!strcmp(name, "q")) {
139 ctx.qry.search = xstrdup(value); 139 ctx.qry.search = xstrdup(value);
140 } else if (!strcmp(name, "h")) { 140 } else if (!strcmp(name, "h")) {
141 ctx.qry.head = xstrdup(value); 141 ctx.qry.head = xstrdup(value);
142 ctx.qry.has_symref = 1; 142 ctx.qry.has_symref = 1;
143 } else if (!strcmp(name, "id")) { 143 } else if (!strcmp(name, "id")) {
144 ctx.qry.sha1 = xstrdup(value); 144 ctx.qry.sha1 = xstrdup(value);
145 ctx.qry.has_sha1 = 1; 145 ctx.qry.has_sha1 = 1;
146 } else if (!strcmp(name, "id2")) { 146 } else if (!strcmp(name, "id2")) {
147 ctx.qry.sha2 = xstrdup(value); 147 ctx.qry.sha2 = xstrdup(value);
148 ctx.qry.has_sha1 = 1; 148 ctx.qry.has_sha1 = 1;
149 } else if (!strcmp(name, "ofs")) { 149 } else if (!strcmp(name, "ofs")) {
150 ctx.qry.ofs = atoi(value); 150 ctx.qry.ofs = atoi(value);
151 } else if (!strcmp(name, "path")) { 151 } else if (!strcmp(name, "path")) {
152 ctx.qry.path = trim_end(value, '/'); 152 ctx.qry.path = trim_end(value, '/');
153 } else if (!strcmp(name, "name")) { 153 } else if (!strcmp(name, "name")) {
154 ctx.qry.name = xstrdup(value); 154 ctx.qry.name = xstrdup(value);
155 } else if (!strcmp(name, "mimetype")) { 155 } else if (!strcmp(name, "mimetype")) {
156 ctx.qry.mimetype = xstrdup(value); 156 ctx.qry.mimetype = xstrdup(value);
157 } else if (!strcmp(name, "s")){ 157 } else if (!strcmp(name, "s")){
158 ctx.qry.sort = xstrdup(value); 158 ctx.qry.sort = xstrdup(value);
159 } else if (!strcmp(name, "showmsg")) {
160 ctx.qry.showmsg = atoi(value);
159 } 161 }
160} 162}
161 163
162static void prepare_context(struct cgit_context *ctx) 164static void prepare_context(struct cgit_context *ctx)
163{ 165{
164 memset(ctx, 0, sizeof(ctx)); 166 memset(ctx, 0, sizeof(ctx));
165 ctx->cfg.agefile = "info/web/last-modified"; 167 ctx->cfg.agefile = "info/web/last-modified";
166 ctx->cfg.nocache = 0; 168 ctx->cfg.nocache = 0;
167 ctx->cfg.cache_size = 0; 169 ctx->cfg.cache_size = 0;
168 ctx->cfg.cache_dynamic_ttl = 5; 170 ctx->cfg.cache_dynamic_ttl = 5;
169 ctx->cfg.cache_max_create_time = 5; 171 ctx->cfg.cache_max_create_time = 5;
170 ctx->cfg.cache_repo_ttl = 5; 172 ctx->cfg.cache_repo_ttl = 5;
171 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 173 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
172 ctx->cfg.cache_root_ttl = 5; 174 ctx->cfg.cache_root_ttl = 5;
173 ctx->cfg.cache_static_ttl = -1; 175 ctx->cfg.cache_static_ttl = -1;
174 ctx->cfg.css = "/cgit.css"; 176 ctx->cfg.css = "/cgit.css";
175 ctx->cfg.logo = "/git-logo.png"; 177 ctx->cfg.logo = "/git-logo.png";
176 ctx->cfg.local_time = 0; 178 ctx->cfg.local_time = 0;
177 ctx->cfg.max_repo_count = 50; 179 ctx->cfg.max_repo_count = 50;
178 ctx->cfg.max_commit_count = 50; 180 ctx->cfg.max_commit_count = 50;
179 ctx->cfg.max_lock_attempts = 5; 181 ctx->cfg.max_lock_attempts = 5;
180 ctx->cfg.max_msg_len = 80; 182 ctx->cfg.max_msg_len = 80;
181 ctx->cfg.max_repodesc_len = 80; 183 ctx->cfg.max_repodesc_len = 80;
182 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 184 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
183 ctx->cfg.renamelimit = -1; 185 ctx->cfg.renamelimit = -1;
184 ctx->cfg.robots = "index, nofollow"; 186 ctx->cfg.robots = "index, nofollow";
185 ctx->cfg.root_title = "Git repository browser"; 187 ctx->cfg.root_title = "Git repository browser";
186 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 188 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
187 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 189 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
188 ctx->cfg.summary_branches = 10; 190 ctx->cfg.summary_branches = 10;
189 ctx->cfg.summary_log = 10; 191 ctx->cfg.summary_log = 10;
190 ctx->cfg.summary_tags = 10; 192 ctx->cfg.summary_tags = 10;
191 ctx->page.mimetype = "text/html"; 193 ctx->page.mimetype = "text/html";
192 ctx->page.charset = PAGE_ENCODING; 194 ctx->page.charset = PAGE_ENCODING;
193 ctx->page.filename = NULL; 195 ctx->page.filename = NULL;
194 ctx->page.size = 0; 196 ctx->page.size = 0;
195 ctx->page.modified = time(NULL); 197 ctx->page.modified = time(NULL);
196 ctx->page.expires = ctx->page.modified; 198 ctx->page.expires = ctx->page.modified;
197} 199}
198 200
199struct refmatch { 201struct refmatch {
200 char *req_ref; 202 char *req_ref;
201 char *first_ref; 203 char *first_ref;
202 int match; 204 int match;
203}; 205};
204 206
205int find_current_ref(const char *refname, const unsigned char *sha1, 207int find_current_ref(const char *refname, const unsigned char *sha1,
206 int flags, void *cb_data) 208 int flags, void *cb_data)
207{ 209{
208 struct refmatch *info; 210 struct refmatch *info;
209 211
210 info = (struct refmatch *)cb_data; 212 info = (struct refmatch *)cb_data;
211 if (!strcmp(refname, info->req_ref)) 213 if (!strcmp(refname, info->req_ref))
212 info->match = 1; 214 info->match = 1;
213 if (!info->first_ref) 215 if (!info->first_ref)
214 info->first_ref = xstrdup(refname); 216 info->first_ref = xstrdup(refname);
215 return info->match; 217 return info->match;
216} 218}
217 219
218char *find_default_branch(struct cgit_repo *repo) 220char *find_default_branch(struct cgit_repo *repo)
219{ 221{
220 struct refmatch info; 222 struct refmatch info;
221 char *ref; 223 char *ref;
222 224
223 info.req_ref = repo->defbranch; 225 info.req_ref = repo->defbranch;
224 info.first_ref = NULL; 226 info.first_ref = NULL;
225 info.match = 0; 227 info.match = 0;
226 for_each_branch_ref(find_current_ref, &info); 228 for_each_branch_ref(find_current_ref, &info);
227 if (info.match) 229 if (info.match)
228 ref = info.req_ref; 230 ref = info.req_ref;
229 else 231 else
230 ref = info.first_ref; 232 ref = info.first_ref;
231 if (ref) 233 if (ref)
232 ref = xstrdup(ref); 234 ref = xstrdup(ref);
233 return ref; 235 return ref;
234} 236}
235 237
236static int prepare_repo_cmd(struct cgit_context *ctx) 238static int prepare_repo_cmd(struct cgit_context *ctx)
237{ 239{
238 char *tmp; 240 char *tmp;
239 unsigned char sha1[20]; 241 unsigned char sha1[20];
240 int nongit = 0; 242 int nongit = 0;
241 243
242 setenv("GIT_DIR", ctx->repo->path, 1); 244 setenv("GIT_DIR", ctx->repo->path, 1);
243 setup_git_directory_gently(&nongit); 245 setup_git_directory_gently(&nongit);
244 if (nongit) { 246 if (nongit) {
245 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 247 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
246 "config error"); 248 "config error");
247 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 249 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
248 ctx->repo = NULL; 250 ctx->repo = NULL;
249 cgit_print_http_headers(ctx); 251 cgit_print_http_headers(ctx);
250 cgit_print_docstart(ctx); 252 cgit_print_docstart(ctx);
251 cgit_print_pageheader(ctx); 253 cgit_print_pageheader(ctx);
252 cgit_print_error(tmp); 254 cgit_print_error(tmp);
253 cgit_print_docend(); 255 cgit_print_docend();
254 return 1; 256 return 1;
diff --git a/cgit.css b/cgit.css
index a37d218..7928c2f 100644
--- a/cgit.css
+++ b/cgit.css
@@ -27,215 +27,230 @@ table {
27table#header { 27table#header {
28 width: 100%; 28 width: 100%;
29 margin-bottom: 1em; 29 margin-bottom: 1em;
30} 30}
31 31
32table#header td.logo { 32table#header td.logo {
33 width: 96px; 33 width: 96px;
34} 34}
35 35
36table#header td.main { 36table#header td.main {
37 font-size: 250%; 37 font-size: 250%;
38 padding-left: 10px; 38 padding-left: 10px;
39 white-space: nowrap; 39 white-space: nowrap;
40} 40}
41 41
42table#header td.main a { 42table#header td.main a {
43 color: #000; 43 color: #000;
44} 44}
45 45
46table#header td.form { 46table#header td.form {
47 text-align: right; 47 text-align: right;
48 vertical-align: bottom; 48 vertical-align: bottom;
49 padding-right: 1em; 49 padding-right: 1em;
50 padding-bottom: 2px; 50 padding-bottom: 2px;
51 white-space: nowrap; 51 white-space: nowrap;
52} 52}
53 53
54table#header td.form form, 54table#header td.form form,
55table#header td.form input, 55table#header td.form input,
56table#header td.form select { 56table#header td.form select {
57 font-size: 90%; 57 font-size: 90%;
58} 58}
59 59
60table#header td.sub { 60table#header td.sub {
61 color: #777; 61 color: #777;
62 border-top: solid 1px #ccc; 62 border-top: solid 1px #ccc;
63 padding-left: 10px; 63 padding-left: 10px;
64} 64}
65 65
66table.tabs { 66table.tabs {
67 /* border-bottom: solid 2px #ccc; */ 67 /* border-bottom: solid 2px #ccc; */
68 border-collapse: collapse; 68 border-collapse: collapse;
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 {
124 background: #eee;
125}
126
123table.list tr:hover { 127table.list tr:hover {
124 background: #eee; 128 background: #eee;
125} 129}
126 130
127table.list tr.nohover:hover { 131table.list tr.nohover:hover {
128 background: white; 132 background: white;
129} 133}
130 134
131table.list th { 135table.list th {
132 font-weight: bold; 136 font-weight: bold;
133 /* color: #888; 137 /* color: #888;
134 border-top: dashed 1px #888; 138 border-top: dashed 1px #888;
135 border-bottom: dashed 1px #888; 139 border-bottom: dashed 1px #888;
136 */ 140 */
137 padding: 0.1em 0.5em 0.05em 0.5em; 141 padding: 0.1em 0.5em 0.05em 0.5em;
138 vertical-align: baseline; 142 vertical-align: baseline;
139} 143}
140 144
141table.list td { 145table.list td {
142 border: none; 146 border: none;
143 padding: 0.1em 0.5em 0.1em 0.5em; 147 padding: 0.1em 0.5em 0.1em 0.5em;
144} 148}
145 149
150table.list td.logsubject {
151 font-family: monospace;
152 font-weight: bold;
153}
154
155table.list td.logmsg {
156 font-family: monospace;
157 white-space: pre;
158 padding: 1em 0em 2em 0em;
159}
160
146table.list td a { 161table.list td a {
147 color: black; 162 color: black;
148} 163}
149 164
150table.list td a:hover { 165table.list td a:hover {
151 color: #00f; 166 color: #00f;
152} 167}
153 168
154img { 169img {
155 border: none; 170 border: none;
156} 171}
157 172
158input#switch-btn { 173input#switch-btn {
159 margin: 2px 0px 0px 0px; 174 margin: 2px 0px 0px 0px;
160} 175}
161 176
162td#sidebar input.txt { 177td#sidebar input.txt {
163 width: 100%; 178 width: 100%;
164 margin: 2px 0px 0px 0px; 179 margin: 2px 0px 0px 0px;
165} 180}
166 181
167table#grid { 182table#grid {
168 margin: 0px; 183 margin: 0px;
169} 184}
170 185
171td#content { 186td#content {
172 vertical-align: top; 187 vertical-align: top;
173 padding: 1em 2em 1em 1em; 188 padding: 1em 2em 1em 1em;
174 border: none; 189 border: none;
175} 190}
176 191
177div#summary { 192div#summary {
178 vertical-align: top; 193 vertical-align: top;
179 margin-bottom: 1em; 194 margin-bottom: 1em;
180} 195}
181 196
182table#downloads { 197table#downloads {
183 float: right; 198 float: right;
184 border-collapse: collapse; 199 border-collapse: collapse;
185 border: solid 1px #777; 200 border: solid 1px #777;
186 margin-left: 0.5em; 201 margin-left: 0.5em;
187 margin-bottom: 0.5em; 202 margin-bottom: 0.5em;
188} 203}
189 204
190table#downloads th { 205table#downloads th {
191 background-color: #ccc; 206 background-color: #ccc;
192} 207}
193 208
194div#blob { 209div#blob {
195 border: solid 1px black; 210 border: solid 1px black;
196} 211}
197 212
198div.error { 213div.error {
199 color: red; 214 color: red;
200 font-weight: bold; 215 font-weight: bold;
201 margin: 1em 2em; 216 margin: 1em 2em;
202} 217}
203 218
204a.ls-blob, a.ls-dir, a.ls-mod { 219a.ls-blob, a.ls-dir, a.ls-mod {
205 font-family: monospace; 220 font-family: monospace;
206} 221}
207 222
208td.ls-size { 223td.ls-size {
209 text-align: right; 224 text-align: right;
210 font-family: monospace; 225 font-family: monospace;
211 width: 10em; 226 width: 10em;
212} 227}
213 228
214td.ls-mode { 229td.ls-mode {
215 font-family: monospace; 230 font-family: monospace;
216 width: 10em; 231 width: 10em;
217} 232}
218 233
219table.blob { 234table.blob {
220 margin-top: 0.5em; 235 margin-top: 0.5em;
221 border-top: solid 1px black; 236 border-top: solid 1px black;
222} 237}
223 238
224table.blob td.no { 239table.blob td.no {
225 border-right: solid 1px black; 240 border-right: solid 1px black;
226 color: black; 241 color: black;
227 background-color: #eee; 242 background-color: #eee;
228 text-align: right; 243 text-align: right;
229} 244}
230 245
231table.blob td.no a { 246table.blob td.no a {
232 color: black; 247 color: black;
233} 248}
234 249
235table.blob td.no a:hover { 250table.blob td.no a:hover {
236 color: black; 251 color: black;
237 text-decoration: none; 252 text-decoration: none;
238} 253}
239 254
240table.blob td.txt { 255table.blob td.txt {
241 white-space: pre; 256 white-space: pre;
diff --git a/cgit.h b/cgit.h
index f1fbeca..cb2f176 100644
--- a/cgit.h
+++ b/cgit.h
@@ -30,192 +30,193 @@
30/* 30/*
31 * Limits used for relative dates 31 * Limits used for relative dates
32 */ 32 */
33#define TM_MIN 60 33#define TM_MIN 60
34#define TM_HOUR (TM_MIN * 60) 34#define TM_HOUR (TM_MIN * 60)
35#define TM_DAY (TM_HOUR * 24) 35#define TM_DAY (TM_HOUR * 24)
36#define TM_WEEK (TM_DAY * 7) 36#define TM_WEEK (TM_DAY * 7)
37#define TM_YEAR (TM_DAY * 365) 37#define TM_YEAR (TM_DAY * 365)
38#define TM_MONTH (TM_YEAR / 12.0) 38#define TM_MONTH (TM_YEAR / 12.0)
39 39
40 40
41/* 41/*
42 * Default encoding 42 * Default encoding
43 */ 43 */
44#define PAGE_ENCODING "UTF-8" 44#define PAGE_ENCODING "UTF-8"
45 45
46typedef void (*configfn)(const char *name, const char *value); 46typedef void (*configfn)(const char *name, const char *value);
47typedef void (*filepair_fn)(struct diff_filepair *pair); 47typedef void (*filepair_fn)(struct diff_filepair *pair);
48typedef void (*linediff_fn)(char *line, int len); 48typedef void (*linediff_fn)(char *line, int len);
49 49
50struct cgit_repo { 50struct cgit_repo {
51 char *url; 51 char *url;
52 char *name; 52 char *name;
53 char *path; 53 char *path;
54 char *desc; 54 char *desc;
55 char *owner; 55 char *owner;
56 char *defbranch; 56 char *defbranch;
57 char *group; 57 char *group;
58 char *module_link; 58 char *module_link;
59 char *readme; 59 char *readme;
60 char *clone_url; 60 char *clone_url;
61 int snapshots; 61 int snapshots;
62 int enable_log_filecount; 62 int enable_log_filecount;
63 int enable_log_linecount; 63 int enable_log_linecount;
64 time_t mtime; 64 time_t mtime;
65}; 65};
66 66
67struct cgit_repolist { 67struct cgit_repolist {
68 int length; 68 int length;
69 int count; 69 int count;
70 struct cgit_repo *repos; 70 struct cgit_repo *repos;
71}; 71};
72 72
73struct commitinfo { 73struct commitinfo {
74 struct commit *commit; 74 struct commit *commit;
75 char *author; 75 char *author;
76 char *author_email; 76 char *author_email;
77 unsigned long author_date; 77 unsigned long author_date;
78 char *committer; 78 char *committer;
79 char *committer_email; 79 char *committer_email;
80 unsigned long committer_date; 80 unsigned long committer_date;
81 char *subject; 81 char *subject;
82 char *msg; 82 char *msg;
83 char *msg_encoding; 83 char *msg_encoding;
84}; 84};
85 85
86struct taginfo { 86struct taginfo {
87 char *tagger; 87 char *tagger;
88 char *tagger_email; 88 char *tagger_email;
89 unsigned long tagger_date; 89 unsigned long tagger_date;
90 char *msg; 90 char *msg;
91}; 91};
92 92
93struct refinfo { 93struct refinfo {
94 const char *refname; 94 const char *refname;
95 struct object *object; 95 struct object *object;
96 union { 96 union {
97 struct taginfo *tag; 97 struct taginfo *tag;
98 struct commitinfo *commit; 98 struct commitinfo *commit;
99 }; 99 };
100}; 100};
101 101
102struct reflist { 102struct reflist {
103 struct refinfo **refs; 103 struct refinfo **refs;
104 int alloc; 104 int alloc;
105 int count; 105 int count;
106}; 106};
107 107
108struct cgit_query { 108struct cgit_query {
109 int has_symref; 109 int has_symref;
110 int has_sha1; 110 int has_sha1;
111 char *raw; 111 char *raw;
112 char *repo; 112 char *repo;
113 char *page; 113 char *page;
114 char *search; 114 char *search;
115 char *grep; 115 char *grep;
116 char *head; 116 char *head;
117 char *sha1; 117 char *sha1;
118 char *sha2; 118 char *sha2;
119 char *path; 119 char *path;
120 char *name; 120 char *name;
121 char *mimetype; 121 char *mimetype;
122 char *url; 122 char *url;
123 int ofs; 123 int ofs;
124 int nohead; 124 int nohead;
125 char *sort; 125 char *sort;
126 int showmsg;
126}; 127};
127 128
128struct cgit_config { 129struct cgit_config {
129 char *agefile; 130 char *agefile;
130 char *cache_root; 131 char *cache_root;
131 char *clone_prefix; 132 char *clone_prefix;
132 char *css; 133 char *css;
133 char *favicon; 134 char *favicon;
134 char *footer; 135 char *footer;
135 char *index_header; 136 char *index_header;
136 char *index_info; 137 char *index_info;
137 char *logo; 138 char *logo;
138 char *logo_link; 139 char *logo_link;
139 char *module_link; 140 char *module_link;
140 char *repo_group; 141 char *repo_group;
141 char *robots; 142 char *robots;
142 char *root_title; 143 char *root_title;
143 char *root_desc; 144 char *root_desc;
144 char *root_readme; 145 char *root_readme;
145 char *script_name; 146 char *script_name;
146 char *virtual_root; 147 char *virtual_root;
147 int cache_size; 148 int cache_size;
148 int cache_dynamic_ttl; 149 int cache_dynamic_ttl;
149 int cache_max_create_time; 150 int cache_max_create_time;
150 int cache_repo_ttl; 151 int cache_repo_ttl;
151 int cache_root_ttl; 152 int cache_root_ttl;
152 int cache_static_ttl; 153 int cache_static_ttl;
153 int enable_index_links; 154 int enable_index_links;
154 int enable_log_filecount; 155 int enable_log_filecount;
155 int enable_log_linecount; 156 int enable_log_linecount;
156 int local_time; 157 int local_time;
157 int max_repo_count; 158 int max_repo_count;
158 int max_commit_count; 159 int max_commit_count;
159 int max_lock_attempts; 160 int max_lock_attempts;
160 int max_msg_len; 161 int max_msg_len;
161 int max_repodesc_len; 162 int max_repodesc_len;
162 int nocache; 163 int nocache;
163 int renamelimit; 164 int renamelimit;
164 int snapshots; 165 int snapshots;
165 int summary_branches; 166 int summary_branches;
166 int summary_log; 167 int summary_log;
167 int summary_tags; 168 int summary_tags;
168}; 169};
169 170
170struct cgit_page { 171struct cgit_page {
171 time_t modified; 172 time_t modified;
172 time_t expires; 173 time_t expires;
173 size_t size; 174 size_t size;
174 char *mimetype; 175 char *mimetype;
175 char *charset; 176 char *charset;
176 char *filename; 177 char *filename;
177 char *title; 178 char *title;
178}; 179};
179 180
180struct cgit_context { 181struct cgit_context {
181 struct cgit_query qry; 182 struct cgit_query qry;
182 struct cgit_config cfg; 183 struct cgit_config cfg;
183 struct cgit_repo *repo; 184 struct cgit_repo *repo;
184 struct cgit_page page; 185 struct cgit_page page;
185}; 186};
186 187
187struct cgit_snapshot_format { 188struct cgit_snapshot_format {
188 const char *suffix; 189 const char *suffix;
189 const char *mimetype; 190 const char *mimetype;
190 write_archive_fn_t write_func; 191 write_archive_fn_t write_func;
191 int bit; 192 int bit;
192}; 193};
193 194
194extern const char *cgit_version; 195extern const char *cgit_version;
195 196
196extern struct cgit_repolist cgit_repolist; 197extern struct cgit_repolist cgit_repolist;
197extern struct cgit_context ctx; 198extern struct cgit_context ctx;
198extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 199extern const struct cgit_snapshot_format cgit_snapshot_formats[];
199 200
200extern struct cgit_repo *cgit_add_repo(const char *url); 201extern struct cgit_repo *cgit_add_repo(const char *url);
201extern struct cgit_repo *cgit_get_repoinfo(const char *url); 202extern struct cgit_repo *cgit_get_repoinfo(const char *url);
202extern void cgit_repo_config_cb(const char *name, const char *value); 203extern void cgit_repo_config_cb(const char *name, const char *value);
203 204
204extern int chk_zero(int result, char *msg); 205extern int chk_zero(int result, char *msg);
205extern int chk_positive(int result, char *msg); 206extern int chk_positive(int result, char *msg);
206extern int chk_non_negative(int result, char *msg); 207extern int chk_non_negative(int result, char *msg);
207 208
208extern char *trim_end(const char *str, char c); 209extern char *trim_end(const char *str, char c);
209extern char *strlpart(char *txt, int maxlen); 210extern char *strlpart(char *txt, int maxlen);
210extern char *strrpart(char *txt, int maxlen); 211extern char *strrpart(char *txt, int maxlen);
211 212
212extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 213extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
213extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 214extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
214 int flags, void *cb_data); 215 int flags, void *cb_data);
215 216
216extern void *cgit_free_commitinfo(struct commitinfo *info); 217extern void *cgit_free_commitinfo(struct commitinfo *info);
217 218
218extern int cgit_diff_files(const unsigned char *old_sha1, 219extern int cgit_diff_files(const unsigned char *old_sha1,
219 const unsigned char *new_sha1, 220 const unsigned char *new_sha1,
220 linediff_fn fn); 221 linediff_fn fn);
221 222
diff --git a/ui-log.c b/ui-log.c
index d212984..2f90778 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,168 +1,189 @@
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 files++; 29 files++;
30 if (ctx.repo->enable_log_linecount) 30 if (ctx.repo->enable_log_linecount)
31 cgit_diff_files(pair->one->sha1, pair->two->sha1, count_lines); 31 cgit_diff_files(pair->one->sha1, pair->two->sha1, count_lines);
32} 32}
33 33
34void print_commit(struct commit *commit) 34void print_commit(struct commit *commit)
35{ 35{
36 struct commitinfo *info; 36 struct commitinfo *info;
37 char *tmp; 37 char *tmp;
38 int cols = 2;
38 39
39 info = cgit_parse_commit(commit); 40 info = cgit_parse_commit(commit);
40 html("<tr><td>"); 41 htmlf("<tr%s><td>",
42 ctx.qry.showmsg ? " class='logheader'" : "");
41 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 43 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
42 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 44 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
43 html_link_open(tmp, NULL, NULL); 45 html_link_open(tmp, NULL, NULL);
44 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 46 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
45 html_link_close(); 47 html_link_close();
46 html("</td><td>"); 48 htmlf("</td><td%s>",
49 ctx.qry.showmsg ? " class='logsubject'" : "");
47 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 50 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
48 sha1_to_hex(commit->object.sha1)); 51 sha1_to_hex(commit->object.sha1));
49 html("</td><td>"); 52 html("</td><td>");
50 html_txt(info->author); 53 html_txt(info->author);
51 if (ctx.repo->enable_log_filecount) { 54 if (ctx.repo->enable_log_filecount) {
52 files = 0; 55 files = 0;
53 add_lines = 0; 56 add_lines = 0;
54 rem_lines = 0; 57 rem_lines = 0;
55 cgit_diff_commit(commit, inspect_files); 58 cgit_diff_commit(commit, inspect_files);
56 html("</td><td>"); 59 html("</td><td>");
57 htmlf("%d", files); 60 htmlf("%d", files);
58 if (ctx.repo->enable_log_linecount) { 61 if (ctx.repo->enable_log_linecount) {
59 html("</td><td>"); 62 html("</td><td>");
60 htmlf("-%d/+%d", rem_lines, add_lines); 63 htmlf("-%d/+%d", rem_lines, add_lines);
61 } 64 }
62 } 65 }
63 html("</td></tr>\n"); 66 html("</td></tr>\n");
67 if (ctx.qry.showmsg) {
68 if (ctx.repo->enable_log_filecount) {
69 cols++;
70 if (ctx.repo->enable_log_linecount)
71 cols++;
72 }
73 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
74 cols);
75 html_txt(info->msg);
76 html("</td></tr>\n");
77 }
64 cgit_free_commitinfo(info); 78 cgit_free_commitinfo(info);
65} 79}
66 80
67static const char *disambiguate_ref(const char *ref) 81static const char *disambiguate_ref(const char *ref)
68{ 82{
69 unsigned char sha1[20]; 83 unsigned char sha1[20];
70 const char *longref; 84 const char *longref;
71 85
72 longref = fmt("refs/heads/%s", ref); 86 longref = fmt("refs/heads/%s", ref);
73 if (get_sha1(longref, sha1) == 0) 87 if (get_sha1(longref, sha1) == 0)
74 return longref; 88 return longref;
75 89
76 return ref; 90 return ref;
77} 91}
78 92
79void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 93void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
80 char *path, int pager) 94 char *path, int pager)
81{ 95{
82 struct rev_info rev; 96 struct rev_info rev;
83 struct commit *commit; 97 struct commit *commit;
84 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 98 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
85 int argc = 2; 99 int argc = 2;
86 int i, columns = 3; 100 int i, columns = 3;
87 101
88 if (!tip) 102 if (!tip)
89 tip = ctx.qry.head; 103 tip = ctx.qry.head;
90 104
91 argv[1] = disambiguate_ref(tip); 105 argv[1] = disambiguate_ref(tip);
92 106
93 if (grep && pattern && (!strcmp(grep, "grep") || 107 if (grep && pattern && (!strcmp(grep, "grep") ||
94 !strcmp(grep, "author") || 108 !strcmp(grep, "author") ||
95 !strcmp(grep, "committer"))) 109 !strcmp(grep, "committer")))
96 argv[argc++] = fmt("--%s=%s", grep, pattern); 110 argv[argc++] = fmt("--%s=%s", grep, pattern);
97 111
98 if (path) { 112 if (path) {
99 argv[argc++] = "--"; 113 argv[argc++] = "--";
100 argv[argc++] = path; 114 argv[argc++] = path;
101 } 115 }
102 init_revisions(&rev, NULL); 116 init_revisions(&rev, NULL);
103 rev.abbrev = DEFAULT_ABBREV; 117 rev.abbrev = DEFAULT_ABBREV;
104 rev.commit_format = CMIT_FMT_DEFAULT; 118 rev.commit_format = CMIT_FMT_DEFAULT;
105 rev.verbose_header = 1; 119 rev.verbose_header = 1;
106 rev.show_root_diff = 0; 120 rev.show_root_diff = 0;
107 setup_revisions(argc, argv, &rev, NULL); 121 setup_revisions(argc, argv, &rev, NULL);
108 rev.grep_filter.regflags |= REG_ICASE; 122 rev.grep_filter.regflags |= REG_ICASE;
109 compile_grep_patterns(&rev.grep_filter); 123 compile_grep_patterns(&rev.grep_filter);
110 prepare_revision_walk(&rev); 124 prepare_revision_walk(&rev);
111 125
112 if (pager) 126 if (pager)
113 html("<table class='list nowrap'>"); 127 html("<table class='list nowrap'>");
114 128
115 html("<tr class='nohover'><th class='left'>Age</th>" 129 html("<tr class='nohover'><th class='left'>Age</th>"
116 "<th class='left'>Commit message</th>" 130 "<th class='left'>Commit message");
117 "<th class='left'>Author</th>"); 131 if (pager) {
132 html(" (");
133 cgit_log_link("toggle", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
134 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep,
135 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
136 html(")");
137 }
138 html("</th><th class='left'>Author</th>");
118 if (ctx.repo->enable_log_filecount) { 139 if (ctx.repo->enable_log_filecount) {
119 html("<th class='left'>Files</th>"); 140 html("<th class='left'>Files</th>");
120 columns++; 141 columns++;
121 if (ctx.repo->enable_log_linecount) { 142 if (ctx.repo->enable_log_linecount) {
122 html("<th class='left'>Lines</th>"); 143 html("<th class='left'>Lines</th>");
123 columns++; 144 columns++;
124 } 145 }
125 } 146 }
126 html("</tr>\n"); 147 html("</tr>\n");
127 148
128 if (ofs<0) 149 if (ofs<0)
129 ofs = 0; 150 ofs = 0;
130 151
131 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 152 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
132 free(commit->buffer); 153 free(commit->buffer);
133 commit->buffer = NULL; 154 commit->buffer = NULL;
134 free_commit_list(commit->parents); 155 free_commit_list(commit->parents);
135 commit->parents = NULL; 156 commit->parents = NULL;
136 } 157 }
137 158
138 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 159 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
139 print_commit(commit); 160 print_commit(commit);
140 free(commit->buffer); 161 free(commit->buffer);
141 commit->buffer = NULL; 162 commit->buffer = NULL;
142 free_commit_list(commit->parents); 163 free_commit_list(commit->parents);
143 commit->parents = NULL; 164 commit->parents = NULL;
144 } 165 }
145 if (pager) { 166 if (pager) {
146 htmlf("</table><div class='pager'>", 167 htmlf("</table><div class='pager'>",
147 columns); 168 columns);
148 if (ofs > 0) { 169 if (ofs > 0) {
149 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 170 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
150 ctx.qry.sha1, ctx.qry.path, 171 ctx.qry.sha1, ctx.qry.path,
151 ofs - cnt, ctx.qry.grep, 172 ofs - cnt, ctx.qry.grep,
152 ctx.qry.search); 173 ctx.qry.search, ctx.qry.showmsg);
153 html("&nbsp;"); 174 html("&nbsp;");
154 } 175 }
155 if ((commit = get_revision(&rev)) != NULL) { 176 if ((commit = get_revision(&rev)) != NULL) {
156 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 177 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
157 ctx.qry.sha1, ctx.qry.path, 178 ctx.qry.sha1, ctx.qry.path,
158 ofs + cnt, ctx.qry.grep, 179 ofs + cnt, ctx.qry.grep,
159 ctx.qry.search); 180 ctx.qry.search, ctx.qry.showmsg);
160 } 181 }
161 html("</div>"); 182 html("</div>");
162 } else if ((commit = get_revision(&rev)) != NULL) { 183 } else if ((commit = get_revision(&rev)) != NULL) {
163 html("<tr class='nohover'><td colspan='3'>"); 184 html("<tr class='nohover'><td colspan='3'>");
164 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 185 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0,
165 NULL, NULL); 186 NULL, NULL, ctx.qry.showmsg);
166 html("</td></tr>\n"); 187 html("</td></tr>\n");
167 } 188 }
168} 189}
diff --git a/ui-refs.c b/ui-refs.c
index 0805fc8..d61ee7c 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -1,157 +1,158 @@
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 49
50 return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date); 50 return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date);
51} 51}
52 52
53static int print_branch(struct refinfo *ref) 53static int print_branch(struct refinfo *ref)
54{ 54{
55 struct commitinfo *info = ref->commit; 55 struct commitinfo *info = ref->commit;
56 char *name = (char *)ref->refname; 56 char *name = (char *)ref->refname;
57 57
58 if (!info) 58 if (!info)
59 return 1; 59 return 1;
60 html("<tr><td>"); 60 html("<tr><td>");
61 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL); 61 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
62 ctx.qry.showmsg);
62 html("</td><td>"); 63 html("</td><td>");
63 64
64 if (ref->object->type == OBJ_COMMIT) { 65 if (ref->object->type == OBJ_COMMIT) {
65 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 66 cgit_commit_link(info->subject, NULL, NULL, name, NULL);
66 html("</td><td>"); 67 html("</td><td>");
67 html_txt(info->author); 68 html_txt(info->author);
68 html("</td><td colspan='2'>"); 69 html("</td><td colspan='2'>");
69 cgit_print_age(info->commit->date, -1, NULL); 70 cgit_print_age(info->commit->date, -1, NULL);
70 } else { 71 } else {
71 html("</td><td></td><td>"); 72 html("</td><td></td><td>");
72 cgit_object_link(ref->object); 73 cgit_object_link(ref->object);
73 } 74 }
74 html("</td></tr>\n"); 75 html("</td></tr>\n");
75 return 0; 76 return 0;
76} 77}
77 78
78static void print_tag_header() 79static void print_tag_header()
79{ 80{
80 html("<tr class='nohover'><th class='left'>Tag</th>" 81 html("<tr class='nohover'><th class='left'>Tag</th>"
81 "<th class='left'>Download</th>" 82 "<th class='left'>Download</th>"
82 "<th class='left'>Author</th>" 83 "<th class='left'>Author</th>"
83 "<th class='left' colspan='2'>Age</th></tr>\n"); 84 "<th class='left' colspan='2'>Age</th></tr>\n");
84 header = 1; 85 header = 1;
85} 86}
86 87
87static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 88static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
88{ 89{
89 const struct cgit_snapshot_format* f; 90 const struct cgit_snapshot_format* f;
90 char *filename; 91 char *filename;
91 const char *basename; 92 const char *basename;
92 93
93 if (!ref || strlen(ref) < 2) 94 if (!ref || strlen(ref) < 2)
94 return; 95 return;
95 96
96 basename = cgit_repobasename(repo->url); 97 basename = cgit_repobasename(repo->url);
97 if (prefixcmp(ref, basename) != 0) { 98 if (prefixcmp(ref, basename) != 0) {
98 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) 99 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
99 ref++; 100 ref++;
100 if (isdigit(ref[0])) 101 if (isdigit(ref[0]))
101 ref = xstrdup(fmt("%s-%s", basename, ref)); 102 ref = xstrdup(fmt("%s-%s", basename, ref));
102 } 103 }
103 104
104 for (f = cgit_snapshot_formats; f->suffix; f++) { 105 for (f = cgit_snapshot_formats; f->suffix; f++) {
105 if (!(repo->snapshots & f->bit)) 106 if (!(repo->snapshots & f->bit))
106 continue; 107 continue;
107 filename = fmt("%s%s", ref, f->suffix); 108 filename = fmt("%s%s", ref, f->suffix);
108 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 109 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
109 html("&nbsp;&nbsp;"); 110 html("&nbsp;&nbsp;");
110 } 111 }
111} 112}
112static int print_tag(struct refinfo *ref) 113static int print_tag(struct refinfo *ref)
113{ 114{
114 struct tag *tag; 115 struct tag *tag;
115 struct taginfo *info; 116 struct taginfo *info;
116 char *name = (char *)ref->refname; 117 char *name = (char *)ref->refname;
117 118
118 if (ref->object->type == OBJ_TAG) { 119 if (ref->object->type == OBJ_TAG) {
119 tag = (struct tag *)ref->object; 120 tag = (struct tag *)ref->object;
120 info = ref->tag; 121 info = ref->tag;
121 if (!tag || !info) 122 if (!tag || !info)
122 return 1; 123 return 1;
123 html("<tr><td>"); 124 html("<tr><td>");
124 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 125 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
125 html("</td><td>"); 126 html("</td><td>");
126 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) 127 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
127 print_tag_downloads(ctx.repo, name); 128 print_tag_downloads(ctx.repo, name);
128 else 129 else
129 cgit_object_link(tag->tagged); 130 cgit_object_link(tag->tagged);
130 html("</td><td>"); 131 html("</td><td>");
131 if (info->tagger) 132 if (info->tagger)
132 html(info->tagger); 133 html(info->tagger);
133 html("</td><td colspan='2'>"); 134 html("</td><td colspan='2'>");
134 if (info->tagger_date > 0) 135 if (info->tagger_date > 0)
135 cgit_print_age(info->tagger_date, -1, NULL); 136 cgit_print_age(info->tagger_date, -1, NULL);
136 html("</td></tr>\n"); 137 html("</td></tr>\n");
137 } else { 138 } else {
138 if (!header) 139 if (!header)
139 print_tag_header(); 140 print_tag_header();
140 html("<tr><td>"); 141 html("<tr><td>");
141 html_txt(name); 142 html_txt(name);
142 html("</td><td>"); 143 html("</td><td>");
143 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) 144 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
144 print_tag_downloads(ctx.repo, name); 145 print_tag_downloads(ctx.repo, name);
145 else 146 else
146 cgit_object_link(ref->object); 147 cgit_object_link(ref->object);
147 html("</td></tr>\n"); 148 html("</td></tr>\n");
148 } 149 }
149 return 0; 150 return 0;
150} 151}
151 152
152static void print_refs_link(char *path) 153static void print_refs_link(char *path)
153{ 154{
154 html("<tr class='nohover'><td colspan='4'>"); 155 html("<tr class='nohover'><td colspan='4'>");
155 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 156 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
156 html("</td></tr>"); 157 html("</td></tr>");
157} 158}
diff --git a/ui-repolist.c b/ui-repolist.c
index 87196f0..2c13d50 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -164,115 +164,115 @@ static int sort_idle(const void *a, const void *b)
164 const struct cgit_repo *r2 = b; 164 const struct cgit_repo *r2 = b;
165 time_t t1, t2; 165 time_t t1, t2;
166 166
167 t1 = t2 = 0; 167 t1 = t2 = 0;
168 get_repo_modtime(r1, &t1); 168 get_repo_modtime(r1, &t1);
169 get_repo_modtime(r2, &t2); 169 get_repo_modtime(r2, &t2);
170 return t2 - t1; 170 return t2 - t1;
171} 171}
172 172
173struct sortcolumn { 173struct sortcolumn {
174 const char *name; 174 const char *name;
175 int (*fn)(const void *a, const void *b); 175 int (*fn)(const void *a, const void *b);
176}; 176};
177 177
178struct sortcolumn sortcolumn[] = { 178struct sortcolumn sortcolumn[] = {
179 {"name", sort_name}, 179 {"name", sort_name},
180 {"desc", sort_desc}, 180 {"desc", sort_desc},
181 {"owner", sort_owner}, 181 {"owner", sort_owner},
182 {"idle", sort_idle}, 182 {"idle", sort_idle},
183 {NULL, NULL} 183 {NULL, NULL}
184}; 184};
185 185
186int sort_repolist(char *field) 186int sort_repolist(char *field)
187{ 187{
188 struct sortcolumn *column; 188 struct sortcolumn *column;
189 189
190 for (column = &sortcolumn[0]; column->name; column++) { 190 for (column = &sortcolumn[0]; column->name; column++) {
191 if (strcmp(field, column->name)) 191 if (strcmp(field, column->name))
192 continue; 192 continue;
193 qsort(cgit_repolist.repos, cgit_repolist.count, 193 qsort(cgit_repolist.repos, cgit_repolist.count,
194 sizeof(struct cgit_repo), column->fn); 194 sizeof(struct cgit_repo), column->fn);
195 return 1; 195 return 1;
196 } 196 }
197 return 0; 197 return 0;
198} 198}
199 199
200 200
201void cgit_print_repolist() 201void cgit_print_repolist()
202{ 202{
203 int i, columns = 4, hits = 0, header = 0; 203 int i, columns = 4, hits = 0, header = 0;
204 char *last_group = NULL; 204 char *last_group = NULL;
205 int sorted = 0; 205 int sorted = 0;
206 206
207 if (ctx.cfg.enable_index_links) 207 if (ctx.cfg.enable_index_links)
208 columns++; 208 columns++;
209 209
210 ctx.page.title = ctx.cfg.root_title; 210 ctx.page.title = ctx.cfg.root_title;
211 cgit_print_http_headers(&ctx); 211 cgit_print_http_headers(&ctx);
212 cgit_print_docstart(&ctx); 212 cgit_print_docstart(&ctx);
213 cgit_print_pageheader(&ctx); 213 cgit_print_pageheader(&ctx);
214 214
215 if (ctx.cfg.index_header) 215 if (ctx.cfg.index_header)
216 html_include(ctx.cfg.index_header); 216 html_include(ctx.cfg.index_header);
217 217
218 if(ctx.qry.sort) 218 if(ctx.qry.sort)
219 sorted = sort_repolist(ctx.qry.sort); 219 sorted = sort_repolist(ctx.qry.sort);
220 220
221 html("<table summary='repository list' class='list nowrap'>"); 221 html("<table summary='repository list' class='list nowrap'>");
222 for (i=0; i<cgit_repolist.count; i++) { 222 for (i=0; i<cgit_repolist.count; i++) {
223 ctx.repo = &cgit_repolist.repos[i]; 223 ctx.repo = &cgit_repolist.repos[i];
224 if (!(is_match(ctx.repo) && is_in_url(ctx.repo))) 224 if (!(is_match(ctx.repo) && is_in_url(ctx.repo)))
225 continue; 225 continue;
226 hits++; 226 hits++;
227 if (hits <= ctx.qry.ofs) 227 if (hits <= ctx.qry.ofs)
228 continue; 228 continue;
229 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) 229 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
230 continue; 230 continue;
231 if (!header++) 231 if (!header++)
232 print_header(columns); 232 print_header(columns);
233 if (!sorted && 233 if (!sorted &&
234 ((last_group == NULL && ctx.repo->group != NULL) || 234 ((last_group == NULL && ctx.repo->group != NULL) ||
235 (last_group != NULL && ctx.repo->group == NULL) || 235 (last_group != NULL && ctx.repo->group == NULL) ||
236 (last_group != NULL && ctx.repo->group != NULL && 236 (last_group != NULL && ctx.repo->group != NULL &&
237 strcmp(ctx.repo->group, last_group)))) { 237 strcmp(ctx.repo->group, last_group)))) {
238 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", 238 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>",
239 columns); 239 columns);
240 html_txt(ctx.repo->group); 240 html_txt(ctx.repo->group);
241 html("</td></tr>"); 241 html("</td></tr>");
242 last_group = ctx.repo->group; 242 last_group = ctx.repo->group;
243 } 243 }
244 htmlf("<tr><td class='%s'>", 244 htmlf("<tr><td class='%s'>",
245 !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); 245 !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo");
246 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); 246 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
247 html("</td><td>"); 247 html("</td><td>");
248 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); 248 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
249 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); 249 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc);
250 html_link_close(); 250 html_link_close();
251 html("</td><td>"); 251 html("</td><td>");
252 html_txt(ctx.repo->owner); 252 html_txt(ctx.repo->owner);
253 html("</td><td>"); 253 html("</td><td>");
254 print_modtime(ctx.repo); 254 print_modtime(ctx.repo);
255 html("</td>"); 255 html("</td>");
256 if (ctx.cfg.enable_index_links) { 256 if (ctx.cfg.enable_index_links) {
257 html("<td>"); 257 html("<td>");
258 cgit_summary_link("summary", NULL, "button", NULL); 258 cgit_summary_link("summary", NULL, "button", NULL);
259 cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 259 cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
260 0, NULL, NULL); 260 0, NULL, NULL, ctx.qry.showmsg);
261 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); 261 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
262 html("</td>"); 262 html("</td>");
263 } 263 }
264 html("</tr>\n"); 264 html("</tr>\n");
265 } 265 }
266 html("</table>"); 266 html("</table>");
267 if (!hits) 267 if (!hits)
268 cgit_print_error("No repositories found"); 268 cgit_print_error("No repositories found");
269 else if (hits > ctx.cfg.max_repo_count) 269 else if (hits > ctx.cfg.max_repo_count)
270 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search); 270 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search);
271 cgit_print_docend(); 271 cgit_print_docend();
272} 272}
273 273
274void cgit_print_site_readme() 274void cgit_print_site_readme()
275{ 275{
276 if (ctx.cfg.root_readme) 276 if (ctx.cfg.root_readme)
277 html_include(ctx.cfg.root_readme); 277 html_include(ctx.cfg.root_readme);
278} 278}
diff --git a/ui-shared.c b/ui-shared.c
index 9319881..95dfeb4 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -188,216 +188,222 @@ void cgit_index_link(char *name, char *title, char *class, char *pattern,
188 site_link(NULL, name, title, class, pattern, ofs); 188 site_link(NULL, name, title, class, pattern, ofs);
189} 189}
190 190
191static char *repolink(char *title, char *class, char *page, char *head, 191static char *repolink(char *title, char *class, char *page, char *head,
192 char *path) 192 char *path)
193{ 193{
194 char *delim = "?"; 194 char *delim = "?";
195 195
196 html("<a"); 196 html("<a");
197 if (title) { 197 if (title) {
198 html(" title='"); 198 html(" title='");
199 html_attr(title); 199 html_attr(title);
200 html("'"); 200 html("'");
201 } 201 }
202 if (class) { 202 if (class) {
203 html(" class='"); 203 html(" class='");
204 html_attr(class); 204 html_attr(class);
205 html("'"); 205 html("'");
206 } 206 }
207 html(" href='"); 207 html(" href='");
208 if (ctx.cfg.virtual_root) { 208 if (ctx.cfg.virtual_root) {
209 html_url_path(ctx.cfg.virtual_root); 209 html_url_path(ctx.cfg.virtual_root);
210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
211 html("/"); 211 html("/");
212 html_url_path(ctx.repo->url); 212 html_url_path(ctx.repo->url);
213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
214 html("/"); 214 html("/");
215 if (page) { 215 if (page) {
216 html_url_path(page); 216 html_url_path(page);
217 html("/"); 217 html("/");
218 if (path) 218 if (path)
219 html_url_path(path); 219 html_url_path(path);
220 } 220 }
221 } else { 221 } else {
222 html(ctx.cfg.script_name); 222 html(ctx.cfg.script_name);
223 html("?url="); 223 html("?url=");
224 html_url_arg(ctx.repo->url); 224 html_url_arg(ctx.repo->url);
225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
226 html("/"); 226 html("/");
227 if (page) { 227 if (page) {
228 html_url_arg(page); 228 html_url_arg(page);
229 html("/"); 229 html("/");
230 if (path) 230 if (path)
231 html_url_arg(path); 231 html_url_arg(path);
232 } 232 }
233 delim = "&amp;"; 233 delim = "&amp;";
234 } 234 }
235 if (head && strcmp(head, ctx.repo->defbranch)) { 235 if (head && strcmp(head, ctx.repo->defbranch)) {
236 html(delim); 236 html(delim);
237 html("h="); 237 html("h=");
238 html_url_arg(head); 238 html_url_arg(head);
239 delim = "&amp;"; 239 delim = "&amp;";
240 } 240 }
241 return fmt("%s", delim); 241 return fmt("%s", delim);
242} 242}
243 243
244static void reporevlink(char *page, char *name, char *title, char *class, 244static void reporevlink(char *page, char *name, char *title, char *class,
245 char *head, char *rev, char *path) 245 char *head, char *rev, char *path)
246{ 246{
247 char *delim; 247 char *delim;
248 248
249 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
250 if (rev && strcmp(rev, ctx.qry.head)) { 250 if (rev && strcmp(rev, ctx.qry.head)) {
251 html(delim); 251 html(delim);
252 html("id="); 252 html("id=");
253 html_url_arg(rev); 253 html_url_arg(rev);
254 } 254 }
255 html("'>"); 255 html("'>");
256 html_txt(name); 256 html_txt(name);
257 html("</a>"); 257 html("</a>");
258} 258}
259 259
260void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(char *name, char *title, char *class, char *head)
261{ 261{
262 reporevlink(NULL, name, title, class, head, NULL, NULL); 262 reporevlink(NULL, name, title, class, head, NULL, NULL);
263} 263}
264 264
265void cgit_tag_link(char *name, char *title, char *class, char *head, 265void cgit_tag_link(char *name, char *title, char *class, char *head,
266 char *rev) 266 char *rev)
267{ 267{
268 reporevlink("tag", name, title, class, head, rev, NULL); 268 reporevlink("tag", name, title, class, head, rev, NULL);
269} 269}
270 270
271void cgit_tree_link(char *name, char *title, char *class, char *head, 271void cgit_tree_link(char *name, char *title, char *class, char *head,
272 char *rev, char *path) 272 char *rev, char *path)
273{ 273{
274 reporevlink("tree", name, title, class, head, rev, path); 274 reporevlink("tree", name, title, class, head, rev, path);
275} 275}
276 276
277void cgit_plain_link(char *name, char *title, char *class, char *head, 277void cgit_plain_link(char *name, char *title, char *class, char *head,
278 char *rev, char *path) 278 char *rev, char *path)
279{ 279{
280 reporevlink("plain", name, title, class, head, rev, path); 280 reporevlink("plain", name, title, class, head, rev, path);
281} 281}
282 282
283void cgit_log_link(char *name, char *title, char *class, char *head, 283void cgit_log_link(char *name, char *title, char *class, char *head,
284 char *rev, char *path, int ofs, char *grep, char *pattern) 284 char *rev, char *path, int ofs, char *grep, char *pattern,
285 int showmsg)
285{ 286{
286 char *delim; 287 char *delim;
287 288
288 delim = repolink(title, class, "log", head, path); 289 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 290 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 291 html(delim);
291 html("id="); 292 html("id=");
292 html_url_arg(rev); 293 html_url_arg(rev);
293 delim = "&"; 294 delim = "&";
294 } 295 }
295 if (grep && pattern) { 296 if (grep && pattern) {
296 html(delim); 297 html(delim);
297 html("qt="); 298 html("qt=");
298 html_url_arg(grep); 299 html_url_arg(grep);
299 delim = "&"; 300 delim = "&";
300 html(delim); 301 html(delim);
301 html("q="); 302 html("q=");
302 html_url_arg(pattern); 303 html_url_arg(pattern);
303 } 304 }
304 if (ofs > 0) { 305 if (ofs > 0) {
305 html(delim); 306 html(delim);
306 html("ofs="); 307 html("ofs=");
307 htmlf("%d", ofs); 308 htmlf("%d", ofs);
309 delim = "&";
310 }
311 if (showmsg) {
312 html(delim);
313 html("showmsg=1");
308 } 314 }
309 html("'>"); 315 html("'>");
310 html_txt(name); 316 html_txt(name);
311 html("</a>"); 317 html("</a>");
312} 318}
313 319
314void cgit_commit_link(char *name, char *title, char *class, char *head, 320void cgit_commit_link(char *name, char *title, char *class, char *head,
315 char *rev) 321 char *rev)
316{ 322{
317 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 323 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
318 name[ctx.cfg.max_msg_len] = '\0'; 324 name[ctx.cfg.max_msg_len] = '\0';
319 name[ctx.cfg.max_msg_len - 1] = '.'; 325 name[ctx.cfg.max_msg_len - 1] = '.';
320 name[ctx.cfg.max_msg_len - 2] = '.'; 326 name[ctx.cfg.max_msg_len - 2] = '.';
321 name[ctx.cfg.max_msg_len - 3] = '.'; 327 name[ctx.cfg.max_msg_len - 3] = '.';
322 } 328 }
323 reporevlink("commit", name, title, class, head, rev, NULL); 329 reporevlink("commit", name, title, class, head, rev, NULL);
324} 330}
325 331
326void cgit_refs_link(char *name, char *title, char *class, char *head, 332void cgit_refs_link(char *name, char *title, char *class, char *head,
327 char *rev, char *path) 333 char *rev, char *path)
328{ 334{
329 reporevlink("refs", name, title, class, head, rev, path); 335 reporevlink("refs", name, title, class, head, rev, path);
330} 336}
331 337
332void cgit_snapshot_link(char *name, char *title, char *class, char *head, 338void cgit_snapshot_link(char *name, char *title, char *class, char *head,
333 char *rev, char *archivename) 339 char *rev, char *archivename)
334{ 340{
335 reporevlink("snapshot", name, title, class, head, rev, archivename); 341 reporevlink("snapshot", name, title, class, head, rev, archivename);
336} 342}
337 343
338void cgit_diff_link(char *name, char *title, char *class, char *head, 344void cgit_diff_link(char *name, char *title, char *class, char *head,
339 char *new_rev, char *old_rev, char *path) 345 char *new_rev, char *old_rev, char *path)
340{ 346{
341 char *delim; 347 char *delim;
342 348
343 delim = repolink(title, class, "diff", head, path); 349 delim = repolink(title, class, "diff", head, path);
344 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 350 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
345 html(delim); 351 html(delim);
346 html("id="); 352 html("id=");
347 html_url_arg(new_rev); 353 html_url_arg(new_rev);
348 delim = "&amp;"; 354 delim = "&amp;";
349 } 355 }
350 if (old_rev) { 356 if (old_rev) {
351 html(delim); 357 html(delim);
352 html("id2="); 358 html("id2=");
353 html_url_arg(old_rev); 359 html_url_arg(old_rev);
354 } 360 }
355 html("'>"); 361 html("'>");
356 html_txt(name); 362 html_txt(name);
357 html("</a>"); 363 html("</a>");
358} 364}
359 365
360void cgit_patch_link(char *name, char *title, char *class, char *head, 366void cgit_patch_link(char *name, char *title, char *class, char *head,
361 char *rev) 367 char *rev)
362{ 368{
363 reporevlink("patch", name, title, class, head, rev, NULL); 369 reporevlink("patch", name, title, class, head, rev, NULL);
364} 370}
365 371
366void cgit_object_link(struct object *obj) 372void cgit_object_link(struct object *obj)
367{ 373{
368 char *page, *shortrev, *fullrev, *name; 374 char *page, *shortrev, *fullrev, *name;
369 375
370 fullrev = sha1_to_hex(obj->sha1); 376 fullrev = sha1_to_hex(obj->sha1);
371 shortrev = xstrdup(fullrev); 377 shortrev = xstrdup(fullrev);
372 shortrev[10] = '\0'; 378 shortrev[10] = '\0';
373 if (obj->type == OBJ_COMMIT) { 379 if (obj->type == OBJ_COMMIT) {
374 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 380 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
375 ctx.qry.head, fullrev); 381 ctx.qry.head, fullrev);
376 return; 382 return;
377 } else if (obj->type == OBJ_TREE) 383 } else if (obj->type == OBJ_TREE)
378 page = "tree"; 384 page = "tree";
379 else if (obj->type == OBJ_TAG) 385 else if (obj->type == OBJ_TAG)
380 page = "tag"; 386 page = "tag";
381 else 387 else
382 page = "blob"; 388 page = "blob";
383 name = fmt("%s %s...", typename(obj->type), shortrev); 389 name = fmt("%s %s...", typename(obj->type), shortrev);
384 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 390 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
385} 391}
386 392
387void cgit_print_date(time_t secs, char *format, int local_time) 393void cgit_print_date(time_t secs, char *format, int local_time)
388{ 394{
389 char buf[64]; 395 char buf[64];
390 struct tm *time; 396 struct tm *time;
391 397
392 if (!secs) 398 if (!secs)
393 return; 399 return;
394 if(local_time) 400 if(local_time)
395 time = localtime(&secs); 401 time = localtime(&secs);
396 else 402 else
397 time = gmtime(&secs); 403 time = gmtime(&secs);
398 strftime(buf, sizeof(buf)-1, format, time); 404 strftime(buf, sizeof(buf)-1, format, time);
399 html_txt(buf); 405 html_txt(buf);
400} 406}
401 407
402void cgit_print_age(time_t t, time_t max_relative, char *format) 408void cgit_print_age(time_t t, time_t max_relative, char *format)
403{ 409{
@@ -477,239 +483,241 @@ void cgit_print_docstart(struct cgit_context *ctx)
477 if (ctx->cfg.favicon) { 483 if (ctx->cfg.favicon) {
478 html("<link rel='shortcut icon' href='"); 484 html("<link rel='shortcut icon' href='");
479 html_attr(ctx->cfg.favicon); 485 html_attr(ctx->cfg.favicon);
480 html("'/>\n"); 486 html("'/>\n");
481 } 487 }
482 if (host && ctx->repo) { 488 if (host && ctx->repo) {
483 html("<link rel='alternate' title='Atom feed' href='http://"); 489 html("<link rel='alternate' title='Atom feed' href='http://");
484 html_attr(cgit_hosturl()); 490 html_attr(cgit_hosturl());
485 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 491 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
486 fmt("h=%s", ctx->qry.head))); 492 fmt("h=%s", ctx->qry.head)));
487 html("' type='application/atom+xml'/>"); 493 html("' type='application/atom+xml'/>");
488 } 494 }
489 html("</head>\n"); 495 html("</head>\n");
490 html("<body>\n"); 496 html("<body>\n");
491} 497}
492 498
493void cgit_print_docend() 499void cgit_print_docend()
494{ 500{
495 html("</div>"); 501 html("</div>");
496 if (ctx.cfg.footer) 502 if (ctx.cfg.footer)
497 html_include(ctx.cfg.footer); 503 html_include(ctx.cfg.footer);
498 else { 504 else {
499 htmlf("<div class='footer'>generated by cgit %s at ", 505 htmlf("<div class='footer'>generated by cgit %s at ",
500 cgit_version); 506 cgit_version);
501 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 507 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
502 html("</div>\n"); 508 html("</div>\n");
503 } 509 }
504 html("</body>\n</html>\n"); 510 html("</body>\n</html>\n");
505} 511}
506 512
507int print_branch_option(const char *refname, const unsigned char *sha1, 513int print_branch_option(const char *refname, const unsigned char *sha1,
508 int flags, void *cb_data) 514 int flags, void *cb_data)
509{ 515{
510 char *name = (char *)refname; 516 char *name = (char *)refname;
511 html_option(name, name, ctx.qry.head); 517 html_option(name, name, ctx.qry.head);
512 return 0; 518 return 0;
513} 519}
514 520
515int print_archive_ref(const char *refname, const unsigned char *sha1, 521int print_archive_ref(const char *refname, const unsigned char *sha1,
516 int flags, void *cb_data) 522 int flags, void *cb_data)
517{ 523{
518 struct tag *tag; 524 struct tag *tag;
519 struct taginfo *info; 525 struct taginfo *info;
520 struct object *obj; 526 struct object *obj;
521 char buf[256], *url; 527 char buf[256], *url;
522 unsigned char fileid[20]; 528 unsigned char fileid[20];
523 int *header = (int *)cb_data; 529 int *header = (int *)cb_data;
524 530
525 if (prefixcmp(refname, "refs/archives")) 531 if (prefixcmp(refname, "refs/archives"))
526 return 0; 532 return 0;
527 strncpy(buf, refname+14, sizeof(buf)); 533 strncpy(buf, refname+14, sizeof(buf));
528 obj = parse_object(sha1); 534 obj = parse_object(sha1);
529 if (!obj) 535 if (!obj)
530 return 1; 536 return 1;
531 if (obj->type == OBJ_TAG) { 537 if (obj->type == OBJ_TAG) {
532 tag = lookup_tag(sha1); 538 tag = lookup_tag(sha1);
533 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 539 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
534 return 0; 540 return 0;
535 hashcpy(fileid, tag->tagged->sha1); 541 hashcpy(fileid, tag->tagged->sha1);
536 } else if (obj->type != OBJ_BLOB) { 542 } else if (obj->type != OBJ_BLOB) {
537 return 0; 543 return 0;
538 } else { 544 } else {
539 hashcpy(fileid, sha1); 545 hashcpy(fileid, sha1);
540 } 546 }
541 if (!*header) { 547 if (!*header) {
542 html("<h1>download</h1>\n"); 548 html("<h1>download</h1>\n");
543 *header = 1; 549 *header = 1;
544 } 550 }
545 url = cgit_pageurl(ctx.qry.repo, "blob", 551 url = cgit_pageurl(ctx.qry.repo, "blob",
546 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 552 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
547 buf)); 553 buf));
548 html_link_open(url, NULL, "menu"); 554 html_link_open(url, NULL, "menu");
549 html_txt(strlpart(buf, 20)); 555 html_txt(strlpart(buf, 20));
550 html_link_close(); 556 html_link_close();
551 return 0; 557 return 0;
552} 558}
553 559
554void add_hidden_formfields(int incl_head, int incl_search, char *page) 560void add_hidden_formfields(int incl_head, int incl_search, char *page)
555{ 561{
556 char *url; 562 char *url;
557 563
558 if (!ctx.cfg.virtual_root) { 564 if (!ctx.cfg.virtual_root) {
559 url = fmt("%s/%s", ctx.qry.repo, page); 565 url = fmt("%s/%s", ctx.qry.repo, page);
560 if (ctx.qry.path) 566 if (ctx.qry.path)
561 url = fmt("%s/%s", url, ctx.qry.path); 567 url = fmt("%s/%s", url, ctx.qry.path);
562 html_hidden("url", url); 568 html_hidden("url", url);
563 } 569 }
564 570
565 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 571 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
566 strcmp(ctx.qry.head, ctx.repo->defbranch)) 572 strcmp(ctx.qry.head, ctx.repo->defbranch))
567 html_hidden("h", ctx.qry.head); 573 html_hidden("h", ctx.qry.head);
568 574
569 if (ctx.qry.sha1) 575 if (ctx.qry.sha1)
570 html_hidden("id", ctx.qry.sha1); 576 html_hidden("id", ctx.qry.sha1);
571 if (ctx.qry.sha2) 577 if (ctx.qry.sha2)
572 html_hidden("id2", ctx.qry.sha2); 578 html_hidden("id2", ctx.qry.sha2);
579 if (ctx.qry.showmsg)
580 html_hidden("showmsg", "1");
573 581
574 if (incl_search) { 582 if (incl_search) {
575 if (ctx.qry.grep) 583 if (ctx.qry.grep)
576 html_hidden("qt", ctx.qry.grep); 584 html_hidden("qt", ctx.qry.grep);
577 if (ctx.qry.search) 585 if (ctx.qry.search)
578 html_hidden("q", ctx.qry.search); 586 html_hidden("q", ctx.qry.search);
579 } 587 }
580} 588}
581 589
582char *hc(struct cgit_cmd *cmd, const char *page) 590char *hc(struct cgit_cmd *cmd, const char *page)
583{ 591{
584 return (strcmp(cmd->name, page) ? NULL : "active"); 592 return (strcmp(cmd->name, page) ? NULL : "active");
585} 593}
586 594
587void cgit_print_pageheader(struct cgit_context *ctx) 595void cgit_print_pageheader(struct cgit_context *ctx)
588{ 596{
589 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 597 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
590 598
591 html("<table id='header'>\n"); 599 html("<table id='header'>\n");
592 html("<tr>\n"); 600 html("<tr>\n");
593 html("<td class='logo' rowspan='2'><a href='"); 601 html("<td class='logo' rowspan='2'><a href='");
594 if (ctx->cfg.logo_link) 602 if (ctx->cfg.logo_link)
595 html_attr(ctx->cfg.logo_link); 603 html_attr(ctx->cfg.logo_link);
596 else 604 else
597 html_attr(cgit_rooturl()); 605 html_attr(cgit_rooturl());
598 html("'><img src='"); 606 html("'><img src='");
599 html_attr(ctx->cfg.logo); 607 html_attr(ctx->cfg.logo);
600 html("' alt='cgit logo'/></a></td>\n"); 608 html("' alt='cgit logo'/></a></td>\n");
601 609
602 html("<td class='main'>"); 610 html("<td class='main'>");
603 if (ctx->repo) { 611 if (ctx->repo) {
604 cgit_index_link("index", NULL, NULL, NULL, 0); 612 cgit_index_link("index", NULL, NULL, NULL, 0);
605 html(" : "); 613 html(" : ");
606 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 614 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
607 html("</td><td class='form'>"); 615 html("</td><td class='form'>");
608 html("<form method='get' action=''>\n"); 616 html("<form method='get' action=''>\n");
609 add_hidden_formfields(0, 1, ctx->qry.page); 617 add_hidden_formfields(0, 1, ctx->qry.page);
610 html("<select name='h' onchange='this.form.submit();'>\n"); 618 html("<select name='h' onchange='this.form.submit();'>\n");
611 for_each_branch_ref(print_branch_option, ctx->qry.head); 619 for_each_branch_ref(print_branch_option, ctx->qry.head);
612 html("</select> "); 620 html("</select> ");
613 html("<input type='submit' name='' value='switch'/>"); 621 html("<input type='submit' name='' value='switch'/>");
614 html("</form>"); 622 html("</form>");
615 } else 623 } else
616 html_txt(ctx->cfg.root_title); 624 html_txt(ctx->cfg.root_title);
617 html("</td></tr>\n"); 625 html("</td></tr>\n");
618 626
619 html("<tr><td class='sub'>"); 627 html("<tr><td class='sub'>");
620 if (ctx->repo) { 628 if (ctx->repo) {
621 html_txt(ctx->repo->desc); 629 html_txt(ctx->repo->desc);
622 html("</td><td class='sub right'>"); 630 html("</td><td class='sub right'>");
623 html_txt(ctx->repo->owner); 631 html_txt(ctx->repo->owner);
624 } else { 632 } else {
625 if (ctx->cfg.root_desc) 633 if (ctx->cfg.root_desc)
626 html_txt(ctx->cfg.root_desc); 634 html_txt(ctx->cfg.root_desc);
627 else if (ctx->cfg.index_info) 635 else if (ctx->cfg.index_info)
628 html_include(ctx->cfg.index_info); 636 html_include(ctx->cfg.index_info);
629 } 637 }
630 html("</td></tr></table>\n"); 638 html("</td></tr></table>\n");
631 639
632 html("<table class='tabs'><tr><td>\n"); 640 html("<table class='tabs'><tr><td>\n");
633 if (ctx->repo) { 641 if (ctx->repo) {
634 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 642 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
635 ctx->qry.head); 643 ctx->qry.head);
636 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 644 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
637 ctx->qry.sha1, NULL); 645 ctx->qry.sha1, NULL);
638 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 646 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
639 NULL, NULL, 0, NULL, NULL); 647 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
640 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 648 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
641 ctx->qry.sha1, NULL); 649 ctx->qry.sha1, NULL);
642 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 650 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
643 ctx->qry.head, ctx->qry.sha1); 651 ctx->qry.head, ctx->qry.sha1);
644 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 652 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
645 ctx->qry.sha1, ctx->qry.sha2, NULL); 653 ctx->qry.sha1, ctx->qry.sha2, NULL);
646 if (ctx->repo->readme) 654 if (ctx->repo->readme)
647 reporevlink("about", "about", NULL, 655 reporevlink("about", "about", NULL,
648 hc(cmd, "about"), ctx->qry.head, NULL, 656 hc(cmd, "about"), ctx->qry.head, NULL,
649 NULL); 657 NULL);
650 html("</td><td class='form'>"); 658 html("</td><td class='form'>");
651 html("<form class='right' method='get' action='"); 659 html("<form class='right' method='get' action='");
652 if (ctx->cfg.virtual_root) 660 if (ctx->cfg.virtual_root)
653 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 661 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
654 ctx->qry.path, NULL)); 662 ctx->qry.path, NULL));
655 html("'>\n"); 663 html("'>\n");
656 add_hidden_formfields(1, 0, "log"); 664 add_hidden_formfields(1, 0, "log");
657 html("<select name='qt'>\n"); 665 html("<select name='qt'>\n");
658 html_option("grep", "log msg", ctx->qry.grep); 666 html_option("grep", "log msg", ctx->qry.grep);
659 html_option("author", "author", ctx->qry.grep); 667 html_option("author", "author", ctx->qry.grep);
660 html_option("committer", "committer", ctx->qry.grep); 668 html_option("committer", "committer", ctx->qry.grep);
661 html("</select>\n"); 669 html("</select>\n");
662 html("<input class='txt' type='text' size='10' name='q' value='"); 670 html("<input class='txt' type='text' size='10' name='q' value='");
663 html_attr(ctx->qry.search); 671 html_attr(ctx->qry.search);
664 html("'/>\n"); 672 html("'/>\n");
665 html("<input type='submit' value='search'/>\n"); 673 html("<input type='submit' value='search'/>\n");
666 html("</form>\n"); 674 html("</form>\n");
667 } else { 675 } else {
668 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 676 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
669 if (ctx->cfg.root_readme) 677 if (ctx->cfg.root_readme)
670 site_link("about", "about", NULL, hc(cmd, "about"), 678 site_link("about", "about", NULL, hc(cmd, "about"),
671 NULL, 0); 679 NULL, 0);
672 html("</td><td class='form'>"); 680 html("</td><td class='form'>");
673 html("<form method='get' action='"); 681 html("<form method='get' action='");
674 html_attr(cgit_rooturl()); 682 html_attr(cgit_rooturl());
675 html("'>\n"); 683 html("'>\n");
676 html("<input type='text' name='q' size='10' value='"); 684 html("<input type='text' name='q' size='10' value='");
677 html_attr(ctx->qry.search); 685 html_attr(ctx->qry.search);
678 html("'/>\n"); 686 html("'/>\n");
679 html("<input type='submit' value='search'/>\n"); 687 html("<input type='submit' value='search'/>\n");
680 html("</form>"); 688 html("</form>");
681 } 689 }
682 html("</td></tr></table>\n"); 690 html("</td></tr></table>\n");
683 html("<div class='content'>"); 691 html("<div class='content'>");
684} 692}
685 693
686void cgit_print_filemode(unsigned short mode) 694void cgit_print_filemode(unsigned short mode)
687{ 695{
688 if (S_ISDIR(mode)) 696 if (S_ISDIR(mode))
689 html("d"); 697 html("d");
690 else if (S_ISLNK(mode)) 698 else if (S_ISLNK(mode))
691 html("l"); 699 html("l");
692 else if (S_ISGITLINK(mode)) 700 else if (S_ISGITLINK(mode))
693 html("m"); 701 html("m");
694 else 702 else
695 html("-"); 703 html("-");
696 html_fileperm(mode >> 6); 704 html_fileperm(mode >> 6);
697 html_fileperm(mode >> 3); 705 html_fileperm(mode >> 3);
698 html_fileperm(mode); 706 html_fileperm(mode);
699} 707}
700 708
701void cgit_print_snapshot_links(const char *repo, const char *head, 709void cgit_print_snapshot_links(const char *repo, const char *head,
702 const char *hex, int snapshots) 710 const char *hex, int snapshots)
703{ 711{
704 const struct cgit_snapshot_format* f; 712 const struct cgit_snapshot_format* f;
705 char *filename; 713 char *filename;
706 714
707 for (f = cgit_snapshot_formats; f->suffix; f++) { 715 for (f = cgit_snapshot_formats; f->suffix; f++) {
708 if (!(snapshots & f->bit)) 716 if (!(snapshots & f->bit))
709 continue; 717 continue;
710 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 718 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
711 f->suffix); 719 f->suffix);
712 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 720 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
713 html("<br/>"); 721 html("<br/>");
714 } 722 }
715} 723}
diff --git a/ui-shared.h b/ui-shared.h
index 3c8a6d0..2ab53ae 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,46 +1,46 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_hosturl(); 4extern char *cgit_hosturl();
5extern char *cgit_repourl(const char *reponame); 5extern char *cgit_repourl(const char *reponame);
6extern char *cgit_fileurl(const char *reponame, const char *pagename, 6extern char *cgit_fileurl(const char *reponame, const char *pagename,
7 const char *filename, const char *query); 7 const char *filename, const char *query);
8extern char *cgit_pageurl(const char *reponame, const char *pagename, 8extern char *cgit_pageurl(const char *reponame, const char *pagename,
9 const char *query); 9 const char *query);
10 10
11extern void cgit_index_link(char *name, char *title, char *class, 11extern void cgit_index_link(char *name, char *title, char *class,
12 char *pattern, int ofs); 12 char *pattern, int ofs);
13extern void cgit_summary_link(char *name, char *title, char *class, char *head); 13extern void cgit_summary_link(char *name, char *title, char *class, char *head);
14extern void cgit_tag_link(char *name, char *title, char *class, char *head, 14extern void cgit_tag_link(char *name, char *title, char *class, char *head,
15 char *rev); 15 char *rev);
16extern void cgit_tree_link(char *name, char *title, char *class, char *head, 16extern void cgit_tree_link(char *name, char *title, char *class, char *head,
17 char *rev, char *path); 17 char *rev, char *path);
18extern void cgit_plain_link(char *name, char *title, char *class, char *head, 18extern void cgit_plain_link(char *name, char *title, char *class, char *head,
19 char *rev, char *path); 19 char *rev, char *path);
20extern void cgit_log_link(char *name, char *title, char *class, char *head, 20extern void cgit_log_link(char *name, char *title, char *class, char *head,
21 char *rev, char *path, int ofs, char *grep, 21 char *rev, char *path, int ofs, char *grep,
22 char *pattern); 22 char *pattern, int showmsg);
23extern void cgit_commit_link(char *name, char *title, char *class, char *head, 23extern void cgit_commit_link(char *name, char *title, char *class, char *head,
24 char *rev); 24 char *rev);
25extern void cgit_patch_link(char *name, char *title, char *class, char *head, 25extern void cgit_patch_link(char *name, char *title, char *class, char *head,
26 char *rev); 26 char *rev);
27extern void cgit_refs_link(char *name, char *title, char *class, char *head, 27extern void cgit_refs_link(char *name, char *title, char *class, char *head,
28 char *rev, char *path); 28 char *rev, char *path);
29extern void cgit_snapshot_link(char *name, char *title, char *class, 29extern void cgit_snapshot_link(char *name, char *title, char *class,
30 char *head, char *rev, char *archivename); 30 char *head, char *rev, char *archivename);
31extern void cgit_diff_link(char *name, char *title, char *class, char *head, 31extern void cgit_diff_link(char *name, char *title, char *class, char *head,
32 char *new_rev, char *old_rev, char *path); 32 char *new_rev, char *old_rev, char *path);
33extern void cgit_object_link(struct object *obj); 33extern void cgit_object_link(struct object *obj);
34 34
35extern void cgit_print_error(char *msg); 35extern void cgit_print_error(char *msg);
36extern void cgit_print_date(time_t secs, char *format, int local_time); 36extern void cgit_print_date(time_t secs, char *format, int local_time);
37extern void cgit_print_age(time_t t, time_t max_relative, char *format); 37extern void cgit_print_age(time_t t, time_t max_relative, char *format);
38extern void cgit_print_http_headers(struct cgit_context *ctx); 38extern void cgit_print_http_headers(struct cgit_context *ctx);
39extern void cgit_print_docstart(struct cgit_context *ctx); 39extern void cgit_print_docstart(struct cgit_context *ctx);
40extern void cgit_print_docend(); 40extern void cgit_print_docend();
41extern void cgit_print_pageheader(struct cgit_context *ctx); 41extern void cgit_print_pageheader(struct cgit_context *ctx);
42extern void cgit_print_filemode(unsigned short mode); 42extern void cgit_print_filemode(unsigned short mode);
43extern void cgit_print_snapshot_links(const char *repo, const char *head, 43extern void cgit_print_snapshot_links(const char *repo, const char *head,
44 const char *hex, int snapshots); 44 const char *hex, int snapshots);
45 45
46#endif /* UI_SHARED_H */ 46#endif /* UI_SHARED_H */
diff --git a/ui-tree.c b/ui-tree.c
index 79332fc..051db7c 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -13,193 +13,193 @@
13char *curr_rev; 13char *curr_rev;
14char *match_path; 14char *match_path;
15int header = 0; 15int header = 0;
16 16
17static void print_object(const unsigned char *sha1, char *path) 17static void print_object(const unsigned char *sha1, char *path)
18{ 18{
19 enum object_type type; 19 enum object_type type;
20 char *buf; 20 char *buf;
21 unsigned long size, lineno, start, idx; 21 unsigned long size, lineno, start, idx;
22 const char *linefmt = "<tr><td class='no'><a id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a></td><td class='txt'>"; 22 const char *linefmt = "<tr><td class='no'><a id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a></td><td class='txt'>";
23 23
24 type = sha1_object_info(sha1, &size); 24 type = sha1_object_info(sha1, &size);
25 if (type == OBJ_BAD) { 25 if (type == OBJ_BAD) {
26 cgit_print_error(fmt("Bad object name: %s", 26 cgit_print_error(fmt("Bad object name: %s",
27 sha1_to_hex(sha1))); 27 sha1_to_hex(sha1)));
28 return; 28 return;
29 } 29 }
30 30
31 buf = read_sha1_file(sha1, &type, &size); 31 buf = read_sha1_file(sha1, &type, &size);
32 if (!buf) { 32 if (!buf) {
33 cgit_print_error(fmt("Error reading object %s", 33 cgit_print_error(fmt("Error reading object %s",
34 sha1_to_hex(sha1))); 34 sha1_to_hex(sha1)));
35 return; 35 return;
36 } 36 }
37 37
38 html(" ("); 38 html(" (");
39 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 39 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
40 curr_rev, path); 40 curr_rev, path);
41 htmlf(")<br/>blob: %s", sha1_to_hex(sha1)); 41 htmlf(")<br/>blob: %s", sha1_to_hex(sha1));
42 42
43 html("<table summary='blob content' class='blob'>\n"); 43 html("<table summary='blob content' class='blob'>\n");
44 idx = 0; 44 idx = 0;
45 start = 0; 45 start = 0;
46 lineno = 0; 46 lineno = 0;
47 while(idx < size) { 47 while(idx < size) {
48 if (buf[idx] == '\n') { 48 if (buf[idx] == '\n') {
49 buf[idx] = '\0'; 49 buf[idx] = '\0';
50 htmlf(linefmt, ++lineno); 50 htmlf(linefmt, ++lineno);
51 html_txt(buf + start); 51 html_txt(buf + start);
52 html("</td></tr>\n"); 52 html("</td></tr>\n");
53 start = idx + 1; 53 start = idx + 1;
54 } 54 }
55 idx++; 55 idx++;
56 } 56 }
57 htmlf(linefmt, ++lineno); 57 htmlf(linefmt, ++lineno);
58 html_txt(buf + start); 58 html_txt(buf + start);
59 html("</td></tr>\n"); 59 html("</td></tr>\n");
60 html("</table>\n"); 60 html("</table>\n");
61} 61}
62 62
63 63
64static int ls_item(const unsigned char *sha1, const char *base, int baselen, 64static int ls_item(const unsigned char *sha1, const char *base, int baselen,
65 const char *pathname, unsigned int mode, int stage, 65 const char *pathname, unsigned int mode, int stage,
66 void *cbdata) 66 void *cbdata)
67{ 67{
68 char *name; 68 char *name;
69 char *fullpath; 69 char *fullpath;
70 enum object_type type; 70 enum object_type type;
71 unsigned long size = 0; 71 unsigned long size = 0;
72 72
73 name = xstrdup(pathname); 73 name = xstrdup(pathname);
74 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 74 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
75 ctx.qry.path ? "/" : "", name); 75 ctx.qry.path ? "/" : "", name);
76 76
77 if (!S_ISGITLINK(mode)) { 77 if (!S_ISGITLINK(mode)) {
78 type = sha1_object_info(sha1, &size); 78 type = sha1_object_info(sha1, &size);
79 if (type == OBJ_BAD) { 79 if (type == OBJ_BAD) {
80 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 80 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
81 name, 81 name,
82 sha1_to_hex(sha1)); 82 sha1_to_hex(sha1));
83 return 0; 83 return 0;
84 } 84 }
85 } 85 }
86 86
87 html("<tr><td class='ls-mode'>"); 87 html("<tr><td class='ls-mode'>");
88 cgit_print_filemode(mode); 88 cgit_print_filemode(mode);
89 html("</td><td>"); 89 html("</td><td>");
90 if (S_ISGITLINK(mode)) { 90 if (S_ISGITLINK(mode)) {
91 htmlf("<a class='ls-mod' href='"); 91 htmlf("<a class='ls-mod' href='");
92 html_attr(fmt(ctx.repo->module_link, 92 html_attr(fmt(ctx.repo->module_link,
93 name, 93 name,
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 html("'>"); 95 html("'>");
96 html_txt(name); 96 html_txt(name);
97 html("</a>"); 97 html("</a>");
98 } else if (S_ISDIR(mode)) { 98 } else if (S_ISDIR(mode)) {
99 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 99 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
100 curr_rev, fullpath); 100 curr_rev, fullpath);
101 } else { 101 } else {
102 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head, 102 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head,
103 curr_rev, fullpath); 103 curr_rev, fullpath);
104 } 104 }
105 htmlf("</td><td class='ls-size'>%li</td>", size); 105 htmlf("</td><td class='ls-size'>%li</td>", size);
106 106
107 html("<td>"); 107 html("<td>");
108 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 108 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
109 fullpath, 0, NULL, NULL); 109 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
110 html("</td></tr>\n"); 110 html("</td></tr>\n");
111 free(name); 111 free(name);
112 return 0; 112 return 0;
113} 113}
114 114
115static void ls_head() 115static void ls_head()
116{ 116{
117 html("<table summary='tree listing' class='list'>\n"); 117 html("<table summary='tree listing' class='list'>\n");
118 html("<tr class='nohover'>"); 118 html("<tr class='nohover'>");
119 html("<th class='left'>Mode</th>"); 119 html("<th class='left'>Mode</th>");
120 html("<th class='left'>Name</th>"); 120 html("<th class='left'>Name</th>");
121 html("<th class='right'>Size</th>"); 121 html("<th class='right'>Size</th>");
122 html("<th/>"); 122 html("<th/>");
123 html("</tr>\n"); 123 html("</tr>\n");
124 header = 1; 124 header = 1;
125} 125}
126 126
127static void ls_tail() 127static void ls_tail()
128{ 128{
129 if (!header) 129 if (!header)
130 return; 130 return;
131 html("</table>\n"); 131 html("</table>\n");
132 header = 0; 132 header = 0;
133} 133}
134 134
135static void ls_tree(const unsigned char *sha1, char *path) 135static void ls_tree(const unsigned char *sha1, char *path)
136{ 136{
137 struct tree *tree; 137 struct tree *tree;
138 138
139 tree = parse_tree_indirect(sha1); 139 tree = parse_tree_indirect(sha1);
140 if (!tree) { 140 if (!tree) {
141 cgit_print_error(fmt("Not a tree object: %s", 141 cgit_print_error(fmt("Not a tree object: %s",
142 sha1_to_hex(sha1))); 142 sha1_to_hex(sha1)));
143 return; 143 return;
144 } 144 }
145 145
146 ls_head(); 146 ls_head();
147 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 147 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
148 ls_tail(); 148 ls_tail();
149} 149}
150 150
151 151
152static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 152static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
153 const char *pathname, unsigned mode, int stage, 153 const char *pathname, unsigned mode, int stage,
154 void *cbdata) 154 void *cbdata)
155{ 155{
156 static int state; 156 static int state;
157 static char buffer[PATH_MAX]; 157 static char buffer[PATH_MAX];
158 char *url; 158 char *url;
159 159
160 if (state == 0) { 160 if (state == 0) {
161 memcpy(buffer, base, baselen); 161 memcpy(buffer, base, baselen);
162 strcpy(buffer+baselen, pathname); 162 strcpy(buffer+baselen, pathname);
163 url = cgit_pageurl(ctx.qry.repo, "tree", 163 url = cgit_pageurl(ctx.qry.repo, "tree",
164 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 164 fmt("h=%s&amp;path=%s", curr_rev, buffer));
165 html("/"); 165 html("/");
166 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 166 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
167 curr_rev, buffer); 167 curr_rev, buffer);
168 168
169 if (strcmp(match_path, buffer)) 169 if (strcmp(match_path, buffer))
170 return READ_TREE_RECURSIVE; 170 return READ_TREE_RECURSIVE;
171 171
172 if (S_ISDIR(mode)) { 172 if (S_ISDIR(mode)) {
173 state = 1; 173 state = 1;
174 ls_head(); 174 ls_head();
175 return READ_TREE_RECURSIVE; 175 return READ_TREE_RECURSIVE;
176 } else { 176 } else {
177 print_object(sha1, buffer); 177 print_object(sha1, buffer);
178 return 0; 178 return 0;
179 } 179 }
180 } 180 }
181 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 181 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
182 return 0; 182 return 0;
183} 183}
184 184
185 185
186/* 186/*
187 * Show a tree or a blob 187 * Show a tree or a blob
188 * rev: the commit pointing at the root tree object 188 * rev: the commit pointing at the root tree object
189 * path: path to tree or blob 189 * path: path to tree or blob
190 */ 190 */
191void cgit_print_tree(const char *rev, char *path) 191void cgit_print_tree(const char *rev, char *path)
192{ 192{
193 unsigned char sha1[20]; 193 unsigned char sha1[20];
194 struct commit *commit; 194 struct commit *commit;
195 const char *paths[] = {path, NULL}; 195 const char *paths[] = {path, NULL};
196 196
197 if (!rev) 197 if (!rev)
198 rev = ctx.qry.head; 198 rev = ctx.qry.head;
199 199
200 curr_rev = xstrdup(rev); 200 curr_rev = xstrdup(rev);
201 if (get_sha1(rev, sha1)) { 201 if (get_sha1(rev, sha1)) {
202 cgit_print_error(fmt("Invalid revision name: %s", rev)); 202 cgit_print_error(fmt("Invalid revision name: %s", rev));
203 return; 203 return;
204 } 204 }
205 commit = lookup_commit_reference(sha1); 205 commit = lookup_commit_reference(sha1);