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