delta.c (5639B)
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 /* HunkLines */ 13 static GitHunkLine* githunkline_create(git_patch* patch, 14 size_t hunk_id, 15 size_t line_id); 16 static void githunkline_free(GitHunkLine* hunk_line); 17 18 /* Hunks */ 19 static GitHunk* githunk_create(git_patch* patch, size_t id); 20 static void githunk_free(GitHunk* hunk); 21 22 /* Deltas */ 23 static char status_for_delta(const git_diff_delta* delta); 24 static char* gitdelta_graph(const GitDelta* delta, 25 size_t length, 26 char character, 27 size_t max_width); 28 29 static GitHunkLine* githunkline_create(git_patch* patch, 30 size_t hunk_id, 31 size_t line_id) { 32 assert(patch != NULL); 33 const git_diff_line* line; 34 if (git_patch_get_line_in_hunk(&line, patch, hunk_id, line_id)) { 35 return NULL; 36 } 37 38 GitHunkLine* line_out = ecalloc(1, sizeof(GitHunkLine)); 39 line_out->id = line_id; 40 line_out->old_lineno = line->old_lineno; 41 line_out->new_lineno = line->new_lineno; 42 line_out->content_len = line->content_len; 43 line_out->content = ecalloc(line->content_len + 1, sizeof(char)); 44 memcpy(line_out->content, line->content, line->content_len); 45 return line_out; 46 } 47 48 static void githunkline_free(GitHunkLine* hunk_line) { 49 if (!hunk_line) { 50 return; 51 } 52 free(hunk_line->content); 53 free(hunk_line); 54 } 55 56 static GitHunk* githunk_create(git_patch* patch, size_t hunk_id) { 57 assert(patch != NULL); 58 const git_diff_hunk* hunk; 59 size_t line_count; 60 if (git_patch_get_hunk(&hunk, &line_count, patch, hunk_id)) { 61 return NULL; 62 } 63 64 GitHunk* hunk_out = ecalloc(1, sizeof(GitHunk)); 65 hunk_out->id = hunk_id; 66 hunk_out->header = estrdup(hunk->header); 67 hunk_out->lines = ecalloc(line_count, sizeof(GitHunkLine*)); 68 size_t lines_out_count = 0; 69 for (size_t i = 0; i < line_count; i++) { 70 GitHunkLine* hunk_line = githunkline_create(patch, hunk_id, i); 71 if (hunk_line) { 72 hunk_out->lines[lines_out_count++] = hunk_line; 73 } 74 } 75 hunk_out->lines_len = lines_out_count; 76 return hunk_out; 77 } 78 79 static void githunk_free(GitHunk* hunk) { 80 if (!hunk) { 81 return; 82 } 83 free(hunk->header); 84 for (size_t i = 0; i < hunk->lines_len; i++) { 85 githunkline_free(hunk->lines[i]); 86 } 87 free(hunk->lines); 88 free(hunk); 89 } 90 91 static char status_for_delta(const git_diff_delta* delta) { 92 assert(delta != NULL); 93 switch (delta->status) { 94 case GIT_DELTA_ADDED: 95 return 'A'; 96 case GIT_DELTA_COPIED: 97 return 'C'; 98 case GIT_DELTA_DELETED: 99 return 'D'; 100 case GIT_DELTA_MODIFIED: 101 return 'M'; 102 case GIT_DELTA_RENAMED: 103 return 'R'; 104 case GIT_DELTA_TYPECHANGE: 105 return 'T'; 106 case GIT_DELTA_CONFLICTED: 107 case GIT_DELTA_IGNORED: 108 case GIT_DELTA_UNMODIFIED: 109 case GIT_DELTA_UNREADABLE: 110 case GIT_DELTA_UNTRACKED: 111 return ' '; 112 } 113 return ' '; 114 } 115 116 GitDelta* gitdelta_create(git_patch* patch) { 117 assert(patch != NULL); 118 GitDelta* delta = ecalloc(1, sizeof(GitDelta)); 119 120 const git_diff_delta* git_delta = git_patch_get_delta(patch); 121 delta->status = status_for_delta(git_delta); 122 delta->is_binary = git_delta->flags & GIT_DIFF_FLAG_BINARY; 123 delta->old_file_path = 124 git_delta->old_file.path ? estrdup(git_delta->old_file.path) : NULL; 125 delta->new_file_path = 126 git_delta->new_file.path ? estrdup(git_delta->new_file.path) : NULL; 127 delta->addcount = 0; 128 delta->delcount = 0; 129 130 /* Skip stats for binary data. */ 131 if (delta->is_binary) { 132 git_patch_free(patch); 133 return delta; 134 } 135 136 /* Populate hunks array. */ 137 size_t hunk_count = git_patch_num_hunks(patch); 138 delta->hunks = ecalloc(hunk_count, sizeof(GitHunk*)); 139 size_t hunks_out_count = 0; 140 for (size_t i = 0; i < hunk_count; i++) { 141 GitHunk* hunk = githunk_create(patch, i); 142 if (hunk) { 143 delta->hunks[hunks_out_count++] = hunk; 144 } 145 } 146 delta->hunks_len = hunks_out_count; 147 148 /* Increment added/deleted line counts. */ 149 for (size_t i = 0; i < delta->hunks_len; i++) { 150 GitHunk* hunk = delta->hunks[i]; 151 for (size_t j = 0; j < hunk->lines_len; j++) { 152 GitHunkLine* line = hunk->lines[j]; 153 if (line->old_lineno == -1) { 154 delta->addcount++; 155 } else if (line->new_lineno == -1) { 156 delta->delcount++; 157 } 158 } 159 } 160 git_patch_free(patch); 161 return delta; 162 } 163 164 void gitdelta_free(GitDelta* delta) { 165 if (!delta) { 166 return; 167 } 168 free(delta->old_file_path); 169 free(delta->new_file_path); 170 for (size_t i = 0; i < delta->hunks_len; i++) { 171 githunk_free(delta->hunks[i]); 172 } 173 free(delta->hunks); 174 free(delta); 175 } 176 177 static char* gitdelta_graph(const GitDelta* delta, 178 size_t length, 179 char c, 180 size_t max_width) { 181 assert(delta != NULL); 182 size_t changed = delta->addcount + delta->delcount; 183 if (changed > max_width && length > 0) { 184 length = (length * max_width) / changed; 185 if (length == 0) { 186 length = 1; 187 } 188 } 189 char* graph = ecalloc(length + 1, sizeof(char)); 190 memset(graph, c, length); 191 return graph; 192 } 193 194 char* gitdelta_added_graph(const GitDelta* delta, size_t max_width) { 195 assert(delta != NULL); 196 return gitdelta_graph(delta, delta->addcount, '+', max_width); 197 } 198 199 char* gitdelta_deleted_graph(const GitDelta* delta, size_t max_width) { 200 assert(delta != NULL); 201 return gitdelta_graph(delta, delta->delcount, '-', max_width); 202 }