delta.c (7191B)
1 #include "git/delta.h" 2 3 #include <assert.h> 4 #include <git2/diff.h> 5 #include <git2/patch.h> 6 #include <stdlib.h> 7 #include <string.h> 8 9 #include "git/internal.h" 10 #include "utils.h" 11 12 struct GitHunkLine { 13 size_t id; 14 int old_lineno; 15 int new_lineno; 16 const char* content; 17 size_t content_len; 18 }; 19 20 /* HunkLines */ 21 static GitHunkLine* githunkline_create(git_patch* patch, 22 size_t hunk_id, 23 size_t line_id); 24 static void githunkline_free(GitHunkLine* hunk_line); 25 26 /* Hunks */ 27 static GitHunk* githunk_create(git_patch* patch, size_t id); 28 static void githunk_free(GitHunk* hunk); 29 30 /* Deltas */ 31 static char status_for_delta(const git_diff_delta* delta); 32 static char* gitdelta_graph(const GitDelta* delta, 33 size_t length, 34 char character, 35 size_t max_width); 36 37 GitHunkLine* githunkline_create(git_patch* patch, 38 size_t hunk_id, 39 size_t line_id) { 40 const git_diff_line* line; 41 if (git_patch_get_line_in_hunk(&line, patch, hunk_id, line_id)) { 42 return NULL; 43 } 44 45 GitHunkLine* line_out = ecalloc(1, sizeof(GitHunkLine)); 46 line_out->id = line_id; 47 line_out->old_lineno = line->old_lineno; 48 line_out->new_lineno = line->new_lineno; 49 line_out->content = line->content; 50 line_out->content_len = line->content_len; 51 return line_out; 52 } 53 54 void githunkline_free(GitHunkLine* hunk_line) { 55 free(hunk_line); 56 } 57 58 size_t githunkline_id(const GitHunkLine* line) { 59 return line->id; 60 } 61 62 int githunkline_old_lineno(const GitHunkLine* line) { 63 return line->old_lineno; 64 } 65 66 int githunkline_new_lineno(const GitHunkLine* line) { 67 return line->new_lineno; 68 } 69 70 const char* githunkline_content(const GitHunkLine* line) { 71 return line->content; 72 } 73 74 size_t githunkline_content_len(const GitHunkLine* line) { 75 return line->content_len; 76 } 77 78 struct GitHunk { 79 size_t id; 80 const char* header; 81 GitHunkLine** lines; 82 size_t lines_len; 83 }; 84 85 GitHunk* githunk_create(git_patch* patch, size_t hunk_id) { 86 const git_diff_hunk* hunk; 87 size_t line_count; 88 if (git_patch_get_hunk(&hunk, &line_count, patch, hunk_id)) { 89 return NULL; 90 } 91 92 GitHunk* hunk_out = ecalloc(1, sizeof(GitHunk)); 93 hunk_out->id = hunk_id; 94 hunk_out->header = hunk->header; 95 hunk_out->lines = ecalloc(line_count, sizeof(GitHunkLine*)); 96 size_t lines_out_count = 0; 97 for (size_t i = 0; i < line_count; i++) { 98 GitHunkLine* hunk_line = githunkline_create(patch, hunk_id, i); 99 if (hunk_line) { 100 hunk_out->lines[lines_out_count++] = hunk_line; 101 } 102 } 103 hunk_out->lines_len = lines_out_count; 104 return hunk_out; 105 } 106 107 void githunk_free(GitHunk* hunk) { 108 if (!hunk) { 109 return; 110 } 111 for (size_t i = 0; i < hunk->lines_len; i++) { 112 githunkline_free(hunk->lines[i]); 113 hunk->lines[i] = NULL; 114 } 115 free(hunk->lines); 116 hunk->lines = NULL; 117 free(hunk); 118 } 119 120 size_t githunk_id(const GitHunk* hunk) { 121 return hunk->id; 122 } 123 124 const char* githunk_header(const GitHunk* hunk) { 125 return hunk->header; 126 } 127 128 GitHunkLine* githunk_line(const GitHunk* hunk, size_t line) { 129 assert(line < hunk->lines_len); 130 return hunk->lines[line]; 131 } 132 133 size_t githunk_line_count(const GitHunk* hunk) { 134 return hunk->lines_len; 135 } 136 137 char status_for_delta(const git_diff_delta* delta) { 138 switch (delta->status) { 139 case GIT_DELTA_ADDED: 140 return 'A'; 141 case GIT_DELTA_COPIED: 142 return 'C'; 143 case GIT_DELTA_DELETED: 144 return 'D'; 145 case GIT_DELTA_MODIFIED: 146 return 'M'; 147 case GIT_DELTA_RENAMED: 148 return 'R'; 149 case GIT_DELTA_TYPECHANGE: 150 return 'T'; 151 case GIT_DELTA_CONFLICTED: 152 case GIT_DELTA_IGNORED: 153 case GIT_DELTA_UNMODIFIED: 154 case GIT_DELTA_UNREADABLE: 155 case GIT_DELTA_UNTRACKED: 156 return ' '; 157 } 158 return ' '; 159 } 160 161 struct GitDelta { 162 bool is_binary; 163 char status; 164 const char* old_file_path; 165 const char* new_file_path; 166 size_t addcount; 167 size_t delcount; 168 GitHunk** hunks; 169 size_t hunks_len; 170 171 git_patch* patch; 172 }; 173 174 GitDelta* gitdelta_create(git_patch* patch) { 175 GitDelta* delta = ecalloc(1, sizeof(GitDelta)); 176 177 /* Take ownership of patch. */ 178 delta->patch = patch; 179 180 const git_diff_delta* git_delta = git_patch_get_delta(patch); 181 delta->status = status_for_delta(git_delta); 182 delta->is_binary = git_delta->flags & GIT_DIFF_FLAG_BINARY; 183 delta->old_file_path = git_delta->old_file.path; 184 delta->new_file_path = git_delta->new_file.path; 185 delta->addcount = 0; 186 delta->delcount = 0; 187 188 /* Skip stats for binary data. */ 189 if (delta->is_binary) { 190 return delta; 191 } 192 193 /* Populate hunks array. */ 194 size_t hunk_count = git_patch_num_hunks(patch); 195 delta->hunks = ecalloc(hunk_count, sizeof(GitHunk*)); 196 size_t hunks_out_count = 0; 197 for (size_t i = 0; i < hunk_count; i++) { 198 const git_diff_hunk* hunk; 199 size_t line_count; 200 if (!git_patch_get_hunk(&hunk, &line_count, patch, i)) { 201 GitHunk* hunk = githunk_create(patch, i); 202 if (hunk) { 203 delta->hunks[hunks_out_count++] = hunk; 204 } 205 } 206 } 207 delta->hunks_len = hunks_out_count; 208 209 /* Increment added/deleted line counts. */ 210 for (size_t i = 0; i < delta->hunks_len; i++) { 211 GitHunk* hunk = delta->hunks[i]; 212 for (size_t j = 0; j < hunk->lines_len; j++) { 213 GitHunkLine* line = hunk->lines[j]; 214 if (line->old_lineno == -1) { 215 delta->addcount++; 216 } else if (line->new_lineno == -1) { 217 delta->delcount++; 218 } 219 } 220 } 221 return delta; 222 } 223 224 void gitdelta_free(GitDelta* delta) { 225 if (!delta) { 226 return; 227 } 228 for (size_t i = 0; i < delta->hunks_len; i++) { 229 githunk_free(delta->hunks[i]); 230 delta->hunks[i] = NULL; 231 } 232 free(delta->hunks); 233 delta->hunks = NULL; 234 git_patch_free(delta->patch); 235 delta->patch = NULL; 236 free(delta); 237 } 238 239 bool gitdelta_is_binary(const GitDelta* delta) { 240 return delta->is_binary; 241 } 242 243 char gitdelta_status(const GitDelta* delta) { 244 return delta->status; 245 } 246 247 const char* gitdelta_old_file_path(const GitDelta* delta) { 248 return delta->old_file_path; 249 } 250 251 const char* gitdelta_new_file_path(const GitDelta* delta) { 252 return delta->new_file_path; 253 } 254 255 size_t gitdelta_lines_added(const GitDelta* delta) { 256 return delta->addcount; 257 } 258 259 size_t gitdelta_lines_deleted(const GitDelta* delta) { 260 return delta->delcount; 261 } 262 263 GitHunk* gitdelta_hunk(const GitDelta* delta, size_t id) { 264 assert(id < delta->hunks_len); 265 return delta->hunks[id]; 266 } 267 268 size_t gitdelta_hunk_count(const GitDelta* delta) { 269 return delta->hunks_len; 270 } 271 272 char* gitdelta_graph(const GitDelta* delta, 273 size_t length, 274 char c, 275 size_t max_width) { 276 size_t changed = delta->addcount + delta->delcount; 277 if (changed > max_width && length > 0) { 278 length = ((float)max_width / changed * length) + 1; 279 } 280 char* graph = ecalloc(length + 1, sizeof(char)); 281 memset(graph, c, length); 282 return graph; 283 } 284 285 char* gitdelta_added_graph(const GitDelta* delta, size_t max_width) { 286 return gitdelta_graph(delta, delta->addcount, '+', max_width); 287 } 288 289 char* gitdelta_deleted_graph(const GitDelta* delta, size_t max_width) { 290 return gitdelta_graph(delta, delta->delcount, '-', max_width); 291 }