main.c
1 #include <sys/stat.h> 2 #include <sys/types.h> 3 4 #include <err.h> 5 #include <errno.h> 6 #include <libgen.h> 7 #include <stdint.h> 8 #include <stdio.h> 9 #include <stdlib.h> 10 #include <string.h> 11 #include <time.h> 12 #include <unistd.h> 13 #include <limits.h> 14 15 #include <git2.h> 16 17 #include "compat.h" 18 #include "opt.h" 19 #include "files.h" 20 21 #define CPUT(f, c) putc((c), (f)) 22 #define put_xml(fp, s) put_xml_len((fp), (s), strlen(s)) 23 #define SPUTF(f, s) fputs((s), (f)) 24 #define LENGTH(s) (sizeof(s)/sizeof(*s)) 25 26 #include "config.h" 27 28 struct DeltaInfo { 29 git_patch *patch; 30 31 size_t add_count; 32 size_t del_count; 33 }; 34 35 struct CommitInfo { 36 const git_oid *id; 37 38 char oid[GIT_OID_HEXSZ + 1]; 39 char parentoid[GIT_OID_HEXSZ + 1]; 40 41 const git_signature *author; 42 const git_signature *committer; 43 const char *summary; 44 const char *msg; 45 46 git_diff *diff; 47 git_commit *commit; 48 git_commit *parent; 49 git_tree *commit_tree; 50 git_tree *parent_tree; 51 52 size_t add_count; 53 size_t del_count; 54 size_t file_count; 55 56 struct DeltaInfo **deltas; 57 size_t ndeltas; 58 }; 59 60 /* reference and associated data for sorting */ 61 struct ReferenceInfo { 62 struct git_reference *ref; 63 struct CommitInfo *ci; 64 }; 65 66 struct Weight { 67 size_t bytes; 68 size_t lines; 69 }; 70 71 static const char orders[] = { ' ', 'k', 'M', 'G', 'T', 'P', 'E' }; 72 static const char tab[] = "0123456789ABCDEF"; 73 74 /* GLOBALS */ 75 // TODO untangle these, pass them around instead... 76 77 static git_repository *repo; 78 79 /* flags + arguments */ 80 static Opt opt; 81 static const char *repo_dir; 82 83 static const char *license_files[] = { LICENSE_FILES }; 84 static const char *readme_files[] = { README_FILES }; 85 86 // TODO extract these into a struct. LMFAO 87 static char *name = ""; 88 static char *repo_name = ""; 89 static char description[255]; 90 static char clone_url[1024]; 91 static char *submodules; 92 static const char *license; 93 static const char *readme; 94 95 /* bad globals for index.... */ 96 // TODO move this stuff to index.h? 97 static FILE *index_fp; 98 static char index_fname[255] = "index.html"; 99 100 void find_owner(); 101 int put_index_log(FILE *fp, git_repository *repo, const char *description, const char *repo_name, const char *readme); 102 void put_index_header(FILE *fp); 103 void put_index_footer(FILE *fp); 104 105 void 106 get_page_path(char *buf, size_t buf_size, const char *dir, char *name) 107 { 108 int index = 0, chars = 0; 109 110 // TODO tweak this so that it correctly places slashes between names 111 chars = snprintf(buf + index, buf_size - index, "%s", dir); 112 if (chars < 0 || (size_t) (index += chars) >= buf_size) 113 goto page_path_err; 114 115 if (chars != 0) buf[index++] = '/'; 116 117 chars = snprintf(buf + index, buf_size - index, PAGE_DEST); 118 if (chars < 0 || (size_t) (index += chars) >= buf_size) 119 goto page_path_err; 120 121 if (chars != 0) buf[index++] = '/'; 122 123 chars = snprintf(buf + index, buf_size - index, "%s", name); 124 if (chars < 0 || (size_t) (index += chars) >= buf_size) 125 goto page_path_err; 126 127 return; 128 129 page_path_err: 130 errx(1, "page path truncated! '%s' / '%s'" "\n", dir, name); 131 } 132 133 void 134 get_index_page_path(char *buf, size_t buf_size, const char *dir, char *name) 135 { 136 int index = 0, chars = 0; 137 138 // TODO tweak this so that it correctly places slashes between names 139 chars = snprintf(buf + index, buf_size - index, "%s", dir); 140 if (chars < 0 || (size_t) (index += chars) >= buf_size) 141 goto page_path_err; 142 143 const char *format = dir[0] == '\0' ? "%s" : "/%s"; 144 chars = snprintf(buf + index, buf_size - index, format, name); 145 if (chars < 0 || (size_t) (index += chars) >= buf_size) 146 goto page_path_err; 147 148 printf("Index page path: %s" "\n", buf); 149 150 return; 151 152 page_path_err: 153 errx(1, "page path truncated! '%s' / '%s'" "\n", dir, name); 154 } 155 156 FILE * 157 git_fopen(char *path, int path_size, const char *file) 158 { 159 FILE *ret; 160 161 /* try dir/(whatever), */ 162 /* (+5 removes the leading '.git/'.) */ 163 join_path(path, path_size, repo_dir, file + 5); 164 if ((ret = fopen(path, "r"))) return ret; 165 166 /* then try dir/.git/(whatever). */ 167 join_path(path, path_size, repo_dir, file); 168 return fopen(path, "r"); 169 } 170 171 void 172 find_license() 173 { 174 git_object *obj = NULL; 175 int i; 176 for (i = 0; i < LENGTH(license_files) && !license; i++) { 177 if ( 178 !git_revparse_single(&obj, repo, license_files[i]) && 179 git_object_type(obj) == GIT_OBJ_BLOB 180 ) 181 license = license_files[i] + strlen("HEAD:"); 182 git_object_free(obj); 183 } 184 } 185 186 void 187 find_readme() 188 { 189 git_object *obj = NULL; 190 int i; 191 readme = NULL; 192 for (i = 0; i < LENGTH(readme_files) && !readme; i++) { 193 if ( 194 !git_revparse_single(&obj, repo, readme_files[i]) && 195 git_object_type(obj) == GIT_OBJ_BLOB 196 ) 197 readme = readme_files[i] + strlen("HEAD:"); 198 git_object_free(obj); 199 } 200 } 201 202 void 203 delta_info_free(struct DeltaInfo *di) 204 { 205 if (!di) return; 206 207 git_patch_free(di->patch); 208 memset(di, 0, sizeof(*di)); 209 free(di); 210 } 211 212 int 213 commit_info_get_stats(struct CommitInfo *ci) 214 { 215 struct DeltaInfo *di; 216 git_diff_options opts; 217 git_diff_find_options fopts; 218 const git_diff_delta *delta; 219 const git_diff_hunk *hunk; 220 const git_diff_line *line; 221 git_patch *patch = NULL; 222 size_t ndeltas, nhunks, nhunklines; 223 size_t i, j, k; 224 225 if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit))) 226 goto err; 227 if (!git_commit_parent(&(ci->parent), ci->commit, 0)) { 228 if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) { 229 ci->parent = NULL; 230 ci->parent_tree = NULL; 231 } 232 } 233 234 git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION); 235 opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH | 236 GIT_DIFF_IGNORE_SUBMODULES | 237 GIT_DIFF_INCLUDE_TYPECHANGE; 238 if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts)) 239 goto err; 240 241 if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION)) 242 goto err; 243 /* find renames and copies, exact matches (no heuristic) for renames. */ 244 fopts.flags |= GIT_DIFF_FIND_RENAMES | 245 GIT_DIFF_FIND_COPIES | 246 GIT_DIFF_FIND_EXACT_MATCH_ONLY; 247 if (git_diff_find_similar(ci->diff, &fopts)) 248 goto err; 249 250 ndeltas = git_diff_num_deltas(ci->diff); 251 if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct DeltaInfo *)))) 252 err(1, "calloc"); 253 254 for (i = 0; i < ndeltas; i++) { 255 if (git_patch_from_diff(&patch, ci->diff, i)) 256 goto err; 257 258 if (!(di = calloc(1, sizeof(struct DeltaInfo)))) 259 err(1, "calloc"); 260 di->patch = patch; 261 ci->deltas[i] = di; 262 263 delta = git_patch_get_delta(patch); 264 265 /* skip stats for binary data */ 266 if (delta->flags & GIT_DIFF_FLAG_BINARY) 267 continue; 268 269 nhunks = git_patch_num_hunks(patch); 270 for (j = 0; j < nhunks; j++) { 271 if (git_patch_get_hunk(&hunk, &nhunklines, patch, j)) 272 break; 273 for (k = 0; ; k++) { 274 if (git_patch_get_line_in_hunk(&line, patch, j, k)) 275 break; 276 if (line->old_lineno == -1) { 277 di->add_count++; 278 ci->add_count++; 279 } else if (line->new_lineno == -1) { 280 di->del_count++; 281 ci->del_count++; 282 } 283 } 284 } 285 } 286 ci->ndeltas = i; 287 ci->file_count = i; 288 289 return 0; 290 291 err: 292 git_diff_free(ci->diff); 293 ci->diff = NULL; 294 git_tree_free(ci->commit_tree); 295 ci->commit_tree = NULL; 296 git_tree_free(ci->parent_tree); 297 ci->parent_tree = NULL; 298 git_commit_free(ci->parent); 299 ci->parent = NULL; 300 301 if (ci->deltas) 302 for (i = 0; i < ci->ndeltas; i++) 303 delta_info_free(ci->deltas[i]); 304 free(ci->deltas); 305 ci->deltas = NULL; 306 ci->ndeltas = 0; 307 ci->add_count = 0; 308 ci->del_count = 0; 309 ci->file_count = 0; 310 311 return -1; 312 } 313 314 void 315 commit_info_free(struct CommitInfo *ci) 316 { 317 size_t i; 318 319 if (!ci) 320 return; 321 if (ci->deltas) 322 for (i = 0; i < ci->ndeltas; i++) 323 delta_info_free(ci->deltas[i]); 324 325 free(ci->deltas); 326 git_diff_free(ci->diff); 327 git_tree_free(ci->commit_tree); 328 git_tree_free(ci->parent_tree); 329 git_commit_free(ci->commit); 330 git_commit_free(ci->parent); 331 memset(ci, 0, sizeof(*ci)); 332 free(ci); 333 } 334 335 struct CommitInfo * 336 commit_info_get_by_oid(const git_oid *id) 337 { 338 struct CommitInfo *ci; 339 340 if (!(ci = calloc(1, sizeof(struct CommitInfo)))) 341 err(1, "calloc"); 342 343 if (git_commit_lookup(&(ci->commit), repo, id)) 344 goto err; 345 ci->id = id; 346 347 git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit)); 348 git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0)); 349 350 ci->author = git_commit_author(ci->commit); 351 ci->committer = git_commit_committer(ci->commit); 352 ci->summary = git_commit_summary(ci->commit); 353 ci->msg = git_commit_message(ci->commit); 354 355 return ci; 356 357 err: 358 commit_info_free(ci); 359 360 return NULL; 361 } 362 363 int 364 refs_cmp(const void *v1, const void *v2) 365 { 366 const struct ReferenceInfo *r1 = v1, *r2 = v2; 367 time_t t1, t2; 368 int r; 369 370 if ((r = git_reference_is_tag(r1->ref) - git_reference_is_tag(r2->ref))) 371 return r; 372 373 t1 = r1->ci->author ? r1->ci->author->when.time : 0; 374 t2 = r2->ci->author ? r2->ci->author->when.time : 0; 375 if ((r = t1 > t2 ? -1 : (t1 == t2 ? 0 : 1))) 376 return r; 377 378 return strcmp( 379 git_reference_shorthand(r1->ref), 380 git_reference_shorthand(r2->ref) 381 ); 382 } 383 384 int 385 get_refs(struct ReferenceInfo **pris, size_t *prefcount) 386 { 387 struct ReferenceInfo *ris = NULL; 388 struct CommitInfo *ci = NULL; 389 git_reference_iterator *it = NULL; 390 const git_oid *id = NULL; 391 git_object *obj = NULL; 392 git_reference *dref = NULL, *r, *ref = NULL; 393 size_t i, refcount; 394 395 *pris = NULL; 396 *prefcount = 0; 397 398 if (git_reference_iterator_new(&it, repo)) 399 return -1; 400 401 for (refcount = 0; !git_reference_next(&ref, it); ) { 402 if (!git_reference_is_branch(ref) && !git_reference_is_tag(ref)) { 403 git_reference_free(ref); 404 ref = NULL; 405 continue; 406 } 407 408 switch (git_reference_type(ref)) { 409 case GIT_REF_SYMBOLIC: 410 if (git_reference_resolve(&dref, ref)) 411 goto err; 412 r = dref; 413 break; 414 case GIT_REF_OID: 415 r = ref; 416 break; 417 default: 418 continue; 419 } 420 if ( 421 !git_reference_target(r) || 422 git_reference_peel(&obj, r, GIT_OBJ_ANY) 423 ) 424 goto err; 425 if (!(id = git_object_id(obj))) 426 goto err; 427 if (!(ci = commit_info_get_by_oid(id))) 428 break; 429 430 if (!(ris = reallocarray(ris, refcount + 1, sizeof(*ris)))) 431 err(1, "realloc"); 432 ris[refcount].ci = ci; 433 ris[refcount].ref = r; 434 refcount++; 435 436 git_object_free(obj); 437 obj = NULL; 438 git_reference_free(dref); 439 dref = NULL; 440 } 441 git_reference_iterator_free(it); 442 443 /* sort by type, date then shorthand name */ 444 qsort(ris, refcount, sizeof(*ris), refs_cmp); 445 446 *pris = ris; 447 *prefcount = refcount; 448 449 return 0; 450 451 err: 452 git_object_free(obj); 453 git_reference_free(dref); 454 commit_info_free(ci); 455 for (i = 0; i < refcount; i++) { 456 commit_info_free(ris[i].ci); 457 git_reference_free(ris[i].ref); 458 } 459 free(ris); 460 461 return -1; 462 } 463 464 /* Percent-encode, see RFC3986 section 2.1. */ 465 void 466 put_percent_char(FILE *fp, unsigned char uc) 467 { 468 /* NOTE: do not encode '/' for paths or ",-." */ 469 if ( 470 uc < ',' || 471 uc >= 127 || 472 (uc >= ':' && uc <= '@') || 473 uc == '[' || 474 uc == ']' 475 ) { 476 CPUT(fp, '%'); 477 CPUT(fp, tab[(uc >> 4) & 0x0f]); 478 CPUT(fp, tab[uc & 0x0f]); 479 return; 480 } 481 482 CPUT(fp, uc); 483 } 484 485 void 486 put_percent_encoded(FILE *fp, const char *s, size_t len) 487 { 488 size_t i; 489 490 for (i = 0; *s && i < len; s++, i++) 491 put_percent_char(fp, *s); 492 } 493 494 /* Escape characters below as HTML 2.0 / XML 1.0. */ 495 void 496 put_xml_char(FILE *fp, const char *s) 497 { 498 switch(*s) { 499 case '<': fputs("<", fp); break; 500 case '>': fputs(">", fp); break; 501 case '\'': fputs("'", fp); break; 502 case '&': fputs("&", fp); break; 503 case '"': fputs(""", fp); break; 504 default: putc(*s, fp); 505 } 506 } 507 508 void 509 put_xml_len(FILE *fp, const char *s, size_t len) 510 { 511 size_t i = 0; 512 for (; *s && i < len; s++, i++) 513 put_xml_char(fp, s); 514 } 515 516 /* terminate on, but don't print '\r' or '\n' */ 517 void 518 put_xml_line(FILE *fp, const char *s, size_t len) 519 { 520 size_t i = 0; 521 for (; *s && i < len; s++, i++) { 522 if (*s == '\r' || *s == '\n') 523 break; 524 put_xml_char(fp, s); 525 } 526 } 527 528 void 529 printtimez(FILE *fp, const git_time *intime) 530 { 531 struct tm *intm; 532 time_t t; 533 char out[32]; 534 535 t = (time_t)intime->time; 536 if (!(intm = gmtime(&t))) 537 return; 538 strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm); 539 fputs(out, fp); 540 } 541 542 void 543 printtime(FILE *fp, const git_time *intime) 544 { 545 struct tm *intm; 546 time_t t; 547 char out[32]; 548 549 t = (time_t)intime->time + (intime->offset * 60); 550 if (!(intm = gmtime(&t))) 551 return; 552 strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm); 553 if (intime->offset < 0) { 554 fprintf( 555 fp, 556 "%s -%02d%02d", 557 out, 558 -(intime->offset) / 60, 559 -(intime->offset) % 60 560 ); 561 } else { 562 fprintf( 563 fp, 564 "%s +%02d%02d", out, 565 intime->offset / 60, 566 intime->offset % 60 567 ); 568 } 569 } 570 571 void 572 printtimeshort(FILE *fp, const git_time *intime) 573 { 574 struct tm *intm; 575 time_t t; 576 char out[32]; 577 578 t = (time_t)intime->time; 579 if (!(intm = gmtime(&t))) 580 return; 581 strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm); 582 fputs(out, fp); 583 } 584 585 void 586 put_absolute_path(FILE *fp, const char * path) 587 { 588 if (opt.base_url[0] != '\0') 589 fprintf(fp, "/%s", opt.base_url); 590 CPUT(fp, '/'); 591 fprintf(fp, PAGE_DEST); /* should have no leading/trailing slashes */ 592 fprintf(fp, "/%s", path); 593 } 594 595 void 596 put_header(FILE *fp, const char *title) 597 { 598 fputs( 599 "<!DOCTYPE html>\n" 600 "<html>" "\n" 601 "<head>" "\n" 602 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" 603 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n" 604 "<title>", 605 fp 606 ); 607 put_xml(fp, title); 608 if (title[0] && repo_name[0]) 609 fputs(" - ", fp); 610 put_xml(fp, repo_name); 611 if (description[0]) 612 fputs(" - ", fp); 613 put_xml(fp, description); 614 SPUTF(fp, "</title>" "\n"); 615 616 SPUTF(fp, "<link rel=\"icon\" type=\"image/png\" href=\""); 617 put_absolute_path(fp, "favicon.png"); 618 SPUTF(fp, "\" />" "\n"); 619 620 SPUTF(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\""); 621 put_xml(fp, name); 622 SPUTF(fp, " Atom Feed\" href=\""); 623 put_absolute_path(fp, "atom.xml"); 624 SPUTF(fp, "\" />" "\n"); 625 626 SPUTF(fp, "<link rel=\"alternate\" type=\"application/atom+xml\" title=\""); 627 put_xml(fp, name); 628 SPUTF(fp, " Atom Feed (tags)\" href=\""); 629 put_absolute_path(fp, "tags.xml"); 630 SPUTF(fp, "\" />" "\n"); 631 632 SPUTF(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\""); 633 fprintf(fp, PAGE_STYLE_HREF); 634 SPUTF(fp, "\" />" "\n"); 635 SPUTF(fp, "</head>" "\n" "<body>" "\n"); 636 637 fprintf(fp, PAGE_HEADER); 638 639 SPUTF(fp, "<table>" "<tr><td>"); 640 641 SPUTF(fp, "<a href=\""); 642 fprintf(fp, PAGE_LOGO_HREF); 643 SPUTF(fp, "\"><img alt=\"logo\" width=\"32\" height=\"32\" src=\""); 644 fprintf(fp, PAGE_LOGO_SRC); 645 SPUTF(fp, "\"></a>"); 646 647 SPUTF(fp, "</td><td>" "<h1>"); 648 put_xml(fp, repo_name); 649 SPUTF(fp, "</h1>" "<span class=\"desc\">"); 650 put_xml(fp, description); 651 SPUTF(fp, "</span>" "</td></tr>"); 652 if (clone_url[0]) { 653 SPUTF(fp, "<tr class=\"url\"><td></td><td>git clone <a href=\""); 654 put_xml(fp, clone_url); /* not percent encoded */ 655 SPUTF(fp, "\">"); 656 put_xml(fp, clone_url); 657 SPUTF(fp, "</a></td></tr>"); 658 } 659 SPUTF(fp, "<tr><td></td><td>" "\n"); 660 661 if (readme) { 662 SPUTF(fp, "<a href=\""); 663 put_absolute_path(fp, "files/"); 664 fprintf(fp, "%s.html", readme); 665 SPUTF(fp, "\">README</a> | "); 666 } 667 668 SPUTF(fp, "<a href=\""); 669 put_absolute_path(fp, "files.html"); 670 SPUTF(fp, "\">Files</a> | "); 671 672 SPUTF(fp, "<a href=\""); 673 put_absolute_path(fp, "log.html"); 674 SPUTF(fp, "\">Log</a> | "); 675 676 SPUTF(fp, "<a href=\""); 677 put_absolute_path(fp, "refs.html"); 678 SPUTF(fp, "\">Refs</a>"); 679 680 if (submodules) { 681 SPUTF(fp, " | <a href=\""); 682 put_absolute_path(fp, "files/"); 683 fprintf(fp, "%s.html", submodules); 684 SPUTF(fp, "\">Submodules</a>"); 685 } 686 687 if (license) { 688 SPUTF(fp, " | <a href=\""); 689 put_absolute_path(fp, "files/"); 690 fprintf(fp, "%s.html", license); 691 SPUTF(fp, "\">LICENSE</a>"); 692 } 693 694 SPUTF(fp, "</td></tr></table>" "\n"); 695 SPUTF(fp, "<hr/>" "\n"); 696 697 SPUTF(fp, "<div id=\"content\">" "\n"); 698 } 699 700 void 701 put_footer(FILE *fp) 702 { 703 SPUTF(fp, "</div>" "\n"); 704 fprintf(fp, PAGE_FOOTER); 705 SPUTF(fp, "</body>" "\n" "</html>" "\n"); 706 } 707 708 size_t 709 put_file_html(FILE *fp, const git_blob *blob) 710 { 711 size_t n = 0, i, len, prev; 712 const char *number_format = 713 "<a href=\"#l%zu\" class=\"line\" id=\"l%zu\">%7zu </a>"; 714 const char *s = git_blob_rawcontent(blob); 715 716 len = git_blob_rawsize(blob); 717 SPUTF(fp, "<pre id=\"blob\">" "\n"); 718 719 if (len > 0) { 720 for (i = 0, prev = 0; i < len; i++) { 721 if (s[i] != '\n') 722 continue; 723 n++; 724 fprintf(fp, number_format, n, n, n); 725 put_xml_line(fp, &s[prev], i - prev + 1); 726 SPUTF(fp, "\n"); 727 prev = i + 1; 728 } 729 /* trailing data */ 730 if ((len - prev) > 0) { 731 n++; 732 fprintf(fp, number_format, n, n, n); 733 put_xml_line(fp, &s[prev], len - prev); 734 } 735 } 736 737 SPUTF(fp, "</pre>" "\n"); 738 739 return n; 740 } 741 742 void 743 print_commit(FILE *fp, struct CommitInfo *ci) 744 { 745 SPUTF(fp, "<b>commit</b> <a href=\""); 746 put_absolute_path(fp, "commits/"); 747 fprintf(fp, "%s.html", ci->oid); 748 fprintf(fp, "\">%s</a>" "\n", ci->oid); 749 750 if (ci->parentoid[0]) { 751 SPUTF(fp, "<b>parent</b> <a href=\""); 752 put_absolute_path(fp, "commits/"); 753 fprintf(fp, "%s.html", ci->parentoid); 754 fprintf(fp, "\">%s</a>" "\n", ci->parentoid); 755 } 756 757 if (ci->author) { 758 SPUTF(fp, "<b>Author:</b> "); 759 put_xml(fp, ci->author->name); 760 SPUTF(fp, " <<a href=\"mailto:"); 761 put_xml(fp, ci->author->email); /* not percent encoded */ 762 SPUTF(fp, "\">"); 763 put_xml(fp, ci->author->email); 764 SPUTF(fp, "</a>>\n<b>Date:</b> "); 765 printtime(fp, &(ci->author->when)); 766 CPUT(fp, '\n'); 767 } 768 if (ci->msg) { 769 CPUT(fp, '\n'); 770 put_xml(fp, ci->msg); 771 CPUT(fp, '\n'); 772 } 773 } 774 775 void 776 put_show_file(FILE *fp, struct CommitInfo *ci) 777 { 778 const git_diff_delta *delta; 779 const git_diff_hunk *hunk; 780 const git_diff_line *line; 781 git_patch *patch; 782 size_t nhunks, nhunklines, changed, add, del, total, i, j, k; 783 char linestr[80]; 784 int c; 785 786 print_commit(fp, ci); 787 788 if (!ci->deltas) 789 return; 790 791 if ( 792 ci->file_count > 1000 || 793 ci->ndeltas > 1000 || 794 ci->add_count > 100000 || 795 ci->del_count > 100000 796 ) { 797 fputs("Diff is too large, output suppressed.\n", fp); 798 return; 799 } 800 801 /* diff stat */ 802 fputs("<b>Diffstat:</b>" "\n", fp); 803 fputs("<table id=\"diffstat\">", fp); 804 for (i = 0; i < ci->ndeltas; i++) { 805 delta = git_patch_get_delta(ci->deltas[i]->patch); 806 807 switch (delta->status) { 808 case GIT_DELTA_ADDED: c = 'A'; break; 809 case GIT_DELTA_COPIED: c = 'C'; break; 810 case GIT_DELTA_DELETED: c = 'D'; break; 811 case GIT_DELTA_MODIFIED: c = 'M'; break; 812 case GIT_DELTA_RENAMED: c = 'R'; break; 813 case GIT_DELTA_TYPECHANGE: c = 'T'; break; 814 default: c = ' '; break; 815 } 816 if (c == ' ') 817 fprintf(fp, "<tr><td>%c", c); 818 else 819 fprintf(fp, "<tr><td class=\"%c\">%c", c, c); 820 821 fprintf(fp, "</td><td><a href=\"#h%zu\">", i); 822 put_xml(fp, delta->old_file.path); 823 if (strcmp(delta->old_file.path, delta->new_file.path)) { 824 fputs(" -> ", fp); 825 put_xml(fp, delta->new_file.path); 826 } 827 828 add = ci->deltas[i]->add_count; 829 del = ci->deltas[i]->del_count; 830 changed = add + del; 831 total = sizeof(linestr) - 2; 832 if (changed > total) { 833 if (add) 834 add = ((float)total / changed * add) + 1; 835 if (del) 836 del = ((float)total / changed * del) + 1; 837 } 838 memset(&linestr, '+', add); 839 memset(&linestr[add], '-', del); 840 841 fprintf( 842 fp, 843 "</a></td>" 844 // "<td> | </td>" 845 "<td class=\"num\">%zu</td><td><span class=\"i\">", 846 ci->deltas[i]->add_count + ci->deltas[i]->del_count 847 ); 848 fwrite(&linestr, 1, add, fp); 849 fputs("</span><span class=\"d\">", fp); 850 fwrite(&linestr[add], 1, del, fp); 851 fputs("</span></td></tr>\n", fp); 852 } 853 fprintf( 854 fp, 855 "</table></pre><pre>%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n", 856 ci->file_count, ci->file_count == 1 ? "" : "s", 857 ci->add_count, ci->add_count == 1 ? "" : "s", 858 ci->del_count, ci->del_count == 1 ? "" : "s" 859 ); 860 861 fputs("<hr/>", fp); 862 863 for (i = 0; i < ci->ndeltas; i++) { 864 patch = ci->deltas[i]->patch; 865 delta = git_patch_get_delta(patch); 866 867 SPUTF(fp, "<b>diff --git a/"); 868 put_xml(fp, delta->old_file.path); 869 fprintf(fp, " b/<a id=\"h%zu\" href=\"#h%zu\">", i, i); 870 put_xml(fp, delta->new_file.path); 871 SPUTF(fp, "</a></b>" "\n"); 872 873 /* check binary data */ 874 if (delta->flags & GIT_DIFF_FLAG_BINARY) { 875 SPUTF(fp, "Binary files differ." "\n"); 876 continue; 877 } 878 879 nhunks = git_patch_num_hunks(patch); 880 for (j = 0; j < nhunks; j++) { 881 if (git_patch_get_hunk(&hunk, &nhunklines, patch, j)) 882 break; 883 884 fprintf(fp, "<a href=\"#h%zu-%zu\" id=\"h%zu-%zu\" class=\"h\">", i, j, i, j); 885 put_xml_len(fp, hunk->header, hunk->header_len); 886 SPUTF(fp, "</a>"); 887 888 for (k = 0; ; k++) { 889 if (git_patch_get_line_in_hunk(&line, patch, j, k)) 890 break; 891 if (line->old_lineno == -1) 892 fprintf( 893 fp, 894 "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"i\">+", 895 i, j, k, i, j, k 896 ); 897 else if (line->new_lineno == -1) 898 fprintf( 899 fp, 900 "<a href=\"#h%zu-%zu-%zu\" id=\"h%zu-%zu-%zu\" class=\"d\">-", 901 i, j, k, i, j, k 902 ); 903 else 904 CPUT(fp, ' '); 905 put_xml_line(fp, line->content, line->content_len); 906 CPUT(fp, '\n'); 907 if (line->old_lineno == -1 || line->new_lineno == -1) 908 SPUTF(fp, "</a>"); 909 } 910 } 911 } 912 } 913 914 void 915 put_log_line(FILE *fp, struct CommitInfo *ci) 916 { 917 fputs("<tr><td>", fp); 918 if (ci->author) 919 printtimeshort(fp, &(ci->author->when)); 920 fputs("</td><td>", fp); 921 if (ci->summary) { 922 SPUTF(fp, "<a href=\""); 923 put_absolute_path(fp, "commits/"); 924 fprintf(fp, "%s.html", ci->oid); 925 SPUTF(fp, "\">"); 926 put_xml(fp, ci->summary); 927 SPUTF(fp, "</a>"); 928 } 929 SPUTF(fp, "</td><td>"); 930 if (ci->author) put_xml(fp, ci->author->name); 931 SPUTF(fp, "</td><td class=\"num\" align=\"right\">"); 932 fprintf(fp, "%zu", ci->file_count); 933 SPUTF(fp, "</td><td class=\"num\" align=\"right\">"); 934 fprintf(fp, "+%zu", ci->add_count); 935 SPUTF(fp, "</td><td class=\"num\" align=\"right\">"); 936 fprintf(fp, "-%zu", ci->del_count); 937 SPUTF(fp, "</td></tr>" "\n"); 938 } 939 940 void 941 write_commit_page(char *name, struct CommitInfo *ci) 942 { 943 char fname[PATH_MAX]; 944 get_page_path(fname, sizeof(fname), opt.out_dir, name); 945 FILE *fp = fopen_w(fname); 946 // [old] fpfile = efopen(path, "w"); 947 948 put_header(fp, ci->summary); 949 950 SPUTF(fp, "<pre>"); 951 put_show_file(fp, ci); 952 SPUTF(fp, "</pre>" "\n"); 953 954 put_footer(fp); 955 check_file_error(fp, fname, 'w'); 956 fclose(fp); 957 } 958 959 int 960 put_log(FILE *fp, const git_oid *oid) 961 { 962 struct CommitInfo *ci; 963 git_revwalk *w = NULL; 964 git_oid id; 965 char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1]; 966 size_t remcommits = 0; 967 int r; 968 int commits_left = MAX_COMMITS; 969 970 git_revwalk_new(&w, repo); 971 git_revwalk_push(w, oid); 972 973 // TODO avoid processing very large commits 974 while (!git_revwalk_next(&id, w)) { 975 git_oid_tostr(oidstr, sizeof(oidstr), &id); 976 r = snprintf(path, sizeof(path), "commits/%s.html", oidstr); 977 if (r < 0 || (size_t)r >= sizeof(path)) 978 errx(1, "path truncated: 'commits/%s.html'", oidstr); 979 r = access(path, F_OK); 980 981 /* optimization: if there are no log lines to write and 982 the commit file already exists: skip the diffstat */ 983 if (!commits_left) { 984 remcommits++; 985 if (!r) 986 continue; 987 } 988 989 if (!(ci = commit_info_get_by_oid(&id))) 990 break; 991 /* diffstat: for stagit HTML required for the log.html line */ 992 if (commit_info_get_stats(ci) == -1) 993 goto err; 994 995 if (commits_left != 0) { 996 put_log_line(fp, ci); 997 if (commits_left > 0) 998 commits_left--; 999 } 1000 1001 /* check if file exists if so skip it */ 1002 if (r) write_commit_page(path, ci); 1003 1004 err: 1005 commit_info_free(ci); 1006 } 1007 git_revwalk_free(w); 1008 1009 if (commits_left == 0 && remcommits != 0) 1010 fprintf( 1011 fp, 1012 "<tr><td></td><td colspan=\"5\">" 1013 "%zu more commits remaining, fetch the repository" 1014 "</td></tr>" "\n", 1015 remcommits 1016 ); 1017 1018 return 0; 1019 } 1020 1021 void 1022 put_log_html(FILE *fp, const git_oid *head) 1023 { 1024 put_header(fp, "Log"); 1025 fputs( 1026 "<table id=\"log\"><thead>" "\n" 1027 "<tr>" 1028 "<td><b>Date</b></td>" 1029 "<td><b>Commit message</b></td>" 1030 "<td><b>Author</b></td>" 1031 "<td class=\"num\" align=\"right\"><b>Files</b></td>" 1032 "<td class=\"num\" align=\"right\"><b>+</b></td>" 1033 "<td class=\"num\" align=\"right\"><b>-</b></td>" 1034 "</tr>" "\n" 1035 "</thead><tbody>" "\n", 1036 fp 1037 ); 1038 1039 if (head) put_log(fp, head); 1040 1041 fputs("</tbody></table>", fp); 1042 put_footer(fp); 1043 } 1044 1045 void 1046 print_commit_atom(FILE *fp, struct CommitInfo *ci, const char *tag) 1047 { 1048 fputs("<entry>\n", fp); 1049 1050 fprintf(fp, "<id>%s</id>\n", ci->oid); 1051 if (ci->author) { 1052 SPUTF(fp, "<published>"); 1053 printtimez(fp, &(ci->author->when)); 1054 SPUTF(fp, "</published>\n"); 1055 } 1056 if (ci->committer) { 1057 SPUTF(fp, "<updated>"); 1058 printtimez(fp, &(ci->committer->when)); 1059 SPUTF(fp, "</updated>\n"); 1060 } 1061 if (ci->summary) { 1062 SPUTF(fp, "<title>"); 1063 if (tag && tag[0]) { 1064 SPUTF(fp, "["); 1065 put_xml(fp, tag); 1066 SPUTF(fp, "] "); 1067 } 1068 put_xml(fp, ci->summary); 1069 SPUTF(fp, "</title>\n"); 1070 } 1071 SPUTF(fp, "<link rel=\"alternate\" type=\"text/html\" href=\""); 1072 put_absolute_path(fp, "commits/"); 1073 fprintf(fp, "%s.html\" />" "\n", ci->oid); 1074 1075 if (ci->author) { 1076 SPUTF(fp, "<author>" "\n" "<name>"); 1077 put_xml(fp, ci->author->name); 1078 SPUTF(fp, "</name>" "\n" "<email>"); 1079 put_xml(fp, ci->author->email); 1080 SPUTF(fp, "</email>" "\n" "</author>" "\n"); 1081 } 1082 1083 SPUTF(fp, "<content>"); 1084 fprintf(fp, "commit %s" "\n", ci->oid); 1085 if (ci->parentoid[0]) 1086 fprintf(fp, "parent %s" "\n", ci->parentoid); 1087 if (ci->author) { 1088 SPUTF(fp, "Author: "); 1089 put_xml(fp, ci->author->name); 1090 SPUTF(fp, " <"); 1091 put_xml(fp, ci->author->email); 1092 SPUTF(fp, ">" "\n" "Date: "); 1093 printtime(fp, &(ci->author->when)); 1094 CPUT(fp, '\n'); 1095 } 1096 if (ci->msg) { 1097 CPUT(fp, '\n'); 1098 put_xml(fp, ci->msg); 1099 } 1100 SPUTF(fp, "\n" "</content>" "\n" "</entry>" "\n"); 1101 } 1102 1103 int 1104 write_atom(FILE *fp, int all) 1105 { 1106 struct ReferenceInfo *ris = NULL; 1107 size_t refcount = 0; 1108 struct CommitInfo *ci; 1109 git_revwalk *w = NULL; 1110 git_oid id; 1111 size_t i, m = 100; /* last 'm' commits */ 1112 1113 SPUTF( 1114 fp, 1115 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" "\n" 1116 "<feed xmlns=\"http://www.w3.org/2005/Atom\">" "\n" "<title>" 1117 ); 1118 put_xml(fp, repo_name); 1119 SPUTF(fp, ", branch HEAD</title>" "\n" "<subtitle>"); 1120 put_xml(fp, description); 1121 SPUTF(fp, "</subtitle>" "\n"); 1122 1123 /* all commits or only tags? */ 1124 if (all) { 1125 git_revwalk_new(&w, repo); 1126 git_revwalk_push_head(w); 1127 for (i = 0; i < m && !git_revwalk_next(&id, w); i++) { 1128 if (!(ci = commit_info_get_by_oid(&id))) 1129 break; 1130 print_commit_atom(fp, ci, ""); 1131 commit_info_free(ci); 1132 } 1133 git_revwalk_free(w); 1134 } else if (get_refs(&ris, &refcount) != -1) { 1135 /* references: tags */ 1136 for (i = 0; i < refcount; i++) { 1137 if (git_reference_is_tag(ris[i].ref)) 1138 print_commit_atom( 1139 fp, ris[i].ci, 1140 git_reference_shorthand(ris[i].ref) 1141 ); 1142 1143 commit_info_free(ris[i].ci); 1144 git_reference_free(ris[i].ref); 1145 } 1146 free(ris); 1147 } 1148 1149 fputs("</feed>\n", fp); 1150 1151 return 0; 1152 } 1153 1154 void 1155 write_binary_file(const char *filename, git_blob *blob) { 1156 char dest[PATH_MAX]; 1157 const char *raw = git_blob_rawcontent(blob); 1158 int size = git_blob_rawsize(blob); 1159 1160 /* set dest */ 1161 join_path(dest, PATH_MAX, "files", filename); 1162 1163 /* write the file! */ 1164 char fname[PATH_MAX]; 1165 get_page_path(fname, sizeof(fname), opt.out_dir, dest); 1166 FILE *fp = fopen_w(fname); 1167 1168 fwrite(raw, size, 1, fp); 1169 check_file_error(fp, fname, 'w'); 1170 fclose(fp); 1171 } 1172 1173 void 1174 put_size(FILE *fp, size_t size, int fixed) 1175 { 1176 int order = 0; 1177 int decimal; 1178 1179 if (size == 0) { 1180 fprintf(fp, " - "); 1181 return; 1182 } 1183 1184 while (size > 1000) { 1185 order++; 1186 decimal = (size / 100) % 10; 1187 size /= 1000; 1188 } 1189 1190 if (size < 10 && order > 0) fprintf(fp, "%1zu.%1d", size, decimal); 1191 else fprintf(fp, fixed ? "%3zu" : "%zu", size); 1192 1193 if (order != 0 || fixed) fprintf(fp, "%c", orders[order]); 1194 } 1195 1196 void 1197 put_file_title(FILE *fp, const char *name, int is_dir) 1198 { 1199 int i = 0; 1200 int len = 0; 1201 1202 /* project root */ 1203 if (name[0] == '\0') 1204 return; 1205 1206 if (!DIR_PAGES) { 1207 fprintf(fp, "<p>%s</p>", name); 1208 return; 1209 } 1210 1211 if (BACKLINKS && !is_dir) { 1212 for (i = 0; name[i] != '\0'; i++) 1213 if (name[i] == '/') len = i; 1214 1215 if (len != 0) { 1216 SPUTF(fp, "<p><a href=\""); 1217 put_absolute_path(fp, "files/"); 1218 for (i = 0; i < len; i++) put_percent_char(fp, name[i]); 1219 SPUTF(fp, ".html\">"); 1220 for (i = 0; i < len; i++) put_percent_char(fp, name[i]); 1221 fprintf(fp, "</a>%s</p>", name + len); 1222 if (!is_dir) SPUTF(fp, "<hr>"); 1223 return; 1224 } 1225 } 1226 1227 1228 fprintf(fp, "<p>%s</p>", name); 1229 if (!is_dir) SPUTF(fp, "<hr>"); 1230 } 1231 1232 void 1233 put_binary_blob_stub(FILE *fp, const char *file_url) 1234 { 1235 SPUTF(fp, "<p>Binary file: <a href=\""); 1236 put_absolute_path(fp, "files/"); 1237 fprintf(fp, "%s\">raw</a></p>" "\n", file_url); 1238 SPUTF(fp, "<object data=\""); 1239 put_absolute_path(fp, "files/"); 1240 fprintf(fp, "%s\">", file_url); 1241 SPUTF(fp, "no preview available.</object></p>" "\n"); 1242 } 1243 1244 struct Weight 1245 write_blob( 1246 git_blob *blob, 1247 char *filename, 1248 const char *entrypath, 1249 const char *name 1250 ) { 1251 char fname[PATH_MAX]; 1252 FILE *fp; 1253 struct Weight ret; 1254 1255 ret.lines = 0; 1256 ret.bytes = 0; 1257 1258 get_page_path(fname, sizeof(fname), opt.out_dir, filename); 1259 fp = fopen_w(fname); 1260 1261 put_header(fp, name); 1262 put_file_title(fp, entrypath, 0); 1263 1264 if (git_blob_is_binary(blob)) { 1265 put_binary_blob_stub(fp, entrypath); // name); 1266 write_binary_file(entrypath, blob); 1267 ret.bytes = git_blob_rawsize(blob); 1268 } else { 1269 ret.lines = put_file_html(fp, blob); 1270 } 1271 1272 put_footer(fp); 1273 check_file_error(fp, fname, 'w'); 1274 fclose(fp); 1275 1276 return ret; 1277 } 1278 1279 char 1280 get_filemode_type(git_filemode_t m) 1281 { 1282 if (S_ISREG(m)) return '-'; 1283 if (S_ISBLK(m)) return 'b'; 1284 if (S_ISCHR(m)) return 'c'; 1285 if (S_ISDIR(m)) return 'd'; 1286 if (S_ISFIFO(m)) return 'p'; 1287 if (S_ISLNK(m)) return 'l'; 1288 if (S_ISSOCK(m)) return 's'; 1289 return '?'; 1290 } 1291 1292 const char * 1293 get_filemode(const git_tree_entry *entry) 1294 { 1295 git_filemode_t mode; 1296 static char ret[11]; 1297 1298 /* used for backlink: .. */ 1299 if (entry == NULL) return "d---------"; 1300 1301 mode = git_tree_entry_filemode(entry); 1302 1303 memset(ret, '-', sizeof(ret) - 1); 1304 ret[0] = get_filemode_type(mode); 1305 if (mode & S_IRUSR) ret[1] = 'r'; 1306 if (mode & S_IWUSR) ret[2] = 'w'; 1307 if (mode & S_IXUSR) ret[3] = 'x'; 1308 if (mode & S_ISUID) ret[3] = (ret[3] == 'x') ? 's' : 'S'; 1309 if (mode & S_IRGRP) ret[4] = 'r'; 1310 if (mode & S_IWGRP) ret[5] = 'w'; 1311 if (mode & S_IXGRP) ret[6] = 'x'; 1312 if (mode & S_ISGID) ret[6] = (ret[6] == 'x') ? 's' : 'S'; 1313 if (mode & S_IROTH) ret[7] = 'r'; 1314 if (mode & S_IWOTH) ret[8] = 'w'; 1315 if (mode & S_IXOTH) ret[9] = 'x'; 1316 if (mode & S_ISVTX) ret[9] = (ret[9] == 'x') ? 't' : 'T'; 1317 ret[10] = '\0'; 1318 1319 return ret; 1320 } 1321 1322 void 1323 put_filetree_file_line( 1324 FILE *fp, 1325 git_object *obj, 1326 const git_tree_entry *entry, 1327 char *filename, 1328 char *entrypath, 1329 const char *name, 1330 struct Weight weight 1331 ) { 1332 SPUTF(fp, "<tr><td>"); 1333 SPUTF(fp, get_filemode(entry)); 1334 SPUTF(fp, "</td><td><a href=\""); 1335 put_absolute_path(fp, ""); 1336 put_percent_encoded(fp, filename, strlen(filename)); 1337 SPUTF(fp, "\">"); 1338 if (DIR_PAGES) put_xml(fp, name); 1339 else put_xml(fp, entrypath); 1340 SPUTF(fp, "</a></td><td class=\"num\" align=\"right\">"); 1341 put_size(fp, weight.lines, 1); 1342 SPUTF(fp, "</td><td class=\"num\" align=\"right\">"); 1343 put_size(fp, weight.bytes, 1); 1344 SPUTF(fp, "</td></tr>\n"); 1345 } 1346 1347 void 1348 put_filetree_dir_line( 1349 FILE *fp, 1350 const git_tree_entry *entry, 1351 char *destination, 1352 const char *title, 1353 size_t lines, 1354 size_t bytes 1355 ) { 1356 SPUTF(fp, "<tr><td>"); 1357 SPUTF(fp, get_filemode(entry)); 1358 SPUTF(fp, "</td><td><a href=\""); 1359 put_absolute_path(fp, ""); 1360 put_percent_encoded(fp, destination, strlen(destination)); 1361 SPUTF(fp, "\">"); 1362 put_xml(fp, title); 1363 SPUTF(fp, "</a></td><td class=\"num\" align=\"right\">"); 1364 put_size(fp, lines, 1); 1365 SPUTF(fp, "</td><td class=\"num\" align=\"right\">"); 1366 put_size(fp, bytes, 1); 1367 SPUTF(fp, "</td></tr>\n"); 1368 } 1369 1370 void put_filetree_backlink_line(FILE *fp, const char *path) { 1371 char previous_path[PATH_MAX]; 1372 char previous_filename[PATH_MAX]; 1373 int i, r, last_slash = 0; 1374 1375 /* figure out previous directory */ 1376 // TODO how to do this more cleanly... 1377 for (i = 0; path[i] != '\0'; i++) { 1378 previous_path[i] = path[i]; 1379 if (path[i] == '/') last_slash = i; 1380 } 1381 previous_path[last_slash] = '\0'; 1382 1383 if (last_slash == 0) { 1384 put_filetree_dir_line(fp, NULL, "files.html", "..", 0, 0); 1385 } else { 1386 r = snprintf(previous_filename, sizeof(previous_filename), "files/%s.html", previous_path); 1387 if (r < 0 || (size_t) r >= sizeof(previous_filename)) 1388 errx(1, "path truncated: 'files/%s.html'", previous_path); 1389 put_filetree_dir_line(fp, NULL, previous_filename, "..", 0, 0); 1390 } 1391 } 1392 1393 /* need this here to allow recursion */ 1394 struct Weight write_filetree( 1395 const char *path, 1396 const char *filename, 1397 git_tree *tree 1398 ); 1399 struct Weight put_file_list(FILE *fp, git_tree *tree, const char *path); 1400 1401 struct Weight 1402 put_entry_obj(FILE *fp, git_object *obj, const git_tree_entry *entry, char *entrypath, const char *name) 1403 { 1404 struct Weight ret; 1405 git_object_t obj_type = git_object_type(obj); 1406 char filename[PATH_MAX]; 1407 int r; 1408 1409 /* filename = f("files/%s.html", entrypath) */ 1410 r = snprintf(filename, sizeof(filename), "files/%s.html", entrypath); 1411 if (r < 0 || (size_t) r >= sizeof(filename)) 1412 errx(1, "path truncated: 'files/%s.html'", entrypath); 1413 1414 switch (obj_type) { 1415 case GIT_OBJ_TREE: 1416 if (DIR_PAGES) { 1417 /* create dir page */ 1418 ret = write_filetree(entrypath, filename, (git_tree *) obj); 1419 1420 /* add link to that dir page */ 1421 put_filetree_dir_line(fp, entry, filename, name, ret.lines, ret.bytes); 1422 } else { 1423 /* list files on this page */ 1424 ret = put_file_list(fp, (git_tree *) obj, entrypath); 1425 } 1426 break; 1427 case GIT_OBJ_BLOB: 1428 ret = write_blob((git_blob *) obj, filename, entrypath, name); 1429 put_filetree_file_line(fp, obj, entry, filename, entrypath, name, ret); 1430 break; 1431 default: break; 1432 } 1433 1434 return ret; 1435 } 1436 1437 void 1438 put_submodule_obj(FILE *fp, const git_tree_entry *entry, const char *name) 1439 { 1440 char oid[8]; 1441 1442 SPUTF(fp, "<tr>" "<td>m---------</td>" "<td><a href=\""); 1443 put_absolute_path(fp, "files/.gitmodules.html"); 1444 SPUTF(fp, "\">"); 1445 put_xml(fp, name); 1446 SPUTF(fp, "</a> @ "); 1447 git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry)); 1448 put_xml(fp, oid); 1449 SPUTF(fp, "</td>" "<td class=\"num\" align=\"right\"></td>" "\n"); 1450 SPUTF(fp, "<td class=\"num\" align=\"right\"></td>" "</tr>" "\n"); 1451 } 1452 1453 struct Weight 1454 put_file_list(FILE *fp, git_tree *tree, const char *path) 1455 { 1456 struct Weight ret; 1457 size_t count; 1458 size_t i; 1459 1460 ret.lines = ret.bytes = 0; 1461 1462 count = git_tree_entrycount(tree); 1463 for (i = 0; i < count; i++) { 1464 const git_tree_entry *entry = NULL; 1465 const char *name; 1466 char entrypath[PATH_MAX]; 1467 git_object *obj = NULL; 1468 1469 if ( 1470 !(entry = git_tree_entry_byindex(tree, i)) || 1471 !(name = git_tree_entry_name(entry)) 1472 ) 1473 return ret; 1474 1475 combine_paths(entrypath, path, name); 1476 1477 if (!git_tree_entry_to_object(&obj, repo, entry)) { 1478 struct Weight weight = 1479 put_entry_obj(fp, obj, entry, entrypath, name); 1480 ret.bytes += weight.bytes; 1481 ret.lines += weight.lines; 1482 git_object_free(obj); 1483 continue; 1484 } 1485 1486 if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) { 1487 put_submodule_obj(fp, entry, name); 1488 continue; 1489 } 1490 } 1491 1492 return ret; 1493 } 1494 1495 struct Weight 1496 write_filetree(const char *path, const char *filename, git_tree *tree) 1497 { 1498 struct Weight ret; 1499 char fname[PATH_MAX] = ""; 1500 FILE *fp; 1501 1502 get_page_path(fname, sizeof(fname), opt.out_dir, (char *) filename); 1503 fp = fopen_w(fname); 1504 1505 put_header(fp, "Files"); 1506 1507 put_file_title(fp, path, 1); 1508 1509 SPUTF( 1510 fp, 1511 "<table id=\"files\"><thead>" "\n" "<tr>" 1512 "<td><b>Mode</b></td><td><b>Name</b></td>" 1513 "<td class=\"num\" align=\"right\"><b> L </b></td>" 1514 "<td class=\"num\" align=\"right\"><b> B </b></td>" 1515 "</tr>" "\n" "</thead><tbody>" "\n" 1516 ); 1517 1518 if (tree != NULL) { 1519 if (BACKLINKS && path[0] != '\0') 1520 put_filetree_backlink_line(fp, path); 1521 1522 ret = put_file_list(fp, tree, path); 1523 } 1524 1525 SPUTF(fp, "</tbody></table>"); 1526 1527 // TODO how can this get printed before the contents... 1528 /* 1529 SPUTF(fp, "<p>Totals: "); 1530 put_size(fp, ret.lines, 0); 1531 SPUTF(fp, "L "); 1532 put_size(fp, ret.bytes, 0); 1533 SPUTF(fp, "B</p>"); 1534 */ 1535 1536 put_footer(fp); 1537 check_file_error(fp, fname, 'w'); 1538 fclose(fp); 1539 1540 return ret; 1541 } 1542 1543 void 1544 walk_git_tree(const git_oid *id) 1545 { 1546 git_tree *tree = NULL; 1547 git_commit *commit = NULL; 1548 int has_files; 1549 1550 if (!id) return; 1551 1552 has_files = !git_commit_lookup(&commit, repo, id) && 1553 !git_commit_tree(&tree, commit); 1554 1555 write_filetree("", "files.html", has_files ? tree : NULL); 1556 1557 git_commit_free(commit); 1558 git_tree_free(tree); 1559 } 1560 1561 int 1562 put_refs(FILE *fp) 1563 { 1564 struct ReferenceInfo *ris = NULL; 1565 struct CommitInfo *ci; 1566 size_t count, i, j, refcount; 1567 const char *titles[] = { "Branches", "Tags" }; 1568 const char *ids[] = { "branches", "tags" }; 1569 const char *s; 1570 1571 if (get_refs(&ris, &refcount) == -1) 1572 return -1; 1573 1574 for (i = 0, j = 0, count = 0; i < refcount; i++) { 1575 if (j == 0 && git_reference_is_tag(ris[i].ref)) { 1576 if (count) 1577 fputs("</tbody></table><br/>\n", fp); 1578 count = 0; 1579 j = 1; 1580 } 1581 1582 /* print header if it has an entry (first). */ 1583 if (++count == 1) 1584 fprintf( 1585 fp, 1586 "<h2>%s</h2><table id=\"%s\">" 1587 "<thead>\n<tr><td><b>Name</b></td>" 1588 "<td><b>Last commit date</b></td>" 1589 "<td><b>Author</b></td>\n</tr>\n" 1590 "</thead><tbody>\n", 1591 titles[j], 1592 ids[j] 1593 ); 1594 1595 ci = ris[i].ci; 1596 s = git_reference_shorthand(ris[i].ref); 1597 1598 fputs("<tr><td>", fp); 1599 put_xml(fp, s); 1600 fputs("</td><td>", fp); 1601 if (ci->author) 1602 printtimeshort(fp, &(ci->author->when)); 1603 fputs("</td><td>", fp); 1604 if (ci->author) 1605 put_xml(fp, ci->author->name); 1606 fputs("</td></tr>\n", fp); 1607 } 1608 1609 if (count) 1610 fputs("</tbody></table><br/>\n", fp); 1611 1612 for (i = 0; i < refcount; i++) { 1613 commit_info_free(ris[i].ci); 1614 git_reference_free(ris[i].ref); 1615 } 1616 free(ris); 1617 1618 return 0; 1619 } 1620 1621 void 1622 write_log_page(const git_oid *head) 1623 { 1624 FILE *fp; 1625 char fname[PATH_MAX]; 1626 1627 get_page_path(fname, sizeof(fname), opt.out_dir, "log.html"); 1628 fp = fopen_w(fname); 1629 1630 put_log_html(fp, head); 1631 1632 check_file_error(fp, fname, 'w'); 1633 fclose(fp); 1634 } 1635 1636 void 1637 write_refs_page() 1638 { 1639 FILE *fp; 1640 char fname[PATH_MAX]; 1641 1642 get_page_path(fname, sizeof(fname), opt.out_dir, "refs.html"); 1643 fp = fopen_w(fname); 1644 1645 put_header(fp, "Refs"); 1646 put_refs(fp); 1647 put_footer(fp); 1648 1649 check_file_error(fp, fname, 'w'); 1650 fclose(fp); 1651 } 1652 1653 void 1654 write_feed() 1655 { 1656 FILE *fp; 1657 char fname[PATH_MAX]; 1658 1659 get_page_path(fname, sizeof(fname), opt.out_dir, "atom.xml"); 1660 fp = fopen_w(fname); 1661 1662 write_atom(fp, 1); 1663 1664 check_file_error(fp, fname, 'w'); 1665 fclose(fp); 1666 } 1667 1668 void 1669 write_releases_feed() 1670 { 1671 FILE *fp; 1672 char fname[PATH_MAX]; 1673 1674 get_page_path(fname, sizeof(fname), opt.out_dir, "tags.xml"); 1675 fp = fopen_w(fname); 1676 1677 write_atom(fp, 0); 1678 1679 check_file_error(fp, fname, 'w'); 1680 fclose(fp); 1681 } 1682 1683 void 1684 find_submodules() 1685 { 1686 git_object *obj = NULL; 1687 if ( 1688 !git_revparse_single(&obj, repo, "HEAD:.gitmodules") && 1689 git_object_type(obj) == GIT_OBJ_BLOB 1690 ) 1691 submodules = ".gitmodules"; 1692 git_object_free(obj); 1693 } 1694 1695 void 1696 find_description() 1697 { 1698 char path[PATH_MAX]; 1699 FILE *fp; 1700 if ((fp = git_fopen(path, sizeof(path), ".git/description"))) { 1701 if (!fgets(description, sizeof(description), fp)) 1702 description[0] = '\0'; 1703 check_file_error(fp, path, 'r'); 1704 fclose(fp); 1705 } 1706 } 1707 1708 void 1709 find_clone_url() 1710 { 1711 char path[PATH_MAX]; 1712 FILE *fp; 1713 if ((fp = git_fopen(path, sizeof(path), ".git/url"))) { 1714 if (!fgets(clone_url, sizeof(clone_url), fp)) 1715 clone_url[0] = '\0'; 1716 check_file_error(fp, path, 'r'); 1717 fclose(fp); 1718 1719 // TODO ??? 1720 clone_url[strcspn(clone_url, "\n")] = '\0'; 1721 } 1722 } 1723 1724 void 1725 find_repo_name() 1726 { 1727 char abs_dir[PATH_MAX + 1]; 1728 char *p; 1729 1730 /* set abs_dir */ 1731 if (!realpath(repo_dir, abs_dir)) 1732 err(1, "realpath"); 1733 1734 if ((name = strrchr(abs_dir, '/'))) name++; 1735 else name = ""; 1736 1737 if (!(repo_name = strdup(name))) err(1, "strdup"); 1738 if ((p = strrchr(repo_name, '.'))) 1739 if (!strcmp(p, ".git")) 1740 *p = '\0'; 1741 1742 if (repo_name[0] == '\0') 1743 repo_name = "untitled"; 1744 } 1745 1746 int 1747 write_repo_pages(const char* dir) 1748 { 1749 1750 // TODO fully stop using globals like this.... 1751 repo_dir = dir; 1752 1753 #ifdef __OpenBSD__ 1754 if (unveil(repo_dir, "r") == -1) err(1, "unveil: %s", repo_dir); 1755 if (unveil(".", "rwc") == -1) err(1, "unveil: ."); 1756 1757 /* 1758 if (cachefile) { 1759 if (unveil(cachefile, "rwc") == -1) err(1, "unveil: %s", cachefile); 1760 if (pledge("stdio rpath wpath cpath fattr", NULL) == -1) err(1, "pledge"); 1761 } else { 1762 if (pledge("stdio rpath wpath cpath", NULL) == -1) err(1, "pledge"); 1763 } 1764 */ 1765 #endif 1766 1767 /* open the git repo */ 1768 int result = git_repository_open_ext( 1769 &repo, repo_dir, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL 1770 ); 1771 if (result < 0) { 1772 fprintf(stderr, "fatal: cannot open repository '%s'" "\n", dir); 1773 return 1; 1774 } 1775 1776 /* find HEAD */ 1777 const git_oid *head = NULL; 1778 git_object *obj = NULL; 1779 if (!git_revparse_single(&obj, repo, "HEAD")) 1780 head = git_object_id(obj); 1781 git_object_free(obj); 1782 1783 /* set global vars... */ 1784 // TODO repo = open_repo(); 1785 find_repo_name(); 1786 find_description(); 1787 find_clone_url(); 1788 find_license(); 1789 find_readme(); 1790 find_submodules(); 1791 if (opt.index) find_owner(); 1792 1793 if (opt.index) { 1794 put_index_log(index_fp, repo, description, repo_name, readme); 1795 } 1796 1797 if (opt.pages) { 1798 write_log_page(head); 1799 walk_git_tree(head); 1800 write_refs_page(); 1801 write_feed(); 1802 write_releases_feed(); 1803 } 1804 1805 /* clean up */ 1806 // TODO close_repo(); 1807 git_repository_free(repo); 1808 1809 return 0; 1810 } 1811 1812 void 1813 begin_index() 1814 { 1815 get_index_page_path( 1816 index_fname, 1817 sizeof(index_fname), 1818 opt.out_dir, 1819 INDEX_DEST 1820 ); 1821 index_fp = fopen_w(index_fname); 1822 put_index_header(index_fp); 1823 } 1824 1825 void 1826 end_index() 1827 { 1828 put_index_footer(index_fp); 1829 check_file_error(index_fp, index_fname, 'w'); 1830 fclose(index_fp); 1831 } 1832 1833 void 1834 init_lib_git() 1835 { 1836 int i; 1837 1838 /* do not search outside the git repository: 1839 GIT_CONFIG_LEVEL_APP is the highest level currently */ 1840 git_libgit2_init(); 1841 for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++) 1842 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, ""); 1843 1844 /* do not require the git repository to be owned by the current user */ 1845 git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0); 1846 } 1847 1848 int 1849 main(int argc, const char *argv[]) 1850 { 1851 int i, result; 1852 1853 if (parse_opts(&opt, argc, argv) == OPT_FAIL) { 1854 PRINT_USAGE(argv[0]); 1855 return 1; 1856 } 1857 1858 if (!opt.pages && !opt.index) { 1859 printf("Nothing to do, use -i and/or -p." "\n\n"); 1860 PRINT_USAGE(argv[0]); 1861 return 1; 1862 } 1863 1864 init_lib_git(); 1865 1866 if (opt.index) begin_index(); 1867 for (i = 0; i < opt.repo_count; i++) { 1868 result = write_repo_pages(opt.repo_dirs[i]); 1869 if(result > 0) return 1; 1870 } 1871 if (opt.index) end_index(); 1872 1873 git_libgit2_shutdown(); 1874 1875 return 0; 1876 }