gout

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

commit.c (8701B)


      1 #include "writer/gopher/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/gopher/page.h"
     15 
     16 struct GopherCommit {
     17   FILE* out;
     18   const FileSystem* fs;
     19   GopherPage* page;
     20   DiffLimits limits;
     21 };
     22 
     23 static void gopher_commit_write_summary(GopherCommit* commit,
     24                                         const GitCommit* git_commit);
     25 static void gopher_commit_write_diffstat(GopherCommit* commit,
     26                                          const GitCommit* git_commit);
     27 static void gopher_commit_write_diffstat_row(GopherCommit* commit,
     28                                              const GitDelta* delta);
     29 static void gopher_commit_write_diff_content(GopherCommit* commit,
     30                                              const GitCommit* git_commit);
     31 static void gopher_commit_write_diff_delta(GopherCommit* commit,
     32                                            const GitDelta* delta);
     33 static void gopher_commit_write_diff_hunk(GopherCommit* commit,
     34                                           const GitHunk* hunk);
     35 
     36 GopherCommit* gopher_commit_create(const GitRepo* repo,
     37                                    const FileSystem* fs,
     38                                    const char* oid,
     39                                    const char* title) {
     40   assert(repo != NULL);
     41   assert(fs != NULL);
     42   assert(oid != NULL);
     43   GopherCommit* commit = ecalloc(1, sizeof(GopherCommit));
     44   commit->fs = fs;
     45   char filename[PATH_MAX];
     46   int r = snprintf(filename, sizeof(filename), "%s.gph", oid);
     47   if (r < 0 || (size_t)r >= sizeof(filename)) {
     48     errx(1, "snprintf: filename truncated or error");
     49   }
     50   char* path = path_concat("commit", filename);
     51   commit->out = fs->fopen(path, "w");
     52   if (!commit->out) {
     53     err(1, "fopen: %s", path);
     54   }
     55   free(path);
     56   commit->page = gopher_page_create(commit->out, repo, fs, title, "../");
     57   commit->limits.max_files = 1000;
     58   commit->limits.max_deltas = 1000;
     59   commit->limits.max_delta_lines = 100000;
     60   return commit;
     61 }
     62 
     63 void gopher_commit_free(GopherCommit* commit) {
     64   if (!commit) {
     65     return;
     66   }
     67   commit->fs->fclose(commit->out);
     68   gopher_page_free(commit->page);
     69   free(commit);
     70 }
     71 
     72 void gopher_commit_begin(GopherCommit* commit) {
     73   assert(commit != NULL);
     74   gopher_page_begin(commit->page);
     75 }
     76 
     77 void gopher_commit_add_commit(GopherCommit* commit,
     78                               const GitCommit* git_commit) {
     79   assert(commit != NULL);
     80   assert(git_commit != NULL);
     81   FILE* out = commit->out;
     82 
     83   gopher_commit_write_summary(commit, git_commit);
     84 
     85   size_t deltas_len = git_commit->deltas_len;
     86   if (deltas_len == 0) {
     87     return;
     88   }
     89   size_t addcount = git_commit->addcount;
     90   size_t delcount = git_commit->delcount;
     91   size_t filecount = git_commit->filecount;
     92   if (filecount > commit->limits.max_files ||
     93       deltas_len > commit->limits.max_deltas ||
     94       addcount > commit->limits.max_delta_lines ||
     95       delcount > commit->limits.max_delta_lines) {
     96     fprintf(out, "Diff is too large, output suppressed.\n");
     97     return;
     98   }
     99 
    100   gopher_commit_write_diffstat(commit, git_commit);
    101   gopher_commit_write_diff_content(commit, git_commit);
    102 }
    103 
    104 void gopher_commit_set_diff_limits(GopherCommit* commit,
    105                                    const DiffLimits* limits) {
    106   assert(commit != NULL);
    107   assert(limits != NULL);
    108   commit->limits = *limits;
    109 }
    110 
    111 void gopher_commit_end(GopherCommit* commit) {
    112   assert(commit != NULL);
    113   gopher_page_end(commit->page);
    114 }
    115 
    116 static void gopher_commit_write_summary(GopherCommit* commit,
    117                                         const GitCommit* git_commit) {
    118   FILE* out = commit->out;
    119   const char* oid = git_commit->oid;
    120   fprintf(out, "[1|commit %s|../commit/%s.gph|server|port]\n", oid, oid);
    121 
    122   const char* parentoid = git_commit->parentoid;
    123   if (parentoid && parentoid[0] != '\0') {
    124     fprintf(out, "[1|parent %s|../commit/%s.gph|server|port]\n", parentoid,
    125             parentoid);
    126   }
    127 
    128   fprintf(out, "[h|Author: ");
    129   print_gopher_link(commit->out, git_commit->author_name);
    130   fprintf(out, " <");
    131   print_gopher_link(commit->out, git_commit->author_email);
    132   fprintf(out, ">|URL:mailto:");
    133   print_gopher_link(commit->out, git_commit->author_email);
    134   fprintf(out, "|server|port]\n");
    135   fprintf(out, "Date:   ");
    136   print_time(out, git_commit->author_time, git_commit->author_timezone_offset);
    137   fprintf(out, "\n");
    138 
    139   const char* message = git_commit->message;
    140   if (message) {
    141     fprintf(out, "\n");
    142     print_gopher_text(out, message, true);
    143     fprintf(out, "\n");
    144   }
    145 }
    146 
    147 static void gopher_commit_write_diffstat(GopherCommit* commit,
    148                                          const GitCommit* git_commit) {
    149   fprintf(commit->out, "Diffstat:\n");
    150   size_t delta_count = git_commit->deltas_len;
    151   for (size_t i = 0; i < delta_count; i++) {
    152     gopher_commit_write_diffstat_row(commit, git_commit->deltas[i]);
    153   }
    154 }
    155 
    156 static void gopher_commit_write_diffstat_row(GopherCommit* commit,
    157                                              const GitDelta* delta) {
    158   static const size_t kGraphWidth = 30;
    159   FILE* out = commit->out;
    160 
    161   fprintf(out, "  %c ", delta->status);
    162   char filename[PATH_MAX];
    163   const char* old_file_path = delta->old_file_path;
    164   const char* new_file_path = delta->new_file_path;
    165   int r;
    166   if (strcmp(old_file_path, new_file_path) == 0) {
    167     r = snprintf(filename, sizeof(filename), "%s", old_file_path);
    168   } else {
    169     r = snprintf(filename, sizeof(filename), "%s -> %s", old_file_path,
    170                  new_file_path);
    171   }
    172   if (r < 0 || (size_t)r >= sizeof(filename)) {
    173     errx(1, "snprintf: filename truncated or error");
    174   }
    175   print_gopher_link_padded(out, filename, 35, ' ');
    176 
    177   size_t changed = delta->addcount + delta->delcount;
    178   fprintf(out, " | ");
    179   fprintf(out, "%7zu ", changed);
    180   char* added_graph = gitdelta_added_graph(delta, kGraphWidth);
    181   fprintf(out, "%s", added_graph);
    182   free(added_graph);
    183   char* deleted_graph = gitdelta_deleted_graph(delta, kGraphWidth);
    184   fprintf(out, "%s", deleted_graph);
    185   free(deleted_graph);
    186   fprintf(out, "\n");
    187 }
    188 
    189 static void gopher_commit_write_diff_content(GopherCommit* commit,
    190                                              const GitCommit* git_commit) {
    191   FILE* out = commit->out;
    192   size_t addcount = git_commit->addcount;
    193   size_t delcount = git_commit->delcount;
    194   size_t filecount = git_commit->filecount;
    195   fprintf(out, "\n%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
    196           filecount, filecount == 1 ? "" : "s",  //
    197           addcount, addcount == 1 ? "" : "s",    //
    198           delcount, delcount == 1 ? "" : "s");
    199   fprintf(commit->out, "---\n");
    200 
    201   size_t delta_count = git_commit->deltas_len;
    202   for (size_t i = 0; i < delta_count; i++) {
    203     gopher_commit_write_diff_delta(commit, git_commit->deltas[i]);
    204   }
    205 }
    206 
    207 static void gopher_commit_write_diff_delta(GopherCommit* commit,
    208                                            const GitDelta* delta) {
    209   FILE* out = commit->out;
    210   fprintf(out, "[1|diff --git a/");
    211   print_gopher_link(out, delta->old_file_path);
    212   fprintf(out, " b/");
    213   print_gopher_link(out, delta->new_file_path);
    214   fprintf(out, "|../file/");
    215   print_gopher_link(out, delta->new_file_path);
    216   fprintf(out, ".gph|server|port]\n");
    217 
    218   if (delta->is_binary) {
    219     fprintf(out, "Binary files differ.\n");
    220   } else {
    221     size_t hunk_count = delta->hunks_len;
    222     for (size_t i = 0; i < hunk_count; i++) {
    223       gopher_commit_write_diff_hunk(commit, delta->hunks[i]);
    224     }
    225   }
    226 }
    227 
    228 static void gopher_commit_write_diff_hunk(GopherCommit* commit,
    229                                           const GitHunk* hunk) {
    230   FILE* out = commit->out;
    231 
    232   // Output header. e.g. @@ -0,0 +1,3 @@
    233   const char* header = hunk->header;
    234   print_gopher_text(out, header, false);
    235   fprintf(out, "\n");
    236 
    237   // Iterate over lines in hunk.
    238   size_t line_count = hunk->lines_len;
    239   for (size_t i = 0; i < line_count; i++) {
    240     const GitHunkLine* line = hunk->lines[i];
    241 
    242     const char* content = line->content;
    243     size_t content_len = line->content_len;
    244     int old_lineno = line->old_lineno;
    245     int new_lineno = line->new_lineno;
    246     if (old_lineno == -1) {
    247       // Added line. Prefix with +.
    248       fprintf(out, "+");
    249       print_gopher_text_len(out, content, content_len, false);
    250       fprintf(out, "\n");
    251     } else if (new_lineno == -1) {
    252       // Removed line. Prefix with -.
    253       fprintf(out, "-");
    254       print_gopher_text_len(out, content, content_len, false);
    255       fprintf(out, "\n");
    256     } else {
    257       // Unchanged line. Prefix with ' '. No link.
    258       fprintf(out, " ");
    259       print_gopher_text_len(out, content, content_len, false);
    260       fprintf(out, "\n");
    261     }
    262   }
    263 }