gitout

A static git page generator
git clone https://git.bracken.jp/gitout.git
Log | Files | Refs | Submodules | README | LICENSE

commit.c (9585B)


      1 #include "writer/html/commit.h"
      2 
      3 #include <err.h>
      4 #include <limits.h>
      5 #include <stdbool.h>
      6 #include <stdint.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 #include <string.h>
     10 
     11 #include "format.h"
     12 #include "git/delta.h"
     13 #include "utils.h"
     14 #include "writer/html/page.h"
     15 
     16 struct HtmlCommit {
     17   FILE* out;
     18   HtmlPage* page;
     19 };
     20 
     21 static void html_commit_write_summary(HtmlCommit* commit,
     22                                       const GitCommit* git_commit);
     23 static void html_commit_write_diffstat(HtmlCommit* commit,
     24                                        const GitCommit* git_commit);
     25 static void html_commit_write_diffstat_row(HtmlCommit* commit,
     26                                            size_t row,
     27                                            const GitDelta* delta);
     28 static void html_commit_write_diff_content(HtmlCommit* commit,
     29                                            const GitCommit* git_commit);
     30 static void html_commit_write_diff_delta(HtmlCommit* commit,
     31                                          size_t file_num,
     32                                          const GitDelta* delta);
     33 static void html_commit_write_diff_hunk(HtmlCommit* commit,
     34                                         size_t file_num,
     35                                         const GitHunk* hunk);
     36 
     37 HtmlCommit* html_commit_create(const GitRepo* repo,
     38                                const char* oid,
     39                                const char* title) {
     40   HtmlCommit* commit = ecalloc(1, sizeof(HtmlCommit));
     41   char filename[PATH_MAX];
     42   if (snprintf(filename, sizeof(filename), "%s.html", oid) < 0) {
     43     err(1, "snprintf");
     44   }
     45   char path[PATH_MAX];
     46   path_concat(path, sizeof(path), "commit", filename);
     47   commit->out = efopen(path, "w");
     48   commit->page = html_page_create(commit->out, repo, title, "../");
     49   return commit;
     50 }
     51 
     52 void html_commit_free(HtmlCommit* commit) {
     53   if (!commit) {
     54     return;
     55   }
     56   fclose(commit->out);
     57   commit->out = NULL;
     58   html_page_free(commit->page);
     59   commit->page = NULL;
     60   free(commit);
     61 }
     62 
     63 void html_commit_begin(HtmlCommit* commit) {
     64   html_page_begin(commit->page);
     65 }
     66 
     67 void html_commit_add_commit(HtmlCommit* commit, const GitCommit* git_commit) {
     68   static const uint64_t kDiffMaxFiles = 1000;
     69   static const uint64_t kDiffMaxDeltas = 1000;
     70   static const uint64_t kDiffMaxDeltaLines = 100000;
     71   FILE* out = commit->out;
     72 
     73   html_commit_write_summary(commit, git_commit);
     74 
     75   size_t deltas_len = gitcommit_delta_count(git_commit);
     76   if (deltas_len == 0) {
     77     return;
     78   }
     79   size_t addcount = gitcommit_addcount(git_commit);
     80   size_t delcount = gitcommit_delcount(git_commit);
     81   size_t filecount = gitcommit_filecount(git_commit);
     82   if (filecount > kDiffMaxFiles || deltas_len > kDiffMaxDeltas ||
     83       addcount > kDiffMaxDeltaLines || delcount > kDiffMaxDeltaLines) {
     84     fprintf(out, "<pre>Diff is too large, output suppressed.</pre>\n");
     85     return;
     86   }
     87 
     88   html_commit_write_diffstat(commit, git_commit);
     89   html_commit_write_diff_content(commit, git_commit);
     90 }
     91 
     92 void html_commit_end(HtmlCommit* commit) {
     93   html_page_end(commit->page);
     94 }
     95 
     96 void html_commit_write_summary(HtmlCommit* commit,
     97                                const GitCommit* git_commit) {
     98   FILE* out = commit->out;
     99   const char* oid = gitcommit_oid(git_commit);
    100   fprintf(out, "<pre><b>commit</b> ");
    101   fprintf(out, "<a href=\"../commit/%s.html\">%s</a>\n", oid, oid);
    102 
    103   const char* parentoid = gitcommit_parentoid(git_commit);
    104   if (parentoid[0] != '\0') {
    105     fprintf(out, "<b>parent</b> ");
    106     fprintf(out, "<a href=\"../commit/%s.html\">%s</a>\n", parentoid,
    107             parentoid);
    108   }
    109 
    110   fprintf(out, "<b>Author:</b> ");
    111   print_xml_encoded(out, gitcommit_author_name(git_commit));
    112   fprintf(out, " &lt;<a href=\"mailto:");
    113   print_xml_encoded(out, gitcommit_author_email(git_commit));
    114   fprintf(out, "\">");
    115   print_xml_encoded(out, gitcommit_author_email(git_commit));
    116   fprintf(out, "</a>&gt;\n");
    117   fprintf(out, "<b>Date:</b>   ");
    118   print_time(out, gitcommit_author_time(git_commit),
    119              gitcommit_author_timezone_offset(git_commit));
    120   fprintf(out, "\n");
    121 
    122   const char* message = gitcommit_message(git_commit);
    123   if (message) {
    124     fprintf(out, "\n");
    125     print_xml_encoded(out, message);
    126     fprintf(out, "\n");
    127   }
    128 }
    129 
    130 void html_commit_write_diffstat(HtmlCommit* commit,
    131                                 const GitCommit* git_commit) {
    132   fprintf(commit->out, "<b>Diffstat:</b>\n<table>");
    133   size_t delta_count = gitcommit_delta_count(git_commit);
    134   for (size_t i = 0; i < delta_count; i++) {
    135     html_commit_write_diffstat_row(commit, i, gitcommit_delta(git_commit, i));
    136   }
    137   fprintf(commit->out, "</table></pre>");
    138 }
    139 
    140 void html_commit_write_diffstat_row(HtmlCommit* commit,
    141                                     size_t row,
    142                                     const GitDelta* delta) {
    143   static const size_t kGraphWidth = 78;
    144   FILE* out = commit->out;
    145 
    146   char status = gitdelta_status(delta);
    147   if (status == ' ') {
    148     fprintf(out, "<tr><td>");
    149   } else {
    150     fprintf(out, "<tr><td class=\"%c\">%c", status, status);
    151   }
    152   fprintf(out, "</td>");
    153   fprintf(out, "<td>");
    154   fprintf(out, "<a href=\"#h%zu\">", row);
    155   const char* old_file_path = gitdelta_old_file_path(delta);
    156   const char* new_file_path = gitdelta_new_file_path(delta);
    157   print_xml_encoded(out, old_file_path);
    158   if (strcmp(old_file_path, new_file_path) != 0) {
    159     fprintf(out, " -&gt; ");
    160     print_xml_encoded(out, new_file_path);
    161   }
    162   fprintf(out, "</a></td>");
    163 
    164   size_t changed = gitdelta_lines_added(delta) + gitdelta_lines_deleted(delta);
    165   fprintf(out, "<td> | </td>");
    166   fprintf(out, "<td class=\"num\">%zu</td>", changed);
    167   char* added_graph = gitdelta_added_graph(delta, kGraphWidth);
    168   fprintf(out, "<td><span class=\"i\">%s</span>", added_graph);
    169   free(added_graph);
    170   char* deleted_graph = gitdelta_deleted_graph(delta, kGraphWidth);
    171   fprintf(out, "<span class=\"d\">%s</span>", deleted_graph);
    172   free(deleted_graph);
    173   fprintf(out, "</td></tr>\n");
    174 }
    175 
    176 void html_commit_write_diff_content(HtmlCommit* commit,
    177                                     const GitCommit* git_commit) {
    178   FILE* out = commit->out;
    179   size_t filecount = gitcommit_filecount(git_commit);
    180   size_t addcount = gitcommit_addcount(git_commit);
    181   size_t delcount = gitcommit_delcount(git_commit);
    182   fprintf(out, "<pre>");
    183   fprintf(out, "%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
    184           filecount, filecount == 1 ? "" : "s",  //
    185           addcount, addcount == 1 ? "" : "s",    //
    186           delcount, delcount == 1 ? "" : "s");
    187   fprintf(out, "<hr/>");
    188 
    189   size_t delta_count = gitcommit_delta_count(git_commit);
    190   for (size_t i = 0; i < delta_count; i++) {
    191     html_commit_write_diff_delta(commit, i, gitcommit_delta(git_commit, i));
    192   }
    193   fprintf(out, "</pre>\n");
    194 }
    195 
    196 void html_commit_write_diff_delta(HtmlCommit* commit,
    197                                   size_t file_num,
    198                                   const GitDelta* delta) {
    199   const char* old_file_path = gitdelta_old_file_path(delta);
    200   const char* new_file_path = gitdelta_new_file_path(delta);
    201   fprintf(commit->out, "<b>diff --git ");
    202   fprintf(commit->out, "a/<a id=\"h%zu\" href=\"../file/", file_num);
    203   print_percent_encoded(commit->out, old_file_path);
    204   fprintf(commit->out, ".html\">");
    205   print_xml_encoded(commit->out, old_file_path);
    206   fprintf(commit->out, "</a> b/<a href=\"../file/");
    207   print_percent_encoded(commit->out, new_file_path);
    208   fprintf(commit->out, ".html\">");
    209   print_xml_encoded(commit->out, new_file_path);
    210   fprintf(commit->out, "</a></b>\n");
    211 
    212   if (gitdelta_is_binary(delta)) {
    213     fprintf(commit->out, "Binary files differ.\n");
    214   } else {
    215     size_t hunk_count = gitdelta_hunk_count(delta);
    216     for (size_t i = 0; i < hunk_count; i++) {
    217       html_commit_write_diff_hunk(commit, file_num, gitdelta_hunk(delta, i));
    218     }
    219   }
    220 }
    221 
    222 void html_commit_write_diff_hunk(HtmlCommit* commit,
    223                                  size_t file_num,
    224                                  const GitHunk* hunk) {
    225   FILE* out = commit->out;
    226 
    227   // Output header. e.g. @@ -0,0 +1,3 @@
    228   char hdr_id[32];
    229   size_t hunkid = githunk_id(hunk);
    230   if (snprintf(hdr_id, sizeof(hdr_id), "h%zu-%zu", file_num, hunkid) < 0) {
    231     err(1, "snprintf");
    232   }
    233   fprintf(out, "<a href=\"#%s\" id=\"%s\" class=\"h\">", hdr_id, hdr_id);
    234   const char* header = githunk_header(hunk);
    235   print_xml_encoded(out, header);
    236   fprintf(out, "</a>");
    237 
    238   // Iterate over lines in hunk.
    239   size_t line_count = githunk_line_count(hunk);
    240   for (size_t i = 0; i < line_count; i++) {
    241     const GitHunkLine* line = githunk_line(hunk, i);
    242     size_t hunklineid = githunkline_id(line);
    243     char line_id[64];
    244     if (snprintf(line_id, sizeof(line_id), "%s-%zu", hdr_id, hunklineid) < 0) {
    245       err(1, "snprintf");
    246     }
    247     const char* content = githunkline_content(line);
    248     size_t content_len = githunkline_content_len(line);
    249     int old_lineno = githunkline_old_lineno(line);
    250     int new_lineno = githunkline_new_lineno(line);
    251     if (old_lineno == -1) {
    252       // Added line. Prefix with +.
    253       fprintf(out, "<a href=\"#%s\" id=\"%s\" class=\"i\">+", line_id, line_id);
    254       print_xml_encoded_len(out, content, content_len, false);
    255       fprintf(out, "\n</a>");
    256     } else if (new_lineno == -1) {
    257       // Removed line. Prefix with -.
    258       fprintf(out, "<a href=\"#%s\" id=\"%s\" class=\"d\">-", line_id, line_id);
    259       print_xml_encoded_len(out, content, content_len, false);
    260       fprintf(out, "\n</a>");
    261     } else {
    262       // Unchanged line. Prefix with ' '. No link.
    263       fprintf(out, " ");
    264       print_xml_encoded_len(out, content, content_len, false);
    265       fprintf(out, "\n");
    266     }
    267   }
    268 }