gout

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

repo.c (7785B)


      1 #include "git/repo.h"
      2 
      3 #include <assert.h>
      4 #include <err.h>
      5 #include <git2/blob.h>
      6 #include <git2/commit.h>
      7 #include <git2/object.h>
      8 #include <git2/oid.h>
      9 #include <git2/refs.h>
     10 #include <git2/repository.h>
     11 #include <git2/revparse.h>
     12 #include <git2/revwalk.h>
     13 #include <git2/tree.h>
     14 #include <git2/types.h>
     15 #include <libgen.h>
     16 #include <limits.h>
     17 #include <stdbool.h>
     18 #include <stdio.h>
     19 #include <stdlib.h>
     20 #include <string.h>
     21 #include <sys/stat.h>
     22 #include <sys/types.h>
     23 #include <sys/unistd.h>
     24 #include <unistd.h>
     25 
     26 #include "git/internal.h"
     27 #include "third_party/openbsd/reallocarray.h"
     28 
     29 /* Local const data */
     30 static const char* kLicenses[] = {"HEAD:LICENSE", "HEAD:LICENSE.md",
     31                                   "HEAD:COPYING"};
     32 static const size_t kLicensesLen = sizeof(kLicenses) / sizeof(char*);
     33 static const char* kReadmes[] = {"HEAD:README", "HEAD:README.md"};
     34 static const size_t kReadmesLen = sizeof(kReadmes) / sizeof(char*);
     35 
     36 /* Utilities */
     37 static bool string_ends_with(const char* str, const char* suffix);
     38 static char* first_line_of_file(const FileSystem* fs, const char* path);
     39 
     40 /* GitRepo utilities. */
     41 static char* gitrepo_name_from_path(const char* repo_path);
     42 static char* gitrepo_shortname_from_name(const char* name);
     43 static bool gitrepo_has_blob(git_repository* repo, const char* file);
     44 static const char* gitrepo_first_matching_file(git_repository* repo,
     45                                                const char** files,
     46                                                size_t files_len);
     47 void gitrepo_load_filesystem_metadata(GitRepo* repo,
     48                                       const FileSystem* fs,
     49                                       const char* path);
     50 static void gitrepo_load_git_metadata(GitRepo* repo, git_repository* git_repo);
     51 
     52 static bool string_ends_with(const char* str, const char* suffix) {
     53   if (!str || !suffix) {
     54     return false;
     55   }
     56   size_t str_len = strlen(str);
     57   size_t suffix_len = strlen(suffix);
     58   if (str_len < suffix_len) {
     59     return false;
     60   }
     61   return strncmp(str + str_len - suffix_len, suffix, suffix_len) == 0;
     62 }
     63 
     64 static char* first_line_of_file(const FileSystem* fs, const char* path) {
     65   assert(fs != NULL);
     66   assert(path != NULL);
     67   FILE* f = fs->fopen(path, "r");
     68   if (!f) {
     69     return estrdup("");
     70   }
     71   char* buf = NULL;
     72   size_t buf_size = 0;
     73   ssize_t len = getline(&buf, &buf_size, f);
     74   fs->fclose(f);
     75   if (len == -1) {
     76     free(buf);
     77     return estrdup("");
     78   }
     79   // Truncate at the first newline or carriage return.
     80   buf[strcspn(buf, "\r\n")] = '\0';
     81   return buf;
     82 }
     83 
     84 static char* gitrepo_name_from_path(const char* repo_path) {
     85   assert(repo_path != NULL);
     86   char* path_copy = estrdup(repo_path);
     87   const char* filename = basename(path_copy);
     88   if (!filename) {
     89     err(1, "basename");
     90   }
     91   char* result = estrdup(filename);
     92   free(path_copy);
     93   return result;
     94 }
     95 
     96 static char* gitrepo_shortname_from_name(const char* name) {
     97   assert(name != NULL);
     98   char* short_name = estrdup(name);
     99   if (string_ends_with(short_name, ".git")) {
    100     size_t short_name_len = strlen(short_name);
    101     size_t suffix_len = strlen(".git");
    102     short_name[short_name_len - suffix_len] = '\0';
    103   }
    104   return short_name;
    105 }
    106 
    107 static bool gitrepo_has_blob(git_repository* repo, const char* file) {
    108   assert(repo != NULL);
    109   assert(file != NULL);
    110   git_object* obj = NULL;
    111   if (git_revparse_single(&obj, repo, file)) {
    112     return false;
    113   }
    114   bool has_blob = git_object_type(obj) == GIT_OBJECT_BLOB;
    115   git_object_free(obj);
    116   return has_blob;
    117 }
    118 
    119 static const char* gitrepo_first_matching_file(git_repository* repo,
    120                                                const char** files,
    121                                                size_t files_len) {
    122   assert(repo != NULL);
    123   assert(files != NULL);
    124   for (size_t i = 0; i < files_len; i++) {
    125     const char* filename = files[i];
    126     if (gitrepo_has_blob(repo, filename)) {
    127       return filename + strlen("HEAD:");
    128     }
    129   }
    130   return "";
    131 }
    132 
    133 void gitrepo_load_filesystem_metadata(GitRepo* repo,
    134                                       const FileSystem* fs,
    135                                       const char* path) {
    136   assert(repo != NULL);
    137   assert(fs != NULL);
    138   assert(path != NULL);
    139   char* repo_path = fs->realpath(path, NULL);
    140   if (!repo_path) {
    141     err(1, "realpath: %s", path);
    142   }
    143 
    144   char* git_path_maybe = path_concat(repo_path, ".git");
    145   char* git_path = NULL;
    146   if (fs->access(git_path_maybe, F_OK) != 0) {
    147     git_path = estrdup(repo_path);
    148   } else {
    149     git_path = estrdup(git_path_maybe);
    150   }
    151   free(git_path_maybe);
    152 
    153   repo->name = gitrepo_name_from_path(repo_path);
    154   repo->short_name = gitrepo_shortname_from_name(repo->name);
    155 
    156   static const char* metadata_files[] = {"owner", "description", "url"};
    157   char** target_fields[] = {&repo->owner, &repo->description, &repo->clone_url};
    158 
    159   for (size_t i = 0; i < 3; i++) {
    160     char* full_path = path_concat(git_path, metadata_files[i]);
    161     *target_fields[i] = first_line_of_file(fs, full_path);
    162     free(full_path);
    163   }
    164 
    165   free(repo_path);
    166   free(git_path);
    167 }
    168 
    169 static void gitrepo_add_special_file(GitRepo* repo,
    170                                      const char* label,
    171                                      const char* path) {
    172   if (!path || path[0] == '\0') {
    173     return;
    174   }
    175   repo->special_files =
    176       reallocarray(repo->special_files, repo->special_files_len + 1,
    177                    sizeof(RepoSpecialFile));
    178   if (!repo->special_files) {
    179     err(1, "reallocarray");
    180   }
    181   repo->special_files[repo->special_files_len].label = estrdup(label);
    182   repo->special_files[repo->special_files_len].path = estrdup(path);
    183   repo->special_files_len++;
    184 }
    185 
    186 static void gitrepo_load_git_metadata(GitRepo* repo, git_repository* git_repo) {
    187   assert(repo != NULL);
    188   assert(git_repo != NULL);
    189 
    190   if (gitrepo_has_blob(git_repo, "HEAD:.gitmodules")) {
    191     gitrepo_add_special_file(repo, "Submodules", ".gitmodules");
    192   }
    193   gitrepo_add_special_file(
    194       repo, "README",
    195       gitrepo_first_matching_file(git_repo, kReadmes, kReadmesLen));
    196   gitrepo_add_special_file(
    197       repo, "LICENSE",
    198       gitrepo_first_matching_file(git_repo, kLicenses, kLicensesLen));
    199 
    200   repo->last_commit_time = 0;
    201   git_object* obj = NULL;
    202   if (git_revparse_single(&obj, git_repo, "HEAD") == 0) {
    203     git_commit* commit = NULL;
    204     if (git_commit_lookup(&commit, git_repo, git_object_id(obj)) == 0) {
    205       repo->last_commit_time = git_commit_author(commit)->when.time;
    206       git_commit_free(commit);
    207     }
    208     git_object_free(obj);
    209   }
    210 }
    211 
    212 git_repository* gitrepo_open_repository(const char* path) {
    213   assert(path != NULL);
    214   git_repository* repo = NULL;
    215   git_repository_open_flag_t kRepoOpenFlags = GIT_REPOSITORY_OPEN_NO_SEARCH;
    216   if (git_repository_open_ext(&repo, path, kRepoOpenFlags, NULL)) {
    217     errx(1, "invalid git repository: %s", path);
    218   }
    219   return repo;
    220 }
    221 
    222 GitRepo* gitrepo_create_from_repository(const FileSystem* fs,
    223                                         const char* path,
    224                                         void* git_repo) {
    225   assert(fs != NULL);
    226   assert(path != NULL);
    227   assert(git_repo != NULL);
    228   GitRepo* repo = ecalloc(1, sizeof(GitRepo));
    229   gitrepo_load_filesystem_metadata(repo, fs, path);
    230   gitrepo_load_git_metadata(repo, (git_repository*)git_repo);
    231   return repo;
    232 }
    233 
    234 void gitrepo_free(GitRepo* repo) {
    235   if (!repo) {
    236     return;
    237   }
    238   free(repo->name);
    239   repo->name = NULL;
    240   free(repo->short_name);
    241   repo->short_name = NULL;
    242   free(repo->owner);
    243   repo->owner = NULL;
    244   free(repo->description);
    245   repo->description = NULL;
    246   free(repo->clone_url);
    247   repo->clone_url = NULL;
    248 
    249   for (size_t i = 0; i < repo->special_files_len; i++) {
    250     free(repo->special_files[i].label);
    251     free(repo->special_files[i].path);
    252   }
    253   free(repo->special_files);
    254   repo->special_files = NULL;
    255   repo->special_files_len = 0;
    256 
    257   free(repo);
    258 }