gout

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

commit.c (9731B)


      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   const char* email = gitcommit_author_email(git_commit);
    113   if (is_safe_mailto(email)) {
    114     fprintf(out, " &lt;<a href=\"mailto:");
    115     print_xml_encoded(out, email);
    116     fprintf(out, "\">");
    117     print_xml_encoded(out, email);
    118     fprintf(out, "</a>&gt;\n");
    119   } else {
    120     fprintf(out, " &lt;");
    121     print_xml_encoded(out, email);
    122     fprintf(out, "&gt;\n");
    123   }
    124   fprintf(out, "<b>Date:</b>   ");
    125   print_time(out, gitcommit_author_time(git_commit),
    126              gitcommit_author_timezone_offset(git_commit));
    127   fprintf(out, "\n");
    128 
    129   const char* message = gitcommit_message(git_commit);
    130   if (message) {
    131     fprintf(out, "\n");
    132     print_xml_encoded(out, message);
    133     fprintf(out, "\n");
    134   }
    135 }
    136 
    137 void html_commit_write_diffstat(HtmlCommit* commit,
    138                                 const GitCommit* git_commit) {
    139   fprintf(commit->out, "<b>Diffstat:</b>\n<table>");
    140   size_t delta_count = gitcommit_delta_count(git_commit);
    141   for (size_t i = 0; i < delta_count; i++) {
    142     html_commit_write_diffstat_row(commit, i, gitcommit_delta(git_commit, i));
    143   }
    144   fprintf(commit->out, "</table></pre>");
    145 }
    146 
    147 void html_commit_write_diffstat_row(HtmlCommit* commit,
    148                                     size_t row,
    149                                     const GitDelta* delta) {
    150   static const size_t kGraphWidth = 78;
    151   FILE* out = commit->out;
    152 
    153   char status = gitdelta_status(delta);
    154   if (status == ' ') {
    155     fprintf(out, "<tr><td>");
    156   } else {
    157     fprintf(out, "<tr><td class=\"%c\">%c", status, status);
    158   }
    159   fprintf(out, "</td>");
    160   fprintf(out, "<td>");
    161   fprintf(out, "<a href=\"#h%zu\">", row);
    162   const char* old_file_path = gitdelta_old_file_path(delta);
    163   const char* new_file_path = gitdelta_new_file_path(delta);
    164   print_xml_encoded(out, old_file_path);
    165   if (strcmp(old_file_path, new_file_path) != 0) {
    166     fprintf(out, " -&gt; ");
    167     print_xml_encoded(out, new_file_path);
    168   }
    169   fprintf(out, "</a></td>");
    170 
    171   size_t changed = gitdelta_lines_added(delta) + gitdelta_lines_deleted(delta);
    172   fprintf(out, "<td> | </td>");
    173   fprintf(out, "<td class=\"num\">%zu</td>", changed);
    174   char* added_graph = gitdelta_added_graph(delta, kGraphWidth);
    175   fprintf(out, "<td><span class=\"i\">%s</span>", added_graph);
    176   free(added_graph);
    177   char* deleted_graph = gitdelta_deleted_graph(delta, kGraphWidth);
    178   fprintf(out, "<span class=\"d\">%s</span>", deleted_graph);
    179   free(deleted_graph);
    180   fprintf(out, "</td></tr>\n");
    181 }
    182 
    183 void html_commit_write_diff_content(HtmlCommit* commit,
    184                                     const GitCommit* git_commit) {
    185   FILE* out = commit->out;
    186   size_t filecount = gitcommit_filecount(git_commit);
    187   size_t addcount = gitcommit_addcount(git_commit);
    188   size_t delcount = gitcommit_delcount(git_commit);
    189   fprintf(out, "<pre>");
    190   fprintf(out, "%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
    191           filecount, filecount == 1 ? "" : "s",  //
    192           addcount, addcount == 1 ? "" : "s",    //
    193           delcount, delcount == 1 ? "" : "s");
    194   fprintf(out, "<hr/>");
    195 
    196   size_t delta_count = gitcommit_delta_count(git_commit);
    197   for (size_t i = 0; i < delta_count; i++) {
    198     html_commit_write_diff_delta(commit, i, gitcommit_delta(git_commit, i));
    199   }
    200   fprintf(out, "</pre>\n");
    201 }
    202 
    203 void html_commit_write_diff_delta(HtmlCommit* commit,
    204                                   size_t file_num,
    205                                   const GitDelta* delta) {
    206   const char* old_file_path = gitdelta_old_file_path(delta);
    207   const char* new_file_path = gitdelta_new_file_path(delta);
    208   fprintf(commit->out, "<b>diff --git ");
    209   fprintf(commit->out, "a/<a id=\"h%zu\" href=\"../file/", file_num);
    210   print_percent_encoded(commit->out, old_file_path);
    211   fprintf(commit->out, ".html\">");
    212   print_xml_encoded(commit->out, old_file_path);
    213   fprintf(commit->out, "</a> b/<a href=\"../file/");
    214   print_percent_encoded(commit->out, new_file_path);
    215   fprintf(commit->out, ".html\">");
    216   print_xml_encoded(commit->out, new_file_path);
    217   fprintf(commit->out, "</a></b>\n");
    218 
    219   if (gitdelta_is_binary(delta)) {
    220     fprintf(commit->out, "Binary files differ.\n");
    221   } else {
    222     size_t hunk_count = gitdelta_hunk_count(delta);
    223     for (size_t i = 0; i < hunk_count; i++) {
    224       html_commit_write_diff_hunk(commit, file_num, gitdelta_hunk(delta, i));
    225     }
    226   }
    227 }
    228 
    229 void html_commit_write_diff_hunk(HtmlCommit* commit,
    230                                  size_t file_num,
    231                                  const GitHunk* hunk) {
    232   FILE* out = commit->out;
    233 
    234   // Output header. e.g. @@ -0,0 +1,3 @@
    235   char hdr_id[32];
    236   size_t hunkid = githunk_id(hunk);
    237   if (snprintf(hdr_id, sizeof(hdr_id), "h%zu-%zu", file_num, hunkid) < 0) {
    238     err(1, "snprintf");
    239   }
    240   fprintf(out, "<a href=\"#%s\" id=\"%s\" class=\"h\">", hdr_id, hdr_id);
    241   const char* header = githunk_header(hunk);
    242   print_xml_encoded(out, header);
    243   fprintf(out, "</a>");
    244 
    245   // Iterate over lines in hunk.
    246   size_t line_count = githunk_line_count(hunk);
    247   for (size_t i = 0; i < line_count; i++) {
    248     const GitHunkLine* line = githunk_line(hunk, i);
    249     size_t hunklineid = githunkline_id(line);
    250     char line_id[64];
    251     if (snprintf(line_id, sizeof(line_id), "%s-%zu", hdr_id, hunklineid) < 0) {
    252       err(1, "snprintf");
    253     }
    254     const char* content = githunkline_content(line);
    255     size_t content_len = githunkline_content_len(line);
    256     int old_lineno = githunkline_old_lineno(line);
    257     int new_lineno = githunkline_new_lineno(line);
    258     if (old_lineno == -1) {
    259       // Added line. Prefix with +.
    260       fprintf(out, "<a href=\"#%s\" id=\"%s\" class=\"i\">+", line_id, line_id);
    261       print_xml_encoded_len(out, content, content_len, false);
    262       fprintf(out, "\n</a>");
    263     } else if (new_lineno == -1) {
    264       // Removed line. Prefix with -.
    265       fprintf(out, "<a href=\"#%s\" id=\"%s\" class=\"d\">-", line_id, line_id);
    266       print_xml_encoded_len(out, content, content_len, false);
    267       fprintf(out, "\n</a>");
    268     } else {
    269       // Unchanged line. Prefix with ' '. No link.
    270       fprintf(out, " ");
    271       print_xml_encoded_len(out, content, content_len, false);
    272       fprintf(out, "\n");
    273     }
    274   }
    275 }