gout

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

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 }