neocgit

a more 'modern' version of cgit
Log | Files | Refs | Submodules | README | LICENSE | git clone https://git.ne02ptzero.me/git/neocgit

ui-tree.c (16751B)


      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 #include "ui-log.h"
     14 
     15 struct tree_link {
     16     const struct object_id *oid;
     17     struct strbuf          *base;
     18     char                   *pathname;
     19     unsigned               mode;
     20     int                    stage;
     21     struct tree_link       *next;
     22     struct tree_link       *prev;
     23 };
     24 
     25 struct walk_tree_context {
     26 	char *curr_rev;
     27 	char *match_path;
     28 	int state;
     29         struct tree_link *head;
     30 };
     31 
     32 static bool format_match(const char **array, size_t size, const char *filename)
     33 {
     34     int format_cur;
     35 
     36     for (format_cur = strlen(filename); format_cur > 0 && filename[format_cur] != '.'; format_cur--)
     37         ;
     38 
     39     if (format_cur == 0)
     40         return false;
     41 
     42     for (size_t i = 0; i < size; i++)
     43     { 
     44         if (strcmp(array[i], filename + format_cur) == 0)
     45             return true;
     46     }
     47 
     48     return false;
     49 }
     50 
     51 #define COUNT_OF(arr) (sizeof(arr) / sizeof(arr[0]))
     52 static bool file_is_code(const char *filename)
     53 {
     54     const char *formats[] = {
     55         ".c", ".cpp", ".h", ".hpp", ".cxx", ".cc",
     56         ".js", ".html", ".css", ".py", ".php" // XXX
     57     };
     58 
     59     return format_match(formats, COUNT_OF(formats), filename);
     60 }
     61 
     62 static bool file_is_audio(const char *filename)
     63 {
     64     const char *formats[] = {
     65         ".3gp", ".aa", ".aac", ".aax", ".act", ".aiff", ".amr", ".ape", ".au",
     66         ".awb", ".dct", ".dss", ".dvf", ".flac", ".gsm", ".m4a", ".m4b", ".m4p",
     67         ".mmf", ".mp3", ".mpc", ".msv", ".nsf", ".ogg", ".oga", ".mogg", ".opus",
     68         ".ra", ".rm", ".sln", ".tta", ".vox", ".wav", ".wma", ".wv", ".webm"
     69     };
     70 
     71     return format_match(formats, COUNT_OF(formats), filename);
     72 }
     73 
     74 static bool file_is_pdf(const char *filename)
     75 {
     76     const char *formats[] = {
     77         ".pdf"
     78     };
     79 
     80     return format_match(formats, COUNT_OF(formats), filename);
     81 }
     82 
     83 static bool file_is_video(const char *filename)
     84 {
     85     const char *formats[] = {
     86         ".yuv", ".wmv", ".vob", ".svi", ".roq", ".rmvb", ".mpg", ".mpeg", ".m2v",
     87         ".mp2", ".mpe", ".mpv", ".mp4", ".m4p", ".m4v", ".mov", ".qt", ".mng",
     88         ".mkv", ".m4v", ".gifv", ".gif", ".flv", ".drc", ".avi", ".asf", ".amv",
     89         ".3gp", ".3g2"
     90     };
     91 
     92     return format_match(formats, COUNT_OF(formats), filename);
     93 }
     94 
     95 static bool file_is_archive(const char *filename)
     96 {
     97     const char *formats[] = {
     98         ".tar", ".gz", ".zip", ".ar", ".a"
     99     };
    100 
    101     return format_match(formats, COUNT_OF(formats), filename);
    102 }
    103 
    104 static bool file_is_image(const char *filename)
    105 {
    106     const char *formats[] = {
    107         ".png", ".jpg", ".jpeg", ".webp", ".svg", ".ai", ".eps"
    108     };
    109 
    110     return format_match(formats, COUNT_OF(formats), filename);
    111 }
    112 
    113 void print_file_icon(const char *filename, int mode)
    114 {
    115     if (S_ISLNK(mode))
    116         html("<i class='fa fa-file-export'></i>");
    117     else if (S_ISDIR(mode))
    118         html("<i class='fa fa-folder'></i>");
    119     else
    120     {
    121         if (file_is_code(filename))
    122             html("<i class='far fa-file-code'></i>");
    123         else if (file_is_audio(filename))
    124             html("<i class='far fa-file-audio'></i>");
    125         else if (file_is_video(filename))
    126             html("<i class='far fa-file-video'></i>");
    127         else if (file_is_pdf(filename))
    128             html("<i class='far fa-file-pdf'></i>");
    129         else if (file_is_archive(filename))
    130             html("<i class='far fa-file-archive'></i>");
    131         else if (file_is_image(filename))
    132             html("<i class='far fa-file-image'></i>");
    133         else
    134             html("<i class='far fa-file-alt'></i>");
    135     }
    136 }
    137 
    138 
    139 
    140 static void print_text_buffer(const char *name, char *buf, unsigned long size)
    141 {
    142 	unsigned long lineno, idx;
    143 	const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
    144 
    145 	html("<table summary='blob content' class='blob'>\n");
    146 
    147 	if (ctx.cfg.enable_tree_linenumbers) {
    148 		html("<tr><td class='linenumbers'><pre>");
    149 		idx = 0;
    150 		lineno = 0;
    151 
    152 		if (size) {
    153 			htmlf(numberfmt, ++lineno);
    154 			while (idx < size - 1) { // skip absolute last newline
    155 				if (buf[idx] == '\n')
    156 					htmlf(numberfmt, ++lineno);
    157 				idx++;
    158 			}
    159 		}
    160 		html("</pre></td>\n");
    161 	}
    162 	else {
    163 		html("<tr>\n");
    164 	}
    165 
    166 	if (ctx.repo->source_filter) {
    167 		char *filter_arg = xstrdup(name);
    168 		html("<td class='lines'><pre><code>");
    169 		cgit_open_filter(ctx.repo->source_filter, filter_arg);
    170 		html_raw(buf, size);
    171 		cgit_close_filter(ctx.repo->source_filter);
    172 		free(filter_arg);
    173 		html("</code></pre></td></tr></table>\n");
    174 		return;
    175 	}
    176 
    177 	html("<td class='lines'><pre><code>");
    178 	html_txt(buf);
    179 	html("</code></pre></td></tr></table>\n");
    180 }
    181 
    182 #define ROWLEN 32
    183 
    184 static void print_binary_buffer(char *buf, unsigned long size)
    185 {
    186 	unsigned long ofs, idx;
    187 	static char ascii[ROWLEN + 1];
    188 
    189 	html("<table summary='blob content' class='bin-blob'>\n");
    190 	html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
    191 	for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
    192 		htmlf("<tr><td class='right'>%04lx</td><td class='hex'>", ofs);
    193 		for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
    194 			htmlf("%*s%02x",
    195 			      idx == 16 ? 4 : 1, "",
    196 			      buf[idx] & 0xff);
    197 		html(" </td><td class='hex'>");
    198 		for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
    199 			ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
    200 		ascii[idx] = '\0';
    201 		html_txt(ascii);
    202 		html("</td></tr>\n");
    203 	}
    204 	html("</table>\n");
    205 }
    206 
    207 static void print_object(const struct object_id *oid, char *path, const char *basename, const char *rev)
    208 {
    209 	enum object_type type;
    210 	char *buf;
    211 	unsigned long size;
    212 
    213 	type = oid_object_info(the_repository, oid, &size);
    214 	if (type == OBJ_BAD) {
    215 		cgit_print_error_page(404, "Not found",
    216 			"Bad object name: %s", oid_to_hex(oid));
    217 		return;
    218 	}
    219 
    220 	buf = read_object_file(oid, &type, &size);
    221 	if (!buf) {
    222 		cgit_print_error_page(500, "Internal server error",
    223 			"Error reading object %s", oid_to_hex(oid));
    224 		return;
    225 	}
    226 
    227 	cgit_set_title_from_path(path);
    228 
    229 	cgit_print_layout_start();
    230 
    231 	if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
    232 		htmlf("<div class='error'>blob size (%ldKB) exceeds display size limit (%dKB).</div>",
    233 				size / 1024, ctx.cfg.max_blob_size);
    234 		return;
    235 	}
    236 
    237         _html("<div class='repo-file'>") {
    238 
    239             _html("<div class='header'>") {
    240                 print_file_icon(basename, 0);
    241                 htmlf("<strong>%s</strong>", basename);
    242                 htmlf("<span class='size'>%zu Bytes</span>", size);
    243 
    244                _html("<ul class='file-link'>") {
    245                     htmlf("<li class='blame'>"
    246                             "<a href='/%s/blame/%s/?%s=%s'>Blame</a>"
    247                         "</li>",
    248                         ctx.repo->name, path,
    249                         ctx.qry.sha1 != NULL ? "id" : "h",
    250                         ctx.qry.sha1 != NULL ? ctx.qry.sha1 : ctx.qry.head);
    251                     htmlf("<li class='raw'>"
    252                             "<a href='/%s/plain/%s?%s=%s'>Raw</a>"
    253                         "</li>",
    254                         ctx.repo->name, path,
    255                         ctx.qry.sha1 != NULL ? "id" : "h",
    256                         ctx.qry.sha1 != NULL ? ctx.qry.sha1 : ctx.qry.head);
    257                 } _html("</ul>");
    258 
    259             } _html("</div>");
    260 
    261             _html("<div class='content' style='padding: 0'>") {
    262                 if (buffer_is_binary(buf, size))
    263                     print_binary_buffer(buf, size);
    264                 else
    265                     print_text_buffer(basename, buf, size);
    266             } _html("</div>");
    267 
    268         } _html("</div>");
    269 
    270 	free(buf);
    271 }
    272 
    273 struct single_tree_ctx {
    274 	struct strbuf *path;
    275 	struct object_id oid;
    276 	char *name;
    277 	size_t count;
    278 };
    279 
    280 static int single_tree_cb(const struct object_id *oid, struct strbuf *base,
    281 			  const char *pathname, unsigned mode, int stage,
    282 			  void *cbdata)
    283 {
    284 	struct single_tree_ctx *ctx = cbdata;
    285 
    286 	if (++ctx->count > 1)
    287 		return -1;
    288 
    289 	if (!S_ISDIR(mode)) {
    290 		ctx->count = 2;
    291 		return -1;
    292 	}
    293 
    294 	ctx->name = xstrdup(pathname);
    295 	oidcpy(&ctx->oid, oid);
    296 	strbuf_addf(ctx->path, "/%s", pathname);
    297 	return 0;
    298 }
    299 
    300 static void write_tree_link(const struct object_id *oid, char *name,
    301 			    char *rev, struct strbuf *fullpath)
    302 {
    303 	size_t initial_length = fullpath->len;
    304 	struct tree *tree;
    305 	struct single_tree_ctx tree_ctx = {
    306 		.path = fullpath,
    307 		.count = 1,
    308 	};
    309 	struct pathspec paths = {
    310 		.nr = 0
    311 	};
    312 
    313 	oidcpy(&tree_ctx.oid, oid);
    314 
    315 	while (tree_ctx.count == 1) {
    316 		cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev,
    317 			       fullpath->buf);
    318 
    319 		tree = lookup_tree(&tree_ctx.oid);
    320 		if (!tree)
    321 			return;
    322 
    323 		free(tree_ctx.name);
    324 		tree_ctx.name = NULL;
    325 		tree_ctx.count = 0;
    326 
    327 		read_tree_recursive(tree, "", 0, 1, &paths, single_tree_cb,
    328 				    &tree_ctx);
    329 
    330 		if (tree_ctx.count != 1)
    331 			break;
    332 
    333 		html(" / ");
    334 		name = tree_ctx.name;
    335 	}
    336 
    337 	strbuf_setlen(fullpath, initial_length);
    338 }
    339 
    340 static void print_tree_item(struct tree_link *ptr, struct walk_tree_context *tree_ctx)
    341 {
    342     struct strbuf       fullpath = STRBUF_INIT;
    343     struct commitinfo   *info = NULL;
    344     struct commit       *commit = NULL;
    345     char                *name;
    346 
    347     name = xstrdup(ptr->pathname);
    348     strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "",
    349         ctx.qry.path ? "/" : "", name);
    350 
    351     commit = cgit_get_last_commit_from_path(fullpath.buf);
    352     if (commit)
    353         info = cgit_parse_commit(commit);
    354 
    355     _htmlf("<tr onclick=\"location.href='/%s/tree/%s?%s=%s'\" class='tree-entry'>",
    356         ctx.repo->name, fullpath.buf, ctx.qry.sha1 != NULL ? "id" : "h",
    357         ctx.qry.sha1 != NULL ? ctx.qry.sha1 : ctx.qry.head) {
    358 
    359         _html("<td class='table-left file-path'>") {
    360             print_file_icon(name, ptr->mode);
    361             htmlf("<span>%s</span>", name);
    362         } _html("</td>");
    363 
    364         _html("<td class='table-left'>") {
    365            if (info)
    366                 htmlf("<a href='/%s/commit/?id=%s'>%s</a>", ctx.repo->name, oid_to_hex(&commit->object.oid), info->subject);
    367             else
    368                 html("[ERROR]");
    369         } _html("</td>");
    370 
    371         _html("<td class='table-right'>") {
    372             if (info)
    373             {
    374                 cgit_print_age(info->author_date, info->author_tz, -1);
    375                 html(" ago");
    376             }
    377             else
    378                 html("ERROR");
    379         } _html("</td>");
    380     } _html("</tr>");
    381 
    382     if (commit)
    383     {
    384         free_commit_buffer(commit);
    385         free(info);
    386     }
    387 
    388     free(name);
    389 }
    390 
    391 static int ls_item(const struct object_id *oid, struct strbuf *base,
    392 		const char *pathname, unsigned mode, int stage, void *cbdata)
    393 {
    394     struct tree_link *obj;
    395     struct walk_tree_context *walk_tree_ctx = cbdata;
    396 
    397     obj = calloc(1, sizeof(*obj));
    398     obj->oid = oid;
    399     obj->base = base;
    400     obj->pathname = strdup(pathname);
    401     obj->mode = mode;
    402     obj->stage = stage;
    403 
    404     if (walk_tree_ctx->head == NULL)
    405         walk_tree_ctx->head = obj;
    406     else
    407     {
    408         struct tree_link *tmp;
    409 
    410         for (tmp = walk_tree_ctx->head; tmp->next != NULL; tmp = tmp->next)
    411         {
    412             if (strcmp(tmp->pathname, pathname) > 0
    413                 || (S_ISDIR(mode) && !S_ISDIR(tmp->mode)))
    414             {
    415                 break ;
    416             }
    417         }
    418 
    419         if (tmp->next == NULL && (S_ISDIR(tmp->mode) || (!S_ISDIR(mode) && !S_ISDIR(tmp->mode))))
    420         {
    421             obj->prev = tmp;
    422             tmp->next = obj;
    423         }
    424         else if (tmp->prev == NULL)
    425         {
    426             tmp->prev = obj;
    427             obj->next = tmp;
    428             walk_tree_ctx->head = obj;
    429         }
    430         else
    431         {
    432             tmp->prev->next = obj;
    433             obj->next = tmp;
    434             obj->next->prev = obj;
    435         }
    436     }
    437 
    438     return 0;
    439 }
    440 
    441 static void ls_head(void)
    442 {
    443 	cgit_print_layout_start();
    444 }
    445 
    446 static void ls_tail(void)
    447 {
    448 	html("</table>\n");
    449 	cgit_print_layout_end();
    450 }
    451 
    452 static void ls_tree(const struct object_id *oid, char *path, struct walk_tree_context *walk_tree_ctx, bool layout)
    453 {
    454 	struct tree *tree;
    455 	struct pathspec paths = {
    456 		.nr = 0
    457 	};
    458 
    459         tree = parse_tree_indirect(oid);
    460         if (!tree) {
    461                 cgit_print_error_page(404, "Not found",
    462                         "Not a tree object: %s", oid_to_hex(oid));
    463                 return;
    464         }
    465 
    466         if (layout)
    467             cgit_print_layout_start();
    468 
    469         html("<div class='repository-tree'><div class='repo-tree'><table>");
    470         _html("<tr class='table-title'>") {
    471             html("<th class='table-left'>Name</th>");
    472             html("<th class='table-left'>Last commit</th>");
    473             html("<th class='table-right'>Last update</th>");
    474         } _html("</tr>");
    475 
    476         if (ctx.qry.path)
    477         {
    478             htmlf("<tr onclick=\"location.href='/%s/tree/%s/../?%s=%s'\" class='tree-entry'>"
    479                 "<td><span>..</span></td><td></td><td></td></tr>",
    480                 ctx.repo->name, ctx.qry.path,
    481                 ctx.qry.sha1 != NULL ? "id" : "h",
    482                 ctx.qry.sha1 != NULL ? ctx.qry.sha1 : ctx.qry.head);
    483         }
    484 
    485         walk_tree_ctx->head = NULL;
    486         read_tree_recursive(tree, "", 0, 1, &paths, ls_item, walk_tree_ctx);
    487         free_tree_buffer(tree);
    488 
    489         for (struct tree_link *tmp = walk_tree_ctx->head; tmp; tmp = tmp->next)
    490         {
    491             print_tree_item(tmp, walk_tree_ctx);
    492         }
    493 
    494         html("</table></div></div>");
    495 
    496         if (layout)
    497             cgit_print_layout_end();
    498 }
    499 
    500 
    501 static int walk_tree(const struct object_id *oid, struct strbuf *base,
    502 		const char *pathname, unsigned mode, int stage, void *cbdata)
    503 {
    504 	struct walk_tree_context *walk_tree_ctx = cbdata;
    505 
    506 	if (walk_tree_ctx->state == 0) {
    507 		struct strbuf buffer = STRBUF_INIT;
    508 
    509 		strbuf_addbuf(&buffer, base);
    510 		strbuf_addstr(&buffer, pathname);
    511 		if (strcmp(walk_tree_ctx->match_path, buffer.buf))
    512 			return READ_TREE_RECURSIVE;
    513 
    514 		if (S_ISDIR(mode)) {
    515 			walk_tree_ctx->state = 1;
    516 			cgit_set_title_from_path(buffer.buf);
    517 			strbuf_release(&buffer);
    518 			ls_head();
    519 			return READ_TREE_RECURSIVE;
    520 		} else {
    521 			walk_tree_ctx->state = 2;
    522 			print_object(oid, buffer.buf, pathname, walk_tree_ctx->curr_rev);
    523 			strbuf_release(&buffer);
    524 			return 0;
    525 		}
    526 	}
    527 	ls_item(oid, base, pathname, mode, stage, walk_tree_ctx);
    528 	return 0;
    529 }
    530 
    531 /*
    532  * Show a tree or a blob
    533  *   rev:  the commit pointing at the root tree object
    534  *   path: path to tree or blob
    535  */
    536 void cgit_print_tree(const char *rev, char *path, bool layout)
    537 {
    538 	struct object_id oid;
    539 	struct commit *commit;
    540 	struct pathspec_item path_items = {
    541 		.match = path,
    542 		.len = path ? strlen(path) : 0
    543 	};
    544 	struct pathspec paths = {
    545 		.nr = path ? 1 : 0,
    546 		.items = &path_items
    547 	};
    548 	struct walk_tree_context walk_tree_ctx = {
    549 		.match_path = path,
    550 		.state = 0
    551 	};
    552 
    553         if (!rev)
    554                 rev = ctx.qry.head;
    555 
    556         if (get_oid(rev, &oid)) {
    557                 cgit_print_error_page(404, "Not found",
    558                         "Invalid revision name: %s", rev);
    559                 return;
    560         }
    561         commit = lookup_commit_reference(&oid);
    562         if (!commit || parse_commit(commit)) {
    563                 cgit_print_error_page(404, "Not found",
    564                         "Invalid commit reference: %s", rev);
    565                 return;
    566         }
    567 
    568         walk_tree_ctx.curr_rev = xstrdup(rev);
    569 
    570         if (path == NULL) {
    571                 ls_tree(&commit->maybe_tree->object.oid, NULL, &walk_tree_ctx, layout);
    572                 goto cleanup;
    573         }
    574 
    575         read_tree_recursive(commit->maybe_tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx);
    576 
    577         if (walk_tree_ctx.head != NULL)
    578         {
    579             html("<div class='repository-tree'><div class='repo-tree'><table>");
    580             _html("<tr class='table-title'>") {
    581                 html("<th class='table-left'>Name</th>");
    582                 html("<th class='table-left'>Last commit</th>");
    583                 html("<th class='table-right'>Last update</th>");
    584             } _html("</tr>");
    585 
    586 
    587             if (ctx.qry.path)
    588             {
    589                 htmlf("<tr onclick=\"location.href='/%s/tree/%s/../?%s=%s'\" class='tree-entry'>"
    590                 "<td><span>..</span></td><td></td><td></td></tr>",
    591                 ctx.repo->name, ctx.qry.path,
    592                 ctx.qry.sha1 != NULL ? "id" : "h",
    593                 ctx.qry.sha1 != NULL ? ctx.qry.sha1 : ctx.qry.head);
    594             }
    595 
    596             for (struct tree_link *tmp = walk_tree_ctx.head; tmp; tmp = tmp->next)
    597                 print_tree_item(tmp, &walk_tree_ctx);
    598             html("</table></div></div>");
    599         }
    600 
    601         if (walk_tree_ctx.state == 1)
    602                 ls_tail();
    603         else if (walk_tree_ctx.state == 2)
    604 		cgit_print_layout_end();
    605 	else
    606 		cgit_print_error_page(404, "Not found", "Path not found");
    607 
    608 cleanup:
    609 	free(walk_tree_ctx.curr_rev);
    610 }