stagit

stagit-index.c

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