gitout

A static git page generator
git clone https://git.bracken.jp/gitout.git
Log | Files | Refs | Submodules | README | LICENSE

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 }