stagit

custom fork of stagit
Index Commits Files Refs README LICENSE
stagit-index.c (7835B)
   1 #include <err.h>
   2 #include <limits.h>
   3 #include <stdio.h>
   4 #include <stdlib.h>
   5 #include <string.h>
   6 #include <time.h>
   7 #include <unistd.h>
   8 
   9 #include <git2.h>
  10 
  11 /* #define LAST_COMMIT_DATE_FORMAT "%Y-%m-%d %H:%M" */
  12 #define LAST_COMMIT_DATE_FORMAT "%d %b %Y"
  13 
  14 static git_repository *repo;
  15 
  16 static const char *relpath = "";
  17 
  18 static char description[255] = "Martin Klöckner's Git Repositories";
  19 static char *name = "";
  20 static char owner[255];
  21 
  22 /* Handle read or write errors for a FILE * stream */
  23 void
  24 checkfileerror(FILE *fp, const char *name, int mode)
  25 {
  26     if (mode == 'r' && ferror(fp))
  27         errx(1, "read error: %s", name);
  28     else if (mode == 'w' && (fflush(fp) || ferror(fp)))
  29         errx(1, "write error: %s", name);
  30 }
  31 
  32 void
  33 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
  34 {
  35     int r;
  36 
  37     r = snprintf(buf, bufsiz, "%s%s%s",
  38         path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
  39     if (r < 0 || (size_t)r >= bufsiz)
  40         errx(1, "path truncated: '%s%s%s'",
  41             path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
  42 }
  43 
  44 /* Percent-encode, see RFC3986 section 2.1. */
  45 void
  46 percentencode(FILE *fp, const char *s, size_t len)
  47 {
  48     static char tab[] = "0123456789ABCDEF";
  49     unsigned char uc;
  50     size_t i;
  51 
  52     for (i = 0; *s && i < len; s++, i++) {
  53         uc = *s;
  54         /* NOTE: do not encode '/' for paths or ",-." */
  55         if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') ||
  56             uc == '[' || uc == ']') {
  57             putc('%', fp);
  58             putc(tab[(uc >> 4) & 0x0f], fp);
  59             putc(tab[uc & 0x0f], fp);
  60         } else {
  61             putc(uc, fp);
  62         }
  63     }
  64 }
  65 
  66 /* Escape characters below as HTML 2.0 / XML 1.0. */
  67 void
  68 xmlencode(FILE *fp, const char *s, size_t len)
  69 {
  70     size_t i;
  71 
  72     for (i = 0; *s && i < len; s++, i++) {
  73         switch(*s) {
  74         case '<':  fputs("&lt;",   fp); break;
  75         case '>':  fputs("&gt;",   fp); break;
  76         case '\'': fputs("&#39;" , fp); break;
  77         case '&':  fputs("&amp;",  fp); break;
  78         case '"':  fputs("&quot;", fp); break;
  79         default:   putc(*s, fp);
  80         }
  81     }
  82 }
  83 
  84 void
  85 printtimeshort(FILE *fp, const git_time *intime)
  86 {
  87     struct tm *intm;
  88     time_t t;
  89     char out[32];
  90 
  91     t = (time_t)intime->time;
  92     if (!(intm = gmtime(&t)))
  93         return;
  94     strftime(out, sizeof(out), LAST_COMMIT_DATE_FORMAT, intm);
  95     fputs(out, fp);
  96 }
  97 
  98 void
  99 writeheader(FILE *fp)
 100 {
 101     fputs("<!DOCTYPE html>\n"
 102         "<html>\n<head>\n"
 103         "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
 104         "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
 105         "<title>", fp);
 106     xmlencode(fp, description, strlen(description));
 107     fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
 108     fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
 109     fputs("</head>\n<body>\n", fp);
 110     fprintf(fp, "<table id=\"main-header-table\">\n<tr><td id=\"main-header-logo\"><img src=\"%slogo.png\" alt=\"\" width=\"64\" height=\"64\" /></td>\n"
 111             "<td id=\"main-header\"><h1 id=\"main-name\">", relpath);
 112     xmlencode(fp, description, strlen(description));
 113     fputs("</h1>\n"
 114         "<span class=\"main-desc\"><a href=\"https://kloeckner.com.ar/\">https://kloeckner.com.ar</a></span>"
 115         "</td></tr>\n</table>\n<div id=\"content\">\n"
 116         "<table id=\"index\"><thead id=\"legends\">\n"
 117         "<tr><td id=\"name\"><b>Name</b></td><td id=\"description\"><b>Description</b></td><td id=\"owner\"><b>Owner</b></td>"
 118         "<td id=\"last-commit\"><b>Last commit</b></td></tr>"
 119         "</thead><tbody>\n", fp);
 120 }
 121 
 122 void
 123 writefooter(FILE *fp)
 124 {
 125     /* fputs("</tbody>\n</table>\n</div>\n</body>\n</html>\n", fp); */
 126     fputs("</tbody>\n</table>\n</div>\n</body>", fp);
 127     fputs("<footer>Generated with <a href=\"https://git.kloeckner.com.ar/stagit/\">Stagit</a></footer>\n", fp);
 128     fputs("</html>\n", fp);
 129 }
 130 
 131 int
 132 writelog(FILE *fp)
 133 {
 134     git_commit *commit = NULL;
 135     const git_signature *author;
 136     git_revwalk *w = NULL;
 137     git_oid id;
 138     char *stripped_name = NULL, *p;
 139     int ret = 0;
 140 
 141     git_revwalk_new(&w, repo);
 142     git_revwalk_push_head(w);
 143 
 144     if (git_revwalk_next(&id, w) ||
 145         git_commit_lookup(&commit, repo, &id)) {
 146         ret = -1;
 147         goto err;
 148     }
 149 
 150     author = git_commit_author(commit);
 151 
 152     /* strip .git suffix */
 153     if (!(stripped_name = strdup(name)))
 154         err(1, "strdup");
 155     if ((p = strrchr(stripped_name, '.')))
 156         if (!strcmp(p, ".git"))
 157             *p = '\0';
 158 
 159     /* fputs("<tr style=\"cursor: pointer; cursor: hand;\" onclick=\"\ window.location='/",fp); */
 160     /* percentencode(fp, stripped_name, strlen(stripped_name)); */
 161     /* fputs("';\"><td id=\"name\"><a href=\"", fp); */
 162 
 163     /* fputs("<tr><td id=\"name\"><a href=\"",fp); */
 164     /* percentencode(fp, stripped_name, strlen(stripped_name)); */
 165     /* fputs("/files.html\">", fp); */
 166     /* xmlencode(fp, stripped_name, strlen(stripped_name)); */
 167     /* fputs("</a></td>", fp); */
 168 
 169     fputs("<tr id=\"entry\" onclick=\"window.location='/",fp);
 170     percentencode(fp, stripped_name, strlen(stripped_name));
 171     fputs("';\">", fp);
 172 
 173     fputs("<td id=\"name\"><a href=\"",fp);
 174     percentencode(fp, stripped_name, strlen(stripped_name));
 175     fputs("/files.html\">", fp);
 176     xmlencode(fp, stripped_name, strlen(stripped_name));
 177     fputs("</a></td>", fp);
 178 
 179     fputs("<td id=\"description\"><a href=\"", fp);
 180     percentencode(fp, stripped_name, strlen(stripped_name));
 181     fputs("/files.html\">", fp);
 182     xmlencode(fp, description, strlen(description));
 183     fputs("</a></td>", fp);
 184 
 185     fputs("<td id=\"owner\"><a href=\"", fp);
 186     percentencode(fp, stripped_name, strlen(stripped_name));
 187     fputs("/files.html\">", fp);
 188     xmlencode(fp, owner, strlen(owner));
 189     fputs("</a></td>", fp);
 190 
 191     fputs("<td id=\"last-commit\"><a href=\"", fp);
 192     percentencode(fp, stripped_name, strlen(stripped_name));
 193     fputs("/files.html\">", fp);
 194 
 195     if (author)
 196         printtimeshort(fp, &(author->when));
 197     fputs("</a></td></tr>", fp);
 198 
 199     git_commit_free(commit);
 200 err:
 201     git_revwalk_free(w);
 202     free(stripped_name);
 203 
 204     return ret;
 205 }
 206 
 207 int
 208 main(int argc, char *argv[])
 209 {
 210     FILE *fp;
 211     char path[PATH_MAX], repodirabs[PATH_MAX + 1];
 212     const char *repodir;
 213     int i, ret = 0;
 214 
 215     if (argc < 2) {
 216         fprintf(stderr, "usage: %s [repodir...]\n", argv[0]);
 217         return 1;
 218     }
 219 
 220     /* do not search outside the git repository:
 221        GIT_CONFIG_LEVEL_APP is the highest level currently */
 222     git_libgit2_init();
 223     for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
 224         git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
 225     /* do not require the git repository to be owned by the current user */
 226     git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
 227 
 228 #ifdef __OpenBSD__
 229     if (pledge("stdio rpath", NULL) == -1)
 230         err(1, "pledge");
 231 #endif
 232 
 233     writeheader(stdout);
 234 
 235     for (i = 1; i < argc; i++) {
 236         repodir = argv[i];
 237         if (!realpath(repodir, repodirabs))
 238             err(1, "realpath");
 239 
 240         if (git_repository_open_ext(&repo, repodir,
 241             GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) {
 242             fprintf(stderr, "%s: cannot open repository\n", argv[0]);
 243             ret = 1;
 244             continue;
 245         }
 246 
 247         /* use directory name as name */
 248         if ((name = strrchr(repodirabs, '/')))
 249             name++;
 250         else
 251             name = "";
 252 
 253         /* read description or .git/description */
 254         joinpath(path, sizeof(path), repodir, "description");
 255         if (!(fp = fopen(path, "r"))) {
 256             joinpath(path, sizeof(path), repodir, ".git/description");
 257             fp = fopen(path, "r");
 258         }
 259         description[0] = '\0';
 260         if (fp) {
 261             if (!fgets(description, sizeof(description), fp))
 262                 description[0] = '\0';
 263             checkfileerror(fp, "description", 'r');
 264             fclose(fp);
 265         }
 266 
 267         /* read owner or .git/owner */
 268         joinpath(path, sizeof(path), repodir, "owner");
 269         if (!(fp = fopen(path, "r"))) {
 270             joinpath(path, sizeof(path), repodir, ".git/owner");
 271             fp = fopen(path, "r");
 272         }
 273         owner[0] = '\0';
 274         if (fp) {
 275             if (!fgets(owner, sizeof(owner), fp))
 276                 owner[0] = '\0';
 277             checkfileerror(fp, "owner", 'r');
 278             fclose(fp);
 279             owner[strcspn(owner, "\n")] = '\0';
 280         }
 281         writelog(stdout);
 282     }
 283     writefooter(stdout);
 284 
 285     /* cleanup */
 286     git_repository_free(repo);
 287     git_libgit2_shutdown();
 288 
 289     checkfileerror(stdout, "<stdout>", 'w');
 290 
 291     return ret;
 292 }