gout

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

commit.c (8767B)


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