gout

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

commit.c (10170B)


      1 #include "writer/html/commit.h"
      2 
      3 #include <assert.h>
      4 #include <err.h>
      5 #include <limits.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   const FileSystem* fs;
     19   HtmlPage* page;
     20   DiffLimits limits;
     21 };
     22 
     23 static void html_commit_write_summary(HtmlCommit* commit,
     24                                       const GitCommit* git_commit);
     25 static void html_commit_write_diffstat(HtmlCommit* commit,
     26                                        const GitCommit* git_commit);
     27 static void html_commit_write_diffstat_row(HtmlCommit* commit,
     28                                            size_t row,
     29                                            const GitDelta* delta);
     30 static void html_commit_write_diff_content(HtmlCommit* commit,
     31                                            const GitCommit* git_commit);
     32 static void html_commit_write_diff_delta(HtmlCommit* commit,
     33                                          size_t file_num,
     34                                          const GitDelta* delta);
     35 static void html_commit_write_diff_hunk(HtmlCommit* commit,
     36                                         size_t file_num,
     37                                         const GitHunk* hunk);
     38 
     39 HtmlCommit* html_commit_create(const GitRepo* repo,
     40                                const FileSystem* fs,
     41                                const char* oid,
     42                                const char* title) {
     43   assert(repo != NULL);
     44   assert(fs != NULL);
     45   assert(oid != NULL);
     46   HtmlCommit* commit = ecalloc(1, sizeof(HtmlCommit));
     47   commit->fs = fs;
     48   char filename[PATH_MAX];
     49   int r = snprintf(filename, sizeof(filename), "%s.html", oid);
     50   if (r < 0 || (size_t)r >= sizeof(filename)) {
     51     errx(1, "snprintf: filename truncated or error");
     52   }
     53   char* path = path_concat("commit", filename);
     54   commit->out = fs->fopen(path, "w");
     55   if (!commit->out) {
     56     err(1, "fopen: %s", path);
     57   }
     58   free(path);
     59   commit->page = html_page_create(commit->out, repo, fs, title, "../");
     60   commit->limits.max_files = 1000;
     61   commit->limits.max_deltas = 1000;
     62   commit->limits.max_delta_lines = 100000;
     63   return commit;
     64 }
     65 
     66 void html_commit_free(HtmlCommit* commit) {
     67   if (!commit) {
     68     return;
     69   }
     70   commit->fs->fclose(commit->out);
     71   html_page_free(commit->page);
     72   free(commit);
     73 }
     74 
     75 void html_commit_begin(HtmlCommit* commit) {
     76   assert(commit != NULL);
     77   html_page_begin(commit->page);
     78 }
     79 
     80 void html_commit_add_commit(HtmlCommit* commit, const GitCommit* git_commit) {
     81   assert(commit != NULL);
     82   assert(git_commit != NULL);
     83   FILE* out = commit->out;
     84 
     85   html_commit_write_summary(commit, git_commit);
     86 
     87   size_t deltas_len = git_commit->deltas_len;
     88   if (deltas_len == 0) {
     89     return;
     90   }
     91   size_t addcount = git_commit->addcount;
     92   size_t delcount = git_commit->delcount;
     93   size_t filecount = git_commit->filecount;
     94   if (filecount > commit->limits.max_files ||
     95       deltas_len > commit->limits.max_deltas ||
     96       addcount > commit->limits.max_delta_lines ||
     97       delcount > commit->limits.max_delta_lines) {
     98     fprintf(out, "<pre>Diff is too large, output suppressed.</pre>\n");
     99     return;
    100   }
    101 
    102   html_commit_write_diffstat(commit, git_commit);
    103   html_commit_write_diff_content(commit, git_commit);
    104 }
    105 
    106 void html_commit_set_diff_limits(HtmlCommit* commit, const DiffLimits* limits) {
    107   assert(commit != NULL);
    108   assert(limits != NULL);
    109   commit->limits = *limits;
    110 }
    111 
    112 void html_commit_end(HtmlCommit* commit) {
    113   assert(commit != NULL);
    114   html_page_end(commit->page);
    115 }
    116 
    117 static void html_commit_write_summary(HtmlCommit* commit,
    118                                       const GitCommit* git_commit) {
    119   FILE* out = commit->out;
    120   const char* oid = git_commit->oid;
    121   fprintf(out, "<pre><b>commit</b> ");
    122   fprintf(out, "<a href=\"../commit/%s.html\">%s</a>\n", oid, oid);
    123 
    124   const char* parentoid = git_commit->parentoid;
    125   if (parentoid && parentoid[0] != '\0') {
    126     fprintf(out, "<b>parent</b> ");
    127     fprintf(out, "<a href=\"../commit/%s.html\">%s</a>\n", parentoid,
    128             parentoid);
    129   }
    130 
    131   fprintf(out, "<b>Author:</b> ");
    132   print_xml_encoded(out, git_commit->author_name);
    133   const char* email = git_commit->author_email;
    134   if (is_safe_mailto(email)) {
    135     fprintf(out, " &lt;<a href=\"mailto:");
    136     print_xml_encoded(out, email);
    137     fprintf(out, "\">");
    138     print_xml_encoded(out, email);
    139     fprintf(out, "</a>&gt;\n");
    140   } else {
    141     fprintf(out, " &lt;");
    142     print_xml_encoded(out, email);
    143     fprintf(out, "&gt;\n");
    144   }
    145   fprintf(out, "<b>Date:</b>   ");
    146   print_time(out, git_commit->author_time, git_commit->author_timezone_offset);
    147   fprintf(out, "\n");
    148 
    149   const char* message = git_commit->message;
    150   if (message) {
    151     fprintf(out, "\n");
    152     print_xml_encoded(out, message);
    153     fprintf(out, "\n");
    154   }
    155 }
    156 
    157 static void html_commit_write_diffstat(HtmlCommit* commit,
    158                                        const GitCommit* git_commit) {
    159   fprintf(commit->out, "<b>Diffstat:</b>\n<table>");
    160   size_t delta_count = git_commit->deltas_len;
    161   for (size_t i = 0; i < delta_count; i++) {
    162     html_commit_write_diffstat_row(commit, i, git_commit->deltas[i]);
    163   }
    164   fprintf(commit->out, "</table></pre>");
    165 }
    166 
    167 static void html_commit_write_diffstat_row(HtmlCommit* commit,
    168                                            size_t row,
    169                                            const GitDelta* delta) {
    170   static const size_t kGraphWidth = 78;
    171   FILE* out = commit->out;
    172 
    173   char status = delta->status;
    174   if (status == ' ') {
    175     fprintf(out, "<tr><td>");
    176   } else {
    177     fprintf(out, "<tr><td class=\"%c\">%c", status, status);
    178   }
    179   fprintf(out, "</td>");
    180   fprintf(out, "<td>");
    181   fprintf(out, "<a href=\"#h%zu\">", row);
    182   const char* old_file_path = delta->old_file_path;
    183   const char* new_file_path = delta->new_file_path;
    184   print_xml_encoded(out, old_file_path);
    185   if (strcmp(old_file_path, new_file_path) != 0) {
    186     fprintf(out, " -&gt; ");
    187     print_xml_encoded(out, new_file_path);
    188   }
    189   fprintf(out, "</a></td>");
    190 
    191   size_t changed = delta->addcount + delta->delcount;
    192   fprintf(out, "<td> | </td>");
    193   fprintf(out, "<td class=\"num\">%zu</td>", changed);
    194   char* added_graph = gitdelta_added_graph(delta, kGraphWidth);
    195   fprintf(out, "<td><span class=\"i\">%s</span>", added_graph);
    196   free(added_graph);
    197   char* deleted_graph = gitdelta_deleted_graph(delta, kGraphWidth);
    198   fprintf(out, "<span class=\"d\">%s</span>", deleted_graph);
    199   free(deleted_graph);
    200   fprintf(out, "</td></tr>\n");
    201 }
    202 
    203 static void html_commit_write_diff_content(HtmlCommit* commit,
    204                                            const GitCommit* git_commit) {
    205   FILE* out = commit->out;
    206   size_t filecount = git_commit->filecount;
    207   size_t addcount = git_commit->addcount;
    208   size_t delcount = git_commit->delcount;
    209   fprintf(out, "<pre>");
    210   fprintf(out, "%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
    211           filecount, filecount == 1 ? "" : "s",  //
    212           addcount, addcount == 1 ? "" : "s",    //
    213           delcount, delcount == 1 ? "" : "s");
    214   fprintf(out, "<hr/>");
    215 
    216   size_t delta_count = git_commit->deltas_len;
    217   for (size_t i = 0; i < delta_count; i++) {
    218     html_commit_write_diff_delta(commit, i, git_commit->deltas[i]);
    219   }
    220   fprintf(out, "</pre>\n");
    221 }
    222 
    223 static void html_commit_write_diff_delta(HtmlCommit* commit,
    224                                          size_t file_num,
    225                                          const GitDelta* delta) {
    226   const char* old_file_path = delta->old_file_path;
    227   const char* new_file_path = delta->new_file_path;
    228   fprintf(commit->out, "<b>diff --git ");
    229   fprintf(commit->out, "a/<a id=\"h%zu\" href=\"../file/", file_num);
    230   print_percent_encoded(commit->out, old_file_path);
    231   fprintf(commit->out, ".html\">");
    232   print_xml_encoded(commit->out, old_file_path);
    233   fprintf(commit->out, "</a> b/<a href=\"../file/");
    234   print_percent_encoded(commit->out, new_file_path);
    235   fprintf(commit->out, ".html\">");
    236   print_xml_encoded(commit->out, new_file_path);
    237   fprintf(commit->out, "</a></b>\n");
    238 
    239   if (delta->is_binary) {
    240     fprintf(commit->out, "Binary files differ.\n");
    241   } else {
    242     size_t hunk_count = delta->hunks_len;
    243     for (size_t i = 0; i < hunk_count; i++) {
    244       html_commit_write_diff_hunk(commit, file_num, delta->hunks[i]);
    245     }
    246   }
    247 }
    248 
    249 static void html_commit_write_diff_hunk(HtmlCommit* commit,
    250                                         size_t file_num,
    251                                         const GitHunk* hunk) {
    252   FILE* out = commit->out;
    253 
    254   // Output header. e.g. @@ -0,0 +1,3 @@
    255   char hdr_id[32];
    256   size_t hunkid = hunk->id;
    257   int r = snprintf(hdr_id, sizeof(hdr_id), "h%zu-%zu", file_num, hunkid);
    258   if (r < 0 || (size_t)r >= sizeof(hdr_id)) {
    259     errx(1, "snprintf: hunk ID truncated");
    260   }
    261   fprintf(out, "<a href=\"#%s\" id=\"%s\" class=\"h\">", hdr_id, hdr_id);
    262   const char* header = hunk->header;
    263   print_xml_encoded(out, header);
    264   fprintf(out, "</a>");
    265 
    266   // Iterate over lines in hunk.
    267   size_t line_count = hunk->lines_len;
    268   for (size_t i = 0; i < line_count; i++) {
    269     const GitHunkLine* line = hunk->lines[i];
    270     size_t hunklineid = line->id;
    271     char line_id[64];
    272     int r = snprintf(line_id, sizeof(line_id), "%s-%zu", hdr_id, hunklineid);
    273     if (r < 0 || (size_t)r >= sizeof(line_id)) {
    274       errx(1, "snprintf: line ID truncated");
    275     }
    276     const char* content = line->content;
    277     size_t content_len = line->content_len;
    278     int old_lineno = line->old_lineno;
    279     int new_lineno = line->new_lineno;
    280     if (old_lineno == -1) {
    281       // Added line. Prefix with +.
    282       fprintf(out, "<a href=\"#%s\" id=\"%s\" class=\"i\">+", line_id, line_id);
    283       print_xml_encoded_len(out, content, content_len, false);
    284       fprintf(out, "\n</a>");
    285     } else if (new_lineno == -1) {
    286       // Removed line. Prefix with -.
    287       fprintf(out, "<a href=\"#%s\" id=\"%s\" class=\"d\">-", line_id, line_id);
    288       print_xml_encoded_len(out, content, content_len, false);
    289       fprintf(out, "\n</a>");
    290     } else {
    291       // Unchanged line. Prefix with ' '. No link.
    292       fprintf(out, " ");
    293       print_xml_encoded_len(out, content, content_len, false);
    294       fprintf(out, "\n");
    295     }
    296   }
    297 }