summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2010-06-22 14:15:48 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2010-06-22 14:15:48 (UTC)
commit37a24e4e39737edaa5cdde501346a65eeb280e63 (patch) (unidiff)
treeff23eb0d57edf108edd1aa3e64a8d6cf70062954
parent47187b33be1c3de43fbed6f350ab05bc978aacd0 (diff)
parenta2cbd3c30b64a26b52b2003ba6569f3c083f4092 (diff)
downloadcgit-37a24e4e39737edaa5cdde501346a65eeb280e63.zip
cgit-37a24e4e39737edaa5cdde501346a65eeb280e63.tar.gz
cgit-37a24e4e39737edaa5cdde501346a65eeb280e63.tar.bz2
Merge branch 'jh/path-limit'
Conflicts: cgit.h ui-commit.c
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c6
-rw-r--r--cgit.css10
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt2
-rw-r--r--cmd.c46
-rw-r--r--cmd.h3
-rw-r--r--shared.c1
-rw-r--r--ui-commit.c20
-rw-r--r--ui-commit.h2
-rw-r--r--ui-diff.c8
-rw-r--r--ui-log.c27
-rw-r--r--ui-patch.c6
-rw-r--r--ui-patch.h2
-rw-r--r--ui-refs.c2
-rw-r--r--ui-shared.c216
-rw-r--r--ui-shared.h72
-rw-r--r--ui-tree.c15
17 files changed, 275 insertions, 164 deletions
diff --git a/cgit.c b/cgit.c
index 38bc136..d4fcfa7 100644
--- a/cgit.c
+++ b/cgit.c
@@ -308,256 +308,262 @@ static void prepare_context(struct cgit_context *ctx)
308 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 308 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
309 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 309 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
310 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 310 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
311 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 311 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
312 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 312 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
313 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 313 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
314 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 314 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
315 ctx->page.mimetype = "text/html"; 315 ctx->page.mimetype = "text/html";
316 ctx->page.charset = PAGE_ENCODING; 316 ctx->page.charset = PAGE_ENCODING;
317 ctx->page.filename = NULL; 317 ctx->page.filename = NULL;
318 ctx->page.size = 0; 318 ctx->page.size = 0;
319 ctx->page.modified = time(NULL); 319 ctx->page.modified = time(NULL);
320 ctx->page.expires = ctx->page.modified; 320 ctx->page.expires = ctx->page.modified;
321 ctx->page.etag = NULL; 321 ctx->page.etag = NULL;
322 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 322 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
323 if (ctx->env.script_name) 323 if (ctx->env.script_name)
324 ctx->cfg.script_name = ctx->env.script_name; 324 ctx->cfg.script_name = ctx->env.script_name;
325 if (ctx->env.query_string) 325 if (ctx->env.query_string)
326 ctx->qry.raw = ctx->env.query_string; 326 ctx->qry.raw = ctx->env.query_string;
327 if (!ctx->env.cgit_config) 327 if (!ctx->env.cgit_config)
328 ctx->env.cgit_config = CGIT_CONFIG; 328 ctx->env.cgit_config = CGIT_CONFIG;
329} 329}
330 330
331struct refmatch { 331struct refmatch {
332 char *req_ref; 332 char *req_ref;
333 char *first_ref; 333 char *first_ref;
334 int match; 334 int match;
335}; 335};
336 336
337int find_current_ref(const char *refname, const unsigned char *sha1, 337int find_current_ref(const char *refname, const unsigned char *sha1,
338 int flags, void *cb_data) 338 int flags, void *cb_data)
339{ 339{
340 struct refmatch *info; 340 struct refmatch *info;
341 341
342 info = (struct refmatch *)cb_data; 342 info = (struct refmatch *)cb_data;
343 if (!strcmp(refname, info->req_ref)) 343 if (!strcmp(refname, info->req_ref))
344 info->match = 1; 344 info->match = 1;
345 if (!info->first_ref) 345 if (!info->first_ref)
346 info->first_ref = xstrdup(refname); 346 info->first_ref = xstrdup(refname);
347 return info->match; 347 return info->match;
348} 348}
349 349
350char *find_default_branch(struct cgit_repo *repo) 350char *find_default_branch(struct cgit_repo *repo)
351{ 351{
352 struct refmatch info; 352 struct refmatch info;
353 char *ref; 353 char *ref;
354 354
355 info.req_ref = repo->defbranch; 355 info.req_ref = repo->defbranch;
356 info.first_ref = NULL; 356 info.first_ref = NULL;
357 info.match = 0; 357 info.match = 0;
358 for_each_branch_ref(find_current_ref, &info); 358 for_each_branch_ref(find_current_ref, &info);
359 if (info.match) 359 if (info.match)
360 ref = info.req_ref; 360 ref = info.req_ref;
361 else 361 else
362 ref = info.first_ref; 362 ref = info.first_ref;
363 if (ref) 363 if (ref)
364 ref = xstrdup(ref); 364 ref = xstrdup(ref);
365 return ref; 365 return ref;
366} 366}
367 367
368static int prepare_repo_cmd(struct cgit_context *ctx) 368static int prepare_repo_cmd(struct cgit_context *ctx)
369{ 369{
370 char *tmp; 370 char *tmp;
371 unsigned char sha1[20]; 371 unsigned char sha1[20];
372 int nongit = 0; 372 int nongit = 0;
373 373
374 setenv("GIT_DIR", ctx->repo->path, 1); 374 setenv("GIT_DIR", ctx->repo->path, 1);
375 setup_git_directory_gently(&nongit); 375 setup_git_directory_gently(&nongit);
376 if (nongit) { 376 if (nongit) {
377 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 377 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
378 "config error"); 378 "config error");
379 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 379 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
380 ctx->repo = NULL; 380 ctx->repo = NULL;
381 cgit_print_http_headers(ctx); 381 cgit_print_http_headers(ctx);
382 cgit_print_docstart(ctx); 382 cgit_print_docstart(ctx);
383 cgit_print_pageheader(ctx); 383 cgit_print_pageheader(ctx);
384 cgit_print_error(tmp); 384 cgit_print_error(tmp);
385 cgit_print_docend(); 385 cgit_print_docend();
386 return 1; 386 return 1;
387 } 387 }
388 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 388 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
389 389
390 if (!ctx->qry.head) { 390 if (!ctx->qry.head) {
391 ctx->qry.nohead = 1; 391 ctx->qry.nohead = 1;
392 ctx->qry.head = find_default_branch(ctx->repo); 392 ctx->qry.head = find_default_branch(ctx->repo);
393 ctx->repo->defbranch = ctx->qry.head; 393 ctx->repo->defbranch = ctx->qry.head;
394 } 394 }
395 395
396 if (!ctx->qry.head) { 396 if (!ctx->qry.head) {
397 cgit_print_http_headers(ctx); 397 cgit_print_http_headers(ctx);
398 cgit_print_docstart(ctx); 398 cgit_print_docstart(ctx);
399 cgit_print_pageheader(ctx); 399 cgit_print_pageheader(ctx);
400 cgit_print_error("Repository seems to be empty"); 400 cgit_print_error("Repository seems to be empty");
401 cgit_print_docend(); 401 cgit_print_docend();
402 return 1; 402 return 1;
403 } 403 }
404 404
405 if (get_sha1(ctx->qry.head, sha1)) { 405 if (get_sha1(ctx->qry.head, sha1)) {
406 tmp = xstrdup(ctx->qry.head); 406 tmp = xstrdup(ctx->qry.head);
407 ctx->qry.head = ctx->repo->defbranch; 407 ctx->qry.head = ctx->repo->defbranch;
408 ctx->page.status = 404; 408 ctx->page.status = 404;
409 ctx->page.statusmsg = "not found"; 409 ctx->page.statusmsg = "not found";
410 cgit_print_http_headers(ctx); 410 cgit_print_http_headers(ctx);
411 cgit_print_docstart(ctx); 411 cgit_print_docstart(ctx);
412 cgit_print_pageheader(ctx); 412 cgit_print_pageheader(ctx);
413 cgit_print_error(fmt("Invalid branch: %s", tmp)); 413 cgit_print_error(fmt("Invalid branch: %s", tmp));
414 cgit_print_docend(); 414 cgit_print_docend();
415 return 1; 415 return 1;
416 } 416 }
417 return 0; 417 return 0;
418} 418}
419 419
420static void process_request(void *cbdata) 420static void process_request(void *cbdata)
421{ 421{
422 struct cgit_context *ctx = cbdata; 422 struct cgit_context *ctx = cbdata;
423 struct cgit_cmd *cmd; 423 struct cgit_cmd *cmd;
424 424
425 cmd = cgit_get_cmd(ctx); 425 cmd = cgit_get_cmd(ctx);
426 if (!cmd) { 426 if (!cmd) {
427 ctx->page.title = "cgit error"; 427 ctx->page.title = "cgit error";
428 cgit_print_http_headers(ctx); 428 cgit_print_http_headers(ctx);
429 cgit_print_docstart(ctx); 429 cgit_print_docstart(ctx);
430 cgit_print_pageheader(ctx); 430 cgit_print_pageheader(ctx);
431 cgit_print_error("Invalid request"); 431 cgit_print_error("Invalid request");
432 cgit_print_docend(); 432 cgit_print_docend();
433 return; 433 return;
434 } 434 }
435 435
436 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
437 * in-project path limit to be made available at ctx->qry.vpath.
438 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
439 */
440 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
441
436 if (cmd->want_repo && !ctx->repo) { 442 if (cmd->want_repo && !ctx->repo) {
437 cgit_print_http_headers(ctx); 443 cgit_print_http_headers(ctx);
438 cgit_print_docstart(ctx); 444 cgit_print_docstart(ctx);
439 cgit_print_pageheader(ctx); 445 cgit_print_pageheader(ctx);
440 cgit_print_error(fmt("No repository selected")); 446 cgit_print_error(fmt("No repository selected"));
441 cgit_print_docend(); 447 cgit_print_docend();
442 return; 448 return;
443 } 449 }
444 450
445 if (ctx->repo && prepare_repo_cmd(ctx)) 451 if (ctx->repo && prepare_repo_cmd(ctx))
446 return; 452 return;
447 453
448 if (cmd->want_layout) { 454 if (cmd->want_layout) {
449 cgit_print_http_headers(ctx); 455 cgit_print_http_headers(ctx);
450 cgit_print_docstart(ctx); 456 cgit_print_docstart(ctx);
451 cgit_print_pageheader(ctx); 457 cgit_print_pageheader(ctx);
452 } 458 }
453 459
454 cmd->fn(ctx); 460 cmd->fn(ctx);
455 461
456 if (cmd->want_layout) 462 if (cmd->want_layout)
457 cgit_print_docend(); 463 cgit_print_docend();
458} 464}
459 465
460int cmp_repos(const void *a, const void *b) 466int cmp_repos(const void *a, const void *b)
461{ 467{
462 const struct cgit_repo *ra = a, *rb = b; 468 const struct cgit_repo *ra = a, *rb = b;
463 return strcmp(ra->url, rb->url); 469 return strcmp(ra->url, rb->url);
464} 470}
465 471
466char *build_snapshot_setting(int bitmap) 472char *build_snapshot_setting(int bitmap)
467{ 473{
468 const struct cgit_snapshot_format *f; 474 const struct cgit_snapshot_format *f;
469 char *result = xstrdup(""); 475 char *result = xstrdup("");
470 char *tmp; 476 char *tmp;
471 int len; 477 int len;
472 478
473 for (f = cgit_snapshot_formats; f->suffix; f++) { 479 for (f = cgit_snapshot_formats; f->suffix; f++) {
474 if (f->bit & bitmap) { 480 if (f->bit & bitmap) {
475 tmp = result; 481 tmp = result;
476 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 482 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
477 free(tmp); 483 free(tmp);
478 } 484 }
479 } 485 }
480 len = strlen(result); 486 len = strlen(result);
481 if (len) 487 if (len)
482 result[len - 1] = '\0'; 488 result[len - 1] = '\0';
483 return result; 489 return result;
484} 490}
485 491
486char *get_first_line(char *txt) 492char *get_first_line(char *txt)
487{ 493{
488 char *t = xstrdup(txt); 494 char *t = xstrdup(txt);
489 char *p = strchr(t, '\n'); 495 char *p = strchr(t, '\n');
490 if (p) 496 if (p)
491 *p = '\0'; 497 *p = '\0';
492 return t; 498 return t;
493} 499}
494 500
495void print_repo(FILE *f, struct cgit_repo *repo) 501void print_repo(FILE *f, struct cgit_repo *repo)
496{ 502{
497 fprintf(f, "repo.url=%s\n", repo->url); 503 fprintf(f, "repo.url=%s\n", repo->url);
498 fprintf(f, "repo.name=%s\n", repo->name); 504 fprintf(f, "repo.name=%s\n", repo->name);
499 fprintf(f, "repo.path=%s\n", repo->path); 505 fprintf(f, "repo.path=%s\n", repo->path);
500 if (repo->owner) 506 if (repo->owner)
501 fprintf(f, "repo.owner=%s\n", repo->owner); 507 fprintf(f, "repo.owner=%s\n", repo->owner);
502 if (repo->desc) { 508 if (repo->desc) {
503 char *tmp = get_first_line(repo->desc); 509 char *tmp = get_first_line(repo->desc);
504 fprintf(f, "repo.desc=%s\n", tmp); 510 fprintf(f, "repo.desc=%s\n", tmp);
505 free(tmp); 511 free(tmp);
506 } 512 }
507 if (repo->readme) 513 if (repo->readme)
508 fprintf(f, "repo.readme=%s\n", repo->readme); 514 fprintf(f, "repo.readme=%s\n", repo->readme);
509 if (repo->defbranch) 515 if (repo->defbranch)
510 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 516 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
511 if (repo->module_link) 517 if (repo->module_link)
512 fprintf(f, "repo.module-link=%s\n", repo->module_link); 518 fprintf(f, "repo.module-link=%s\n", repo->module_link);
513 if (repo->section) 519 if (repo->section)
514 fprintf(f, "repo.section=%s\n", repo->section); 520 fprintf(f, "repo.section=%s\n", repo->section);
515 if (repo->clone_url) 521 if (repo->clone_url)
516 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 522 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
517 fprintf(f, "repo.enable-log-filecount=%d\n", 523 fprintf(f, "repo.enable-log-filecount=%d\n",
518 repo->enable_log_filecount); 524 repo->enable_log_filecount);
519 fprintf(f, "repo.enable-log-linecount=%d\n", 525 fprintf(f, "repo.enable-log-linecount=%d\n",
520 repo->enable_log_linecount); 526 repo->enable_log_linecount);
521 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 527 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
522 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 528 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
523 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 529 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
524 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 530 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
525 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 531 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
526 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 532 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
527 if (repo->snapshots != ctx.cfg.snapshots) { 533 if (repo->snapshots != ctx.cfg.snapshots) {
528 char *tmp = build_snapshot_setting(repo->snapshots); 534 char *tmp = build_snapshot_setting(repo->snapshots);
529 fprintf(f, "repo.snapshots=%s\n", tmp); 535 fprintf(f, "repo.snapshots=%s\n", tmp);
530 free(tmp); 536 free(tmp);
531 } 537 }
532 if (repo->max_stats != ctx.cfg.max_stats) 538 if (repo->max_stats != ctx.cfg.max_stats)
533 fprintf(f, "repo.max-stats=%s\n", 539 fprintf(f, "repo.max-stats=%s\n",
534 cgit_find_stats_periodname(repo->max_stats)); 540 cgit_find_stats_periodname(repo->max_stats));
535 fprintf(f, "\n"); 541 fprintf(f, "\n");
536} 542}
537 543
538void print_repolist(FILE *f, struct cgit_repolist *list, int start) 544void print_repolist(FILE *f, struct cgit_repolist *list, int start)
539{ 545{
540 int i; 546 int i;
541 547
542 for(i = start; i < list->count; i++) 548 for(i = start; i < list->count; i++)
543 print_repo(f, &list->repos[i]); 549 print_repo(f, &list->repos[i]);
544} 550}
545 551
546/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 552/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
547 * and return 0 on success. 553 * and return 0 on success.
548 */ 554 */
549static int generate_cached_repolist(const char *path, const char *cached_rc) 555static int generate_cached_repolist(const char *path, const char *cached_rc)
550{ 556{
551 char *locked_rc; 557 char *locked_rc;
552 int idx; 558 int idx;
553 FILE *f; 559 FILE *f;
554 560
555 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 561 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
556 f = fopen(locked_rc, "wx"); 562 f = fopen(locked_rc, "wx");
557 if (!f) { 563 if (!f) {
558 /* Inform about the error unless the lockfile already existed, 564 /* Inform about the error unless the lockfile already existed,
559 * since that only means we've got concurrent requests. 565 * since that only means we've got concurrent requests.
560 */ 566 */
561 if (errno != EEXIST) 567 if (errno != EEXIST)
562 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 568 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
563 locked_rc, strerror(errno), errno); 569 locked_rc, strerror(errno), errno);
diff --git a/cgit.css b/cgit.css
index 6198403..6e47eb3 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,236 +1,242 @@
1body, table, form { 1body, table, form {
2 padding: 0em; 2 padding: 0em;
3 margin: 0em; 3 margin: 0em;
4} 4}
5 5
6body { 6body {
7 font-family: sans-serif; 7 font-family: sans-serif;
8 font-size: 10pt; 8 font-size: 10pt;
9 color: #333; 9 color: #333;
10 background: white; 10 background: white;
11 padding: 4px; 11 padding: 4px;
12} 12}
13 13
14a { 14a {
15 color: blue; 15 color: blue;
16 text-decoration: none; 16 text-decoration: none;
17} 17}
18 18
19a:hover { 19a:hover {
20 text-decoration: underline; 20 text-decoration: underline;
21} 21}
22 22
23table { 23table {
24 border-collapse: collapse; 24 border-collapse: collapse;
25} 25}
26 26
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 3px #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.path {
106 margin: 0px;
107 padding: 5px 2em 2px 2em;
108 color: #000;
109 background-color: #eee;
110}
111
105div.content { 112div.content {
106 margin: 0px; 113 margin: 0px;
107 padding: 2em; 114 padding: 2em;
108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 115 border-bottom: solid 3px #ccc;
110} 116}
111 117
112 118
113table.list { 119table.list {
114 width: 100%; 120 width: 100%;
115 border: none; 121 border: none;
116 border-collapse: collapse; 122 border-collapse: collapse;
117} 123}
118 124
119table.list tr { 125table.list tr {
120 background: white; 126 background: white;
121} 127}
122 128
123table.list tr.logheader { 129table.list tr.logheader {
124 background: #eee; 130 background: #eee;
125} 131}
126 132
127table.list tr:hover { 133table.list tr:hover {
128 background: #eee; 134 background: #eee;
129} 135}
130 136
131table.list tr.nohover:hover { 137table.list tr.nohover:hover {
132 background: white; 138 background: white;
133} 139}
134 140
135table.list th { 141table.list th {
136 font-weight: bold; 142 font-weight: bold;
137 /* color: #888; 143 /* color: #888;
138 border-top: dashed 1px #888; 144 border-top: dashed 1px #888;
139 border-bottom: dashed 1px #888; 145 border-bottom: dashed 1px #888;
140 */ 146 */
141 padding: 0.1em 0.5em 0.05em 0.5em; 147 padding: 0.1em 0.5em 0.05em 0.5em;
142 vertical-align: baseline; 148 vertical-align: baseline;
143} 149}
144 150
145table.list td { 151table.list td {
146 border: none; 152 border: none;
147 padding: 0.1em 0.5em 0.1em 0.5em; 153 padding: 0.1em 0.5em 0.1em 0.5em;
148} 154}
149 155
150table.list td.logsubject { 156table.list td.logsubject {
151 font-family: monospace; 157 font-family: monospace;
152 font-weight: bold; 158 font-weight: bold;
153} 159}
154 160
155table.list td.logmsg { 161table.list td.logmsg {
156 font-family: monospace; 162 font-family: monospace;
157 white-space: pre; 163 white-space: pre;
158 padding: 1em 0.5em 2em 0.5em; 164 padding: 1em 0.5em 2em 0.5em;
159} 165}
160 166
161table.list td a { 167table.list td a {
162 color: black; 168 color: black;
163} 169}
164 170
165table.list td a.ls-dir { 171table.list td a.ls-dir {
166 font-weight: bold; 172 font-weight: bold;
167 color: #00f; 173 color: #00f;
168} 174}
169 175
170table.list td a:hover { 176table.list td a:hover {
171 color: #00f; 177 color: #00f;
172} 178}
173 179
174img { 180img {
175 border: none; 181 border: none;
176} 182}
177 183
178input#switch-btn { 184input#switch-btn {
179 margin: 2px 0px 0px 0px; 185 margin: 2px 0px 0px 0px;
180} 186}
181 187
182td#sidebar input.txt { 188td#sidebar input.txt {
183 width: 100%; 189 width: 100%;
184 margin: 2px 0px 0px 0px; 190 margin: 2px 0px 0px 0px;
185} 191}
186 192
187table#grid { 193table#grid {
188 margin: 0px; 194 margin: 0px;
189} 195}
190 196
191td#content { 197td#content {
192 vertical-align: top; 198 vertical-align: top;
193 padding: 1em 2em 1em 1em; 199 padding: 1em 2em 1em 1em;
194 border: none; 200 border: none;
195} 201}
196 202
197div#summary { 203div#summary {
198 vertical-align: top; 204 vertical-align: top;
199 margin-bottom: 1em; 205 margin-bottom: 1em;
200} 206}
201 207
202table#downloads { 208table#downloads {
203 float: right; 209 float: right;
204 border-collapse: collapse; 210 border-collapse: collapse;
205 border: solid 1px #777; 211 border: solid 1px #777;
206 margin-left: 0.5em; 212 margin-left: 0.5em;
207 margin-bottom: 0.5em; 213 margin-bottom: 0.5em;
208} 214}
209 215
210table#downloads th { 216table#downloads th {
211 background-color: #ccc; 217 background-color: #ccc;
212} 218}
213 219
214div#blob { 220div#blob {
215 border: solid 1px black; 221 border: solid 1px black;
216} 222}
217 223
218div.error { 224div.error {
219 color: red; 225 color: red;
220 font-weight: bold; 226 font-weight: bold;
221 margin: 1em 2em; 227 margin: 1em 2em;
222} 228}
223 229
224a.ls-blob, a.ls-dir, a.ls-mod { 230a.ls-blob, a.ls-dir, a.ls-mod {
225 font-family: monospace; 231 font-family: monospace;
226} 232}
227 233
228td.ls-size { 234td.ls-size {
229 text-align: right; 235 text-align: right;
230 font-family: monospace; 236 font-family: monospace;
231 width: 10em; 237 width: 10em;
232} 238}
233 239
234td.ls-mode { 240td.ls-mode {
235 font-family: monospace; 241 font-family: monospace;
236 width: 10em; 242 width: 10em;
diff --git a/cgit.h b/cgit.h
index 8884f9e..80c3902 100644
--- a/cgit.h
+++ b/cgit.h
@@ -22,256 +22,257 @@
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
52struct cgit_filter { 52struct cgit_filter {
53 char *cmd; 53 char *cmd;
54 char **argv; 54 char **argv;
55 int old_stdout; 55 int old_stdout;
56 int pipe_fh[2]; 56 int pipe_fh[2];
57 int pid; 57 int pid;
58 int exitstatus; 58 int exitstatus;
59}; 59};
60 60
61struct cgit_repo { 61struct cgit_repo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *module_link; 68 char *module_link;
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int enable_remote_branches; 75 int enable_remote_branches;
76 int enable_subject_links; 76 int enable_subject_links;
77 int max_stats; 77 int max_stats;
78 time_t mtime; 78 time_t mtime;
79 struct cgit_filter *about_filter; 79 struct cgit_filter *about_filter;
80 struct cgit_filter *commit_filter; 80 struct cgit_filter *commit_filter;
81 struct cgit_filter *source_filter; 81 struct cgit_filter *source_filter;
82}; 82};
83 83
84typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 84typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
85 const char *value); 85 const char *value);
86 86
87struct cgit_repolist { 87struct cgit_repolist {
88 int length; 88 int length;
89 int count; 89 int count;
90 struct cgit_repo *repos; 90 struct cgit_repo *repos;
91}; 91};
92 92
93struct commitinfo { 93struct commitinfo {
94 struct commit *commit; 94 struct commit *commit;
95 char *author; 95 char *author;
96 char *author_email; 96 char *author_email;
97 unsigned long author_date; 97 unsigned long author_date;
98 char *committer; 98 char *committer;
99 char *committer_email; 99 char *committer_email;
100 unsigned long committer_date; 100 unsigned long committer_date;
101 char *subject; 101 char *subject;
102 char *msg; 102 char *msg;
103 char *msg_encoding; 103 char *msg_encoding;
104}; 104};
105 105
106struct taginfo { 106struct taginfo {
107 char *tagger; 107 char *tagger;
108 char *tagger_email; 108 char *tagger_email;
109 unsigned long tagger_date; 109 unsigned long tagger_date;
110 char *msg; 110 char *msg;
111}; 111};
112 112
113struct refinfo { 113struct refinfo {
114 const char *refname; 114 const char *refname;
115 struct object *object; 115 struct object *object;
116 union { 116 union {
117 struct taginfo *tag; 117 struct taginfo *tag;
118 struct commitinfo *commit; 118 struct commitinfo *commit;
119 }; 119 };
120}; 120};
121 121
122struct reflist { 122struct reflist {
123 struct refinfo **refs; 123 struct refinfo **refs;
124 int alloc; 124 int alloc;
125 int count; 125 int count;
126}; 126};
127 127
128struct cgit_query { 128struct cgit_query {
129 int has_symref; 129 int has_symref;
130 int has_sha1; 130 int has_sha1;
131 char *raw; 131 char *raw;
132 char *repo; 132 char *repo;
133 char *page; 133 char *page;
134 char *search; 134 char *search;
135 char *grep; 135 char *grep;
136 char *head; 136 char *head;
137 char *sha1; 137 char *sha1;
138 char *sha2; 138 char *sha2;
139 char *path; 139 char *path;
140 char *name; 140 char *name;
141 char *mimetype; 141 char *mimetype;
142 char *url; 142 char *url;
143 char *period; 143 char *period;
144 int ofs; 144 int ofs;
145 int nohead; 145 int nohead;
146 char *sort; 146 char *sort;
147 int showmsg; 147 int showmsg;
148 int ssdiff; 148 int ssdiff;
149 int show_all; 149 int show_all;
150 char *vpath;
150}; 151};
151 152
152struct cgit_config { 153struct cgit_config {
153 char *agefile; 154 char *agefile;
154 char *cache_root; 155 char *cache_root;
155 char *clone_prefix; 156 char *clone_prefix;
156 char *css; 157 char *css;
157 char *favicon; 158 char *favicon;
158 char *footer; 159 char *footer;
159 char *head_include; 160 char *head_include;
160 char *header; 161 char *header;
161 char *index_header; 162 char *index_header;
162 char *index_info; 163 char *index_info;
163 char *logo; 164 char *logo;
164 char *logo_link; 165 char *logo_link;
165 char *module_link; 166 char *module_link;
166 char *robots; 167 char *robots;
167 char *root_title; 168 char *root_title;
168 char *root_desc; 169 char *root_desc;
169 char *root_readme; 170 char *root_readme;
170 char *script_name; 171 char *script_name;
171 char *section; 172 char *section;
172 char *virtual_root; 173 char *virtual_root;
173 int cache_size; 174 int cache_size;
174 int cache_dynamic_ttl; 175 int cache_dynamic_ttl;
175 int cache_max_create_time; 176 int cache_max_create_time;
176 int cache_repo_ttl; 177 int cache_repo_ttl;
177 int cache_root_ttl; 178 int cache_root_ttl;
178 int cache_scanrc_ttl; 179 int cache_scanrc_ttl;
179 int cache_static_ttl; 180 int cache_static_ttl;
180 int embedded; 181 int embedded;
181 int enable_filter_overrides; 182 int enable_filter_overrides;
182 int enable_index_links; 183 int enable_index_links;
183 int enable_log_filecount; 184 int enable_log_filecount;
184 int enable_log_linecount; 185 int enable_log_linecount;
185 int enable_remote_branches; 186 int enable_remote_branches;
186 int enable_subject_links; 187 int enable_subject_links;
187 int enable_tree_linenumbers; 188 int enable_tree_linenumbers;
188 int local_time; 189 int local_time;
189 int max_atom_items; 190 int max_atom_items;
190 int max_repo_count; 191 int max_repo_count;
191 int max_commit_count; 192 int max_commit_count;
192 int max_lock_attempts; 193 int max_lock_attempts;
193 int max_msg_len; 194 int max_msg_len;
194 int max_repodesc_len; 195 int max_repodesc_len;
195 int max_blob_size; 196 int max_blob_size;
196 int max_stats; 197 int max_stats;
197 int nocache; 198 int nocache;
198 int noplainemail; 199 int noplainemail;
199 int noheader; 200 int noheader;
200 int renamelimit; 201 int renamelimit;
201 int snapshots; 202 int snapshots;
202 int summary_branches; 203 int summary_branches;
203 int summary_log; 204 int summary_log;
204 int summary_tags; 205 int summary_tags;
205 int ssdiff; 206 int ssdiff;
206 struct string_list mimetypes; 207 struct string_list mimetypes;
207 struct cgit_filter *about_filter; 208 struct cgit_filter *about_filter;
208 struct cgit_filter *commit_filter; 209 struct cgit_filter *commit_filter;
209 struct cgit_filter *source_filter; 210 struct cgit_filter *source_filter;
210}; 211};
211 212
212struct cgit_page { 213struct cgit_page {
213 time_t modified; 214 time_t modified;
214 time_t expires; 215 time_t expires;
215 size_t size; 216 size_t size;
216 char *mimetype; 217 char *mimetype;
217 char *charset; 218 char *charset;
218 char *filename; 219 char *filename;
219 char *etag; 220 char *etag;
220 char *title; 221 char *title;
221 int status; 222 int status;
222 char *statusmsg; 223 char *statusmsg;
223}; 224};
224 225
225struct cgit_environment { 226struct cgit_environment {
226 char *cgit_config; 227 char *cgit_config;
227 char *http_host; 228 char *http_host;
228 char *https; 229 char *https;
229 char *no_http; 230 char *no_http;
230 char *path_info; 231 char *path_info;
231 char *query_string; 232 char *query_string;
232 char *request_method; 233 char *request_method;
233 char *script_name; 234 char *script_name;
234 char *server_name; 235 char *server_name;
235 char *server_port; 236 char *server_port;
236}; 237};
237 238
238struct cgit_context { 239struct cgit_context {
239 struct cgit_environment env; 240 struct cgit_environment env;
240 struct cgit_query qry; 241 struct cgit_query qry;
241 struct cgit_config cfg; 242 struct cgit_config cfg;
242 struct cgit_repo *repo; 243 struct cgit_repo *repo;
243 struct cgit_page page; 244 struct cgit_page page;
244}; 245};
245 246
246struct cgit_snapshot_format { 247struct cgit_snapshot_format {
247 const char *suffix; 248 const char *suffix;
248 const char *mimetype; 249 const char *mimetype;
249 write_archive_fn_t write_func; 250 write_archive_fn_t write_func;
250 int bit; 251 int bit;
251}; 252};
252 253
253extern const char *cgit_version; 254extern const char *cgit_version;
254 255
255extern struct cgit_repolist cgit_repolist; 256extern struct cgit_repolist cgit_repolist;
256extern struct cgit_context ctx; 257extern struct cgit_context ctx;
257extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 258extern const struct cgit_snapshot_format cgit_snapshot_formats[];
258 259
259extern struct cgit_repo *cgit_add_repo(const char *url); 260extern struct cgit_repo *cgit_add_repo(const char *url);
260extern struct cgit_repo *cgit_get_repoinfo(const char *url); 261extern struct cgit_repo *cgit_get_repoinfo(const char *url);
261extern void cgit_repo_config_cb(const char *name, const char *value); 262extern void cgit_repo_config_cb(const char *name, const char *value);
262 263
263extern int chk_zero(int result, char *msg); 264extern int chk_zero(int result, char *msg);
264extern int chk_positive(int result, char *msg); 265extern int chk_positive(int result, char *msg);
265extern int chk_non_negative(int result, char *msg); 266extern int chk_non_negative(int result, char *msg);
266 267
267extern char *trim_end(const char *str, char c); 268extern char *trim_end(const char *str, char c);
268extern char *strlpart(char *txt, int maxlen); 269extern char *strlpart(char *txt, int maxlen);
269extern char *strrpart(char *txt, int maxlen); 270extern char *strrpart(char *txt, int maxlen);
270 271
271extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 272extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
272extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 273extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
273 int flags, void *cb_data); 274 int flags, void *cb_data);
274 275
275extern void *cgit_free_commitinfo(struct commitinfo *info); 276extern void *cgit_free_commitinfo(struct commitinfo *info);
276 277
277extern int cgit_diff_files(const unsigned char *old_sha1, 278extern int cgit_diff_files(const unsigned char *old_sha1,
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 5c24381..a853522 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -319,214 +319,214 @@ repo.defbranch::
319repo.desc:: 319repo.desc::
320 The value to show as repository description. Default value: none. 320 The value to show as repository description. Default value: none.
321 321
322repo.enable-log-filecount:: 322repo.enable-log-filecount::
323 A flag which can be used to disable the global setting 323 A flag which can be used to disable the global setting
324 `enable-log-filecount'. Default value: none. 324 `enable-log-filecount'. Default value: none.
325 325
326repo.enable-log-linecount:: 326repo.enable-log-linecount::
327 A flag which can be used to disable the global setting 327 A flag which can be used to disable the global setting
328 `enable-log-linecount'. Default value: none. 328 `enable-log-linecount'. Default value: none.
329 329
330repo.enable-remote-branches:: 330repo.enable-remote-branches::
331 Flag which, when set to "1", will make cgit display remote branches 331 Flag which, when set to "1", will make cgit display remote branches
332 in the summary and refs views. Default value: <enable-remote-branches>. 332 in the summary and refs views. Default value: <enable-remote-branches>.
333 333
334repo.enable-subject-links:: 334repo.enable-subject-links::
335 A flag which can be used to override the global setting 335 A flag which can be used to override the global setting
336 `enable-subject-links'. Default value: none. 336 `enable-subject-links'. Default value: none.
337 337
338repo.max-stats:: 338repo.max-stats::
339 Override the default maximum statistics period. Valid values are equal 339 Override the default maximum statistics period. Valid values are equal
340 to the values specified for the global "max-stats" setting. Default 340 to the values specified for the global "max-stats" setting. Default
341 value: none. 341 value: none.
342 342
343repo.name:: 343repo.name::
344 The value to show as repository name. Default value: <repo.url>. 344 The value to show as repository name. Default value: <repo.url>.
345 345
346repo.owner:: 346repo.owner::
347 A value used to identify the owner of the repository. Default value: 347 A value used to identify the owner of the repository. Default value:
348 none. 348 none.
349 349
350repo.path:: 350repo.path::
351 An absolute path to the repository directory. For non-bare repositories 351 An absolute path to the repository directory. For non-bare repositories
352 this is the .git-directory. Default value: none. 352 this is the .git-directory. Default value: none.
353 353
354repo.readme:: 354repo.readme::
355 A path (relative to <repo.path>) which specifies a file to include 355 A path (relative to <repo.path>) which specifies a file to include
356 verbatim as the "About" page for this repo. Default value: none. 356 verbatim as the "About" page for this repo. Default value: none.
357 357
358repo.snapshots:: 358repo.snapshots::
359 A mask of allowed snapshot-formats for this repo, restricted by the 359 A mask of allowed snapshot-formats for this repo, restricted by the
360 "snapshots" global setting. Default value: <snapshots>. 360 "snapshots" global setting. Default value: <snapshots>.
361 361
362repo.section:: 362repo.section::
363 Override the current section name for this repository. Default value: 363 Override the current section name for this repository. Default value:
364 none. 364 none.
365 365
366repo.source-filter:: 366repo.source-filter::
367 Override the default source-filter. Default value: none. See also: 367 Override the default source-filter. Default value: none. See also:
368 "enable-filter-overrides". 368 "enable-filter-overrides".
369 369
370repo.url:: 370repo.url::
371 The relative url used to access the repository. This must be the first 371 The relative url used to access the repository. This must be the first
372 setting specified for each repo. Default value: none. 372 setting specified for each repo. Default value: none.
373 373
374 374
375REPOSITORY-SPECIFIC CGITRC FILE 375REPOSITORY-SPECIFIC CGITRC FILE
376------------------------------- 376-------------------------------
377When the option "scan-path" is used to auto-discover git repositories, cgit 377When the option "scan-path" is used to auto-discover git repositories, cgit
378will try to parse the file "cgitrc" within any found repository. Such a 378will try to parse the file "cgitrc" within any found repository. Such a
379repo-specific config file may contain any of the repo-specific options 379repo-specific config file may contain any of the repo-specific options
380described above, except "repo.url" and "repo.path". Additionally, the "filter" 380described above, except "repo.url" and "repo.path". Additionally, the "filter"
381options are only acknowledged in repo-specific config files when 381options are only acknowledged in repo-specific config files when
382"enable-filter-overrides" is set to "1". 382"enable-filter-overrides" is set to "1".
383 383
384Note: the "repo." prefix is dropped from the option names in repo-specific 384Note: the "repo." prefix is dropped from the option names in repo-specific
385config files, e.g. "repo.desc" becomes "desc". 385config files, e.g. "repo.desc" becomes "desc".
386 386
387 387
388EXAMPLE CGITRC FILE 388EXAMPLE CGITRC FILE
389------------------- 389-------------------
390 390
391.... 391....
392# Enable caching of up to 1000 output entriess 392# Enable caching of up to 1000 output entriess
393cache-size=1000 393cache-size=1000
394 394
395 395
396# Specify some default clone prefixes 396# Specify some default clone prefixes
397clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 397clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
398 398
399# Specify the css url 399# Specify the css url
400css=/css/cgit.css 400css=/css/cgit.css
401 401
402 402
403# Show extra links for each repository on the index page 403# Show extra links for each repository on the index page
404enable-index-links=1 404enable-index-links=1
405 405
406 406
407# Show number of affected files per commit on the log pages 407# Show number of affected files per commit on the log pages
408enable-log-filecount=1 408enable-log-filecount=1
409 409
410 410
411# Show number of added/removed lines per commit on the log pages 411# Show number of added/removed lines per commit on the log pages
412enable-log-linecount=1 412enable-log-linecount=1
413 413
414 414
415# Add a cgit favicon 415# Add a cgit favicon
416favicon=/favicon.ico 416favicon=/favicon.ico
417 417
418 418
419# Use a custom logo 419# Use a custom logo
420logo=/img/mylogo.png 420logo=/img/mylogo.png
421 421
422 422
423# Enable statistics per week, month and quarter 423# Enable statistics per week, month and quarter
424max-stats=quarter 424max-stats=quarter
425 425
426 426
427# Set the title and heading of the repository index page 427# Set the title and heading of the repository index page
428root-title=foobar.com git repositories 428root-title=foobar.com git repositories
429 429
430 430
431# Set a subheading for the repository index page 431# Set a subheading for the repository index page
432root-desc=tracking the foobar development 432root-desc=tracking the foobar development
433 433
434 434
435# Include some more info about foobar.com on the index page 435# Include some more info about foobar.com on the index page
436root-readme=/var/www/htdocs/about.html 436root-readme=/var/www/htdocs/about.html
437 437
438 438
439# Allow download of tar.gz, tar.bz2 and zip-files 439# Allow download of tar.gz, tar.bz2 and zip-files
440snapshots=tar.gz tar.bz2 zip 440snapshots=tar.gz tar.bz2 zip
441 441
442 442
443## 443##
444## List of common mimetypes 444## List of common mimetypes
445## 445##
446 446
447mimetype.git=image/git 447mimetype.gif=image/gif
448mimetype.html=text/html 448mimetype.html=text/html
449mimetype.jpg=image/jpeg 449mimetype.jpg=image/jpeg
450mimetype.jpeg=image/jpeg 450mimetype.jpeg=image/jpeg
451mimetype.pdf=application/pdf 451mimetype.pdf=application/pdf
452mimetype.png=image/png 452mimetype.png=image/png
453mimetype.svg=image/svg+xml 453mimetype.svg=image/svg+xml
454 454
455 455
456## 456##
457## List of repositories. 457## List of repositories.
458## PS: Any repositories listed when section is unset will not be 458## PS: Any repositories listed when section is unset will not be
459## displayed under a section heading 459## displayed under a section heading
460## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 460## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
461## and included like this: 461## and included like this:
462## include=/etc/cgitrepos 462## include=/etc/cgitrepos
463## 463##
464 464
465 465
466repo.url=foo 466repo.url=foo
467repo.path=/pub/git/foo.git 467repo.path=/pub/git/foo.git
468repo.desc=the master foo repository 468repo.desc=the master foo repository
469repo.owner=fooman@foobar.com 469repo.owner=fooman@foobar.com
470repo.readme=info/web/about.html 470repo.readme=info/web/about.html
471 471
472 472
473repo.url=bar 473repo.url=bar
474repo.path=/pub/git/bar.git 474repo.path=/pub/git/bar.git
475repo.desc=the bars for your foo 475repo.desc=the bars for your foo
476repo.owner=barman@foobar.com 476repo.owner=barman@foobar.com
477repo.readme=info/web/about.html 477repo.readme=info/web/about.html
478 478
479 479
480# The next repositories will be displayed under the 'extras' heading 480# The next repositories will be displayed under the 'extras' heading
481section=extras 481section=extras
482 482
483 483
484repo.url=baz 484repo.url=baz
485repo.path=/pub/git/baz.git 485repo.path=/pub/git/baz.git
486repo.desc=a set of extensions for bar users 486repo.desc=a set of extensions for bar users
487 487
488repo.url=wiz 488repo.url=wiz
489repo.path=/pub/git/wiz.git 489repo.path=/pub/git/wiz.git
490repo.desc=the wizard of foo 490repo.desc=the wizard of foo
491 491
492 492
493# Add some mirrored repositories 493# Add some mirrored repositories
494section=mirrors 494section=mirrors
495 495
496 496
497repo.url=git 497repo.url=git
498repo.path=/pub/git/git.git 498repo.path=/pub/git/git.git
499repo.desc=the dscm 499repo.desc=the dscm
500 500
501 501
502repo.url=linux 502repo.url=linux
503repo.path=/pub/git/linux.git 503repo.path=/pub/git/linux.git
504repo.desc=the kernel 504repo.desc=the kernel
505 505
506# Disable adhoc downloads of this repo 506# Disable adhoc downloads of this repo
507repo.snapshots=0 507repo.snapshots=0
508 508
509# Disable line-counts for this repo 509# Disable line-counts for this repo
510repo.enable-log-linecount=0 510repo.enable-log-linecount=0
511 511
512# Restrict the max statistics period for this repo 512# Restrict the max statistics period for this repo
513repo.max-stats=month 513repo.max-stats=month
514.... 514....
515 515
516 516
517BUGS 517BUGS
518---- 518----
519Comments currently cannot appear on the same line as a setting; the comment 519Comments currently cannot appear on the same line as a setting; the comment
520will be included as part of the value. E.g. this line: 520will be included as part of the value. E.g. this line:
521 521
522 robots=index # allow indexing 522 robots=index # allow indexing
523 523
524will generate the following html element: 524will generate the following html element:
525 525
526 <meta name='robots' content='index # allow indexing'/> 526 <meta name='robots' content='index # allow indexing'/>
527 527
528 528
529 529
530AUTHOR 530AUTHOR
531------ 531------
532Lars Hjemli <hjemli@gmail.com> 532Lars Hjemli <hjemli@gmail.com>
diff --git a/cmd.c b/cmd.c
index ad784fc..6dc9f5e 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,171 +1,171 @@
1/* cmd.c: the cgit command dispatcher 1/* cmd.c: the cgit command dispatcher
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 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 "cmd.h" 10#include "cmd.h"
11#include "cache.h" 11#include "cache.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13#include "ui-atom.h" 13#include "ui-atom.h"
14#include "ui-blob.h" 14#include "ui-blob.h"
15#include "ui-clone.h" 15#include "ui-clone.h"
16#include "ui-commit.h" 16#include "ui-commit.h"
17#include "ui-diff.h" 17#include "ui-diff.h"
18#include "ui-log.h" 18#include "ui-log.h"
19#include "ui-patch.h" 19#include "ui-patch.h"
20#include "ui-plain.h" 20#include "ui-plain.h"
21#include "ui-refs.h" 21#include "ui-refs.h"
22#include "ui-repolist.h" 22#include "ui-repolist.h"
23#include "ui-snapshot.h" 23#include "ui-snapshot.h"
24#include "ui-stats.h" 24#include "ui-stats.h"
25#include "ui-summary.h" 25#include "ui-summary.h"
26#include "ui-tag.h" 26#include "ui-tag.h"
27#include "ui-tree.h" 27#include "ui-tree.h"
28 28
29static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
30{ 30{
31 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
32} 32}
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items);
37} 37}
38 38
39static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
40{ 40{
41 if (ctx->repo) 41 if (ctx->repo)
42 cgit_print_repo_readme(ctx->qry.path); 42 cgit_print_repo_readme(ctx->qry.path);
43 else 43 else
44 cgit_print_site_readme(); 44 cgit_print_site_readme();
45} 45}
46 46
47static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
48{ 48{
49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
50} 50}
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1); 54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
58{ 58{
59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
60} 60}
61 61
62static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
63{ 63{
64 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
65} 65}
66 66
67static void log_fn(struct cgit_context *ctx) 67static void log_fn(struct cgit_context *ctx)
68{ 68{
69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, 69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1);
71} 71}
72 72
73static void ls_cache_fn(struct cgit_context *ctx) 73static void ls_cache_fn(struct cgit_context *ctx)
74{ 74{
75 ctx->page.mimetype = "text/plain"; 75 ctx->page.mimetype = "text/plain";
76 ctx->page.filename = "ls-cache.txt"; 76 ctx->page.filename = "ls-cache.txt";
77 cgit_print_http_headers(ctx); 77 cgit_print_http_headers(ctx);
78 cache_ls(ctx->cfg.cache_root); 78 cache_ls(ctx->cfg.cache_root);
79} 79}
80 80
81static void objects_fn(struct cgit_context *ctx) 81static void objects_fn(struct cgit_context *ctx)
82{ 82{
83 cgit_clone_objects(ctx); 83 cgit_clone_objects(ctx);
84} 84}
85 85
86static void repolist_fn(struct cgit_context *ctx) 86static void repolist_fn(struct cgit_context *ctx)
87{ 87{
88 cgit_print_repolist(); 88 cgit_print_repolist();
89} 89}
90 90
91static void patch_fn(struct cgit_context *ctx) 91static void patch_fn(struct cgit_context *ctx)
92{ 92{
93 cgit_print_patch(ctx->qry.sha1); 93 cgit_print_patch(ctx->qry.sha1, ctx->qry.path);
94} 94}
95 95
96static void plain_fn(struct cgit_context *ctx) 96static void plain_fn(struct cgit_context *ctx)
97{ 97{
98 cgit_print_plain(ctx); 98 cgit_print_plain(ctx);
99} 99}
100 100
101static void refs_fn(struct cgit_context *ctx) 101static void refs_fn(struct cgit_context *ctx)
102{ 102{
103 cgit_print_refs(); 103 cgit_print_refs();
104} 104}
105 105
106static void snapshot_fn(struct cgit_context *ctx) 106static void snapshot_fn(struct cgit_context *ctx)
107{ 107{
108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, 108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
109 ctx->repo->snapshots, ctx->qry.nohead); 109 ctx->repo->snapshots, ctx->qry.nohead);
110} 110}
111 111
112static void stats_fn(struct cgit_context *ctx) 112static void stats_fn(struct cgit_context *ctx)
113{ 113{
114 cgit_show_stats(ctx); 114 cgit_show_stats(ctx);
115} 115}
116 116
117static void summary_fn(struct cgit_context *ctx) 117static void summary_fn(struct cgit_context *ctx)
118{ 118{
119 cgit_print_summary(); 119 cgit_print_summary();
120} 120}
121 121
122static void tag_fn(struct cgit_context *ctx) 122static void tag_fn(struct cgit_context *ctx)
123{ 123{
124 cgit_print_tag(ctx->qry.sha1); 124 cgit_print_tag(ctx->qry.sha1);
125} 125}
126 126
127static void tree_fn(struct cgit_context *ctx) 127static void tree_fn(struct cgit_context *ctx)
128{ 128{
129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
130} 130}
131 131
132#define def_cmd(name, want_repo, want_layout) \ 132#define def_cmd(name, want_repo, want_layout, want_vpath) \
133 {#name, name##_fn, want_repo, want_layout} 133 {#name, name##_fn, want_repo, want_layout, want_vpath}
134 134
135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
136{ 136{
137 static struct cgit_cmd cmds[] = { 137 static struct cgit_cmd cmds[] = {
138 def_cmd(HEAD, 1, 0), 138 def_cmd(HEAD, 1, 0, 0),
139 def_cmd(atom, 1, 0), 139 def_cmd(atom, 1, 0, 0),
140 def_cmd(about, 0, 1), 140 def_cmd(about, 0, 1, 0),
141 def_cmd(blob, 1, 0), 141 def_cmd(blob, 1, 0, 0),
142 def_cmd(commit, 1, 1), 142 def_cmd(commit, 1, 1, 1),
143 def_cmd(diff, 1, 1), 143 def_cmd(diff, 1, 1, 1),
144 def_cmd(info, 1, 0), 144 def_cmd(info, 1, 0, 0),
145 def_cmd(log, 1, 1), 145 def_cmd(log, 1, 1, 1),
146 def_cmd(ls_cache, 0, 0), 146 def_cmd(ls_cache, 0, 0, 0),
147 def_cmd(objects, 1, 0), 147 def_cmd(objects, 1, 0, 0),
148 def_cmd(patch, 1, 0), 148 def_cmd(patch, 1, 0, 1),
149 def_cmd(plain, 1, 0), 149 def_cmd(plain, 1, 0, 0),
150 def_cmd(refs, 1, 1), 150 def_cmd(refs, 1, 1, 0),
151 def_cmd(repolist, 0, 0), 151 def_cmd(repolist, 0, 0, 0),
152 def_cmd(snapshot, 1, 0), 152 def_cmd(snapshot, 1, 0, 0),
153 def_cmd(stats, 1, 1), 153 def_cmd(stats, 1, 1, 1),
154 def_cmd(summary, 1, 1), 154 def_cmd(summary, 1, 1, 0),
155 def_cmd(tag, 1, 1), 155 def_cmd(tag, 1, 1, 0),
156 def_cmd(tree, 1, 1), 156 def_cmd(tree, 1, 1, 1),
157 }; 157 };
158 int i; 158 int i;
159 159
160 if (ctx->qry.page == NULL) { 160 if (ctx->qry.page == NULL) {
161 if (ctx->repo) 161 if (ctx->repo)
162 ctx->qry.page = "summary"; 162 ctx->qry.page = "summary";
163 else 163 else
164 ctx->qry.page = "repolist"; 164 ctx->qry.page = "repolist";
165 } 165 }
166 166
167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
168 if (!strcmp(ctx->qry.page, cmds[i].name)) 168 if (!strcmp(ctx->qry.page, cmds[i].name))
169 return &cmds[i]; 169 return &cmds[i];
170 return NULL; 170 return NULL;
171} 171}
diff --git a/cmd.h b/cmd.h
index ec9e691..8dc01bd 100644
--- a/cmd.h
+++ b/cmd.h
@@ -1,15 +1,16 @@
1#ifndef CMD_H 1#ifndef CMD_H
2#define CMD_H 2#define CMD_H
3 3
4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx); 4typedef void (*cgit_cmd_fn)(struct cgit_context *ctx);
5 5
6struct cgit_cmd { 6struct cgit_cmd {
7 const char *name; 7 const char *name;
8 cgit_cmd_fn fn; 8 cgit_cmd_fn fn;
9 unsigned int want_repo:1, 9 unsigned int want_repo:1,
10 want_layout:1; 10 want_layout:1,
11 want_vpath:1;
11}; 12};
12 13
13extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx); 14extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx);
14 15
15#endif /* CMD_H */ 16#endif /* CMD_H */
diff --git a/shared.c b/shared.c
index 8b3a045..58837dc 100644
--- a/shared.c
+++ b/shared.c
@@ -1,141 +1,140 @@
1/* shared.c: global vars + some callback functions 1/* shared.c: global vars + some callback functions
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd;
14 13
15int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
16{ 15{
17 if (result != 0) 16 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 17 die("%s: %s", msg, strerror(errno));
19 return result; 18 return result;
20} 19}
21 20
22int chk_positive(int result, char *msg) 21int chk_positive(int result, char *msg)
23{ 22{
24 if (result <= 0) 23 if (result <= 0)
25 die("%s: %s", msg, strerror(errno)); 24 die("%s: %s", msg, strerror(errno));
26 return result; 25 return result;
27} 26}
28 27
29int chk_non_negative(int result, char *msg) 28int chk_non_negative(int result, char *msg)
30{ 29{
31 if (result < 0) 30 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 31 die("%s: %s",msg, strerror(errno));
33 return result; 32 return result;
34} 33}
35 34
36struct cgit_repo *cgit_add_repo(const char *url) 35struct cgit_repo *cgit_add_repo(const char *url)
37{ 36{
38 struct cgit_repo *ret; 37 struct cgit_repo *ret;
39 38
40 if (++cgit_repolist.count > cgit_repolist.length) { 39 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 40 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 41 cgit_repolist.length = 8;
43 else 42 else
44 cgit_repolist.length *= 2; 43 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 44 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 45 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 46 sizeof(struct cgit_repo));
48 } 47 }
49 48
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 49 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 memset(ret, 0, sizeof(struct cgit_repo)); 50 memset(ret, 0, sizeof(struct cgit_repo));
52 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
53 ret->name = ret->url; 52 ret->name = ret->url;
54 ret->path = NULL; 53 ret->path = NULL;
55 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
56 ret->owner = NULL; 55 ret->owner = NULL;
57 ret->section = ctx.cfg.section; 56 ret->section = ctx.cfg.section;
58 ret->defbranch = "master"; 57 ret->defbranch = "master";
59 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches; 61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
63 ret->enable_subject_links = ctx.cfg.enable_subject_links; 62 ret->enable_subject_links = ctx.cfg.enable_subject_links;
64 ret->max_stats = ctx.cfg.max_stats; 63 ret->max_stats = ctx.cfg.max_stats;
65 ret->module_link = ctx.cfg.module_link; 64 ret->module_link = ctx.cfg.module_link;
66 ret->readme = NULL; 65 ret->readme = NULL;
67 ret->mtime = -1; 66 ret->mtime = -1;
68 ret->about_filter = ctx.cfg.about_filter; 67 ret->about_filter = ctx.cfg.about_filter;
69 ret->commit_filter = ctx.cfg.commit_filter; 68 ret->commit_filter = ctx.cfg.commit_filter;
70 ret->source_filter = ctx.cfg.source_filter; 69 ret->source_filter = ctx.cfg.source_filter;
71 return ret; 70 return ret;
72} 71}
73 72
74struct cgit_repo *cgit_get_repoinfo(const char *url) 73struct cgit_repo *cgit_get_repoinfo(const char *url)
75{ 74{
76 int i; 75 int i;
77 struct cgit_repo *repo; 76 struct cgit_repo *repo;
78 77
79 for (i=0; i<cgit_repolist.count; i++) { 78 for (i=0; i<cgit_repolist.count; i++) {
80 repo = &cgit_repolist.repos[i]; 79 repo = &cgit_repolist.repos[i];
81 if (!strcmp(repo->url, url)) 80 if (!strcmp(repo->url, url))
82 return repo; 81 return repo;
83 } 82 }
84 return NULL; 83 return NULL;
85} 84}
86 85
87void *cgit_free_commitinfo(struct commitinfo *info) 86void *cgit_free_commitinfo(struct commitinfo *info)
88{ 87{
89 free(info->author); 88 free(info->author);
90 free(info->author_email); 89 free(info->author_email);
91 free(info->committer); 90 free(info->committer);
92 free(info->committer_email); 91 free(info->committer_email);
93 free(info->subject); 92 free(info->subject);
94 free(info->msg); 93 free(info->msg);
95 free(info->msg_encoding); 94 free(info->msg_encoding);
96 free(info); 95 free(info);
97 return NULL; 96 return NULL;
98} 97}
99 98
100char *trim_end(const char *str, char c) 99char *trim_end(const char *str, char c)
101{ 100{
102 int len; 101 int len;
103 char *s, *t; 102 char *s, *t;
104 103
105 if (str == NULL) 104 if (str == NULL)
106 return NULL; 105 return NULL;
107 t = (char *)str; 106 t = (char *)str;
108 len = strlen(t); 107 len = strlen(t);
109 while(len > 0 && t[len - 1] == c) 108 while(len > 0 && t[len - 1] == c)
110 len--; 109 len--;
111 110
112 if (len == 0) 111 if (len == 0)
113 return NULL; 112 return NULL;
114 113
115 c = t[len]; 114 c = t[len];
116 t[len] = '\0'; 115 t[len] = '\0';
117 s = xstrdup(t); 116 s = xstrdup(t);
118 t[len] = c; 117 t[len] = c;
119 return s; 118 return s;
120} 119}
121 120
122char *strlpart(char *txt, int maxlen) 121char *strlpart(char *txt, int maxlen)
123{ 122{
124 char *result; 123 char *result;
125 124
126 if (!txt) 125 if (!txt)
127 return txt; 126 return txt;
128 127
129 if (strlen(txt) <= maxlen) 128 if (strlen(txt) <= maxlen)
130 return txt; 129 return txt;
131 result = xmalloc(maxlen + 1); 130 result = xmalloc(maxlen + 1);
132 memcpy(result, txt, maxlen - 3); 131 memcpy(result, txt, maxlen - 3);
133 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 132 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
134 result[maxlen] = '\0'; 133 result[maxlen] = '\0';
135 return result; 134 return result;
136} 135}
137 136
138char *strrpart(char *txt, int maxlen) 137char *strrpart(char *txt, int maxlen)
139{ 138{
140 char *result; 139 char *result;
141 140
diff --git a/ui-commit.c b/ui-commit.c
index 41313b9..a11bc5f 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,127 +1,131 @@
1/* ui-commit.c: generate commit view 1/* ui-commit.c: generate commit view
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex, const char *prefix)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info, *parent_info; 18 struct commitinfo *info, *parent_info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp, *tmp2; 21 char *tmp, *tmp2;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 34 return;
35 } 35 }
36 info = cgit_parse_commit(commit); 36 info = cgit_parse_commit(commit);
37 37
38 load_ref_decorations(DECORATE_FULL_REFS); 38 load_ref_decorations(DECORATE_FULL_REFS);
39 39
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 43 if (!ctx.cfg.noplainemail) {
44 html(" "); 44 html(" ");
45 html_txt(info->author_email); 45 html_txt(info->author_email);
46 } 46 }
47 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
49 html("</td></tr>\n"); 49 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 51 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 52 if (!ctx.cfg.noplainemail) {
53 html(" "); 53 html(" ");
54 html_txt(info->committer_email); 54 html_txt(info->committer_email);
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, 0); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
64 html(") ("); 64 html(") (");
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) 65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, 1); 66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
67 else 67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, 1); 68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 1);
69 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
70 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
71 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
73 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
74 if (prefix) {
75 html(" /");
76 cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
77 }
74 html("</td></tr>\n"); 78 html("</td></tr>\n");
75 for (p = commit->parents; p ; p = p->next) { 79 for (p = commit->parents; p ; p = p->next) {
76 parent = lookup_commit_reference(p->item->object.sha1); 80 parent = lookup_commit_reference(p->item->object.sha1);
77 if (!parent) { 81 if (!parent) {
78 html("<tr><td colspan='3'>"); 82 html("<tr><td colspan='3'>");
79 cgit_print_error("Error reading parent commit"); 83 cgit_print_error("Error reading parent commit");
80 html("</td></tr>"); 84 html("</td></tr>");
81 continue; 85 continue;
82 } 86 }
83 html("<tr><th>parent</th>" 87 html("<tr><th>parent</th>"
84 "<td colspan='2' class='sha1'>"); 88 "<td colspan='2' class='sha1'>");
85 tmp = tmp2 = sha1_to_hex(p->item->object.sha1); 89 tmp = tmp2 = sha1_to_hex(p->item->object.sha1);
86 if (ctx.repo->enable_subject_links) { 90 if (ctx.repo->enable_subject_links) {
87 parent_info = cgit_parse_commit(parent); 91 parent_info = cgit_parse_commit(parent);
88 tmp2 = parent_info->subject; 92 tmp2 = parent_info->subject;
89 } 93 }
90 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, 0); 94 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
91 html(" ("); 95 html(" (");
92 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 96 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
93 sha1_to_hex(p->item->object.sha1), NULL, 0); 97 sha1_to_hex(p->item->object.sha1), prefix, 0);
94 html(")</td></tr>"); 98 html(")</td></tr>");
95 parents++; 99 parents++;
96 } 100 }
97 if (ctx.repo->snapshots) { 101 if (ctx.repo->snapshots) {
98 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 102 html("<tr><th>download</th><td colspan='2' class='sha1'>");
99 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 103 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
100 hex, ctx.repo->snapshots); 104 hex, ctx.repo->snapshots);
101 html("</td></tr>"); 105 html("</td></tr>");
102 } 106 }
103 html("</table>\n"); 107 html("</table>\n");
104 html("<div class='commit-subject'>"); 108 html("<div class='commit-subject'>");
105 if (ctx.repo->commit_filter) 109 if (ctx.repo->commit_filter)
106 cgit_open_filter(ctx.repo->commit_filter); 110 cgit_open_filter(ctx.repo->commit_filter);
107 html_txt(info->subject); 111 html_txt(info->subject);
108 if (ctx.repo->commit_filter) 112 if (ctx.repo->commit_filter)
109 cgit_close_filter(ctx.repo->commit_filter); 113 cgit_close_filter(ctx.repo->commit_filter);
110 show_commit_decorations(commit); 114 show_commit_decorations(commit);
111 html("</div>"); 115 html("</div>");
112 html("<div class='commit-msg'>"); 116 html("<div class='commit-msg'>");
113 if (ctx.repo->commit_filter) 117 if (ctx.repo->commit_filter)
114 cgit_open_filter(ctx.repo->commit_filter); 118 cgit_open_filter(ctx.repo->commit_filter);
115 html_txt(info->msg); 119 html_txt(info->msg);
116 if (ctx.repo->commit_filter) 120 if (ctx.repo->commit_filter)
117 cgit_close_filter(ctx.repo->commit_filter); 121 cgit_close_filter(ctx.repo->commit_filter);
118 html("</div>"); 122 html("</div>");
119 if (parents < 3) { 123 if (parents < 3) {
120 if (parents) 124 if (parents)
121 tmp = sha1_to_hex(commit->parents->item->object.sha1); 125 tmp = sha1_to_hex(commit->parents->item->object.sha1);
122 else 126 else
123 tmp = NULL; 127 tmp = NULL;
124 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 128 cgit_print_diff(ctx.qry.sha1, tmp, prefix);
125 } 129 }
126 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
127} 131}
diff --git a/ui-commit.h b/ui-commit.h
index 40bcb31..8198b4b 100644
--- a/ui-commit.h
+++ b/ui-commit.h
@@ -1,6 +1,6 @@
1#ifndef UI_COMMIT_H 1#ifndef UI_COMMIT_H
2#define UI_COMMIT_H 2#define UI_COMMIT_H
3 3
4extern void cgit_print_commit(char *hex); 4extern void cgit_print_commit(char *hex, const char *prefix);
5 5
6#endif /* UI_COMMIT_H */ 6#endif /* UI_COMMIT_H */
diff --git a/ui-diff.c b/ui-diff.c
index a92a768..fb836df 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -29,325 +29,327 @@ static struct fileinfo {
29 unsigned int added; 29 unsigned int added;
30 unsigned int removed; 30 unsigned int removed;
31 unsigned long old_size; 31 unsigned long old_size;
32 unsigned long new_size; 32 unsigned long new_size;
33 int binary:1; 33 int binary:1;
34} *items; 34} *items;
35 35
36static int use_ssdiff = 0; 36static int use_ssdiff = 0;
37 37
38static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
39{ 39{
40 char *class; 40 char *class;
41 41
42 switch (info->status) { 42 switch (info->status) {
43 case DIFF_STATUS_ADDED: 43 case DIFF_STATUS_ADDED:
44 class = "add"; 44 class = "add";
45 break; 45 break;
46 case DIFF_STATUS_COPIED: 46 case DIFF_STATUS_COPIED:
47 class = "cpy"; 47 class = "cpy";
48 break; 48 break;
49 case DIFF_STATUS_DELETED: 49 case DIFF_STATUS_DELETED:
50 class = "del"; 50 class = "del";
51 break; 51 break;
52 case DIFF_STATUS_MODIFIED: 52 case DIFF_STATUS_MODIFIED:
53 class = "upd"; 53 class = "upd";
54 break; 54 break;
55 case DIFF_STATUS_RENAMED: 55 case DIFF_STATUS_RENAMED:
56 class = "mov"; 56 class = "mov";
57 break; 57 break;
58 case DIFF_STATUS_TYPE_CHANGED: 58 case DIFF_STATUS_TYPE_CHANGED:
59 class = "typ"; 59 class = "typ";
60 break; 60 break;
61 case DIFF_STATUS_UNKNOWN: 61 case DIFF_STATUS_UNKNOWN:
62 class = "unk"; 62 class = "unk";
63 break; 63 break;
64 case DIFF_STATUS_UNMERGED: 64 case DIFF_STATUS_UNMERGED:
65 class = "stg"; 65 class = "stg";
66 break; 66 break;
67 default: 67 default:
68 die("bug: unhandled diff status %c", info->status); 68 die("bug: unhandled diff status %c", info->status);
69 } 69 }
70 70
71 html("<tr>"); 71 html("<tr>");
72 htmlf("<td class='mode'>"); 72 htmlf("<td class='mode'>");
73 if (is_null_sha1(info->new_sha1)) { 73 if (is_null_sha1(info->new_sha1)) {
74 cgit_print_filemode(info->old_mode); 74 cgit_print_filemode(info->old_mode);
75 } else { 75 } else {
76 cgit_print_filemode(info->new_mode); 76 cgit_print_filemode(info->new_mode);
77 } 77 }
78 78
79 if (info->old_mode != info->new_mode && 79 if (info->old_mode != info->new_mode &&
80 !is_null_sha1(info->old_sha1) && 80 !is_null_sha1(info->old_sha1) &&
81 !is_null_sha1(info->new_sha1)) { 81 !is_null_sha1(info->new_sha1)) {
82 html("<span class='modechange'>["); 82 html("<span class='modechange'>[");
83 cgit_print_filemode(info->old_mode); 83 cgit_print_filemode(info->old_mode);
84 html("]</span>"); 84 html("]</span>");
85 } 85 }
86 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
88 ctx.qry.sha2, info->new_path, 0); 88 ctx.qry.sha2, info->new_path, 0);
89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
90 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
92 info->old_path); 92 info->old_path);
93 html("</td><td class='right'>"); 93 html("</td><td class='right'>");
94 if (info->binary) { 94 if (info->binary) {
95 htmlf("bin</td><td class='graph'>%d -> %d bytes", 95 htmlf("bin</td><td class='graph'>%d -> %d bytes",
96 info->old_size, info->new_size); 96 info->old_size, info->new_size);
97 return; 97 return;
98 } 98 }
99 htmlf("%d", info->added + info->removed); 99 htmlf("%d", info->added + info->removed);
100 html("</td><td class='graph'>"); 100 html("</td><td class='graph'>");
101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
102 htmlf("<td class='add' style='width: %.1f%%;'/>", 102 htmlf("<td class='add' style='width: %.1f%%;'/>",
103 info->added * 100.0 / max_changes); 103 info->added * 100.0 / max_changes);
104 htmlf("<td class='rem' style='width: %.1f%%;'/>", 104 htmlf("<td class='rem' style='width: %.1f%%;'/>",
105 info->removed * 100.0 / max_changes); 105 info->removed * 100.0 / max_changes);
106 htmlf("<td class='none' style='width: %.1f%%;'/>", 106 htmlf("<td class='none' style='width: %.1f%%;'/>",
107 (max_changes - info->removed - info->added) * 100.0 / max_changes); 107 (max_changes - info->removed - info->added) * 100.0 / max_changes);
108 html("</tr></table></td></tr>\n"); 108 html("</tr></table></td></tr>\n");
109} 109}
110 110
111static void count_diff_lines(char *line, int len) 111static void count_diff_lines(char *line, int len)
112{ 112{
113 if (line && (len > 0)) { 113 if (line && (len > 0)) {
114 if (line[0] == '+') 114 if (line[0] == '+')
115 lines_added++; 115 lines_added++;
116 else if (line[0] == '-') 116 else if (line[0] == '-')
117 lines_removed++; 117 lines_removed++;
118 } 118 }
119} 119}
120 120
121static void inspect_filepair(struct diff_filepair *pair) 121static void inspect_filepair(struct diff_filepair *pair)
122{ 122{
123 int binary = 0; 123 int binary = 0;
124 unsigned long old_size = 0; 124 unsigned long old_size = 0;
125 unsigned long new_size = 0; 125 unsigned long new_size = 0;
126 files++; 126 files++;
127 lines_added = 0; 127 lines_added = 0;
128 lines_removed = 0; 128 lines_removed = 0;
129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
130 &binary, count_diff_lines); 130 &binary, count_diff_lines);
131 if (files >= slots) { 131 if (files >= slots) {
132 if (slots == 0) 132 if (slots == 0)
133 slots = 4; 133 slots = 4;
134 else 134 else
135 slots = slots * 2; 135 slots = slots * 2;
136 items = xrealloc(items, slots * sizeof(struct fileinfo)); 136 items = xrealloc(items, slots * sizeof(struct fileinfo));
137 } 137 }
138 items[files-1].status = pair->status; 138 items[files-1].status = pair->status;
139 hashcpy(items[files-1].old_sha1, pair->one->sha1); 139 hashcpy(items[files-1].old_sha1, pair->one->sha1);
140 hashcpy(items[files-1].new_sha1, pair->two->sha1); 140 hashcpy(items[files-1].new_sha1, pair->two->sha1);
141 items[files-1].old_mode = pair->one->mode; 141 items[files-1].old_mode = pair->one->mode;
142 items[files-1].new_mode = pair->two->mode; 142 items[files-1].new_mode = pair->two->mode;
143 items[files-1].old_path = xstrdup(pair->one->path); 143 items[files-1].old_path = xstrdup(pair->one->path);
144 items[files-1].new_path = xstrdup(pair->two->path); 144 items[files-1].new_path = xstrdup(pair->two->path);
145 items[files-1].added = lines_added; 145 items[files-1].added = lines_added;
146 items[files-1].removed = lines_removed; 146 items[files-1].removed = lines_removed;
147 items[files-1].old_size = old_size; 147 items[files-1].old_size = old_size;
148 items[files-1].new_size = new_size; 148 items[files-1].new_size = new_size;
149 items[files-1].binary = binary; 149 items[files-1].binary = binary;
150 if (lines_added + lines_removed > max_changes) 150 if (lines_added + lines_removed > max_changes)
151 max_changes = lines_added + lines_removed; 151 max_changes = lines_added + lines_removed;
152 total_adds += lines_added; 152 total_adds += lines_added;
153 total_rems += lines_removed; 153 total_rems += lines_removed;
154} 154}
155 155
156void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
157 const unsigned char *new_sha1) 157 const unsigned char *new_sha1, const char *prefix)
158{ 158{
159 int i; 159 int i;
160 160
161 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
163 ctx.qry.sha2, NULL, 0); 163 ctx.qry.sha2, NULL, 0);
164 if (prefix)
165 htmlf(" (limited to '%s')", prefix);
164 html("</div>"); 166 html("</div>");
165 html("<table summary='diffstat' class='diffstat'>"); 167 html("<table summary='diffstat' class='diffstat'>");
166 max_changes = 0; 168 max_changes = 0;
167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 169 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix);
168 for(i = 0; i<files; i++) 170 for(i = 0; i<files; i++)
169 print_fileinfo(&items[i]); 171 print_fileinfo(&items[i]);
170 html("</table>"); 172 html("</table>");
171 html("<div class='diffstat-summary'>"); 173 html("<div class='diffstat-summary'>");
172 htmlf("%d files changed, %d insertions, %d deletions", 174 htmlf("%d files changed, %d insertions, %d deletions",
173 files, total_adds, total_rems); 175 files, total_adds, total_rems);
174 html("</div>"); 176 html("</div>");
175} 177}
176 178
177 179
178/* 180/*
179 * print a single line returned from xdiff 181 * print a single line returned from xdiff
180 */ 182 */
181static void print_line(char *line, int len) 183static void print_line(char *line, int len)
182{ 184{
183 char *class = "ctx"; 185 char *class = "ctx";
184 char c = line[len-1]; 186 char c = line[len-1];
185 187
186 if (line[0] == '+') 188 if (line[0] == '+')
187 class = "add"; 189 class = "add";
188 else if (line[0] == '-') 190 else if (line[0] == '-')
189 class = "del"; 191 class = "del";
190 else if (line[0] == '@') 192 else if (line[0] == '@')
191 class = "hunk"; 193 class = "hunk";
192 194
193 htmlf("<div class='%s'>", class); 195 htmlf("<div class='%s'>", class);
194 line[len-1] = '\0'; 196 line[len-1] = '\0';
195 html_txt(line); 197 html_txt(line);
196 html("</div>"); 198 html("</div>");
197 line[len-1] = c; 199 line[len-1] = c;
198} 200}
199 201
200static void header(unsigned char *sha1, char *path1, int mode1, 202static void header(unsigned char *sha1, char *path1, int mode1,
201 unsigned char *sha2, char *path2, int mode2) 203 unsigned char *sha2, char *path2, int mode2)
202{ 204{
203 char *abbrev1, *abbrev2; 205 char *abbrev1, *abbrev2;
204 int subproject; 206 int subproject;
205 207
206 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 208 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
207 html("<div class='head'>"); 209 html("<div class='head'>");
208 html("diff --git a/"); 210 html("diff --git a/");
209 html_txt(path1); 211 html_txt(path1);
210 html(" b/"); 212 html(" b/");
211 html_txt(path2); 213 html_txt(path2);
212 214
213 if (is_null_sha1(sha1)) 215 if (is_null_sha1(sha1))
214 path1 = "dev/null"; 216 path1 = "dev/null";
215 if (is_null_sha1(sha2)) 217 if (is_null_sha1(sha2))
216 path2 = "dev/null"; 218 path2 = "dev/null";
217 219
218 if (mode1 == 0) 220 if (mode1 == 0)
219 htmlf("<br/>new file mode %.6o", mode2); 221 htmlf("<br/>new file mode %.6o", mode2);
220 222
221 if (mode2 == 0) 223 if (mode2 == 0)
222 htmlf("<br/>deleted file mode %.6o", mode1); 224 htmlf("<br/>deleted file mode %.6o", mode1);
223 225
224 if (!subproject) { 226 if (!subproject) {
225 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 227 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
226 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 228 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
227 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 229 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
228 free(abbrev1); 230 free(abbrev1);
229 free(abbrev2); 231 free(abbrev2);
230 if (mode1 != 0 && mode2 != 0) { 232 if (mode1 != 0 && mode2 != 0) {
231 htmlf(" %.6o", mode1); 233 htmlf(" %.6o", mode1);
232 if (mode2 != mode1) 234 if (mode2 != mode1)
233 htmlf("..%.6o", mode2); 235 htmlf("..%.6o", mode2);
234 } 236 }
235 html("<br/>--- a/"); 237 html("<br/>--- a/");
236 if (mode1 != 0) 238 if (mode1 != 0)
237 cgit_tree_link(path1, NULL, NULL, ctx.qry.head, 239 cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
238 sha1_to_hex(old_rev_sha1), path1); 240 sha1_to_hex(old_rev_sha1), path1);
239 else 241 else
240 html_txt(path1); 242 html_txt(path1);
241 html("<br/>+++ b/"); 243 html("<br/>+++ b/");
242 if (mode2 != 0) 244 if (mode2 != 0)
243 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 245 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
244 sha1_to_hex(new_rev_sha1), path2); 246 sha1_to_hex(new_rev_sha1), path2);
245 else 247 else
246 html_txt(path2); 248 html_txt(path2);
247 } 249 }
248 html("</div>"); 250 html("</div>");
249} 251}
250 252
251static void print_ssdiff_link() 253static void print_ssdiff_link()
252{ 254{
253 if (!strcmp(ctx.qry.page, "diff")) { 255 if (!strcmp(ctx.qry.page, "diff")) {
254 if (use_ssdiff) 256 if (use_ssdiff)
255 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head, 257 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
256 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1); 258 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
257 else 259 else
258 cgit_diff_link("Side-by-side diff", NULL, NULL, 260 cgit_diff_link("Side-by-side diff", NULL, NULL,
259 ctx.qry.head, ctx.qry.sha1, 261 ctx.qry.head, ctx.qry.sha1,
260 ctx.qry.sha2, ctx.qry.path, 1); 262 ctx.qry.sha2, ctx.qry.path, 1);
261 } 263 }
262} 264}
263 265
264static void filepair_cb(struct diff_filepair *pair) 266static void filepair_cb(struct diff_filepair *pair)
265{ 267{
266 unsigned long old_size = 0; 268 unsigned long old_size = 0;
267 unsigned long new_size = 0; 269 unsigned long new_size = 0;
268 int binary = 0; 270 int binary = 0;
269 linediff_fn print_line_fn = print_line; 271 linediff_fn print_line_fn = print_line;
270 272
271 if (use_ssdiff) { 273 if (use_ssdiff) {
272 cgit_ssdiff_header_begin(); 274 cgit_ssdiff_header_begin();
273 print_line_fn = cgit_ssdiff_line_cb; 275 print_line_fn = cgit_ssdiff_line_cb;
274 } 276 }
275 header(pair->one->sha1, pair->one->path, pair->one->mode, 277 header(pair->one->sha1, pair->one->path, pair->one->mode,
276 pair->two->sha1, pair->two->path, pair->two->mode); 278 pair->two->sha1, pair->two->path, pair->two->mode);
277 if (use_ssdiff) 279 if (use_ssdiff)
278 cgit_ssdiff_header_end(); 280 cgit_ssdiff_header_end();
279 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 281 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
280 if (S_ISGITLINK(pair->one->mode)) 282 if (S_ISGITLINK(pair->one->mode))
281 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 283 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
282 if (S_ISGITLINK(pair->two->mode)) 284 if (S_ISGITLINK(pair->two->mode))
283 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 285 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
284 if (use_ssdiff) 286 if (use_ssdiff)
285 cgit_ssdiff_footer(); 287 cgit_ssdiff_footer();
286 return; 288 return;
287 } 289 }
288 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 290 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
289 &new_size, &binary, print_line_fn)) 291 &new_size, &binary, print_line_fn))
290 cgit_print_error("Error running diff"); 292 cgit_print_error("Error running diff");
291 if (binary) { 293 if (binary) {
292 if (use_ssdiff) 294 if (use_ssdiff)
293 html("<tr><td colspan='4'>Binary files differ</td></tr>"); 295 html("<tr><td colspan='4'>Binary files differ</td></tr>");
294 else 296 else
295 html("Binary files differ"); 297 html("Binary files differ");
296 } 298 }
297 if (use_ssdiff) 299 if (use_ssdiff)
298 cgit_ssdiff_footer(); 300 cgit_ssdiff_footer();
299} 301}
300 302
301void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 303void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
302{ 304{
303 enum object_type type; 305 enum object_type type;
304 unsigned long size; 306 unsigned long size;
305 struct commit *commit, *commit2; 307 struct commit *commit, *commit2;
306 308
307 if (!new_rev) 309 if (!new_rev)
308 new_rev = ctx.qry.head; 310 new_rev = ctx.qry.head;
309 get_sha1(new_rev, new_rev_sha1); 311 get_sha1(new_rev, new_rev_sha1);
310 type = sha1_object_info(new_rev_sha1, &size); 312 type = sha1_object_info(new_rev_sha1, &size);
311 if (type == OBJ_BAD) { 313 if (type == OBJ_BAD) {
312 cgit_print_error(fmt("Bad object name: %s", new_rev)); 314 cgit_print_error(fmt("Bad object name: %s", new_rev));
313 return; 315 return;
314 } 316 }
315 commit = lookup_commit_reference(new_rev_sha1); 317 commit = lookup_commit_reference(new_rev_sha1);
316 if (!commit || parse_commit(commit)) 318 if (!commit || parse_commit(commit))
317 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); 319 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
318 320
319 if (old_rev) 321 if (old_rev)
320 get_sha1(old_rev, old_rev_sha1); 322 get_sha1(old_rev, old_rev_sha1);
321 else if (commit->parents && commit->parents->item) 323 else if (commit->parents && commit->parents->item)
322 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 324 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
323 else 325 else
324 hashclr(old_rev_sha1); 326 hashclr(old_rev_sha1);
325 327
326 if (!is_null_sha1(old_rev_sha1)) { 328 if (!is_null_sha1(old_rev_sha1)) {
327 type = sha1_object_info(old_rev_sha1, &size); 329 type = sha1_object_info(old_rev_sha1, &size);
328 if (type == OBJ_BAD) { 330 if (type == OBJ_BAD) {
329 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 331 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
330 return; 332 return;
331 } 333 }
332 commit2 = lookup_commit_reference(old_rev_sha1); 334 commit2 = lookup_commit_reference(old_rev_sha1);
333 if (!commit2 || parse_commit(commit2)) 335 if (!commit2 || parse_commit(commit2))
334 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 336 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
335 } 337 }
336 338
337 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) 339 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
338 use_ssdiff = 1; 340 use_ssdiff = 1;
339 341
340 print_ssdiff_link(); 342 print_ssdiff_link();
341 cgit_print_diffstat(old_rev_sha1, new_rev_sha1); 343 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix);
342 344
343 if (use_ssdiff) { 345 if (use_ssdiff) {
344 html("<table summary='ssdiff' class='ssdiff'>"); 346 html("<table summary='ssdiff' class='ssdiff'>");
345 } else { 347 } else {
346 html("<table summary='diff' class='diff'>"); 348 html("<table summary='diff' class='diff'>");
347 html("<tr><td>"); 349 html("<tr><td>");
348 } 350 }
349 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 351 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
350 if (!use_ssdiff) 352 if (!use_ssdiff)
351 html("</td></tr>"); 353 html("</td></tr>");
352 html("</table>"); 354 html("</table>");
353} 355}
diff --git a/ui-log.c b/ui-log.c
index 0947604..bfbe436 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,234 +1,237 @@
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 unsigned long old_size = 0; 29 unsigned long old_size = 0;
30 unsigned long new_size = 0; 30 unsigned long new_size = 0;
31 int binary = 0; 31 int binary = 0;
32 32
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, count_lines);
37} 37}
38 38
39void show_commit_decorations(struct commit *commit) 39void show_commit_decorations(struct commit *commit)
40{ 40{
41 struct name_decoration *deco; 41 struct name_decoration *deco;
42 static char buf[1024]; 42 static char buf[1024];
43 43
44 buf[sizeof(buf) - 1] = 0; 44 buf[sizeof(buf) - 1] = 0;
45 deco = lookup_decoration(&name_decoration, &commit->object); 45 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 46 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 47 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 48 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 50 ctx.qry.vpath, 0, NULL, NULL,
51 ctx.qry.showmsg);
51 } 52 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 53 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 54 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 55 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 56 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) { 57 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 58 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 59 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 60 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 61 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 62 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 63 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 64 sha1_to_hex(commit->object.sha1),
64 0, NULL, NULL, ctx.qry.showmsg); 65 ctx.qry.vpath, 0, NULL, NULL,
66 ctx.qry.showmsg);
65 } 67 }
66 else { 68 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 69 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 70 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1), 0); 71 sha1_to_hex(commit->object.sha1),
72 ctx.qry.vpath, 0);
70 } 73 }
71 deco = deco->next; 74 deco = deco->next;
72 } 75 }
73} 76}
74 77
75void print_commit(struct commit *commit) 78void print_commit(struct commit *commit)
76{ 79{
77 struct commitinfo *info; 80 struct commitinfo *info;
78 char *tmp; 81 char *tmp;
79 int cols = 2; 82 int cols = 2;
80 83
81 info = cgit_parse_commit(commit); 84 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 85 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 86 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 87 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 88 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
86 html_link_open(tmp, NULL, NULL); 89 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 90 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 91 html_link_close();
89 htmlf("</td><td%s>", 92 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 93 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 94 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1), 0); 95 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
93 show_commit_decorations(commit); 96 show_commit_decorations(commit);
94 html("</td><td>"); 97 html("</td><td>");
95 html_txt(info->author); 98 html_txt(info->author);
96 if (ctx.repo->enable_log_filecount) { 99 if (ctx.repo->enable_log_filecount) {
97 files = 0; 100 files = 0;
98 add_lines = 0; 101 add_lines = 0;
99 rem_lines = 0; 102 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 103 cgit_diff_commit(commit, inspect_files);
101 html("</td><td>"); 104 html("</td><td>");
102 htmlf("%d", files); 105 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 106 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 107 html("</td><td>");
105 htmlf("-%d/+%d", rem_lines, add_lines); 108 htmlf("-%d/+%d", rem_lines, add_lines);
106 } 109 }
107 } 110 }
108 html("</td></tr>\n"); 111 html("</td></tr>\n");
109 if (ctx.qry.showmsg) { 112 if (ctx.qry.showmsg) {
110 if (ctx.repo->enable_log_filecount) { 113 if (ctx.repo->enable_log_filecount) {
111 cols++; 114 cols++;
112 if (ctx.repo->enable_log_linecount) 115 if (ctx.repo->enable_log_linecount)
113 cols++; 116 cols++;
114 } 117 }
115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 118 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
116 cols); 119 cols);
117 html_txt(info->msg); 120 html_txt(info->msg);
118 html("</td></tr>\n"); 121 html("</td></tr>\n");
119 } 122 }
120 cgit_free_commitinfo(info); 123 cgit_free_commitinfo(info);
121} 124}
122 125
123static const char *disambiguate_ref(const char *ref) 126static const char *disambiguate_ref(const char *ref)
124{ 127{
125 unsigned char sha1[20]; 128 unsigned char sha1[20];
126 const char *longref; 129 const char *longref;
127 130
128 longref = fmt("refs/heads/%s", ref); 131 longref = fmt("refs/heads/%s", ref);
129 if (get_sha1(longref, sha1) == 0) 132 if (get_sha1(longref, sha1) == 0)
130 return longref; 133 return longref;
131 134
132 return ref; 135 return ref;
133} 136}
134 137
135void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, 138void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
136 char *path, int pager) 139 char *path, int pager)
137{ 140{
138 struct rev_info rev; 141 struct rev_info rev;
139 struct commit *commit; 142 struct commit *commit;
140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 143 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
141 int argc = 2; 144 int argc = 2;
142 int i, columns = 3; 145 int i, columns = 3;
143 146
144 if (!tip) 147 if (!tip)
145 tip = ctx.qry.head; 148 tip = ctx.qry.head;
146 149
147 argv[1] = disambiguate_ref(tip); 150 argv[1] = disambiguate_ref(tip);
148 151
149 if (grep && pattern && (!strcmp(grep, "grep") || 152 if (grep && pattern && (!strcmp(grep, "grep") ||
150 !strcmp(grep, "author") || 153 !strcmp(grep, "author") ||
151 !strcmp(grep, "committer"))) 154 !strcmp(grep, "committer")))
152 argv[argc++] = fmt("--%s=%s", grep, pattern); 155 argv[argc++] = fmt("--%s=%s", grep, pattern);
153 156
154 if (path) { 157 if (path) {
155 argv[argc++] = "--"; 158 argv[argc++] = "--";
156 argv[argc++] = path; 159 argv[argc++] = path;
157 } 160 }
158 init_revisions(&rev, NULL); 161 init_revisions(&rev, NULL);
159 rev.abbrev = DEFAULT_ABBREV; 162 rev.abbrev = DEFAULT_ABBREV;
160 rev.commit_format = CMIT_FMT_DEFAULT; 163 rev.commit_format = CMIT_FMT_DEFAULT;
161 rev.verbose_header = 1; 164 rev.verbose_header = 1;
162 rev.show_root_diff = 0; 165 rev.show_root_diff = 0;
163 setup_revisions(argc, argv, &rev, NULL); 166 setup_revisions(argc, argv, &rev, NULL);
164 load_ref_decorations(DECORATE_FULL_REFS); 167 load_ref_decorations(DECORATE_FULL_REFS);
165 rev.show_decorations = 1; 168 rev.show_decorations = 1;
166 rev.grep_filter.regflags |= REG_ICASE; 169 rev.grep_filter.regflags |= REG_ICASE;
167 compile_grep_patterns(&rev.grep_filter); 170 compile_grep_patterns(&rev.grep_filter);
168 prepare_revision_walk(&rev); 171 prepare_revision_walk(&rev);
169 172
170 if (pager) 173 if (pager)
171 html("<table class='list nowrap'>"); 174 html("<table class='list nowrap'>");
172 175
173 html("<tr class='nohover'><th class='left'>Age</th>" 176 html("<tr class='nohover'><th class='left'>Age</th>"
174 "<th class='left'>Commit message"); 177 "<th class='left'>Commit message");
175 if (pager) { 178 if (pager) {
176 html(" ("); 179 html(" (");
177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 180 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
178 NULL, ctx.qry.head, ctx.qry.sha1, 181 NULL, ctx.qry.head, ctx.qry.sha1,
179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 182 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 183 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
181 html(")"); 184 html(")");
182 } 185 }
183 html("</th><th class='left'>Author</th>"); 186 html("</th><th class='left'>Author</th>");
184 if (ctx.repo->enable_log_filecount) { 187 if (ctx.repo->enable_log_filecount) {
185 html("<th class='left'>Files</th>"); 188 html("<th class='left'>Files</th>");
186 columns++; 189 columns++;
187 if (ctx.repo->enable_log_linecount) { 190 if (ctx.repo->enable_log_linecount) {
188 html("<th class='left'>Lines</th>"); 191 html("<th class='left'>Lines</th>");
189 columns++; 192 columns++;
190 } 193 }
191 } 194 }
192 html("</tr>\n"); 195 html("</tr>\n");
193 196
194 if (ofs<0) 197 if (ofs<0)
195 ofs = 0; 198 ofs = 0;
196 199
197 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { 200 for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
198 free(commit->buffer); 201 free(commit->buffer);
199 commit->buffer = NULL; 202 commit->buffer = NULL;
200 free_commit_list(commit->parents); 203 free_commit_list(commit->parents);
201 commit->parents = NULL; 204 commit->parents = NULL;
202 } 205 }
203 206
204 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { 207 for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
205 print_commit(commit); 208 print_commit(commit);
206 free(commit->buffer); 209 free(commit->buffer);
207 commit->buffer = NULL; 210 commit->buffer = NULL;
208 free_commit_list(commit->parents); 211 free_commit_list(commit->parents);
209 commit->parents = NULL; 212 commit->parents = NULL;
210 } 213 }
211 if (pager) { 214 if (pager) {
212 htmlf("</table><div class='pager'>", 215 htmlf("</table><div class='pager'>",
213 columns); 216 columns);
214 if (ofs > 0) { 217 if (ofs > 0) {
215 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 218 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
216 ctx.qry.sha1, ctx.qry.path, 219 ctx.qry.sha1, ctx.qry.vpath,
217 ofs - cnt, ctx.qry.grep, 220 ofs - cnt, ctx.qry.grep,
218 ctx.qry.search, ctx.qry.showmsg); 221 ctx.qry.search, ctx.qry.showmsg);
219 html("&nbsp;"); 222 html("&nbsp;");
220 } 223 }
221 if ((commit = get_revision(&rev)) != NULL) { 224 if ((commit = get_revision(&rev)) != NULL) {
222 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 225 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
223 ctx.qry.sha1, ctx.qry.path, 226 ctx.qry.sha1, ctx.qry.vpath,
224 ofs + cnt, ctx.qry.grep, 227 ofs + cnt, ctx.qry.grep,
225 ctx.qry.search, ctx.qry.showmsg); 228 ctx.qry.search, ctx.qry.showmsg);
226 } 229 }
227 html("</div>"); 230 html("</div>");
228 } else if ((commit = get_revision(&rev)) != NULL) { 231 } else if ((commit = get_revision(&rev)) != NULL) {
229 html("<tr class='nohover'><td colspan='3'>"); 232 html("<tr class='nohover'><td colspan='3'>");
230 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 233 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
231 NULL, NULL, ctx.qry.showmsg); 234 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
232 html("</td></tr>\n"); 235 html("</td></tr>\n");
233 } 236 }
234} 237}
diff --git a/ui-patch.c b/ui-patch.c
index 2a8f7a5..25dc9fe 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -1,129 +1,131 @@
1/* ui-patch.c: generate patch view 1/* ui-patch.c: generate patch view
2 * 2 *
3 * Copyright (C) 2007 Lars Hjemli 3 * Copyright (C) 2007 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 void print_line(char *line, int len) 13static void print_line(char *line, int len)
14{ 14{
15 char c = line[len-1]; 15 char c = line[len-1];
16 16
17 line[len-1] = '\0'; 17 line[len-1] = '\0';
18 htmlf("%s\n", line); 18 htmlf("%s\n", line);
19 line[len-1] = c; 19 line[len-1] = c;
20} 20}
21 21
22static void header(unsigned char *sha1, char *path1, int mode1, 22static void header(unsigned char *sha1, char *path1, int mode1,
23 unsigned char *sha2, char *path2, int mode2) 23 unsigned char *sha2, char *path2, int mode2)
24{ 24{
25 char *abbrev1, *abbrev2; 25 char *abbrev1, *abbrev2;
26 int subproject; 26 int subproject;
27 27
28 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 28 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
29 htmlf("diff --git a/%s b/%s\n", path1, path2); 29 htmlf("diff --git a/%s b/%s\n", path1, path2);
30 30
31 if (is_null_sha1(sha1)) 31 if (is_null_sha1(sha1))
32 path1 = "dev/null"; 32 path1 = "dev/null";
33 if (is_null_sha1(sha2)) 33 if (is_null_sha1(sha2))
34 path2 = "dev/null"; 34 path2 = "dev/null";
35 35
36 if (mode1 == 0) 36 if (mode1 == 0)
37 htmlf("new file mode %.6o\n", mode2); 37 htmlf("new file mode %.6o\n", mode2);
38 38
39 if (mode2 == 0) 39 if (mode2 == 0)
40 htmlf("deleted file mode %.6o\n", mode1); 40 htmlf("deleted file mode %.6o\n", mode1);
41 41
42 if (!subproject) { 42 if (!subproject) {
43 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 43 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
44 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 44 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
45 htmlf("index %s..%s", abbrev1, abbrev2); 45 htmlf("index %s..%s", abbrev1, abbrev2);
46 free(abbrev1); 46 free(abbrev1);
47 free(abbrev2); 47 free(abbrev2);
48 if (mode1 != 0 && mode2 != 0) { 48 if (mode1 != 0 && mode2 != 0) {
49 htmlf(" %.6o", mode1); 49 htmlf(" %.6o", mode1);
50 if (mode2 != mode1) 50 if (mode2 != mode1)
51 htmlf("..%.6o", mode2); 51 htmlf("..%.6o", mode2);
52 } 52 }
53 htmlf("\n--- a/%s\n", path1); 53 htmlf("\n--- a/%s\n", path1);
54 htmlf("+++ b/%s\n", path2); 54 htmlf("+++ b/%s\n", path2);
55 } 55 }
56} 56}
57 57
58static void filepair_cb(struct diff_filepair *pair) 58static void filepair_cb(struct diff_filepair *pair)
59{ 59{
60 unsigned long old_size = 0; 60 unsigned long old_size = 0;
61 unsigned long new_size = 0; 61 unsigned long new_size = 0;
62 int binary = 0; 62 int binary = 0;
63 63
64 header(pair->one->sha1, pair->one->path, pair->one->mode, 64 header(pair->one->sha1, pair->one->path, pair->one->mode,
65 pair->two->sha1, pair->two->path, pair->two->mode); 65 pair->two->sha1, pair->two->path, pair->two->mode);
66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 66 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
67 if (S_ISGITLINK(pair->one->mode)) 67 if (S_ISGITLINK(pair->one->mode))
68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 68 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
69 if (S_ISGITLINK(pair->two->mode)) 69 if (S_ISGITLINK(pair->two->mode))
70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 70 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
71 return; 71 return;
72 } 72 }
73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
74 &new_size, &binary, print_line)) 74 &new_size, &binary, print_line))
75 html("Error running diff"); 75 html("Error running diff");
76 if (binary) 76 if (binary)
77 html("Binary files differ\n"); 77 html("Binary files differ\n");
78} 78}
79 79
80void cgit_print_patch(char *hex) 80void cgit_print_patch(char *hex, const char *prefix)
81{ 81{
82 struct commit *commit; 82 struct commit *commit;
83 struct commitinfo *info; 83 struct commitinfo *info;
84 unsigned char sha1[20], old_sha1[20]; 84 unsigned char sha1[20], old_sha1[20];
85 char *patchname; 85 char *patchname;
86 86
87 if (!hex) 87 if (!hex)
88 hex = ctx.qry.head; 88 hex = ctx.qry.head;
89 89
90 if (get_sha1(hex, sha1)) { 90 if (get_sha1(hex, sha1)) {
91 cgit_print_error(fmt("Bad object id: %s", hex)); 91 cgit_print_error(fmt("Bad object id: %s", hex));
92 return; 92 return;
93 } 93 }
94 commit = lookup_commit_reference(sha1); 94 commit = lookup_commit_reference(sha1);
95 if (!commit) { 95 if (!commit) {
96 cgit_print_error(fmt("Bad commit reference: %s", hex)); 96 cgit_print_error(fmt("Bad commit reference: %s", hex));
97 return; 97 return;
98 } 98 }
99 info = cgit_parse_commit(commit); 99 info = cgit_parse_commit(commit);
100 100
101 if (commit->parents && commit->parents->item) 101 if (commit->parents && commit->parents->item)
102 hashcpy(old_sha1, commit->parents->item->object.sha1); 102 hashcpy(old_sha1, commit->parents->item->object.sha1);
103 else 103 else
104 hashclr(old_sha1); 104 hashclr(old_sha1);
105 105
106 patchname = fmt("%s.patch", sha1_to_hex(sha1)); 106 patchname = fmt("%s.patch", sha1_to_hex(sha1));
107 ctx.page.mimetype = "text/plain"; 107 ctx.page.mimetype = "text/plain";
108 ctx.page.filename = patchname; 108 ctx.page.filename = patchname;
109 cgit_print_http_headers(&ctx); 109 cgit_print_http_headers(&ctx);
110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1)); 110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1));
111 htmlf("From: %s", info->author); 111 htmlf("From: %s", info->author);
112 if (!ctx.cfg.noplainemail) { 112 if (!ctx.cfg.noplainemail) {
113 htmlf(" %s", info->author_email); 113 htmlf(" %s", info->author_email);
114 } 114 }
115 html("\n"); 115 html("\n");
116 html("Date: "); 116 html("Date: ");
117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); 117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time);
118 htmlf("Subject: %s\n\n", info->subject); 118 htmlf("Subject: %s\n\n", info->subject);
119 if (info->msg && *info->msg) { 119 if (info->msg && *info->msg) {
120 htmlf("%s", info->msg); 120 htmlf("%s", info->msg);
121 if (info->msg[strlen(info->msg) - 1] != '\n') 121 if (info->msg[strlen(info->msg) - 1] != '\n')
122 html("\n"); 122 html("\n");
123 } 123 }
124 html("---\n"); 124 html("---\n");
125 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); 125 if (prefix)
126 htmlf("(limited to '%s')\n\n", prefix);
127 cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix);
126 html("--\n"); 128 html("--\n");
127 htmlf("cgit %s\n", CGIT_VERSION); 129 htmlf("cgit %s\n", CGIT_VERSION);
128 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
129} 131}
diff --git a/ui-patch.h b/ui-patch.h
index 9f68212..1641cea 100644
--- a/ui-patch.h
+++ b/ui-patch.h
@@ -1,6 +1,6 @@
1#ifndef UI_PATCH_H 1#ifndef UI_PATCH_H
2#define UI_PATCH_H 2#define UI_PATCH_H
3 3
4extern void cgit_print_patch(char *hex); 4extern void cgit_print_patch(char *hex, const char *prefix);
5 5
6#endif /* UI_PATCH_H */ 6#endif /* UI_PATCH_H */
diff --git a/ui-refs.c b/ui-refs.c
index 98738db..94ff6be 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -1,205 +1,205 @@
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 int r1date, r2date; 49 int r1date, r2date;
50 50
51 if (r1->object->type != OBJ_COMMIT) 51 if (r1->object->type != OBJ_COMMIT)
52 r1date = r1->tag->tagger_date; 52 r1date = r1->tag->tagger_date;
53 else 53 else
54 r1date = r1->commit->committer_date; 54 r1date = r1->commit->committer_date;
55 55
56 if (r2->object->type != OBJ_COMMIT) 56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date; 57 r2date = r2->tag->tagger_date;
58 else 58 else
59 r2date = r2->commit->committer_date; 59 r2date = r2->commit->committer_date;
60 60
61 return cmp_age(r1date, r2date); 61 return cmp_age(r1date, r2date);
62} 62}
63 63
64static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
90static void print_tag_header() 90static void print_tag_header()
91{ 91{
92 html("<tr class='nohover'><th class='left'>Tag</th>" 92 html("<tr class='nohover'><th class='left'>Tag</th>"
93 "<th class='left'>Download</th>" 93 "<th class='left'>Download</th>"
94 "<th class='left'>Author</th>" 94 "<th class='left'>Author</th>"
95 "<th class='left' colspan='2'>Age</th></tr>\n"); 95 "<th class='left' colspan='2'>Age</th></tr>\n");
96 header = 1; 96 header = 1;
97} 97}
98 98
99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
100{ 100{
101 const struct cgit_snapshot_format* f; 101 const struct cgit_snapshot_format* f;
102 char *filename; 102 char *filename;
103 const char *basename; 103 const char *basename;
104 104
105 if (!ref || strlen(ref) < 2) 105 if (!ref || strlen(ref) < 2)
106 return; 106 return;
107 107
108 basename = cgit_repobasename(repo->url); 108 basename = cgit_repobasename(repo->url);
109 if (prefixcmp(ref, basename) != 0) { 109 if (prefixcmp(ref, basename) != 0) {
110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) 110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
111 ref++; 111 ref++;
112 if (isdigit(ref[0])) 112 if (isdigit(ref[0]))
113 ref = xstrdup(fmt("%s-%s", basename, ref)); 113 ref = xstrdup(fmt("%s-%s", basename, ref));
114 } 114 }
115 115
116 for (f = cgit_snapshot_formats; f->suffix; f++) { 116 for (f = cgit_snapshot_formats; f->suffix; f++) {
117 if (!(repo->snapshots & f->bit)) 117 if (!(repo->snapshots & f->bit))
118 continue; 118 continue;
119 filename = fmt("%s%s", ref, f->suffix); 119 filename = fmt("%s%s", ref, f->suffix);
120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
121 html("&nbsp;&nbsp;"); 121 html("&nbsp;&nbsp;");
122 } 122 }
123} 123}
124static int print_tag(struct refinfo *ref) 124static int print_tag(struct refinfo *ref)
125{ 125{
126 struct tag *tag; 126 struct tag *tag;
127 struct taginfo *info; 127 struct taginfo *info;
128 char *name = (char *)ref->refname; 128 char *name = (char *)ref->refname;
129 129
130 if (ref->object->type == OBJ_TAG) { 130 if (ref->object->type == OBJ_TAG) {
131 tag = (struct tag *)ref->object; 131 tag = (struct tag *)ref->object;
132 info = ref->tag; 132 info = ref->tag;
133 if (!tag || !info) 133 if (!tag || !info)
134 return 1; 134 return 1;
135 html("<tr><td>"); 135 html("<tr><td>");
136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
137 html("</td><td>"); 137 html("</td><td>");
138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) 138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
139 print_tag_downloads(ctx.repo, name); 139 print_tag_downloads(ctx.repo, name);
140 else 140 else
141 cgit_object_link(tag->tagged); 141 cgit_object_link(tag->tagged);
142 html("</td><td>"); 142 html("</td><td>");
143 if (info->tagger) 143 if (info->tagger)
144 html(info->tagger); 144 html(info->tagger);
145 html("</td><td colspan='2'>"); 145 html("</td><td colspan='2'>");
146 if (info->tagger_date > 0) 146 if (info->tagger_date > 0)
147 cgit_print_age(info->tagger_date, -1, NULL); 147 cgit_print_age(info->tagger_date, -1, NULL);
148 html("</td></tr>\n"); 148 html("</td></tr>\n");
149 } else { 149 } else {
150 if (!header) 150 if (!header)
151 print_tag_header(); 151 print_tag_header();
152 html("<tr><td>"); 152 html("<tr><td>");
153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
154 html("</td><td>"); 154 html("</td><td>");
155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) 155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT))
156 print_tag_downloads(ctx.repo, name); 156 print_tag_downloads(ctx.repo, name);
157 else 157 else
158 cgit_object_link(ref->object); 158 cgit_object_link(ref->object);
159 html("</td><td>"); 159 html("</td><td>");
160 if (ref->object->type == OBJ_COMMIT) 160 if (ref->object->type == OBJ_COMMIT)
161 html(ref->commit->author); 161 html(ref->commit->author);
162 html("</td><td colspan='2'>"); 162 html("</td><td colspan='2'>");
163 if (ref->object->type == OBJ_COMMIT) 163 if (ref->object->type == OBJ_COMMIT)
164 cgit_print_age(ref->commit->commit->date, -1, NULL); 164 cgit_print_age(ref->commit->commit->date, -1, NULL);
165 html("</td></tr>\n"); 165 html("</td></tr>\n");
166 } 166 }
167 return 0; 167 return 0;
168} 168}
169 169
170static void print_refs_link(char *path) 170static void print_refs_link(char *path)
171{ 171{
172 html("<tr class='nohover'><td colspan='4'>"); 172 html("<tr class='nohover'><td colspan='4'>");
173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
174 html("</td></tr>"); 174 html("</td></tr>");
175} 175}
176 176
177void cgit_print_branches(int maxcount) 177void cgit_print_branches(int maxcount)
178{ 178{
179 struct reflist list; 179 struct reflist list;
180 int i; 180 int i;
181 181
182 html("<tr class='nohover'><th class='left'>Branch</th>" 182 html("<tr class='nohover'><th class='left'>Branch</th>"
183 "<th class='left'>Commit message</th>" 183 "<th class='left'>Commit message</th>"
184 "<th class='left'>Author</th>" 184 "<th class='left'>Author</th>"
185 "<th class='left' colspan='2'>Age</th></tr>\n"); 185 "<th class='left' colspan='2'>Age</th></tr>\n");
186 186
187 list.refs = NULL; 187 list.refs = NULL;
188 list.alloc = list.count = 0; 188 list.alloc = list.count = 0;
189 for_each_branch_ref(cgit_refs_cb, &list); 189 for_each_branch_ref(cgit_refs_cb, &list);
190 if (ctx.repo->enable_remote_branches) 190 if (ctx.repo->enable_remote_branches)
191 for_each_remote_ref(cgit_refs_cb, &list); 191 for_each_remote_ref(cgit_refs_cb, &list);
192 192
193 if (maxcount == 0 || maxcount > list.count) 193 if (maxcount == 0 || maxcount > list.count)
194 maxcount = list.count; 194 maxcount = list.count;
195 195
196 if (maxcount < list.count) { 196 if (maxcount < list.count) {
197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
198 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 198 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
199 } 199 }
200 200
201 for(i=0; i<maxcount; i++) 201 for(i=0; i<maxcount; i++)
202 print_branch(list.refs[i]); 202 print_branch(list.refs[i]);
203 203
204 if (maxcount < list.count) 204 if (maxcount < list.count)
205 print_refs_link("heads"); 205 print_refs_link("heads");
diff --git a/ui-shared.c b/ui-shared.c
index 8827fff..e991799 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,800 +1,884 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output functions
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cmd.h" 10#include "cmd.h"
11#include "html.h" 11#include "html.h"
12 12
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(const char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_httpscheme() 37char *cgit_httpscheme()
38{ 38{
39 if (ctx.env.https && !strcmp(ctx.env.https, "on")) 39 if (ctx.env.https && !strcmp(ctx.env.https, "on"))
40 return "https://"; 40 return "https://";
41 else 41 else
42 return "http://"; 42 return "http://";
43} 43}
44 44
45char *cgit_hosturl() 45char *cgit_hosturl()
46{ 46{
47 if (ctx.env.http_host) 47 if (ctx.env.http_host)
48 return ctx.env.http_host; 48 return ctx.env.http_host;
49 if (!ctx.env.server_name) 49 if (!ctx.env.server_name)
50 return NULL; 50 return NULL;
51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) 51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
52 return ctx.env.server_name; 52 return ctx.env.server_name;
53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port)); 53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
54} 54}
55 55
56char *cgit_rooturl() 56char *cgit_rooturl()
57{ 57{
58 if (ctx.cfg.virtual_root) 58 if (ctx.cfg.virtual_root)
59 return fmt("%s/", ctx.cfg.virtual_root); 59 return fmt("%s/", ctx.cfg.virtual_root);
60 else 60 else
61 return ctx.cfg.script_name; 61 return ctx.cfg.script_name;
62} 62}
63 63
64char *cgit_repourl(const char *reponame) 64char *cgit_repourl(const char *reponame)
65{ 65{
66 if (ctx.cfg.virtual_root) { 66 if (ctx.cfg.virtual_root) {
67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
68 } else { 68 } else {
69 return fmt("?r=%s", reponame); 69 return fmt("?r=%s", reponame);
70 } 70 }
71} 71}
72 72
73char *cgit_fileurl(const char *reponame, const char *pagename, 73char *cgit_fileurl(const char *reponame, const char *pagename,
74 const char *filename, const char *query) 74 const char *filename, const char *query)
75{ 75{
76 char *tmp; 76 char *tmp;
77 char *delim; 77 char *delim;
78 78
79 if (ctx.cfg.virtual_root) { 79 if (ctx.cfg.virtual_root) {
80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 80 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
81 pagename, (filename ? filename:"")); 81 pagename, (filename ? filename:""));
82 delim = "?"; 82 delim = "?";
83 } else { 83 } else {
84 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 84 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
85 (filename ? filename : "")); 85 (filename ? filename : ""));
86 delim = "&"; 86 delim = "&";
87 } 87 }
88 if (query) 88 if (query)
89 tmp = fmt("%s%s%s", tmp, delim, query); 89 tmp = fmt("%s%s%s", tmp, delim, query);
90 return tmp; 90 return tmp;
91} 91}
92 92
93char *cgit_pageurl(const char *reponame, const char *pagename, 93char *cgit_pageurl(const char *reponame, const char *pagename,
94 const char *query) 94 const char *query)
95{ 95{
96 return cgit_fileurl(reponame,pagename,0,query); 96 return cgit_fileurl(reponame,pagename,0,query);
97} 97}
98 98
99const char *cgit_repobasename(const char *reponame) 99const char *cgit_repobasename(const char *reponame)
100{ 100{
101 /* I assume we don't need to store more than one repo basename */ 101 /* I assume we don't need to store more than one repo basename */
102 static char rvbuf[1024]; 102 static char rvbuf[1024];
103 int p; 103 int p;
104 const char *rv; 104 const char *rv;
105 strncpy(rvbuf,reponame,sizeof(rvbuf)); 105 strncpy(rvbuf,reponame,sizeof(rvbuf));
106 if(rvbuf[sizeof(rvbuf)-1]) 106 if(rvbuf[sizeof(rvbuf)-1])
107 die("cgit_repobasename: truncated repository name '%s'", reponame); 107 die("cgit_repobasename: truncated repository name '%s'", reponame);
108 p = strlen(rvbuf)-1; 108 p = strlen(rvbuf)-1;
109 /* strip trailing slashes */ 109 /* strip trailing slashes */
110 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 110 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
111 /* strip trailing .git */ 111 /* strip trailing .git */
112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 112 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
113 p -= 3; rvbuf[p--] = 0; 113 p -= 3; rvbuf[p--] = 0;
114 } 114 }
115 /* strip more trailing slashes if any */ 115 /* strip more trailing slashes if any */
116 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 116 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
117 /* find last slash in the remaining string */ 117 /* find last slash in the remaining string */
118 rv = strrchr(rvbuf,'/'); 118 rv = strrchr(rvbuf,'/');
119 if(rv) 119 if(rv)
120 return ++rv; 120 return ++rv;
121 return rvbuf; 121 return rvbuf;
122} 122}
123 123
124char *cgit_currurl() 124char *cgit_currurl()
125{ 125{
126 if (!ctx.cfg.virtual_root) 126 if (!ctx.cfg.virtual_root)
127 return ctx.cfg.script_name; 127 return ctx.cfg.script_name;
128 else if (ctx.qry.page) 128 else if (ctx.qry.page)
129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 129 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
130 else if (ctx.qry.repo) 130 else if (ctx.qry.repo)
131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 131 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
132 else 132 else
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(char *page, char *search, int ofs) 136static void site_url(const char *page, const char *search, int ofs)
137{ 137{
138 char *delim = "?"; 138 char *delim = "?";
139 139
140 if (ctx.cfg.virtual_root) { 140 if (ctx.cfg.virtual_root) {
141 html_attr(ctx.cfg.virtual_root); 141 html_attr(ctx.cfg.virtual_root);
142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 142 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
143 html("/"); 143 html("/");
144 } else 144 } else
145 html(ctx.cfg.script_name); 145 html(ctx.cfg.script_name);
146 146
147 if (page) { 147 if (page) {
148 htmlf("?p=%s", page); 148 htmlf("?p=%s", page);
149 delim = "&"; 149 delim = "&";
150 } 150 }
151 if (search) { 151 if (search) {
152 html(delim); 152 html(delim);
153 html("q="); 153 html("q=");
154 html_attr(search); 154 html_attr(search);
155 delim = "&"; 155 delim = "&";
156 } 156 }
157 if (ofs) { 157 if (ofs) {
158 html(delim); 158 html(delim);
159 htmlf("ofs=%d", ofs); 159 htmlf("ofs=%d", ofs);
160 } 160 }
161} 161}
162 162
163static void site_link(char *page, char *name, char *title, char *class, 163static void site_link(const char *page, const char *name, const char *title,
164 char *search, int ofs) 164 const char *class, const char *search, int ofs)
165{ 165{
166 html("<a"); 166 html("<a");
167 if (title) { 167 if (title) {
168 html(" title='"); 168 html(" title='");
169 html_attr(title); 169 html_attr(title);
170 html("'"); 170 html("'");
171 } 171 }
172 if (class) { 172 if (class) {
173 html(" class='"); 173 html(" class='");
174 html_attr(class); 174 html_attr(class);
175 html("'"); 175 html("'");
176 } 176 }
177 html(" href='"); 177 html(" href='");
178 site_url(page, search, ofs); 178 site_url(page, search, ofs);
179 html("'>"); 179 html("'>");
180 html_txt(name); 180 html_txt(name);
181 html("</a>"); 181 html("</a>");
182} 182}
183 183
184void cgit_index_link(char *name, char *title, char *class, char *pattern, 184void cgit_index_link(const char *name, const char *title, const char *class,
185 int ofs) 185 const char *pattern, int ofs)
186{ 186{
187 site_link(NULL, name, title, class, pattern, ofs); 187 site_link(NULL, name, title, class, pattern, ofs);
188} 188}
189 189
190static char *repolink(char *title, char *class, char *page, char *head, 190static char *repolink(const char *title, const char *class, const char *page,
191 char *path) 191 const char *head, const char *path)
192{ 192{
193 char *delim = "?"; 193 char *delim = "?";
194 194
195 html("<a"); 195 html("<a");
196 if (title) { 196 if (title) {
197 html(" title='"); 197 html(" title='");
198 html_attr(title); 198 html_attr(title);
199 html("'"); 199 html("'");
200 } 200 }
201 if (class) { 201 if (class) {
202 html(" class='"); 202 html(" class='");
203 html_attr(class); 203 html_attr(class);
204 html("'"); 204 html("'");
205 } 205 }
206 html(" href='"); 206 html(" href='");
207 if (ctx.cfg.virtual_root) { 207 if (ctx.cfg.virtual_root) {
208 html_url_path(ctx.cfg.virtual_root); 208 html_url_path(ctx.cfg.virtual_root);
209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 209 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
210 html("/"); 210 html("/");
211 html_url_path(ctx.repo->url); 211 html_url_path(ctx.repo->url);
212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 212 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
213 html("/"); 213 html("/");
214 if (page) { 214 if (page) {
215 html_url_path(page); 215 html_url_path(page);
216 html("/"); 216 html("/");
217 if (path) 217 if (path)
218 html_url_path(path); 218 html_url_path(path);
219 } 219 }
220 } else { 220 } else {
221 html(ctx.cfg.script_name); 221 html(ctx.cfg.script_name);
222 html("?url="); 222 html("?url=");
223 html_url_arg(ctx.repo->url); 223 html_url_arg(ctx.repo->url);
224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 224 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
225 html("/"); 225 html("/");
226 if (page) { 226 if (page) {
227 html_url_arg(page); 227 html_url_arg(page);
228 html("/"); 228 html("/");
229 if (path) 229 if (path)
230 html_url_arg(path); 230 html_url_arg(path);
231 } 231 }
232 delim = "&amp;"; 232 delim = "&amp;";
233 } 233 }
234 if (head && strcmp(head, ctx.repo->defbranch)) { 234 if (head && strcmp(head, ctx.repo->defbranch)) {
235 html(delim); 235 html(delim);
236 html("h="); 236 html("h=");
237 html_url_arg(head); 237 html_url_arg(head);
238 delim = "&amp;"; 238 delim = "&amp;";
239 } 239 }
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(char *page, char *name, char *title, char *class, 243static void reporevlink(const char *page, const char *name, const char *title,
244 char *head, char *rev, char *path) 244 const char *class, const char *head, const char *rev,
245 const char *path)
245{ 246{
246 char *delim; 247 char *delim;
247 248
248 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
249 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { 250 if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) {
250 html(delim); 251 html(delim);
251 html("id="); 252 html("id=");
252 html_url_arg(rev); 253 html_url_arg(rev);
253 } 254 }
254 html("'>"); 255 html("'>");
255 html_txt(name); 256 html_txt(name);
256 html("</a>"); 257 html("</a>");
257} 258}
258 259
259void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(const char *name, const char *title, const char *class,
261 const char *head)
260{ 262{
261 reporevlink(NULL, name, title, class, head, NULL, NULL); 263 reporevlink(NULL, name, title, class, head, NULL, NULL);
262} 264}
263 265
264void cgit_tag_link(char *name, char *title, char *class, char *head, 266void cgit_tag_link(const char *name, const char *title, const char *class,
265 char *rev) 267 const char *head, const char *rev)
266{ 268{
267 reporevlink("tag", name, title, class, head, rev, NULL); 269 reporevlink("tag", name, title, class, head, rev, NULL);
268} 270}
269 271
270void cgit_tree_link(char *name, char *title, char *class, char *head, 272void cgit_tree_link(const char *name, const char *title, const char *class,
271 char *rev, char *path) 273 const char *head, const char *rev, const char *path)
272{ 274{
273 reporevlink("tree", name, title, class, head, rev, path); 275 reporevlink("tree", name, title, class, head, rev, path);
274} 276}
275 277
276void cgit_plain_link(char *name, char *title, char *class, char *head, 278void cgit_plain_link(const char *name, const char *title, const char *class,
277 char *rev, char *path) 279 const char *head, const char *rev, const char *path)
278{ 280{
279 reporevlink("plain", name, title, class, head, rev, path); 281 reporevlink("plain", name, title, class, head, rev, path);
280} 282}
281 283
282void cgit_log_link(char *name, char *title, char *class, char *head, 284void cgit_log_link(const char *name, const char *title, const char *class,
283 char *rev, char *path, int ofs, char *grep, char *pattern, 285 const char *head, const char *rev, const char *path,
284 int showmsg) 286 int ofs, const char *grep, const char *pattern, int showmsg)
285{ 287{
286 char *delim; 288 char *delim;
287 289
288 delim = repolink(title, class, "log", head, path); 290 delim = repolink(title, class, "log", head, path);
289 if (rev && strcmp(rev, ctx.qry.head)) { 291 if (rev && strcmp(rev, ctx.qry.head)) {
290 html(delim); 292 html(delim);
291 html("id="); 293 html("id=");
292 html_url_arg(rev); 294 html_url_arg(rev);
293 delim = "&"; 295 delim = "&";
294 } 296 }
295 if (grep && pattern) { 297 if (grep && pattern) {
296 html(delim); 298 html(delim);
297 html("qt="); 299 html("qt=");
298 html_url_arg(grep); 300 html_url_arg(grep);
299 delim = "&"; 301 delim = "&";
300 html(delim); 302 html(delim);
301 html("q="); 303 html("q=");
302 html_url_arg(pattern); 304 html_url_arg(pattern);
303 } 305 }
304 if (ofs > 0) { 306 if (ofs > 0) {
305 html(delim); 307 html(delim);
306 html("ofs="); 308 html("ofs=");
307 htmlf("%d", ofs); 309 htmlf("%d", ofs);
308 delim = "&"; 310 delim = "&";
309 } 311 }
310 if (showmsg) { 312 if (showmsg) {
311 html(delim); 313 html(delim);
312 html("showmsg=1"); 314 html("showmsg=1");
313 } 315 }
314 html("'>"); 316 html("'>");
315 html_txt(name); 317 html_txt(name);
316 html("</a>"); 318 html("</a>");
317} 319}
318 320
319void cgit_commit_link(char *name, char *title, char *class, char *head, 321void cgit_commit_link(char *name, const char *title, const char *class,
320 char *rev, int toggle_ssdiff) 322 const char *head, const char *rev, const char *path,
323 int toggle_ssdiff)
321{ 324{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 326 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 327 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 328 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 329 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 330 }
328 331
329 char *delim; 332 char *delim;
330 333
331 delim = repolink(title, class, "commit", head, NULL); 334 delim = repolink(title, class, "commit", head, path);
332 if (rev && strcmp(rev, ctx.qry.head)) { 335 if (rev && strcmp(rev, ctx.qry.head)) {
333 html(delim); 336 html(delim);
334 html("id="); 337 html("id=");
335 html_url_arg(rev); 338 html_url_arg(rev);
336 delim = "&amp;"; 339 delim = "&amp;";
337 } 340 }
338 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
339 html(delim); 342 html(delim);
340 html("ss=1"); 343 html("ss=1");
341 } 344 }
342 html("'>"); 345 html("'>");
343 html_txt(name); 346 html_txt(name);
344 html("</a>"); 347 html("</a>");
345} 348}
346 349
347void cgit_refs_link(char *name, char *title, char *class, char *head, 350void cgit_refs_link(const char *name, const char *title, const char *class,
348 char *rev, char *path) 351 const char *head, const char *rev, const char *path)
349{ 352{
350 reporevlink("refs", name, title, class, head, rev, path); 353 reporevlink("refs", name, title, class, head, rev, path);
351} 354}
352 355
353void cgit_snapshot_link(char *name, char *title, char *class, char *head, 356void cgit_snapshot_link(const char *name, const char *title, const char *class,
354 char *rev, char *archivename) 357 const char *head, const char *rev,
358 const char *archivename)
355{ 359{
356 reporevlink("snapshot", name, title, class, head, rev, archivename); 360 reporevlink("snapshot", name, title, class, head, rev, archivename);
357} 361}
358 362
359void cgit_diff_link(char *name, char *title, char *class, char *head, 363void cgit_diff_link(const char *name, const char *title, const char *class,
360 char *new_rev, char *old_rev, char *path, 364 const char *head, const char *new_rev, const char *old_rev,
361 int toggle_ssdiff) 365 const char *path, int toggle_ssdiff)
362{ 366{
363 char *delim; 367 char *delim;
364 368
365 delim = repolink(title, class, "diff", head, path); 369 delim = repolink(title, class, "diff", head, path);
366 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { 370 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
367 html(delim); 371 html(delim);
368 html("id="); 372 html("id=");
369 html_url_arg(new_rev); 373 html_url_arg(new_rev);
370 delim = "&amp;"; 374 delim = "&amp;";
371 } 375 }
372 if (old_rev) { 376 if (old_rev) {
373 html(delim); 377 html(delim);
374 html("id2="); 378 html("id2=");
375 html_url_arg(old_rev); 379 html_url_arg(old_rev);
376 delim = "&amp;"; 380 delim = "&amp;";
377 } 381 }
378 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { 382 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
379 html(delim); 383 html(delim);
380 html("ss=1"); 384 html("ss=1");
381 } 385 }
382 html("'>"); 386 html("'>");
383 html_txt(name); 387 html_txt(name);
384 html("</a>"); 388 html("</a>");
385} 389}
386 390
387void cgit_patch_link(char *name, char *title, char *class, char *head, 391void cgit_patch_link(const char *name, const char *title, const char *class,
388 char *rev) 392 const char *head, const char *rev, const char *path)
389{ 393{
390 reporevlink("patch", name, title, class, head, rev, NULL); 394 reporevlink("patch", name, title, class, head, rev, path);
391} 395}
392 396
393void cgit_stats_link(char *name, char *title, char *class, char *head, 397void cgit_stats_link(const char *name, const char *title, const char *class,
394 char *path) 398 const char *head, const char *path)
395{ 399{
396 reporevlink("stats", name, title, class, head, NULL, path); 400 reporevlink("stats", name, title, class, head, NULL, path);
397} 401}
398 402
403void cgit_self_link(char *name, const char *title, const char *class,
404 struct cgit_context *ctx)
405{
406 if (!strcmp(ctx->qry.page, "repolist"))
407 return cgit_index_link(name, title, class, ctx->qry.search,
408 ctx->qry.ofs);
409 else if (!strcmp(ctx->qry.page, "summary"))
410 return cgit_summary_link(name, title, class, ctx->qry.head);
411 else if (!strcmp(ctx->qry.page, "tag"))
412 return cgit_tag_link(name, title, class, ctx->qry.head,
413 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
414 else if (!strcmp(ctx->qry.page, "tree"))
415 return cgit_tree_link(name, title, class, ctx->qry.head,
416 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
417 ctx->qry.path);
418 else if (!strcmp(ctx->qry.page, "plain"))
419 return cgit_plain_link(name, title, class, ctx->qry.head,
420 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
421 ctx->qry.path);
422 else if (!strcmp(ctx->qry.page, "log"))
423 return cgit_log_link(name, title, class, ctx->qry.head,
424 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
425 ctx->qry.path, ctx->qry.ofs,
426 ctx->qry.grep, ctx->qry.search,
427 ctx->qry.showmsg);
428 else if (!strcmp(ctx->qry.page, "commit"))
429 return cgit_commit_link(name, title, class, ctx->qry.head,
430 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
431 ctx->qry.path, 0);
432 else if (!strcmp(ctx->qry.page, "patch"))
433 return cgit_patch_link(name, title, class, ctx->qry.head,
434 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
435 ctx->qry.path);
436 else if (!strcmp(ctx->qry.page, "refs"))
437 return cgit_refs_link(name, title, class, ctx->qry.head,
438 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
439 ctx->qry.path);
440 else if (!strcmp(ctx->qry.page, "snapshot"))
441 return cgit_snapshot_link(name, title, class, ctx->qry.head,
442 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
443 ctx->qry.path);
444 else if (!strcmp(ctx->qry.page, "diff"))
445 return cgit_diff_link(name, title, class, ctx->qry.head,
446 ctx->qry.sha1, ctx->qry.sha2,
447 ctx->qry.path, 0);
448 else if (!strcmp(ctx->qry.page, "stats"))
449 return cgit_stats_link(name, title, class, ctx->qry.head,
450 ctx->qry.path);
451
452 /* Don't known how to make link for this page */
453 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
454 html("><!-- cgit_self_link() doesn't know how to make link for page '");
455 html_txt(ctx->qry.page);
456 html("' -->");
457 html_txt(name);
458 html("</a>");
459}
460
399void cgit_object_link(struct object *obj) 461void cgit_object_link(struct object *obj)
400{ 462{
401 char *page, *shortrev, *fullrev, *name; 463 char *page, *shortrev, *fullrev, *name;
402 464
403 fullrev = sha1_to_hex(obj->sha1); 465 fullrev = sha1_to_hex(obj->sha1);
404 shortrev = xstrdup(fullrev); 466 shortrev = xstrdup(fullrev);
405 shortrev[10] = '\0'; 467 shortrev[10] = '\0';
406 if (obj->type == OBJ_COMMIT) { 468 if (obj->type == OBJ_COMMIT) {
407 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 469 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
408 ctx.qry.head, fullrev, 0); 470 ctx.qry.head, fullrev, NULL, 0);
409 return; 471 return;
410 } else if (obj->type == OBJ_TREE) 472 } else if (obj->type == OBJ_TREE)
411 page = "tree"; 473 page = "tree";
412 else if (obj->type == OBJ_TAG) 474 else if (obj->type == OBJ_TAG)
413 page = "tag"; 475 page = "tag";
414 else 476 else
415 page = "blob"; 477 page = "blob";
416 name = fmt("%s %s...", typename(obj->type), shortrev); 478 name = fmt("%s %s...", typename(obj->type), shortrev);
417 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 479 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
418} 480}
419 481
420void cgit_print_date(time_t secs, char *format, int local_time) 482void cgit_print_date(time_t secs, const char *format, int local_time)
421{ 483{
422 char buf[64]; 484 char buf[64];
423 struct tm *time; 485 struct tm *time;
424 486
425 if (!secs) 487 if (!secs)
426 return; 488 return;
427 if(local_time) 489 if(local_time)
428 time = localtime(&secs); 490 time = localtime(&secs);
429 else 491 else
430 time = gmtime(&secs); 492 time = gmtime(&secs);
431 strftime(buf, sizeof(buf)-1, format, time); 493 strftime(buf, sizeof(buf)-1, format, time);
432 html_txt(buf); 494 html_txt(buf);
433} 495}
434 496
435void cgit_print_age(time_t t, time_t max_relative, char *format) 497void cgit_print_age(time_t t, time_t max_relative, const char *format)
436{ 498{
437 time_t now, secs; 499 time_t now, secs;
438 500
439 if (!t) 501 if (!t)
440 return; 502 return;
441 time(&now); 503 time(&now);
442 secs = now - t; 504 secs = now - t;
443 505
444 if (secs > max_relative && max_relative >= 0) { 506 if (secs > max_relative && max_relative >= 0) {
445 cgit_print_date(t, format, ctx.cfg.local_time); 507 cgit_print_date(t, format, ctx.cfg.local_time);
446 return; 508 return;
447 } 509 }
448 510
449 if (secs < TM_HOUR * 2) { 511 if (secs < TM_HOUR * 2) {
450 htmlf("<span class='age-mins'>%.0f min.</span>", 512 htmlf("<span class='age-mins'>%.0f min.</span>",
451 secs * 1.0 / TM_MIN); 513 secs * 1.0 / TM_MIN);
452 return; 514 return;
453 } 515 }
454 if (secs < TM_DAY * 2) { 516 if (secs < TM_DAY * 2) {
455 htmlf("<span class='age-hours'>%.0f hours</span>", 517 htmlf("<span class='age-hours'>%.0f hours</span>",
456 secs * 1.0 / TM_HOUR); 518 secs * 1.0 / TM_HOUR);
457 return; 519 return;
458 } 520 }
459 if (secs < TM_WEEK * 2) { 521 if (secs < TM_WEEK * 2) {
460 htmlf("<span class='age-days'>%.0f days</span>", 522 htmlf("<span class='age-days'>%.0f days</span>",
461 secs * 1.0 / TM_DAY); 523 secs * 1.0 / TM_DAY);
462 return; 524 return;
463 } 525 }
464 if (secs < TM_MONTH * 2) { 526 if (secs < TM_MONTH * 2) {
465 htmlf("<span class='age-weeks'>%.0f weeks</span>", 527 htmlf("<span class='age-weeks'>%.0f weeks</span>",
466 secs * 1.0 / TM_WEEK); 528 secs * 1.0 / TM_WEEK);
467 return; 529 return;
468 } 530 }
469 if (secs < TM_YEAR * 2) { 531 if (secs < TM_YEAR * 2) {
470 htmlf("<span class='age-months'>%.0f months</span>", 532 htmlf("<span class='age-months'>%.0f months</span>",
471 secs * 1.0 / TM_MONTH); 533 secs * 1.0 / TM_MONTH);
472 return; 534 return;
473 } 535 }
474 htmlf("<span class='age-years'>%.0f years</span>", 536 htmlf("<span class='age-years'>%.0f years</span>",
475 secs * 1.0 / TM_YEAR); 537 secs * 1.0 / TM_YEAR);
476} 538}
477 539
478void cgit_print_http_headers(struct cgit_context *ctx) 540void cgit_print_http_headers(struct cgit_context *ctx)
479{ 541{
480 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1")) 542 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1"))
481 return; 543 return;
482 544
483 if (ctx->page.status) 545 if (ctx->page.status)
484 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg); 546 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
485 if (ctx->page.mimetype && ctx->page.charset) 547 if (ctx->page.mimetype && ctx->page.charset)
486 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 548 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
487 ctx->page.charset); 549 ctx->page.charset);
488 else if (ctx->page.mimetype) 550 else if (ctx->page.mimetype)
489 htmlf("Content-Type: %s\n", ctx->page.mimetype); 551 htmlf("Content-Type: %s\n", ctx->page.mimetype);
490 if (ctx->page.size) 552 if (ctx->page.size)
491 htmlf("Content-Length: %ld\n", ctx->page.size); 553 htmlf("Content-Length: %ld\n", ctx->page.size);
492 if (ctx->page.filename) 554 if (ctx->page.filename)
493 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 555 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
494 ctx->page.filename); 556 ctx->page.filename);
495 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 557 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
496 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 558 htmlf("Expires: %s\n", http_date(ctx->page.expires));
497 if (ctx->page.etag) 559 if (ctx->page.etag)
498 htmlf("ETag: \"%s\"\n", ctx->page.etag); 560 htmlf("ETag: \"%s\"\n", ctx->page.etag);
499 html("\n"); 561 html("\n");
500 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD")) 562 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD"))
501 exit(0); 563 exit(0);
502} 564}
503 565
504void cgit_print_docstart(struct cgit_context *ctx) 566void cgit_print_docstart(struct cgit_context *ctx)
505{ 567{
506 if (ctx->cfg.embedded) { 568 if (ctx->cfg.embedded) {
507 if (ctx->cfg.header) 569 if (ctx->cfg.header)
508 html_include(ctx->cfg.header); 570 html_include(ctx->cfg.header);
509 return; 571 return;
510 } 572 }
511 573
512 char *host = cgit_hosturl(); 574 char *host = cgit_hosturl();
513 html(cgit_doctype); 575 html(cgit_doctype);
514 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 576 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
515 html("<head>\n"); 577 html("<head>\n");
516 html("<title>"); 578 html("<title>");
517 html_txt(ctx->page.title); 579 html_txt(ctx->page.title);
518 html("</title>\n"); 580 html("</title>\n");
519 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 581 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
520 if (ctx->cfg.robots && *ctx->cfg.robots) 582 if (ctx->cfg.robots && *ctx->cfg.robots)
521 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 583 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
522 html("<link rel='stylesheet' type='text/css' href='"); 584 html("<link rel='stylesheet' type='text/css' href='");
523 html_attr(ctx->cfg.css); 585 html_attr(ctx->cfg.css);
524 html("'/>\n"); 586 html("'/>\n");
525 if (ctx->cfg.favicon) { 587 if (ctx->cfg.favicon) {
526 html("<link rel='shortcut icon' href='"); 588 html("<link rel='shortcut icon' href='");
527 html_attr(ctx->cfg.favicon); 589 html_attr(ctx->cfg.favicon);
528 html("'/>\n"); 590 html("'/>\n");
529 } 591 }
530 if (host && ctx->repo) { 592 if (host && ctx->repo) {
531 html("<link rel='alternate' title='Atom feed' href='"); 593 html("<link rel='alternate' title='Atom feed' href='");
532 html(cgit_httpscheme()); 594 html(cgit_httpscheme());
533 html_attr(cgit_hosturl()); 595 html_attr(cgit_hosturl());
534 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 596 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
535 fmt("h=%s", ctx->qry.head))); 597 fmt("h=%s", ctx->qry.head)));
536 html("' type='application/atom+xml'/>\n"); 598 html("' type='application/atom+xml'/>\n");
537 } 599 }
538 if (ctx->cfg.head_include) 600 if (ctx->cfg.head_include)
539 html_include(ctx->cfg.head_include); 601 html_include(ctx->cfg.head_include);
540 html("</head>\n"); 602 html("</head>\n");
541 html("<body>\n"); 603 html("<body>\n");
542 if (ctx->cfg.header) 604 if (ctx->cfg.header)
543 html_include(ctx->cfg.header); 605 html_include(ctx->cfg.header);
544} 606}
545 607
546void cgit_print_docend() 608void cgit_print_docend()
547{ 609{
548 html("</div> <!-- class=content -->\n"); 610 html("</div> <!-- class=content -->\n");
549 if (ctx.cfg.embedded) { 611 if (ctx.cfg.embedded) {
550 html("</div> <!-- id=cgit -->\n"); 612 html("</div> <!-- id=cgit -->\n");
551 if (ctx.cfg.footer) 613 if (ctx.cfg.footer)
552 html_include(ctx.cfg.footer); 614 html_include(ctx.cfg.footer);
553 return; 615 return;
554 } 616 }
555 if (ctx.cfg.footer) 617 if (ctx.cfg.footer)
556 html_include(ctx.cfg.footer); 618 html_include(ctx.cfg.footer);
557 else { 619 else {
558 htmlf("<div class='footer'>generated by cgit %s at ", 620 htmlf("<div class='footer'>generated by cgit %s at ",
559 cgit_version); 621 cgit_version);
560 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 622 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
561 html("</div>\n"); 623 html("</div>\n");
562 } 624 }
563 html("</div> <!-- id=cgit -->\n"); 625 html("</div> <!-- id=cgit -->\n");
564 html("</body>\n</html>\n"); 626 html("</body>\n</html>\n");
565} 627}
566 628
567int print_branch_option(const char *refname, const unsigned char *sha1, 629int print_branch_option(const char *refname, const unsigned char *sha1,
568 int flags, void *cb_data) 630 int flags, void *cb_data)
569{ 631{
570 char *name = (char *)refname; 632 char *name = (char *)refname;
571 html_option(name, name, ctx.qry.head); 633 html_option(name, name, ctx.qry.head);
572 return 0; 634 return 0;
573} 635}
574 636
575int print_archive_ref(const char *refname, const unsigned char *sha1, 637int print_archive_ref(const char *refname, const unsigned char *sha1,
576 int flags, void *cb_data) 638 int flags, void *cb_data)
577{ 639{
578 struct tag *tag; 640 struct tag *tag;
579 struct taginfo *info; 641 struct taginfo *info;
580 struct object *obj; 642 struct object *obj;
581 char buf[256], *url; 643 char buf[256], *url;
582 unsigned char fileid[20]; 644 unsigned char fileid[20];
583 int *header = (int *)cb_data; 645 int *header = (int *)cb_data;
584 646
585 if (prefixcmp(refname, "refs/archives")) 647 if (prefixcmp(refname, "refs/archives"))
586 return 0; 648 return 0;
587 strncpy(buf, refname+14, sizeof(buf)); 649 strncpy(buf, refname+14, sizeof(buf));
588 obj = parse_object(sha1); 650 obj = parse_object(sha1);
589 if (!obj) 651 if (!obj)
590 return 1; 652 return 1;
591 if (obj->type == OBJ_TAG) { 653 if (obj->type == OBJ_TAG) {
592 tag = lookup_tag(sha1); 654 tag = lookup_tag(sha1);
593 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 655 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
594 return 0; 656 return 0;
595 hashcpy(fileid, tag->tagged->sha1); 657 hashcpy(fileid, tag->tagged->sha1);
596 } else if (obj->type != OBJ_BLOB) { 658 } else if (obj->type != OBJ_BLOB) {
597 return 0; 659 return 0;
598 } else { 660 } else {
599 hashcpy(fileid, sha1); 661 hashcpy(fileid, sha1);
600 } 662 }
601 if (!*header) { 663 if (!*header) {
602 html("<h1>download</h1>\n"); 664 html("<h1>download</h1>\n");
603 *header = 1; 665 *header = 1;
604 } 666 }
605 url = cgit_pageurl(ctx.qry.repo, "blob", 667 url = cgit_pageurl(ctx.qry.repo, "blob",
606 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 668 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
607 buf)); 669 buf));
608 html_link_open(url, NULL, "menu"); 670 html_link_open(url, NULL, "menu");
609 html_txt(strlpart(buf, 20)); 671 html_txt(strlpart(buf, 20));
610 html_link_close(); 672 html_link_close();
611 return 0; 673 return 0;
612} 674}
613 675
614void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) 676void cgit_add_hidden_formfields(int incl_head, int incl_search,
677 const char *page)
615{ 678{
616 char *url; 679 char *url;
617 680
618 if (!ctx.cfg.virtual_root) { 681 if (!ctx.cfg.virtual_root) {
619 url = fmt("%s/%s", ctx.qry.repo, page); 682 url = fmt("%s/%s", ctx.qry.repo, page);
620 if (ctx.qry.path) 683 if (ctx.qry.vpath)
621 url = fmt("%s/%s", url, ctx.qry.path); 684 url = fmt("%s/%s", url, ctx.qry.vpath);
622 html_hidden("url", url); 685 html_hidden("url", url);
623 } 686 }
624 687
625 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 688 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
626 strcmp(ctx.qry.head, ctx.repo->defbranch)) 689 strcmp(ctx.qry.head, ctx.repo->defbranch))
627 html_hidden("h", ctx.qry.head); 690 html_hidden("h", ctx.qry.head);
628 691
629 if (ctx.qry.sha1) 692 if (ctx.qry.sha1)
630 html_hidden("id", ctx.qry.sha1); 693 html_hidden("id", ctx.qry.sha1);
631 if (ctx.qry.sha2) 694 if (ctx.qry.sha2)
632 html_hidden("id2", ctx.qry.sha2); 695 html_hidden("id2", ctx.qry.sha2);
633 if (ctx.qry.showmsg) 696 if (ctx.qry.showmsg)
634 html_hidden("showmsg", "1"); 697 html_hidden("showmsg", "1");
635 698
636 if (incl_search) { 699 if (incl_search) {
637 if (ctx.qry.grep) 700 if (ctx.qry.grep)
638 html_hidden("qt", ctx.qry.grep); 701 html_hidden("qt", ctx.qry.grep);
639 if (ctx.qry.search) 702 if (ctx.qry.search)
640 html_hidden("q", ctx.qry.search); 703 html_hidden("q", ctx.qry.search);
641 } 704 }
642} 705}
643 706
644const char *fallback_cmd = "repolist"; 707static const char *hc(struct cgit_context *ctx, const char *page)
708{
709 return strcmp(ctx->qry.page, page) ? NULL : "active";
710}
645 711
646char *hc(struct cgit_cmd *cmd, const char *page) 712static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
647{ 713{
648 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 714 char *old_path = ctx->qry.path;
715 char *p = path, *q, *end = path + strlen(path);
716
717 ctx->qry.path = NULL;
718 cgit_self_link("root", NULL, NULL, ctx);
719 ctx->qry.path = p = path;
720 while (p < end) {
721 if (!(q = strchr(p, '/')))
722 q = end;
723 *q = '\0';
724 html_txt("/");
725 cgit_self_link(p, NULL, NULL, ctx);
726 if (q < end)
727 *q = '/';
728 p = q + 1;
729 }
730 ctx->qry.path = old_path;
649} 731}
650 732
651static void print_header(struct cgit_context *ctx) 733static void print_header(struct cgit_context *ctx)
652{ 734{
653 html("<table id='header'>\n"); 735 html("<table id='header'>\n");
654 html("<tr>\n"); 736 html("<tr>\n");
655 737
656 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 738 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) {
657 html("<td class='logo' rowspan='2'><a href='"); 739 html("<td class='logo' rowspan='2'><a href='");
658 if (ctx->cfg.logo_link) 740 if (ctx->cfg.logo_link)
659 html_attr(ctx->cfg.logo_link); 741 html_attr(ctx->cfg.logo_link);
660 else 742 else
661 html_attr(cgit_rooturl()); 743 html_attr(cgit_rooturl());
662 html("'><img src='"); 744 html("'><img src='");
663 html_attr(ctx->cfg.logo); 745 html_attr(ctx->cfg.logo);
664 html("' alt='cgit logo'/></a></td>\n"); 746 html("' alt='cgit logo'/></a></td>\n");
665 } 747 }
666 748
667 html("<td class='main'>"); 749 html("<td class='main'>");
668 if (ctx->repo) { 750 if (ctx->repo) {
669 cgit_index_link("index", NULL, NULL, NULL, 0); 751 cgit_index_link("index", NULL, NULL, NULL, 0);
670 html(" : "); 752 html(" : ");
671 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 753 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
672 html("</td><td class='form'>"); 754 html("</td><td class='form'>");
673 html("<form method='get' action=''>\n"); 755 html("<form method='get' action=''>\n");
674 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 756 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
675 html("<select name='h' onchange='this.form.submit();'>\n"); 757 html("<select name='h' onchange='this.form.submit();'>\n");
676 for_each_branch_ref(print_branch_option, ctx->qry.head); 758 for_each_branch_ref(print_branch_option, ctx->qry.head);
677 html("</select> "); 759 html("</select> ");
678 html("<input type='submit' name='' value='switch'/>"); 760 html("<input type='submit' name='' value='switch'/>");
679 html("</form>"); 761 html("</form>");
680 } else 762 } else
681 html_txt(ctx->cfg.root_title); 763 html_txt(ctx->cfg.root_title);
682 html("</td></tr>\n"); 764 html("</td></tr>\n");
683 765
684 html("<tr><td class='sub'>"); 766 html("<tr><td class='sub'>");
685 if (ctx->repo) { 767 if (ctx->repo) {
686 html_txt(ctx->repo->desc); 768 html_txt(ctx->repo->desc);
687 html("</td><td class='sub right'>"); 769 html("</td><td class='sub right'>");
688 html_txt(ctx->repo->owner); 770 html_txt(ctx->repo->owner);
689 } else { 771 } else {
690 if (ctx->cfg.root_desc) 772 if (ctx->cfg.root_desc)
691 html_txt(ctx->cfg.root_desc); 773 html_txt(ctx->cfg.root_desc);
692 else if (ctx->cfg.index_info) 774 else if (ctx->cfg.index_info)
693 html_include(ctx->cfg.index_info); 775 html_include(ctx->cfg.index_info);
694 } 776 }
695 html("</td></tr></table>\n"); 777 html("</td></tr></table>\n");
696} 778}
697 779
698void cgit_print_pageheader(struct cgit_context *ctx) 780void cgit_print_pageheader(struct cgit_context *ctx)
699{ 781{
700 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
701
702 if (!cmd && ctx->repo)
703 fallback_cmd = "summary";
704
705 html("<div id='cgit'>"); 782 html("<div id='cgit'>");
706 if (!ctx->cfg.noheader) 783 if (!ctx->cfg.noheader)
707 print_header(ctx); 784 print_header(ctx);
708 785
709 html("<table class='tabs'><tr><td>\n"); 786 html("<table class='tabs'><tr><td>\n");
710 if (ctx->repo) { 787 if (ctx->repo) {
711 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 788 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
712 ctx->qry.head); 789 ctx->qry.head);
713 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 790 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
714 ctx->qry.sha1, NULL);
715 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
716 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
717 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
718 ctx->qry.sha1, NULL); 791 ctx->qry.sha1, NULL);
719 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 792 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
720 ctx->qry.head, ctx->qry.sha1, 0); 793 NULL, ctx->qry.vpath, 0, NULL, NULL,
721 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 794 ctx->qry.showmsg);
722 ctx->qry.sha1, ctx->qry.sha2, NULL, 0); 795 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
796 ctx->qry.sha1, ctx->qry.vpath);
797 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
798 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
799 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
800 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
723 if (ctx->repo->max_stats) 801 if (ctx->repo->max_stats)
724 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 802 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
725 ctx->qry.head, NULL); 803 ctx->qry.head, ctx->qry.vpath);
726 if (ctx->repo->readme) 804 if (ctx->repo->readme)
727 reporevlink("about", "about", NULL, 805 reporevlink("about", "about", NULL,
728 hc(cmd, "about"), ctx->qry.head, NULL, 806 hc(ctx, "about"), ctx->qry.head, NULL,
729 NULL); 807 NULL);
730 html("</td><td class='form'>"); 808 html("</td><td class='form'>");
731 html("<form class='right' method='get' action='"); 809 html("<form class='right' method='get' action='");
732 if (ctx->cfg.virtual_root) 810 if (ctx->cfg.virtual_root)
733 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 811 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
734 ctx->qry.path, NULL)); 812 ctx->qry.vpath, NULL));
735 html("'>\n"); 813 html("'>\n");
736 cgit_add_hidden_formfields(1, 0, "log"); 814 cgit_add_hidden_formfields(1, 0, "log");
737 html("<select name='qt'>\n"); 815 html("<select name='qt'>\n");
738 html_option("grep", "log msg", ctx->qry.grep); 816 html_option("grep", "log msg", ctx->qry.grep);
739 html_option("author", "author", ctx->qry.grep); 817 html_option("author", "author", ctx->qry.grep);
740 html_option("committer", "committer", ctx->qry.grep); 818 html_option("committer", "committer", ctx->qry.grep);
741 html("</select>\n"); 819 html("</select>\n");
742 html("<input class='txt' type='text' size='10' name='q' value='"); 820 html("<input class='txt' type='text' size='10' name='q' value='");
743 html_attr(ctx->qry.search); 821 html_attr(ctx->qry.search);
744 html("'/>\n"); 822 html("'/>\n");
745 html("<input type='submit' value='search'/>\n"); 823 html("<input type='submit' value='search'/>\n");
746 html("</form>\n"); 824 html("</form>\n");
747 } else { 825 } else {
748 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 826 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
749 if (ctx->cfg.root_readme) 827 if (ctx->cfg.root_readme)
750 site_link("about", "about", NULL, hc(cmd, "about"), 828 site_link("about", "about", NULL, hc(ctx, "about"),
751 NULL, 0); 829 NULL, 0);
752 html("</td><td class='form'>"); 830 html("</td><td class='form'>");
753 html("<form method='get' action='"); 831 html("<form method='get' action='");
754 html_attr(cgit_rooturl()); 832 html_attr(cgit_rooturl());
755 html("'>\n"); 833 html("'>\n");
756 html("<input type='text' name='q' size='10' value='"); 834 html("<input type='text' name='q' size='10' value='");
757 html_attr(ctx->qry.search); 835 html_attr(ctx->qry.search);
758 html("'/>\n"); 836 html("'/>\n");
759 html("<input type='submit' value='search'/>\n"); 837 html("<input type='submit' value='search'/>\n");
760 html("</form>"); 838 html("</form>");
761 } 839 }
762 html("</td></tr></table>\n"); 840 html("</td></tr></table>\n");
841 if (ctx->qry.vpath) {
842 html("<div class='path'>");
843 html("path: ");
844 cgit_print_path_crumbs(ctx, ctx->qry.vpath);
845 html("</div>");
846 }
763 html("<div class='content'>"); 847 html("<div class='content'>");
764} 848}
765 849
766void cgit_print_filemode(unsigned short mode) 850void cgit_print_filemode(unsigned short mode)
767{ 851{
768 if (S_ISDIR(mode)) 852 if (S_ISDIR(mode))
769 html("d"); 853 html("d");
770 else if (S_ISLNK(mode)) 854 else if (S_ISLNK(mode))
771 html("l"); 855 html("l");
772 else if (S_ISGITLINK(mode)) 856 else if (S_ISGITLINK(mode))
773 html("m"); 857 html("m");
774 else 858 else
775 html("-"); 859 html("-");
776 html_fileperm(mode >> 6); 860 html_fileperm(mode >> 6);
777 html_fileperm(mode >> 3); 861 html_fileperm(mode >> 3);
778 html_fileperm(mode); 862 html_fileperm(mode);
779} 863}
780 864
781void cgit_print_snapshot_links(const char *repo, const char *head, 865void cgit_print_snapshot_links(const char *repo, const char *head,
782 const char *hex, int snapshots) 866 const char *hex, int snapshots)
783{ 867{
784 const struct cgit_snapshot_format* f; 868 const struct cgit_snapshot_format* f;
785 char *prefix; 869 char *prefix;
786 char *filename; 870 char *filename;
787 unsigned char sha1[20]; 871 unsigned char sha1[20];
788 872
789 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 && 873 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
790 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1])) 874 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
791 hex++; 875 hex++;
792 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex)); 876 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
793 for (f = cgit_snapshot_formats; f->suffix; f++) { 877 for (f = cgit_snapshot_formats; f->suffix; f++) {
794 if (!(snapshots & f->bit)) 878 if (!(snapshots & f->bit))
795 continue; 879 continue;
796 filename = fmt("%s%s", prefix, f->suffix); 880 filename = fmt("%s%s", prefix, f->suffix);
797 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 881 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
798 html("<br/>"); 882 html("<br/>");
799 } 883 }
800} 884}
diff --git a/ui-shared.h b/ui-shared.h
index 9ebc1f9..3cc1258 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,52 +1,66 @@
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_httpscheme(); 4extern char *cgit_httpscheme();
5extern char *cgit_hosturl(); 5extern char *cgit_hosturl();
6extern char *cgit_rooturl(); 6extern char *cgit_rooturl();
7extern char *cgit_repourl(const char *reponame); 7extern char *cgit_repourl(const char *reponame);
8extern char *cgit_fileurl(const char *reponame, const char *pagename, 8extern char *cgit_fileurl(const char *reponame, const char *pagename,
9 const char *filename, const char *query); 9 const char *filename, const char *query);
10extern char *cgit_pageurl(const char *reponame, const char *pagename, 10extern char *cgit_pageurl(const char *reponame, const char *pagename,
11 const char *query); 11 const char *query);
12 12
13extern void cgit_index_link(char *name, char *title, char *class, 13extern void cgit_index_link(const char *name, const char *title,
14 char *pattern, int ofs); 14 const char *class, const char *pattern, int ofs);
15extern void cgit_summary_link(char *name, char *title, char *class, char *head); 15extern void cgit_summary_link(const char *name, const char *title,
16extern void cgit_tag_link(char *name, char *title, char *class, char *head, 16 const char *class, const char *head);
17 char *rev); 17extern void cgit_tag_link(const char *name, const char *title,
18extern void cgit_tree_link(char *name, char *title, char *class, char *head, 18 const char *class, const char *head,
19 char *rev, char *path); 19 const char *rev);
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_tree_link(const char *name, const char *title,
21 char *rev, char *path); 21 const char *class, const char *head,
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22 const char *rev, const char *path);
23 char *rev, char *path, int ofs, char *grep, 23extern void cgit_plain_link(const char *name, const char *title,
24 char *pattern, int showmsg); 24 const char *class, const char *head,
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25 const char *rev, const char *path);
26 char *rev, int toggle_ssdiff); 26extern void cgit_log_link(const char *name, const char *title,
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27 const char *class, const char *head, const char *rev,
28 char *rev); 28 const char *path, int ofs, const char *grep,
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29 const char *pattern, int showmsg);
30 char *rev, char *path); 30extern void cgit_commit_link(char *name, const char *title,
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31 const char *class, const char *head,
32 char *head, char *rev, char *archivename); 32 const char *rev, const char *path,
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33 int toggle_ssdiff);
34 char *new_rev, char *old_rev, char *path, 34extern void cgit_patch_link(const char *name, const char *title,
35 int toggle_ssdiff); 35 const char *class, const char *head,
36extern void cgit_stats_link(char *name, char *title, char *class, char *head, 36 const char *rev, const char *path);
37 char *path); 37extern void cgit_refs_link(const char *name, const char *title,
38 const char *class, const char *head,
39 const char *rev, const char *path);
40extern void cgit_snapshot_link(const char *name, const char *title,
41 const char *class, const char *head,
42 const char *rev, const char *archivename);
43extern void cgit_diff_link(const char *name, const char *title,
44 const char *class, const char *head,
45 const char *new_rev, const char *old_rev,
46 const char *path, int toggle_ssdiff);
47extern void cgit_stats_link(const char *name, const char *title,
48 const char *class, const char *head,
49 const char *path);
50extern void cgit_self_link(char *name, const char *title,
51 const char *class, struct cgit_context *ctx);
38extern void cgit_object_link(struct object *obj); 52extern void cgit_object_link(struct object *obj);
39 53
40extern void cgit_print_error(char *msg); 54extern void cgit_print_error(const char *msg);
41extern void cgit_print_date(time_t secs, char *format, int local_time); 55extern void cgit_print_date(time_t secs, const char *format, int local_time);
42extern void cgit_print_age(time_t t, time_t max_relative, char *format); 56extern void cgit_print_age(time_t t, time_t max_relative, const char *format);
43extern void cgit_print_http_headers(struct cgit_context *ctx); 57extern void cgit_print_http_headers(struct cgit_context *ctx);
44extern void cgit_print_docstart(struct cgit_context *ctx); 58extern void cgit_print_docstart(struct cgit_context *ctx);
45extern void cgit_print_docend(); 59extern void cgit_print_docend();
46extern void cgit_print_pageheader(struct cgit_context *ctx); 60extern void cgit_print_pageheader(struct cgit_context *ctx);
47extern void cgit_print_filemode(unsigned short mode); 61extern void cgit_print_filemode(unsigned short mode);
48extern void cgit_print_snapshot_links(const char *repo, const char *head, 62extern void cgit_print_snapshot_links(const char *repo, const char *head,
49 const char *hex, int snapshots); 63 const char *hex, int snapshots);
50extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 64extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
51 char *page); 65 const char *page);
52#endif /* UI_SHARED_H */ 66#endif /* UI_SHARED_H */
diff --git a/ui-tree.c b/ui-tree.c
index 0ee38f2..75ec9cb 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,293 +1,282 @@
1/* ui-tree.c: functions for tree output 1/* ui-tree.c: functions for tree 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 <ctype.h> 9#include <ctype.h>
10#include "cgit.h" 10#include "cgit.h"
11#include "html.h" 11#include "html.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13 13
14char *curr_rev; 14char *curr_rev;
15char *match_path; 15char *match_path;
16int header = 0; 16int header = 0;
17 17
18static void print_text_buffer(const char *name, char *buf, unsigned long size) 18static void print_text_buffer(const char *name, char *buf, unsigned long size)
19{ 19{
20 unsigned long lineno, idx; 20 unsigned long lineno, idx;
21 const char *numberfmt = 21 const char *numberfmt =
22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n"; 22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n";
23 23
24 html("<table summary='blob content' class='blob'>\n"); 24 html("<table summary='blob content' class='blob'>\n");
25 25
26 if (ctx.cfg.enable_tree_linenumbers) { 26 if (ctx.cfg.enable_tree_linenumbers) {
27 html("<tr><td class='linenumbers'><pre>"); 27 html("<tr><td class='linenumbers'><pre>");
28 idx = 0; 28 idx = 0;
29 lineno = 0; 29 lineno = 0;
30 30
31 if (size) { 31 if (size) {
32 htmlf(numberfmt, ++lineno); 32 htmlf(numberfmt, ++lineno);
33 while(idx < size - 1) { // skip absolute last newline 33 while(idx < size - 1) { // skip absolute last newline
34 if (buf[idx] == '\n') 34 if (buf[idx] == '\n')
35 htmlf(numberfmt, ++lineno); 35 htmlf(numberfmt, ++lineno);
36 idx++; 36 idx++;
37 } 37 }
38 } 38 }
39 html("</pre></td>\n"); 39 html("</pre></td>\n");
40 } 40 }
41 else { 41 else {
42 html("<tr>\n"); 42 html("<tr>\n");
43 } 43 }
44 44
45 if (ctx.repo->source_filter) { 45 if (ctx.repo->source_filter) {
46 html("<td class='lines'><pre><code>"); 46 html("<td class='lines'><pre><code>");
47 ctx.repo->source_filter->argv[1] = xstrdup(name); 47 ctx.repo->source_filter->argv[1] = xstrdup(name);
48 cgit_open_filter(ctx.repo->source_filter); 48 cgit_open_filter(ctx.repo->source_filter);
49 write(STDOUT_FILENO, buf, size); 49 write(STDOUT_FILENO, buf, size);
50 cgit_close_filter(ctx.repo->source_filter); 50 cgit_close_filter(ctx.repo->source_filter);
51 html("</code></pre></td></tr></table>\n"); 51 html("</code></pre></td></tr></table>\n");
52 return; 52 return;
53 } 53 }
54 54
55 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
56 html_txt(buf); 56 html_txt(buf);
57 html("</code></pre></td></tr></table>\n"); 57 html("</code></pre></td></tr></table>\n");
58} 58}
59 59
60#define ROWLEN 32 60#define ROWLEN 32
61 61
62static void print_binary_buffer(char *buf, unsigned long size) 62static void print_binary_buffer(char *buf, unsigned long size)
63{ 63{
64 unsigned long ofs, idx; 64 unsigned long ofs, idx;
65 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
66 66
67 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); 68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs);
71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
72 htmlf("%*s%02x", 72 htmlf("%*s%02x",
73 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
74 buf[idx] & 0xff); 74 buf[idx] & 0xff);
75 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
78 ascii[idx] = '\0'; 78 ascii[idx] = '\0';
79 html_txt(ascii); 79 html_txt(ascii);
80 html("</td></tr>\n"); 80 html("</td></tr>\n");
81 } 81 }
82 html("</table>\n"); 82 html("</table>\n");
83} 83}
84 84
85static void print_object(const unsigned char *sha1, char *path, const char *basename) 85static void print_object(const unsigned char *sha1, char *path, const char *basename)
86{ 86{
87 enum object_type type; 87 enum object_type type;
88 char *buf; 88 char *buf;
89 unsigned long size; 89 unsigned long size;
90 90
91 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
92 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
93 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
94 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
95 return; 95 return;
96 } 96 }
97 97
98 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
99 if (!buf) { 99 if (!buf) {
100 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
101 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
102 return; 102 return;
103 } 103 }
104 104
105 html(" ("); 105 htmlf("blob: %s (", sha1_to_hex(sha1));
106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
107 curr_rev, path); 107 curr_rev, path);
108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 html(")\n");
109 109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { 110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>", 111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size); 112 size / 1024, ctx.cfg.max_blob_size);
113 return; 113 return;
114 } 114 }
115 115
116 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
117 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
118 else 118 else
119 print_text_buffer(basename, buf, size); 119 print_text_buffer(basename, buf, size);
120} 120}
121 121
122 122
123static int ls_item(const unsigned char *sha1, const char *base, int baselen, 123static int ls_item(const unsigned char *sha1, const char *base, int baselen,
124 const char *pathname, unsigned int mode, int stage, 124 const char *pathname, unsigned int mode, int stage,
125 void *cbdata) 125 void *cbdata)
126{ 126{
127 char *name; 127 char *name;
128 char *fullpath; 128 char *fullpath;
129 char *class; 129 char *class;
130 enum object_type type; 130 enum object_type type;
131 unsigned long size = 0; 131 unsigned long size = 0;
132 132
133 name = xstrdup(pathname); 133 name = xstrdup(pathname);
134 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 134 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
135 ctx.qry.path ? "/" : "", name); 135 ctx.qry.path ? "/" : "", name);
136 136
137 if (!S_ISGITLINK(mode)) { 137 if (!S_ISGITLINK(mode)) {
138 type = sha1_object_info(sha1, &size); 138 type = sha1_object_info(sha1, &size);
139 if (type == OBJ_BAD) { 139 if (type == OBJ_BAD) {
140 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 140 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
141 name, 141 name,
142 sha1_to_hex(sha1)); 142 sha1_to_hex(sha1));
143 return 0; 143 return 0;
144 } 144 }
145 } 145 }
146 146
147 html("<tr><td class='ls-mode'>"); 147 html("<tr><td class='ls-mode'>");
148 cgit_print_filemode(mode); 148 cgit_print_filemode(mode);
149 html("</td><td>"); 149 html("</td><td>");
150 if (S_ISGITLINK(mode)) { 150 if (S_ISGITLINK(mode)) {
151 htmlf("<a class='ls-mod' href='"); 151 htmlf("<a class='ls-mod' href='");
152 html_attr(fmt(ctx.repo->module_link, 152 html_attr(fmt(ctx.repo->module_link,
153 name, 153 name,
154 sha1_to_hex(sha1))); 154 sha1_to_hex(sha1)));
155 html("'>"); 155 html("'>");
156 html_txt(name); 156 html_txt(name);
157 html("</a>"); 157 html("</a>");
158 } else if (S_ISDIR(mode)) { 158 } else if (S_ISDIR(mode)) {
159 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 159 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
160 curr_rev, fullpath); 160 curr_rev, fullpath);
161 } else { 161 } else {
162 class = strrchr(name, '.'); 162 class = strrchr(name, '.');
163 if (class != NULL) { 163 if (class != NULL) {
164 class = fmt("ls-blob %s", class + 1); 164 class = fmt("ls-blob %s", class + 1);
165 } else 165 } else
166 class = "ls-blob"; 166 class = "ls-blob";
167 cgit_tree_link(name, NULL, class, ctx.qry.head, 167 cgit_tree_link(name, NULL, class, ctx.qry.head,
168 curr_rev, fullpath); 168 curr_rev, fullpath);
169 } 169 }
170 htmlf("</td><td class='ls-size'>%li</td>", size); 170 htmlf("</td><td class='ls-size'>%li</td>", size);
171 171
172 html("<td>"); 172 html("<td>");
173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
174 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 174 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
175 if (ctx.repo->max_stats) 175 if (ctx.repo->max_stats)
176 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 176 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
177 fullpath); 177 fullpath);
178 cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev, 178 cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev,
179 fullpath); 179 fullpath);
180 html("</td></tr>\n"); 180 html("</td></tr>\n");
181 free(name); 181 free(name);
182 return 0; 182 return 0;
183} 183}
184 184
185static void ls_head() 185static void ls_head()
186{ 186{
187 html("<table summary='tree listing' class='list'>\n"); 187 html("<table summary='tree listing' class='list'>\n");
188 html("<tr class='nohover'>"); 188 html("<tr class='nohover'>");
189 html("<th class='left'>Mode</th>"); 189 html("<th class='left'>Mode</th>");
190 html("<th class='left'>Name</th>"); 190 html("<th class='left'>Name</th>");
191 html("<th class='right'>Size</th>"); 191 html("<th class='right'>Size</th>");
192 html("<th/>"); 192 html("<th/>");
193 html("</tr>\n"); 193 html("</tr>\n");
194 header = 1; 194 header = 1;
195} 195}
196 196
197static void ls_tail() 197static void ls_tail()
198{ 198{
199 if (!header) 199 if (!header)
200 return; 200 return;
201 html("</table>\n"); 201 html("</table>\n");
202 header = 0; 202 header = 0;
203} 203}
204 204
205static void ls_tree(const unsigned char *sha1, char *path) 205static void ls_tree(const unsigned char *sha1, char *path)
206{ 206{
207 struct tree *tree; 207 struct tree *tree;
208 208
209 tree = parse_tree_indirect(sha1); 209 tree = parse_tree_indirect(sha1);
210 if (!tree) { 210 if (!tree) {
211 cgit_print_error(fmt("Not a tree object: %s", 211 cgit_print_error(fmt("Not a tree object: %s",
212 sha1_to_hex(sha1))); 212 sha1_to_hex(sha1)));
213 return; 213 return;
214 } 214 }
215 215
216 ls_head(); 216 ls_head();
217 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 217 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
218 ls_tail(); 218 ls_tail();
219} 219}
220 220
221 221
222static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 222static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
223 const char *pathname, unsigned mode, int stage, 223 const char *pathname, unsigned mode, int stage,
224 void *cbdata) 224 void *cbdata)
225{ 225{
226 static int state; 226 static int state;
227 static char buffer[PATH_MAX]; 227 static char buffer[PATH_MAX];
228 char *url;
229 228
230 if (state == 0) { 229 if (state == 0) {
231 memcpy(buffer, base, baselen); 230 memcpy(buffer, base, baselen);
232 strcpy(buffer+baselen, pathname); 231 strcpy(buffer+baselen, pathname);
233 url = cgit_pageurl(ctx.qry.repo, "tree",
234 fmt("h=%s&amp;path=%s", curr_rev, buffer));
235 html("/");
236 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
237 curr_rev, buffer);
238
239 if (strcmp(match_path, buffer)) 232 if (strcmp(match_path, buffer))
240 return READ_TREE_RECURSIVE; 233 return READ_TREE_RECURSIVE;
241 234
242 if (S_ISDIR(mode)) { 235 if (S_ISDIR(mode)) {
243 state = 1; 236 state = 1;
244 ls_head(); 237 ls_head();
245 return READ_TREE_RECURSIVE; 238 return READ_TREE_RECURSIVE;
246 } else { 239 } else {
247 print_object(sha1, buffer, pathname); 240 print_object(sha1, buffer, pathname);
248 return 0; 241 return 0;
249 } 242 }
250 } 243 }
251 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 244 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
252 return 0; 245 return 0;
253} 246}
254 247
255 248
256/* 249/*
257 * Show a tree or a blob 250 * Show a tree or a blob
258 * rev: the commit pointing at the root tree object 251 * rev: the commit pointing at the root tree object
259 * path: path to tree or blob 252 * path: path to tree or blob
260 */ 253 */
261void cgit_print_tree(const char *rev, char *path) 254void cgit_print_tree(const char *rev, char *path)
262{ 255{
263 unsigned char sha1[20]; 256 unsigned char sha1[20];
264 struct commit *commit; 257 struct commit *commit;
265 const char *paths[] = {path, NULL}; 258 const char *paths[] = {path, NULL};
266 259
267 if (!rev) 260 if (!rev)
268 rev = ctx.qry.head; 261 rev = ctx.qry.head;
269 262
270 curr_rev = xstrdup(rev); 263 curr_rev = xstrdup(rev);
271 if (get_sha1(rev, sha1)) { 264 if (get_sha1(rev, sha1)) {
272 cgit_print_error(fmt("Invalid revision name: %s", rev)); 265 cgit_print_error(fmt("Invalid revision name: %s", rev));
273 return; 266 return;
274 } 267 }
275 commit = lookup_commit_reference(sha1); 268 commit = lookup_commit_reference(sha1);
276 if (!commit || parse_commit(commit)) { 269 if (!commit || parse_commit(commit)) {
277 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 270 cgit_print_error(fmt("Invalid commit reference: %s", rev));
278 return; 271 return;
279 } 272 }
280 273
281 html("path: <a href='");
282 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
283 html("'>root</a>");
284
285 if (path == NULL) { 274 if (path == NULL) {
286 ls_tree(commit->tree->object.sha1, NULL); 275 ls_tree(commit->tree->object.sha1, NULL);
287 return; 276 return;
288 } 277 }
289 278
290 match_path = path; 279 match_path = path;
291 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 280 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
292 ls_tail(); 281 ls_tail();
293} 282}