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