This is a PoC for grepping the blobs in the current tree.
Unified · Split
| @@ -127,7 +127,10 @@ static void tag_fn(struct cgit_context *ctx) · 126 unmodified lines | ||
| 127 | 127 | |
| 128 | 128 | static void tree_fn(struct cgit_context *ctx) |
| 129 | 129 | { |
| 130 | − cgit_print_tree(ctx->qry.sha1, ctx->qry.path); | |
| 130 | + if (ctx->qry.grep) | |
| 131 | + cgit_grep(ctx->qry.sha1, ctx->qry.path, ctx->qry.grep); | |
| 132 | + else | |
| 133 | + cgit_print_tree(ctx->qry.sha1, ctx->qry.path); | |
| 131 | 134 | } |
| 132 | 135 | |
| 133 | 136 | #define def_cmd(name, want_repo, want_layout, want_vpath, is_clone) \ |
| @@ -246,6 +246,61 @@ static char *repolink(const char *title, const char *class, const char *page, · 245 unmodified lines | ||
| 246 | 246 | return fmt("%s", delim); |
| 247 | 247 | } |
| 248 | 248 | |
| 249 | +static char *repolink2(const char *title, const char *class, const char *page, | |
| 250 | + const char *head, const char *path, int lineno) | |
| 251 | +{ | |
| 252 | + char *delim = "?"; | |
| 253 | + | |
| 254 | + html("<a"); | |
| 255 | + if (title) { | |
| 256 | + html(" title='"); | |
| 257 | + html_attr(title); | |
| 258 | + html("'"); | |
| 259 | + } | |
| 260 | + if (class) { | |
| 261 | + html(" class='"); | |
| 262 | + html_attr(class); | |
| 263 | + html("'"); | |
| 264 | + } | |
| 265 | + html(" href='"); | |
| 266 | + if (ctx.cfg.virtual_root) { | |
| 267 | + html_url_path(ctx.cfg.virtual_root); | |
| 268 | + if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') | |
| 269 | + html("/"); | |
| 270 | + html_url_path(ctx.repo->url); | |
| 271 | + if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') | |
| 272 | + html("/"); | |
| 273 | + if (page) { | |
| 274 | + html_url_path(page); | |
| 275 | + html("/"); | |
| 276 | + if (path) | |
| 277 | + html_url_path(path); | |
| 278 | + } | |
| 279 | + } else { | |
| 280 | + html(ctx.cfg.script_name); | |
| 281 | + html("?url="); | |
| 282 | + html_url_arg(ctx.repo->url); | |
| 283 | + if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') | |
| 284 | + html("/"); | |
| 285 | + if (page) { | |
| 286 | + html_url_arg(page); | |
| 287 | + html("/"); | |
| 288 | + if (path) | |
| 289 | + html_url_arg(path); | |
| 290 | + } | |
| 291 | + delim = "&"; | |
| 292 | + } | |
| 293 | + if (head && strcmp(head, ctx.repo->defbranch)) { | |
| 294 | + html(delim); | |
| 295 | + html("h="); | |
| 296 | + html_url_arg(head); | |
| 297 | + delim = "&"; | |
| 298 | + } | |
| 299 | + if (lineno) | |
| 300 | + htmlf("#n%d", lineno); | |
| 301 | + return fmt("%s", delim); | |
| 302 | +} | |
| 303 | + | |
| 249 | 304 | static void reporevlink(const char *page, const char *name, const char *title, |
| 250 | 305 | const char *class, const char *head, const char *rev, |
| 251 | 306 | const char *path) |
| @@ -263,6 +318,23 @@ static void reporevlink(const char *page, const char *name, const char *title, · 11 unmodified lines | ||
| 263 | 318 | html("</a>"); |
| 264 | 319 | } |
| 265 | 320 | |
| 321 | +static void reporevlink2(const char *page, const char *name, const char *title, | |
| 322 | + const char *class, const char *head, const char *rev, | |
| 323 | + const char *path, int lineno) | |
| 324 | +{ | |
| 325 | + char *delim; | |
| 326 | + | |
| 327 | + delim = repolink2(title, class, page, head, path, lineno); | |
| 328 | + if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { | |
| 329 | + html(delim); | |
| 330 | + html("id="); | |
| 331 | + html_url_arg(rev); | |
| 332 | + } | |
| 333 | + html("'>"); | |
| 334 | + html_txt(name); | |
| 335 | + html("</a>"); | |
| 336 | +} | |
| 337 | + | |
| 266 | 338 | void cgit_summary_link(const char *name, const char *title, const char *class, |
| 267 | 339 | const char *head) |
| 268 | 340 | { |
| @@ -281,6 +353,13 @@ void cgit_tree_link(const char *name, const char *title, const char *class, · 12 unmodified lines | ||
| 281 | 353 | reporevlink("tree", name, title, class, head, rev, path); |
| 282 | 354 | } |
| 283 | 355 | |
| 356 | +void cgit_tree_link2(const char *name, const char *title, const char *class, | |
| 357 | + const char *head, const char *rev, const char *path, | |
| 358 | + int lineno) | |
| 359 | +{ | |
| 360 | + reporevlink2("tree", name, title, class, head, rev, path, lineno); | |
| 361 | +} | |
| 362 | + | |
| 284 | 363 | void cgit_plain_link(const char *name, const char *title, const char *class, |
| 285 | 364 | const char *head, const char *rev, const char *path) |
| 286 | 365 | { |
| @@ -875,6 +954,9 @@ static void print_header(struct cgit_context *ctx) · 588 unmodified lines | ||
| 875 | 954 | |
| 876 | 955 | void cgit_print_pageheader(struct cgit_context *ctx) |
| 877 | 956 | { |
| 957 | + int tree; | |
| 958 | + char *page; | |
| 959 | + | |
| 878 | 960 | html("<div id='cgit'>"); |
| 879 | 961 | if (!ctx->cfg.noheader) |
| 880 | 962 | print_header(ctx); |
| @@ -903,16 +985,23 @@ void cgit_print_pageheader(struct cgit_context *ctx) · 22 unmodified lines | ||
| 903 | 985 | NULL); |
| 904 | 986 | html("</td><td class='form'>"); |
| 905 | 987 | html("<form class='right' method='get' action='"); |
| 906 | − if (ctx->cfg.virtual_root) | |
| 907 | − html_url_path(cgit_fileurl(ctx->qry.repo, "log", | |
| 988 | + if (ctx->cfg.virtual_root) { | |
| 989 | + tree = !strcmp(ctx->qry.page, "tree"); | |
| 990 | + page = tree ? "tree" : "log"; | |
| 991 | + html_url_path(cgit_fileurl(ctx->qry.repo, page, | |
| 908 | 992 | ctx->qry.vpath, NULL)); |
| 993 | + } | |
| 909 | 994 | html("'>\n"); |
| 910 | − cgit_add_hidden_formfields(1, 0, "log"); | |
| 995 | + cgit_add_hidden_formfields(1, 0, page); | |
| 911 | 996 | html("<select name='qt'>\n"); |
| 912 | − html_option("grep", "log msg", ctx->qry.grep); | |
| 913 | − html_option("author", "author", ctx->qry.grep); | |
| 914 | − html_option("committer", "committer", ctx->qry.grep); | |
| 915 | − html_option("range", "range", ctx->qry.grep); | |
| 997 | + if (tree) { | |
| 998 | + html_option("grep", "source", ctx->qry.grep); | |
| 999 | + } else { | |
| 1000 | + html_option("grep", "log msg", ctx->qry.grep); | |
| 1001 | + html_option("author", "author", ctx->qry.grep); | |
| 1002 | + html_option("committer", "committer", ctx->qry.grep); | |
| 1003 | + html_option("range", "range", ctx->qry.grep); | |
| 1004 | + } | |
| 916 | 1005 | html("</select>\n"); |
| 917 | 1006 | html("<input class='txt' type='text' size='10' name='q' value='"); |
| 918 | 1007 | html_attr(ctx->qry.search); |
| @@ -20,6 +20,9 @@ extern void cgit_tag_link(const char *name, const char *title, · 19 unmodified lines | ||
| 20 | 20 | extern void cgit_tree_link(const char *name, const char *title, |
| 21 | 21 | const char *class, const char *head, |
| 22 | 22 | const char *rev, const char *path); |
| 23 | +extern void cgit_tree_link2(const char *name, const char *title, | |
| 24 | + const char *class, const char *head, | |
| 25 | + const char *rev, const char *path, int lno); | |
| 23 | 26 | extern void cgit_plain_link(const char *name, const char *title, |
| 24 | 27 | const char *class, const char *head, |
| 25 | 28 | const char *rev, const char *path); |
| @@ -277,3 +277,122 @@ void cgit_print_tree(const char *rev, char *path) · 276 unmodified lines | ||
| 277 | 277 | read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); |
| 278 | 278 | ls_tail(); |
| 279 | 279 | } |
| 280 | + | |
| 281 | +struct grep_data { | |
| 282 | + regex_t re; | |
| 283 | + char *path; | |
| 284 | + int max_hits; | |
| 285 | +}; | |
| 286 | + | |
| 287 | +static int grep_blob(const unsigned char *sha1, const char *base, int baselen, | |
| 288 | + const char *pathname, struct grep_data *grep) | |
| 289 | +{ | |
| 290 | + enum object_type type; | |
| 291 | + char *buf, *line, *lf, *fullpath; | |
| 292 | + unsigned long size; | |
| 293 | + int hits, lno, rc; | |
| 294 | + | |
| 295 | + type = sha1_object_info(sha1, &size); | |
| 296 | + if (type == OBJ_BAD) { | |
| 297 | + cgit_print_error(fmt("Bad object name: %s", | |
| 298 | + sha1_to_hex(sha1))); | |
| 299 | + return -1; | |
| 300 | + } | |
| 301 | + | |
| 302 | + buf = read_sha1_file(sha1, &type, &size); | |
| 303 | + if (!buf) { | |
| 304 | + cgit_print_error(fmt("Error reading object %s", | |
| 305 | + sha1_to_hex(sha1))); | |
| 306 | + return -1; | |
| 307 | + } | |
| 308 | + hits = 0; | |
| 309 | + line = buf; | |
| 310 | + lno = 1; | |
| 311 | + while (size) { | |
| 312 | + lf = strchr(line, '\n'); | |
| 313 | + if (lf) | |
| 314 | + *lf = 0; | |
| 315 | + rc = regexec(&grep->re, line, 0, NULL, 0); | |
| 316 | + if (!rc) { | |
| 317 | + if (!hits) { | |
| 318 | + fullpath = fmt("%.*s%s", baselen, base, | |
| 319 | + pathname); | |
| 320 | + htmlf("<strong>%s</strong><pre>", fullpath); | |
| 321 | + } | |
| 322 | + htmlf("%6d: ", lno); | |
| 323 | + while (isspace(*line)) | |
| 324 | + line++; | |
| 325 | + cgit_tree_link2(line, NULL, NULL, ctx.qry.head, | |
| 326 | + ctx.qry.sha1, fullpath, lno); | |
| 327 | + html("\n"); | |
| 328 | + ++hits; | |
| 329 | + if (!(--grep->max_hits)) | |
| 330 | + break; | |
| 331 | + } | |
| 332 | + if (!lf) | |
| 333 | + break; | |
| 334 | + size -= (lf - line); | |
| 335 | + *lf = '\n'; | |
| 336 | + line = lf + 1; | |
| 337 | + lno++; | |
| 338 | + } | |
| 339 | + if (hits) | |
| 340 | + html("</pre>"); | |
| 341 | + if (!grep->max_hits) { | |
| 342 | + cgit_print_error("Too many matches found."); | |
| 343 | + return -1; | |
| 344 | + } | |
| 345 | + return 0; | |
| 346 | +} | |
| 347 | + | |
| 348 | +static int grep_tree(const unsigned char *sha1, const char *base, int baselen, | |
| 349 | + const char *pathname, unsigned mode, int stage, | |
| 350 | + void *cbdata) | |
| 351 | +{ | |
| 352 | + int rc; | |
| 353 | + char *fullpath; | |
| 354 | + struct grep_data *grep; | |
| 355 | + | |
| 356 | + grep = cbdata; | |
| 357 | + fullpath = fmt("%.*s%s", baselen, base, pathname); | |
| 358 | + if (S_ISDIR(mode)) { | |
| 359 | + rc = grep->path ? prefixcmp(grep->path, fullpath) : 0; | |
| 360 | + if (!rc) | |
| 361 | + return READ_TREE_RECURSIVE; | |
| 362 | + return (rc > 0 ? -1 : 0); | |
| 363 | + } else if (S_ISREG(mode)) { | |
| 364 | + rc = grep->path ? prefixcmp(fullpath, grep->path) : 0; | |
| 365 | + if (!rc) | |
| 366 | + return grep_blob(sha1, base, baselen, pathname, grep); | |
| 367 | + return (rc < 0 ? -1 : 0); | |
| 368 | + } | |
| 369 | + return 0; | |
| 370 | +} | |
| 371 | + | |
| 372 | +void cgit_grep(const char *rev, char *path, char *grep) | |
| 373 | +{ | |
| 374 | + unsigned char sha1[20]; | |
| 375 | + struct commit *commit; | |
| 376 | + struct grep_data data; | |
| 377 | + | |
| 378 | + if (!rev) | |
| 379 | + rev = ctx.qry.head; | |
| 380 | + | |
| 381 | + curr_rev = xstrdup(rev); | |
| 382 | + if (get_sha1(rev, sha1)) { | |
| 383 | + cgit_print_error(fmt("Invalid revision name: %s", rev)); | |
| 384 | + return; | |
| 385 | + } | |
| 386 | + commit = lookup_commit_reference(sha1); | |
| 387 | + if (!commit || parse_commit(commit)) { | |
| 388 | + cgit_print_error(fmt("Invalid commit reference: %s", rev)); | |
| 389 | + return; | |
| 390 | + } | |
| 391 | + data.path = path; | |
| 392 | + data.max_hits = 100; | |
| 393 | + if (regcomp(&data.re, ctx.qry.search, REG_EXTENDED | REG_ICASE | REG_NOSUB)) { | |
| 394 | + cgit_print_error("Invalid regex"); | |
| 395 | + return; | |
| 396 | + } | |
| 397 | + read_tree_recursive(commit->tree, "", 0, 0, NULL, grep_tree, &data); | |
| 398 | +} | |
| @@ -2,5 +2,6 @@ · 1 unmodified line | ||
| 2 | 2 | #define UI_TREE_H |
| 3 | 3 | |
| 4 | 4 | extern void cgit_print_tree(const char *rev, char *path); |
| 5 | +extern void cgit_grep(const char *rev, char *path, char *grep); | |
| 5 | 6 | |
| 6 | 7 | #endif /* UI_TREE_H */ |