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 }