]> gitweb.ps.run Git - ps-cgit/blob - ui-tree.c
fix building with clang
[ps-cgit] / ui-tree.c
1 /* ui-tree.c: functions for tree output
2  *
3  * Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
4  *
5  * Licensed under GNU General Public License v2
6  *   (see COPYING for full license text)
7  */
8
9 #include "cgit.h"
10 #include "ui-tree.h"
11 #include "html.h"
12 #include "ui-shared.h"
13
14 struct walk_tree_context {
15         char *curr_rev;
16         char *match_path;
17         int state;
18 };
19
20 static void print_text_buffer(const char *name, char *buf, unsigned long size)
21 {
22         unsigned long lineno, idx;
23         const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
24
25         html("<table summary='blob content' class='blob'>\n");
26
27         if (ctx.cfg.enable_tree_linenumbers) {
28                 html("<tr><td class='linenumbers'><pre>");
29                 idx = 0;
30                 lineno = 0;
31
32                 if (size) {
33                         htmlf(numberfmt, ++lineno);
34                         while (idx < size - 1) { // skip absolute last newline
35                                 if (buf[idx] == '\n')
36                                         htmlf(numberfmt, ++lineno);
37                                 idx++;
38                         }
39                 }
40                 html("</pre></td>\n");
41         }
42         else {
43                 html("<tr>\n");
44         }
45
46         if (ctx.repo->source_filter) {
47                 char *filter_arg = xstrdup(name);
48                 html("<td class='lines'><pre><code>");
49                 cgit_open_filter(ctx.repo->source_filter, filter_arg);
50                 html_raw(buf, size);
51                 cgit_close_filter(ctx.repo->source_filter);
52                 free(filter_arg);
53                 html("</code></pre></td></tr></table>\n");
54                 return;
55         }
56
57         html("<td class='lines'><pre><code>");
58         html_txt(buf);
59         html("</code></pre></td></tr></table>\n");
60 }
61
62 #define ROWLEN 32
63
64 static void print_binary_buffer(char *buf, unsigned long size)
65 {
66         unsigned long ofs, idx;
67         static char ascii[ROWLEN + 1];
68
69         html("<table summary='blob content' class='bin-blob'>\n");
70         html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
71         for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
72                 htmlf("<tr><td class='right'>%04lx</td><td class='hex'>", ofs);
73                 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
74                         htmlf("%*s%02x",
75                               idx == 16 ? 4 : 1, "",
76                               buf[idx] & 0xff);
77                 html(" </td><td class='hex'>");
78                 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
79                         ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
80                 ascii[idx] = '\0';
81                 html_txt(ascii);
82                 html("</td></tr>\n");
83         }
84         html("</table>\n");
85 }
86
87 static void print_object(const struct object_id *oid, const char *path, const char *basename, const char *rev)
88 {
89         enum object_type type;
90         char *buf;
91         unsigned long size;
92         bool is_binary;
93
94         type = oid_object_info(the_repository, oid, &size);
95         if (type == OBJ_BAD) {
96                 cgit_print_error_page(404, "Not found",
97                         "Bad object name: %s", oid_to_hex(oid));
98                 return;
99         }
100
101         buf = repo_read_object_file(the_repository, oid, &type, &size);
102         if (!buf) {
103                 cgit_print_error_page(500, "Internal server error",
104                         "Error reading object %s", oid_to_hex(oid));
105                 return;
106         }
107         is_binary = buffer_is_binary(buf, size);
108
109         cgit_set_title_from_path(path);
110
111         cgit_print_layout_start();
112         htmlf("blob: %s (", oid_to_hex(oid));
113         cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
114                         rev, path);
115         if (ctx.repo->enable_blame && !is_binary) {
116                 html(") (");
117                 cgit_blame_link("blame", NULL, NULL, ctx.qry.head,
118                                 rev, path);
119         }
120         html(")\n");
121
122         if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
123                 htmlf("<div class='error'>blob size (%ldKB) exceeds display size limit (%dKB).</div>",
124                                 size / 1024, ctx.cfg.max_blob_size);
125                 return;
126         }
127
128         if (is_binary)
129                 print_binary_buffer(buf, size);
130         else
131                 print_text_buffer(basename, buf, size);
132
133         free(buf);
134 }
135
136 struct single_tree_ctx {
137         struct strbuf *path;
138         struct object_id oid;
139         char *name;
140         size_t count;
141 };
142
143 static int single_tree_cb(const struct object_id *oid, struct strbuf *base,
144                           const char *pathname, unsigned mode, void *cbdata)
145 {
146         struct single_tree_ctx *ctx = cbdata;
147
148         if (++ctx->count > 1)
149                 return -1;
150
151         if (!S_ISDIR(mode)) {
152                 ctx->count = 2;
153                 return -1;
154         }
155
156         ctx->name = xstrdup(pathname);
157         oidcpy(&ctx->oid, oid);
158         strbuf_addf(ctx->path, "/%s", pathname);
159         return 0;
160 }
161
162 static void write_tree_link(const struct object_id *oid, char *name,
163                             char *rev, struct strbuf *fullpath)
164 {
165         size_t initial_length = fullpath->len;
166         struct tree *tree;
167         struct single_tree_ctx tree_ctx = {
168                 .path = fullpath,
169                 .count = 1,
170         };
171         struct pathspec paths = {
172                 .nr = 0
173         };
174
175         oidcpy(&tree_ctx.oid, oid);
176
177         while (tree_ctx.count == 1) {
178                 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev,
179                                fullpath->buf);
180
181                 tree = lookup_tree(the_repository, &tree_ctx.oid);
182                 if (!tree)
183                         return;
184
185                 free(tree_ctx.name);
186                 tree_ctx.name = NULL;
187                 tree_ctx.count = 0;
188
189                 read_tree(the_repository, tree, &paths, single_tree_cb, &tree_ctx);
190
191                 if (tree_ctx.count != 1)
192                         break;
193
194                 html(" / ");
195                 name = tree_ctx.name;
196         }
197
198         strbuf_setlen(fullpath, initial_length);
199 }
200
201 static int ls_item(const struct object_id *oid, struct strbuf *base,
202                 const char *pathname, unsigned mode, void *cbdata)
203 {
204         struct walk_tree_context *walk_tree_ctx = cbdata;
205         char *name;
206         struct strbuf fullpath = STRBUF_INIT;
207         struct strbuf linkpath = STRBUF_INIT;
208         struct strbuf class = STRBUF_INIT;
209         enum object_type type;
210         unsigned long size = 0;
211         char *buf;
212
213         name = xstrdup(pathname);
214         strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "",
215                     ctx.qry.path ? "/" : "", name);
216
217         if (!S_ISGITLINK(mode)) {
218                 type = oid_object_info(the_repository, oid, &size);
219                 if (type == OBJ_BAD) {
220                         htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
221                               name,
222                               oid_to_hex(oid));
223                         goto cleanup;
224                 }
225         }
226
227         html("<tr><td class='ls-mode'>");
228         cgit_print_filemode(mode);
229         html("</td><td>");
230         if (S_ISGITLINK(mode)) {
231                 cgit_submodule_link("ls-mod", fullpath.buf, oid_to_hex(oid));
232         } else if (S_ISDIR(mode)) {
233                 write_tree_link(oid, name, walk_tree_ctx->curr_rev,
234                                 &fullpath);
235         } else {
236                 char *ext = strrchr(name, '.');
237                 strbuf_addstr(&class, "ls-blob");
238                 if (ext)
239                         strbuf_addf(&class, " %s", ext + 1);
240                 cgit_tree_link(name, NULL, class.buf, ctx.qry.head,
241                                walk_tree_ctx->curr_rev, fullpath.buf);
242         }
243         if (S_ISLNK(mode)) {
244                 html(" -> ");
245                 buf = repo_read_object_file(the_repository, oid, &type, &size);
246                 if (!buf) {
247                         htmlf("Error reading object: %s", oid_to_hex(oid));
248                         goto cleanup;
249                 }
250                 strbuf_addbuf(&linkpath, &fullpath);
251                 strbuf_addf(&linkpath, "/../%s", buf);
252                 strbuf_normalize_path(&linkpath);
253                 cgit_tree_link(buf, NULL, class.buf, ctx.qry.head,
254                         walk_tree_ctx->curr_rev, linkpath.buf);
255                 free(buf);
256                 strbuf_release(&linkpath);
257         }
258         htmlf("</td><td class='ls-size'>%li</td>", size);
259
260         html("<td>");
261         cgit_log_link("log", NULL, "button", ctx.qry.head,
262                       walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL,
263                       ctx.qry.showmsg, 0);
264         if (ctx.repo->max_stats)
265                 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
266                                 fullpath.buf);
267         if (!S_ISGITLINK(mode))
268                 cgit_plain_link("plain", NULL, "button", ctx.qry.head,
269                                 walk_tree_ctx->curr_rev, fullpath.buf);
270         if (!S_ISDIR(mode) && ctx.repo->enable_blame)
271                 cgit_blame_link("blame", NULL, "button", ctx.qry.head,
272                                 walk_tree_ctx->curr_rev, fullpath.buf);
273         html("</td></tr>\n");
274
275 cleanup:
276         free(name);
277         strbuf_release(&fullpath);
278         strbuf_release(&class);
279         return 0;
280 }
281
282 static void ls_head(void)
283 {
284         cgit_print_layout_start();
285         html("<table summary='tree listing' class='list'>\n");
286         html("<tr class='nohover'>");
287         html("<th class='left'>Mode</th>");
288         html("<th class='left'>Name</th>");
289         html("<th class='right'>Size</th>");
290         html("<th/>");
291         html("</tr>\n");
292 }
293
294 static void ls_tail(void)
295 {
296         html("</table>\n");
297         cgit_print_layout_end();
298 }
299
300 static void ls_tree(const struct object_id *oid, const char *path, struct walk_tree_context *walk_tree_ctx)
301 {
302         struct tree *tree;
303         struct pathspec paths = {
304                 .nr = 0
305         };
306
307         tree = parse_tree_indirect(oid);
308         if (!tree) {
309                 cgit_print_error_page(404, "Not found",
310                         "Not a tree object: %s", oid_to_hex(oid));
311                 return;
312         }
313
314         ls_head();
315         read_tree(the_repository, tree, &paths, ls_item, walk_tree_ctx);
316         ls_tail();
317 }
318
319
320 static int walk_tree(const struct object_id *oid, struct strbuf *base,
321                 const char *pathname, unsigned mode, void *cbdata)
322 {
323         struct walk_tree_context *walk_tree_ctx = cbdata;
324
325         if (walk_tree_ctx->state == 0) {
326                 struct strbuf buffer = STRBUF_INIT;
327
328                 strbuf_addbuf(&buffer, base);
329                 strbuf_addstr(&buffer, pathname);
330                 if (strcmp(walk_tree_ctx->match_path, buffer.buf))
331                         return READ_TREE_RECURSIVE;
332
333                 if (S_ISDIR(mode)) {
334                         walk_tree_ctx->state = 1;
335                         cgit_set_title_from_path(buffer.buf);
336                         strbuf_release(&buffer);
337                         ls_head();
338                         return READ_TREE_RECURSIVE;
339                 } else {
340                         walk_tree_ctx->state = 2;
341                         print_object(oid, buffer.buf, pathname, walk_tree_ctx->curr_rev);
342                         strbuf_release(&buffer);
343                         return 0;
344                 }
345         }
346         ls_item(oid, base, pathname, mode, walk_tree_ctx);
347         return 0;
348 }
349
350 /*
351  * Show a tree or a blob
352  *   rev:  the commit pointing at the root tree object
353  *   path: path to tree or blob
354  */
355 void cgit_print_tree(const char *rev, char *path)
356 {
357         struct object_id oid;
358         struct commit *commit;
359         struct pathspec_item path_items = {
360                 .match = path,
361                 .len = path ? strlen(path) : 0
362         };
363         struct pathspec paths = {
364                 .nr = path ? 1 : 0,
365                 .items = &path_items
366         };
367         struct walk_tree_context walk_tree_ctx = {
368                 .match_path = path,
369                 .state = 0
370         };
371
372         if (!rev)
373                 rev = ctx.qry.head;
374
375         if (repo_get_oid(the_repository, rev, &oid)) {
376                 cgit_print_error_page(404, "Not found",
377                         "Invalid revision name: %s", rev);
378                 return;
379         }
380         commit = lookup_commit_reference(the_repository, &oid);
381         if (!commit || repo_parse_commit(the_repository, commit)) {
382                 cgit_print_error_page(404, "Not found",
383                         "Invalid commit reference: %s", rev);
384                 return;
385         }
386
387         walk_tree_ctx.curr_rev = xstrdup(rev);
388
389         if (path == NULL) {
390                 ls_tree(get_commit_tree_oid(commit), NULL, &walk_tree_ctx);
391                 goto cleanup;
392         }
393
394         read_tree(the_repository, repo_get_commit_tree(the_repository, commit),
395                   &paths, walk_tree, &walk_tree_ctx);
396         if (walk_tree_ctx.state == 1)
397                 ls_tail();
398         else if (walk_tree_ctx.state == 2)
399                 cgit_print_layout_end();
400         else
401                 cgit_print_error_page(404, "Not found", "Path not found");
402
403 cleanup:
404         free(walk_tree_ctx.curr_rev);
405 }