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