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