gout

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

commit.c (10236B)


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