delta.c (5868B)
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 hunk->lines[i] = NULL; 87 } 88 free(hunk->lines); 89 hunk->lines = NULL; 90 free(hunk); 91 } 92 93 static char status_for_delta(const git_diff_delta* delta) { 94 assert(delta != NULL); 95 switch (delta->status) { 96 case GIT_DELTA_ADDED: 97 return 'A'; 98 case GIT_DELTA_COPIED: 99 return 'C'; 100 case GIT_DELTA_DELETED: 101 return 'D'; 102 case GIT_DELTA_MODIFIED: 103 return 'M'; 104 case GIT_DELTA_RENAMED: 105 return 'R'; 106 case GIT_DELTA_TYPECHANGE: 107 return 'T'; 108 case GIT_DELTA_CONFLICTED: 109 case GIT_DELTA_IGNORED: 110 case GIT_DELTA_UNMODIFIED: 111 case GIT_DELTA_UNREADABLE: 112 case GIT_DELTA_UNTRACKED: 113 return ' '; 114 } 115 return ' '; 116 } 117 118 GitDelta* gitdelta_create(git_patch* patch) { 119 assert(patch != NULL); 120 GitDelta* delta = ecalloc(1, sizeof(GitDelta)); 121 122 const git_diff_delta* git_delta = git_patch_get_delta(patch); 123 delta->status = status_for_delta(git_delta); 124 delta->is_binary = git_delta->flags & GIT_DIFF_FLAG_BINARY; 125 delta->old_file_path = 126 git_delta->old_file.path ? estrdup(git_delta->old_file.path) : NULL; 127 delta->new_file_path = 128 git_delta->new_file.path ? estrdup(git_delta->new_file.path) : NULL; 129 delta->addcount = 0; 130 delta->delcount = 0; 131 132 /* Skip stats for binary data. */ 133 if (delta->is_binary) { 134 git_patch_free(patch); 135 return delta; 136 } 137 138 /* Populate hunks array. */ 139 size_t hunk_count = git_patch_num_hunks(patch); 140 delta->hunks = ecalloc(hunk_count, sizeof(GitHunk*)); 141 size_t hunks_out_count = 0; 142 for (size_t i = 0; i < hunk_count; i++) { 143 const git_diff_hunk* hunk; 144 size_t line_count; 145 if (!git_patch_get_hunk(&hunk, &line_count, patch, i)) { 146 GitHunk* hunk = githunk_create(patch, i); 147 if (hunk) { 148 delta->hunks[hunks_out_count++] = hunk; 149 } 150 } 151 } 152 delta->hunks_len = hunks_out_count; 153 154 /* Increment added/deleted line counts. */ 155 for (size_t i = 0; i < delta->hunks_len; i++) { 156 GitHunk* hunk = delta->hunks[i]; 157 for (size_t j = 0; j < hunk->lines_len; j++) { 158 GitHunkLine* line = hunk->lines[j]; 159 if (line->old_lineno == -1) { 160 delta->addcount++; 161 } else if (line->new_lineno == -1) { 162 delta->delcount++; 163 } 164 } 165 } 166 git_patch_free(patch); 167 return delta; 168 } 169 170 void gitdelta_free(GitDelta* delta) { 171 if (!delta) { 172 return; 173 } 174 free(delta->old_file_path); 175 free(delta->new_file_path); 176 for (size_t i = 0; i < delta->hunks_len; i++) { 177 githunk_free(delta->hunks[i]); 178 delta->hunks[i] = NULL; 179 } 180 free(delta->hunks); 181 delta->hunks = NULL; 182 free(delta); 183 } 184 185 static char* gitdelta_graph(const GitDelta* delta, 186 size_t length, 187 char c, 188 size_t max_width) { 189 assert(delta != NULL); 190 size_t changed = delta->addcount + delta->delcount; 191 if (changed > max_width && length > 0) { 192 length = (length * max_width) / changed; 193 if (length == 0) { 194 length = 1; 195 } 196 } 197 char* graph = ecalloc(length + 1, sizeof(char)); 198 memset(graph, c, length); 199 return graph; 200 } 201 202 char* gitdelta_added_graph(const GitDelta* delta, size_t max_width) { 203 assert(delta != NULL); 204 return gitdelta_graph(delta, delta->addcount, '+', max_width); 205 } 206 207 char* gitdelta_deleted_graph(const GitDelta* delta, size_t max_width) { 208 assert(delta != NULL); 209 return gitdelta_graph(delta, delta->delcount, '-', max_width); 210 }