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 }