stagit-gemini.c - stagit-gemini - Stagit for gemini protocol Openbsd
 (HTM) git clone git://thinkerwim.org/stagit-gemini.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       stagit-gemini.c (35611B)
       ---
            1 #include <sys/stat.h>
            2 #include <sys/types.h>
            3 
            4 #include <err.h>
            5 #include <errno.h>
            6 #include <libgen.h>
            7 #include <limits.h>
            8 #include <locale.h>
            9 #include <stdint.h>
           10 #include <stdio.h>
           11 #include <stdlib.h>
           12 #include <string.h>
           13 #include <time.h>
           14 #include <unistd.h>
           15 #include <wchar.h>
           16 
           17 #include <git2.h>
           18 
           19 #include "compat.h"
           20 
           21 #define LEN(s)    (sizeof(s)/sizeof(*s))
           22 #define PAD_TRUNCATE_SYMBOL    "\xe2\x80\xa6" /* symbol: "ellipsis" */
           23 #define UTF_INVALID_SYMBOL     "\xef\xbf\xbd" /* symbol: "replacement" */
           24 
           25 struct deltainfo {
           26         git_patch *patch;
           27 
           28         size_t addcount;
           29         size_t delcount;
           30 };
           31 
           32 struct commitinfo {
           33         const git_oid *id;
           34 
           35         char oid[GIT_OID_HEXSZ + 1];
           36         char parentoid[GIT_OID_HEXSZ + 1];
           37 
           38         const git_signature *author;
           39         const git_signature *committer;
           40         const char          *summary;
           41         const char          *msg;
           42 
           43         git_diff   *diff;
           44         git_commit *commit;
           45         git_commit *parent;
           46         git_tree   *commit_tree;
           47         git_tree   *parent_tree;
           48 
           49         size_t addcount;
           50         size_t delcount;
           51         size_t filecount;
           52 
           53         struct deltainfo **deltas;
           54         size_t ndeltas;
           55 };
           56 
           57 /* reference and associated data for sorting */
           58 struct referenceinfo {
           59         struct git_reference *ref;
           60         struct commitinfo *ci;
           61 };
           62 
           63 static git_repository *repo;
           64 
           65 static const char *baseurl = ""; /* base URL to make absolute RSS/Atom URI */
           66 static const char *relpath = "";
           67 static const char *repodir;
           68 
           69 static char *name = "";
           70 static char *strippedname = "";
           71 static char description[255];
           72 static char cloneurl[1024];
           73 static char *submodules;
           74 static char *licensefiles[] = { "HEAD:LICENSE", "HEAD:LICENSE.md", "HEAD:COPYING" };
           75 static char *license;
           76 static char *readmefiles[] = { "HEAD:README", "HEAD:README.md" };
           77 static char *readme;
           78 static long long nlogcommits = -1; /* -1 indicates not used */
           79 
           80 /* cache */
           81 static git_oid lastoid;
           82 static char lastoidstr[GIT_OID_HEXSZ + 2]; /* id + newline + NUL byte */
           83 static FILE *rcachefp, *wcachefp;
           84 static const char *cachefile;
           85 
           86 /* Handle read or write errors for a FILE * stream */
           87 void
           88 checkfileerror(FILE *fp, const char *name, int mode)
           89 {
           90         if (mode == 'r' && ferror(fp))
           91                 errx(1, "read error: %s", name);
           92         else if (mode == 'w' && (fflush(fp) || ferror(fp)))
           93                 errx(1, "write error: %s", name);
           94 }
           95 
           96 /* Format `len' columns of characters. If string is shorter pad the rest
           97  * with characters `pad`. */
           98 int
           99 utf8pad(char *buf, size_t bufsiz, const char *s, size_t len, int pad)
          100 {
          101         wchar_t wc;
          102         size_t col = 0, i, slen, siz = 0;
          103         int inc, rl, w;
          104 
          105         if (!bufsiz)
          106                 return -1;
          107         if (!len) {
          108                 buf[0] = '\0';
          109                 return 0;
          110         }
          111 
          112         slen = strlen(s);
          113         for (i = 0; i < slen; i += inc) {
          114                 inc = 1; /* next byte */
          115                 if ((unsigned char)s[i] < 32)
          116                         continue;
          117 
          118                 rl = mbtowc(&wc, &s[i], slen - i < 4 ? slen - i : 4);
          119                 inc = rl;
          120                 if (rl < 0) {
          121                         mbtowc(NULL, NULL, 0); /* reset state */
          122                         inc = 1; /* invalid, seek next byte */
          123                         w = 1; /* replacement char is one width */
          124                 } else if ((w = wcwidth(wc)) == -1) {
          125                         continue;
          126                 }
          127 
          128                 if (col + w > len || (col + w == len && s[i + inc])) {
          129                         if (siz + 4 >= bufsiz)
          130                                 return -1;
          131                         memcpy(&buf[siz], PAD_TRUNCATE_SYMBOL, sizeof(PAD_TRUNCATE_SYMBOL) - 1);
          132                         siz += sizeof(PAD_TRUNCATE_SYMBOL) - 1;
          133                         buf[siz] = '\0';
          134                         col++;
          135                         break;
          136                 } else if (rl < 0) {
          137                         if (siz + 4 >= bufsiz)
          138                                 return -1;
          139                         memcpy(&buf[siz], UTF_INVALID_SYMBOL, sizeof(UTF_INVALID_SYMBOL) - 1);
          140                         siz += sizeof(UTF_INVALID_SYMBOL) - 1;
          141                         buf[siz] = '\0';
          142                         col++;
          143                         continue;
          144                 }
          145                 if (siz + inc + 1 >= bufsiz)
          146                         return -1;
          147                 memcpy(&buf[siz], &s[i], inc);
          148                 siz += inc;
          149                 buf[siz] = '\0';
          150                 col += w;
          151         }
          152 
          153         len -= col;
          154         if (siz + len + 1 >= bufsiz)
          155                 return -1;
          156         memset(&buf[siz], pad, len);
          157         siz += len;
          158         buf[siz] = '\0';
          159 
          160         return 0;
          161 }
          162 
          163 void
          164 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
          165 {
          166         int r;
          167 
          168         r = snprintf(buf, bufsiz, "%s%s%s",
          169                 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
          170         if (r < 0 || (size_t)r >= bufsiz)
          171                 errx(1, "path truncated: '%s%s%s'",
          172                         path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
          173 }
          174 
          175 void
          176 deltainfo_free(struct deltainfo *di)
          177 {
          178         if (!di)
          179                 return;
          180         git_patch_free(di->patch);
          181         memset(di, 0, sizeof(*di));
          182         free(di);
          183 }
          184 
          185 int
          186 commitinfo_getstats(struct commitinfo *ci)
          187 {
          188         struct deltainfo *di;
          189         git_diff_options opts;
          190         git_diff_find_options fopts;
          191         const git_diff_delta *delta;
          192         const git_diff_hunk *hunk;
          193         const git_diff_line *line;
          194         git_patch *patch = NULL;
          195         size_t ndeltas, nhunks, nhunklines;
          196         size_t i, j, k;
          197 
          198         if (git_tree_lookup(&(ci->commit_tree), repo, git_commit_tree_id(ci->commit)))
          199                 goto err;
          200         if (!git_commit_parent(&(ci->parent), ci->commit, 0)) {
          201                 if (git_tree_lookup(&(ci->parent_tree), repo, git_commit_tree_id(ci->parent))) {
          202                         ci->parent = NULL;
          203                         ci->parent_tree = NULL;
          204                 }
          205         }
          206 
          207         git_diff_init_options(&opts, GIT_DIFF_OPTIONS_VERSION);
          208         opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH |
          209                       GIT_DIFF_IGNORE_SUBMODULES |
          210                       GIT_DIFF_INCLUDE_TYPECHANGE;
          211         if (git_diff_tree_to_tree(&(ci->diff), repo, ci->parent_tree, ci->commit_tree, &opts))
          212                 goto err;
          213 
          214         if (git_diff_find_init_options(&fopts, GIT_DIFF_FIND_OPTIONS_VERSION))
          215                 goto err;
          216         /* find renames and copies, exact matches (no heuristic) for renames. */
          217         fopts.flags |= GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES |
          218                        GIT_DIFF_FIND_EXACT_MATCH_ONLY;
          219         if (git_diff_find_similar(ci->diff, &fopts))
          220                 goto err;
          221 
          222         ndeltas = git_diff_num_deltas(ci->diff);
          223         if (ndeltas && !(ci->deltas = calloc(ndeltas, sizeof(struct deltainfo *))))
          224                 err(1, "calloc");
          225 
          226         for (i = 0; i < ndeltas; i++) {
          227                 if (git_patch_from_diff(&patch, ci->diff, i))
          228                         goto err;
          229 
          230                 if (!(di = calloc(1, sizeof(struct deltainfo))))
          231                         err(1, "calloc");
          232                 di->patch = patch;
          233                 ci->deltas[i] = di;
          234 
          235                 delta = git_patch_get_delta(patch);
          236 
          237                 /* skip stats for binary data */
          238                 if (delta->flags & GIT_DIFF_FLAG_BINARY)
          239                         continue;
          240 
          241                 nhunks = git_patch_num_hunks(patch);
          242                 for (j = 0; j < nhunks; j++) {
          243                         if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
          244                                 break;
          245                         for (k = 0; ; k++) {
          246                                 if (git_patch_get_line_in_hunk(&line, patch, j, k))
          247                                         break;
          248                                 if (line->old_lineno == -1) {
          249                                         di->addcount++;
          250                                         ci->addcount++;
          251                                 } else if (line->new_lineno == -1) {
          252                                         di->delcount++;
          253                                         ci->delcount++;
          254                                 }
          255                         }
          256                 }
          257         }
          258         ci->ndeltas = i;
          259         ci->filecount = i;
          260 
          261         return 0;
          262 
          263 err:
          264         git_diff_free(ci->diff);
          265         ci->diff = NULL;
          266         git_tree_free(ci->commit_tree);
          267         ci->commit_tree = NULL;
          268         git_tree_free(ci->parent_tree);
          269         ci->parent_tree = NULL;
          270         git_commit_free(ci->parent);
          271         ci->parent = NULL;
          272         if (ci->deltas)
          273                 for (i = 0; i < ci->ndeltas; i++)
          274                         deltainfo_free(ci->deltas[i]);
          275         free(ci->deltas);
          276         ci->deltas = NULL;
          277         ci->ndeltas = 0;
          278         ci->addcount = 0;
          279         ci->delcount = 0;
          280         ci->filecount = 0;
          281 
          282         return -1;
          283 }
          284 
          285 void
          286 commitinfo_free(struct commitinfo *ci)
          287 {
          288         size_t i;
          289 
          290         if (!ci)
          291                 return;
          292         if (ci->deltas)
          293                 for (i = 0; i < ci->ndeltas; i++)
          294                         deltainfo_free(ci->deltas[i]);
          295         free(ci->deltas);
          296         git_diff_free(ci->diff);
          297         git_tree_free(ci->commit_tree);
          298         git_tree_free(ci->parent_tree);
          299         git_commit_free(ci->commit);
          300         git_commit_free(ci->parent);
          301         memset(ci, 0, sizeof(*ci));
          302         free(ci);
          303 }
          304 
          305 struct commitinfo *
          306 commitinfo_getbyoid(const git_oid *id)
          307 {
          308         struct commitinfo *ci;
          309 
          310         if (!(ci = calloc(1, sizeof(struct commitinfo))))
          311                 err(1, "calloc");
          312 
          313         if (git_commit_lookup(&(ci->commit), repo, id))
          314                 goto err;
          315         ci->id = id;
          316 
          317         git_oid_tostr(ci->oid, sizeof(ci->oid), git_commit_id(ci->commit));
          318         git_oid_tostr(ci->parentoid, sizeof(ci->parentoid), git_commit_parent_id(ci->commit, 0));
          319 
          320         ci->author = git_commit_author(ci->commit);
          321         ci->committer = git_commit_committer(ci->commit);
          322         ci->summary = git_commit_summary(ci->commit);
          323         ci->msg = git_commit_message(ci->commit);
          324 
          325         return ci;
          326 
          327 err:
          328         commitinfo_free(ci);
          329 
          330         return NULL;
          331 }
          332 
          333 int
          334 refs_cmp(const void *v1, const void *v2)
          335 {
          336         const struct referenceinfo *r1 = v1, *r2 = v2;
          337         time_t t1, t2;
          338         int r;
          339 
          340         if ((r = git_reference_is_tag(r1->ref) - git_reference_is_tag(r2->ref)))
          341                 return r;
          342 
          343         t1 = r1->ci->author ? r1->ci->author->when.time : 0;
          344         t2 = r2->ci->author ? r2->ci->author->when.time : 0;
          345         if ((r = t1 > t2 ? -1 : (t1 == t2 ? 0 : 1)))
          346                 return r;
          347 
          348         return strcmp(git_reference_shorthand(r1->ref),
          349                       git_reference_shorthand(r2->ref));
          350 }
          351 
          352 int
          353 getrefs(struct referenceinfo **pris, size_t *prefcount)
          354 {
          355         struct referenceinfo *ris = NULL;
          356         struct commitinfo *ci = NULL;
          357         git_reference_iterator *it = NULL;
          358         const git_oid *id = NULL;
          359         git_object *obj = NULL;
          360         git_reference *dref = NULL, *r, *ref = NULL;
          361         size_t i, refcount;
          362 
          363         *pris = NULL;
          364         *prefcount = 0;
          365 
          366         if (git_reference_iterator_new(&it, repo))
          367                 return -1;
          368 
          369         for (refcount = 0; !git_reference_next(&ref, it); ) {
          370                 if (!git_reference_is_branch(ref) && !git_reference_is_tag(ref)) {
          371                         git_reference_free(ref);
          372                         ref = NULL;
          373                         continue;
          374                 }
          375 
          376                 switch (git_reference_type(ref)) {
          377                 case GIT_REF_SYMBOLIC:
          378                         if (git_reference_resolve(&dref, ref))
          379                                 goto err;
          380                         r = dref;
          381                         break;
          382                 case GIT_REF_OID:
          383                         r = ref;
          384                         break;
          385                 default:
          386                         continue;
          387                 }
          388                 if (!git_reference_target(r) ||
          389                     git_reference_peel(&obj, r, GIT_OBJ_ANY))
          390                         goto err;
          391                 if (!(id = git_object_id(obj)))
          392                         goto err;
          393                 if (!(ci = commitinfo_getbyoid(id)))
          394                         break;
          395 
          396                 if (!(ris = reallocarray(ris, refcount + 1, sizeof(*ris))))
          397                         err(1, "realloc");
          398                 ris[refcount].ci = ci;
          399                 ris[refcount].ref = r;
          400                 refcount++;
          401 
          402                 git_object_free(obj);
          403                 obj = NULL;
          404                 git_reference_free(dref);
          405                 dref = NULL;
          406         }
          407         git_reference_iterator_free(it);
          408 
          409         /* sort by type, date then shorthand name */
          410         qsort(ris, refcount, sizeof(*ris), refs_cmp);
          411 
          412         *pris = ris;
          413         *prefcount = refcount;
          414 
          415         return 0;
          416 
          417 err:
          418         git_object_free(obj);
          419         git_reference_free(dref);
          420         commitinfo_free(ci);
          421         for (i = 0; i < refcount; i++) {
          422                 commitinfo_free(ris[i].ci);
          423                 git_reference_free(ris[i].ref);
          424         }
          425         free(ris);
          426 
          427         return -1;
          428 }
          429 
          430 FILE *
          431 efopen(const char *filename, const char *flags)
          432 {
          433         FILE *fp;
          434 
          435         if (!(fp = fopen(filename, flags)))
          436                 err(1, "fopen: '%s'", filename);
          437 
          438         return fp;
          439 }
          440 
          441 /* Escape characters below as HTML 2.0 / XML 1.0. */
          442 void
          443 xmlencode(FILE *fp, const char *s, size_t len)
          444 {
          445         size_t i;
          446 
          447         for (i = 0; *s && i < len; s++, i++) {
          448                 switch(*s) {
          449                 case '<':  fputs("&lt;",   fp); break;
          450                 case '>':  fputs("&gt;",   fp); break;
          451                 case '\'': fputs("&#39;",  fp); break;
          452                 case '&':  fputs("&amp;",  fp); break;
          453                 case '"':  fputs("&quot;", fp); break;
          454                 default:   putc(*s, fp);
          455                 }
          456         }
          457 }
          458 
          459 /* Escape characters in text in geomyidae .gmi format, with newlines */
          460 void
          461 gmitextnl(FILE *fp, const char *s, size_t len)
          462 {
          463         size_t i, n = 0;
          464 
          465         for (i = 0; s[i] && i < len; i++) {
          466                 switch (s[i]) {
          467                 case '\r': break;
          468                 default: fputc(s[i], fp);
          469                 }
          470                 n = (s[i] != '\n');
          471         }
          472 }
          473 
          474 /* Escape characters in text in geomyidae .gmi format,
          475    newlines are ignored */
          476 void
          477 gmitext(FILE *fp, const char *s, size_t len)
          478 {
          479         size_t i;
          480 
          481         for (i = 0; *s && i < len; s++, i++) {
          482                 switch (*s) {
          483                 case '\r': /* ignore CR */
          484                 case '\n': /* ignore LF */
          485                         break;
          486                 default:
          487                         putc(*s, fp);
          488                         break;
          489                 }
          490         }
          491 }
          492 
          493 /* Escape characters in links in geomyidae .gmi format */
          494 void
          495 gmilink(FILE *fp, const char *s, size_t len)
          496 {
          497         size_t i;
          498 
          499         for (i = 0; *s && i < len; s++, i++) {
          500                 switch (*s) {
          501                 case '\r': /* ignore CR */
          502                 case '\n': /* ignore LF */
          503                         break;
          504                 default:
          505                         putc(*s, fp);
          506                         break;
          507                 }
          508         }
          509 }
          510 
          511 int
          512 mkdirp(const char *path)
          513 {
          514         char tmp[PATH_MAX], *p;
          515 
          516         if (strlcpy(tmp, path, sizeof(tmp)) >= sizeof(tmp))
          517                 errx(1, "path truncated: '%s'", path);
          518         for (p = tmp + (tmp[0] == '/'); *p; p++) {
          519                 if (*p != '/')
          520                         continue;
          521                 *p = '\0';
          522                 if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
          523                         return -1;
          524                 *p = '/';
          525         }
          526         if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
          527                 return -1;
          528         return 0;
          529 }
          530 
          531 void
          532 printtimez(FILE *fp, const git_time *intime)
          533 {
          534         struct tm *intm;
          535         time_t t;
          536         char out[32];
          537 
          538         t = (time_t)intime->time;
          539         if (!(intm = gmtime(&t)))
          540                 return;
          541         strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm);
          542         fputs(out, fp);
          543 }
          544 
          545 void
          546 printtime(FILE *fp, const git_time *intime)
          547 {
          548         struct tm *intm;
          549         time_t t;
          550         char out[32];
          551 
          552         t = (time_t)intime->time + (intime->offset * 60);
          553         if (!(intm = gmtime(&t)))
          554                 return;
          555         strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm);
          556         if (intime->offset < 0)
          557                 fprintf(fp, "%s -%02d%02d", out,
          558                             -(intime->offset) / 60, -(intime->offset) % 60);
          559         else
          560                 fprintf(fp, "%s +%02d%02d", out,
          561                             intime->offset / 60, intime->offset % 60);
          562 }
          563 
          564 void
          565 printtimeshort(FILE *fp, const git_time *intime)
          566 {
          567         struct tm *intm;
          568         time_t t;
          569         char out[32];
          570 
          571         t = (time_t)intime->time;
          572         if (!(intm = gmtime(&t)))
          573                 return;
          574         strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
          575         fputs(out, fp);
          576 }
          577 
          578 void
          579 writeheader(FILE *fp, const char *title)
          580 {
          581         gmitext(fp, title, strlen(title));
          582         if (title[0] && strippedname[0])
          583                 fputs(" - ", fp);
          584         gmitext(fp, strippedname, strlen(strippedname));
          585         if (description[0])
          586                 fputs(" - ", fp);
          587         gmitext(fp, description, strlen(description));
          588         fputs("\n", fp);
          589         if (cloneurl[0]) {
          590     fputs("=> ", fp);
          591                 gmilink(fp, cloneurl, strlen(cloneurl));
          592                 fputs(" git clone:  ", fp);
          593                 gmilink(fp, cloneurl, strlen(cloneurl));
          594                 fputc('\n', fp);
          595         }
          596         fprintf(fp, "=> %s/log.gmi Log\n", relpath);
          597         fprintf(fp, "=> %s/files.gmi Files\n", relpath);
          598         fprintf(fp, "=> %s/refs.gmi Refs\n", relpath);
          599         if (submodules)
          600                 fprintf(fp, "=> %s/file/%s.gmi Submodules\n",
          601                         relpath, submodules);
          602         if (readme)
          603                 fprintf(fp, "=> %s/file/%s.gmi README\n",
          604                         relpath, readme);
          605         if (license)
          606                 fprintf(fp, "=> %s/file/%s.gmi LICENSE\n",
          607                         relpath, license);
          608         fputs("---\n", fp);
          609 }
          610 
          611 void
          612 writefooter(FILE *fp)
          613 {
          614   fputs("\n\ngenerated w/ stagit-gemini\n\n", fp);
          615 }
          616 
          617 size_t
          618 writeblobgmi(FILE *fp, const git_blob *blob)
          619 {
          620         size_t n = 0, i, j, len, prev;
          621         const char *nfmt = "%6zu ";
          622         const char *s = git_blob_rawcontent(blob);
          623 
          624         len = git_blob_rawsize(blob);
          625         if (len > 0) {
          626                 for (i = 0, prev = 0; i < len; i++) {
          627                         if (s[i] != '\n')
          628                                 continue;
          629                         n++;
          630                         fprintf(fp, nfmt, n, n, n);
          631                         for (j = prev; j <= i && s[j]; j++) {
          632                                 switch (s[j]) {
          633                                 case '\r': break;
          634                                 default: putc(s[j], fp);
          635                                 }
          636                         }
          637                         prev = i + 1;
          638                 }
          639                 /* trailing data */
          640                 if ((len - prev) > 0) {
          641                         n++;
          642                         fprintf(fp, nfmt, n, n, n);
          643                         for (j = prev; j < len - prev && s[j]; j++) {
          644                                 switch (s[j]) {
          645                                 case '\r': break;
          646                                 case '\t': fputs("        ", fp); break;
          647                                 default: putc(s[j], fp);
          648                                 }
          649                         }
          650                 }
          651         }
          652 
          653         return n;
          654 }
          655 
          656 void
          657 printcommit(FILE *fp, struct commitinfo *ci)
          658 {
          659         fprintf(fp, "=> %s/commit/%s.gmi commit %s\n",
          660                 relpath, ci->oid, ci->oid);
          661 
          662         if (ci->parentoid[0])
          663                 fprintf(fp, "=> %s/commit/%s.gmi parent %s\n",
          664                         relpath, ci->parentoid, ci->parentoid);
          665 
          666         if (ci->author) {
          667                 fputs(" Author: ", fp);
          668                 gmilink(fp, ci->author->name, strlen(ci->author->name));
          669                 fputs(" <", fp);
          670                 gmilink(fp, ci->author->email, strlen(ci->author->email));
          671                 fputs(">\n", fp);
          672                 fputs("Date:   ", fp);
          673                 printtime(fp, &(ci->author->when));
          674                 putc('\n', fp);
          675         }
          676         if (ci->msg) {
          677                 putc('\n', fp);
          678                 gmitextnl(fp, ci->msg, strlen(ci->msg));
          679                 putc('\n', fp);
          680         }
          681 }
          682 
          683 void
          684 printshowfile(FILE *fp, struct commitinfo *ci)
          685 {
          686         const git_diff_delta *delta;
          687         const git_diff_hunk *hunk;
          688         const git_diff_line *line;
          689         git_patch *patch;
          690         size_t nhunks, nhunklines, changed, add, del, total, i, j, k;
          691         char buf[256], filename[256], linestr[32];
          692         int c;
          693 
          694         printcommit(fp, ci);
          695 
          696         if (!ci->deltas)
          697                 return;
          698 
          699         if (ci->filecount > 1000   ||
          700             ci->ndeltas   > 1000   ||
          701             ci->addcount  > 100000 ||
          702             ci->delcount  > 100000) {
          703                 fputs("\nDiff is too large, output suppressed.\n", fp);
          704                 return;
          705         }
          706 
          707         /* diff stat */
          708         fputs("Diffstat:\n", fp);
          709         for (i = 0; i < ci->ndeltas; i++) {
          710                 delta = git_patch_get_delta(ci->deltas[i]->patch);
          711 
          712                 switch (delta->status) {
          713                 case GIT_DELTA_ADDED:      c = 'A'; break;
          714                 case GIT_DELTA_COPIED:     c = 'C'; break;
          715                 case GIT_DELTA_DELETED:    c = 'D'; break;
          716                 case GIT_DELTA_MODIFIED:   c = 'M'; break;
          717                 case GIT_DELTA_RENAMED:    c = 'R'; break;
          718                 case GIT_DELTA_TYPECHANGE: c = 'T'; break;
          719                 default:                   c = ' '; break;
          720                 }
          721 
          722                 if (strcmp(delta->old_file.path, delta->new_file.path)) {
          723                         snprintf(filename, sizeof(filename), "%s -> %s",
          724                                 delta->old_file.path, delta->new_file.path);
          725                         utf8pad(buf, sizeof(buf), filename, 35, ' ');
          726                 } else {
          727                         utf8pad(buf, sizeof(buf), delta->old_file.path, 35, ' ');
          728                 }
          729                 fprintf(fp, "  %c ", c);
          730                 gmitext(fp, buf, strlen(buf));
          731 
          732                 add = ci->deltas[i]->addcount;
          733                 del = ci->deltas[i]->delcount;
          734                 changed = add + del;
          735                 total = sizeof(linestr) - 2;
          736                 if (changed > total) {
          737                         if (add)
          738                                 add = ((float)total / changed * add) + 1;
          739                         if (del)
          740                                 del = ((float)total / changed * del) + 1;
          741                 }
          742                 memset(&linestr, '+', add);
          743                 memset(&linestr[add], '-', del);
          744 
          745                 fprintf(fp, " | %7zu ",
          746                         ci->deltas[i]->addcount + ci->deltas[i]->delcount);
          747                 fwrite(&linestr, 1, add, fp);
          748                 fwrite(&linestr[add], 1, del, fp);
          749                 fputs("\n", fp);
          750         }
          751         fprintf(fp, "\n%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
          752                 ci->filecount, ci->filecount == 1 ? "" : "s",
          753                 ci->addcount,  ci->addcount  == 1 ? "" : "s",
          754                 ci->delcount,  ci->delcount  == 1 ? "" : "s");
          755 
          756         fputs("---\n", fp);
          757 
          758         for (i = 0; i < ci->ndeltas; i++) {
          759                 patch = ci->deltas[i]->patch;
          760                 delta = git_patch_get_delta(patch);
          761                 /* NOTE: only links to new path */
          762                 fprintf(fp, "=> %s/file/", relpath);
          763                 gmilink(fp, delta->new_file.path, strlen(delta->new_file.path));
          764                 fputs(".gmi diff --git a/", fp);
          765                 gmilink(fp, delta->old_file.path, strlen(delta->old_file.path));
          766                 fputs(" b/", fp);
          767                 gmilink(fp, delta->new_file.path, strlen(delta->new_file.path));
          768     fputc('\n', fp);
          769 
          770                 /* check binary data */
          771                 if (delta->flags & GIT_DIFF_FLAG_BINARY) {
          772                         fputs("Binary files differ.\n", fp);
          773                         continue;
          774                 }
          775 
          776                 nhunks = git_patch_num_hunks(patch);
          777                 for (j = 0; j < nhunks; j++) {
          778                         if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
          779                                 break;
          780 
          781                         if (hunk->header_len > 0 && hunk->header[0] == '[')
          782                                 fputc('t', fp);
          783                         gmitext(fp, hunk->header, hunk->header_len);
          784                         putc('\n', fp);
          785 
          786                         for (k = 0; ; k++) {
          787                                 if (git_patch_get_line_in_hunk(&line, patch, j, k))
          788                                         break;
          789                                 if (line->old_lineno == -1)
          790                                         fputs("+", fp);
          791                                 else if (line->new_lineno == -1)
          792                                         fputs("-", fp);
          793                                 else
          794                                         fputs(" ", fp);
          795                                 gmitext(fp, line->content, line->content_len);
          796                                 putc('\n', fp);
          797                         }
          798                 }
          799         }
          800 }
          801 
          802 void
          803 writelogline(FILE *fp, struct commitinfo *ci)
          804 {
          805         char buf[256];
          806 
          807         fprintf(fp, "=> %s/commit/%s.gmi ", relpath, ci->oid);
          808         if (ci->author)
          809                 printtimeshort(fp, &(ci->author->when));
          810         else
          811                 fputs("                ", fp);
          812         fputs("  ", fp);
          813         utf8pad(buf, sizeof(buf), ci->summary ? ci->summary : "", 40, ' ');
          814         gmilink(fp, buf, strlen(buf));
          815         fputs("  ", fp);
          816         utf8pad(buf, sizeof(buf), ci->author ? ci->author->name : "", 19, '\0');
          817         gmilink(fp, buf, strlen(buf));
          818   fputc('\n', fp);
          819 }
          820 
          821 int
          822 writelog(FILE *fp, const git_oid *oid)
          823 {
          824         struct commitinfo *ci;
          825         git_revwalk *w = NULL;
          826         git_oid id;
          827         char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
          828         FILE *fpfile;
          829         size_t remcommits = 0;
          830         int r;
          831 
          832         git_revwalk_new(&w, repo);
          833         git_revwalk_push(w, oid);
          834 
          835         while (!git_revwalk_next(&id, w)) {
          836                 if (cachefile && !memcmp(&id, &lastoid, sizeof(id)))
          837                         break;
          838 
          839                 git_oid_tostr(oidstr, sizeof(oidstr), &id);
          840                 r = snprintf(path, sizeof(path), "commit/%s.gmi", oidstr);
          841                 if (r < 0 || (size_t)r >= sizeof(path))
          842                         errx(1, "path truncated: 'commit/%s.gmi'", oidstr);
          843                 r = access(path, F_OK);
          844 
          845                 /* optimization: if there are no log lines to write and
          846                    the commit file already exists: skip the diffstat */
          847                 if (!nlogcommits) {
          848                         remcommits++;
          849                         if (!r)
          850                                 continue;
          851                 }
          852 
          853                 if (!(ci = commitinfo_getbyoid(&id)))
          854                         break;
          855 
          856                 if (nlogcommits != 0) {
          857                         writelogline(fp, ci);
          858                         if (nlogcommits > 0)
          859                                 nlogcommits--;
          860                 }
          861 
          862                 if (cachefile)
          863                         writelogline(wcachefp, ci);
          864 
          865                 /* check if file exists if so skip it */
          866                 if (r) {
          867                         /* lookup stats: only required here for gemini */
          868                         if (commitinfo_getstats(ci) == -1)
          869                                 goto err;
          870 
          871                         fpfile = efopen(path, "w");
          872                         writeheader(fpfile, ci->summary);
          873                         printshowfile(fpfile, ci);
          874                         writefooter(fpfile);
          875                         checkfileerror(fpfile, path, 'w');
          876                         fclose(fpfile);
          877                 }
          878 err:
          879                 commitinfo_free(ci);
          880         }
          881         git_revwalk_free(w);
          882 
          883         if (nlogcommits == 0 && remcommits != 0) {
          884                 fprintf(fp, "%16.16s  "
          885                         "%zu more commits remaining, fetch the repository\n",
          886                         "", remcommits);
          887         }
          888 
          889         return 0;
          890 }
          891 
          892 void
          893 printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag)
          894 {
          895         fputs("<entry>\n", fp);
          896 
          897         fprintf(fp, "<id>%s</id>\n", ci->oid);
          898         if (ci->author) {
          899                 fputs("<published>", fp);
          900                 printtimez(fp, &(ci->author->when));
          901                 fputs("</published>\n", fp);
          902         }
          903         if (ci->committer) {
          904                 fputs("<updated>", fp);
          905                 printtimez(fp, &(ci->committer->when));
          906                 fputs("</updated>\n", fp);
          907         }
          908         if (ci->summary) {
          909                 fputs("<title type=\"text\">", fp);
          910                 if (tag && tag[0]) {
          911                         fputs("[", fp);
          912                         xmlencode(fp, tag, strlen(tag));
          913                         fputs("] ", fp);
          914                 }
          915                 xmlencode(fp, ci->summary, strlen(ci->summary));
          916                 fputs("</title>\n", fp);
          917         }
          918         fprintf(fp, "<link rel=\"alternate\" href=\"%scommit/%s.gmi\" />\n",
          919                 baseurl, ci->oid);
          920 
          921         if (ci->author) {
          922                 fputs("<author>\n<name>", fp);
          923                 xmlencode(fp, ci->author->name, strlen(ci->author->name));
          924                 fputs("</name>\n<email>", fp);
          925                 xmlencode(fp, ci->author->email, strlen(ci->author->email));
          926                 fputs("</email>\n</author>\n", fp);
          927         }
          928 
          929         fputs("<content type=\"text\">", fp);
          930         fprintf(fp, "commit %s\n", ci->oid);
          931         if (ci->parentoid[0])
          932                 fprintf(fp, "parent %s\n", ci->parentoid);
          933         if (ci->author) {
          934                 fputs("Author: ", fp);
          935                 xmlencode(fp, ci->author->name, strlen(ci->author->name));
          936                 fputs(" &lt;", fp);
          937                 xmlencode(fp, ci->author->email, strlen(ci->author->email));
          938                 fputs("&gt;\nDate:   ", fp);
          939                 printtime(fp, &(ci->author->when));
          940                 putc('\n', fp);
          941         }
          942         if (ci->msg) {
          943                 putc('\n', fp);
          944                 xmlencode(fp, ci->msg, strlen(ci->msg));
          945         }
          946         fputs("\n</content>\n</entry>\n", fp);
          947 }
          948 
          949 int
          950 writeatom(FILE *fp, int all)
          951 {
          952         struct referenceinfo *ris = NULL;
          953         size_t refcount = 0;
          954         struct commitinfo *ci;
          955         git_revwalk *w = NULL;
          956         git_oid id;
          957         size_t i, m = 100; /* last 'm' commits */
          958 
          959         fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
          960               "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
          961         xmlencode(fp, strippedname, strlen(strippedname));
          962         fputs(", branch HEAD</title>\n<subtitle>", fp);
          963         xmlencode(fp, description, strlen(description));
          964         fputs("</subtitle>\n", fp);
          965 
          966         /* all commits or only tags? */
          967         if (all) {
          968                 git_revwalk_new(&w, repo);
          969                 git_revwalk_push_head(w);
          970                 for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
          971                         if (!(ci = commitinfo_getbyoid(&id)))
          972                                 break;
          973                         printcommitatom(fp, ci, "");
          974                         commitinfo_free(ci);
          975                 }
          976                 git_revwalk_free(w);
          977         } else if (getrefs(&ris, &refcount) != -1) {
          978                 /* references: tags */
          979                 for (i = 0; i < refcount; i++) {
          980                         if (git_reference_is_tag(ris[i].ref))
          981                                 printcommitatom(fp, ris[i].ci,
          982                                                 git_reference_shorthand(ris[i].ref));
          983 
          984                         commitinfo_free(ris[i].ci);
          985                         git_reference_free(ris[i].ref);
          986                 }
          987                 free(ris);
          988         }
          989 
          990         fputs("</feed>\n", fp);
          991 
          992         return 0;
          993 }
          994 
          995 size_t
          996 writeblob(git_object *obj, const char *fpath, const char *filename, size_t filesize)
          997 {
          998         char tmp[PATH_MAX] = "", *d;
          999         size_t lc = 0;
         1000         FILE *fp;
         1001 
         1002         if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
         1003                 errx(1, "path truncated: '%s'", fpath);
         1004         if (!(d = dirname(tmp)))
         1005                 err(1, "dirname");
         1006         if (mkdirp(d))
         1007                 return -1;
         1008 
         1009         fp = efopen(fpath, "w");
         1010         writeheader(fp, filename);
         1011         if (filename[0] == '[')
         1012                 fputs("[|", fp);
         1013         gmitext(fp, filename, strlen(filename));
         1014         fprintf(fp, " (%zuB)\n", filesize);
         1015         fputs("---\n", fp);
         1016 
         1017         if (git_blob_is_binary((git_blob *)obj))
         1018                 fputs("Binary file.\n", fp);
         1019         else
         1020                 lc = writeblobgmi(fp, (git_blob *)obj);
         1021 
         1022         writefooter(fp);
         1023         checkfileerror(fp, fpath, 'w');
         1024         fclose(fp);
         1025 
         1026         return lc;
         1027 }
         1028 
         1029 const char *
         1030 filemode(git_filemode_t m)
         1031 {
         1032         static char mode[11];
         1033 
         1034         memset(mode, '-', sizeof(mode) - 1);
         1035         mode[10] = '\0';
         1036 
         1037         if (S_ISREG(m))
         1038                 mode[0] = '-';
         1039         else if (S_ISBLK(m))
         1040                 mode[0] = 'b';
         1041         else if (S_ISCHR(m))
         1042                 mode[0] = 'c';
         1043         else if (S_ISDIR(m))
         1044                 mode[0] = 'd';
         1045         else if (S_ISFIFO(m))
         1046                 mode[0] = 'p';
         1047         else if (S_ISLNK(m))
         1048                 mode[0] = 'l';
         1049         else if (S_ISSOCK(m))
         1050                 mode[0] = 's';
         1051         else
         1052                 mode[0] = '?';
         1053 
         1054         if (m & S_IRUSR) mode[1] = 'r';
         1055         if (m & S_IWUSR) mode[2] = 'w';
         1056         if (m & S_IXUSR) mode[3] = 'x';
         1057         if (m & S_IRGRP) mode[4] = 'r';
         1058         if (m & S_IWGRP) mode[5] = 'w';
         1059         if (m & S_IXGRP) mode[6] = 'x';
         1060         if (m & S_IROTH) mode[7] = 'r';
         1061         if (m & S_IWOTH) mode[8] = 'w';
         1062         if (m & S_IXOTH) mode[9] = 'x';
         1063 
         1064         if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
         1065         if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
         1066         if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
         1067 
         1068         return mode;
         1069 }
         1070 
         1071 int
         1072 writefilestree(FILE *fp, git_tree *tree, const char *path)
         1073 {
         1074         const git_tree_entry *entry = NULL;
         1075         git_object *obj = NULL;
         1076         const char *entryname;
         1077         char buf[256], filepath[PATH_MAX], entrypath[PATH_MAX], oid[8];
         1078         size_t count, i, lc, filesize;
         1079         int r, ret;
         1080 
         1081         count = git_tree_entrycount(tree);
         1082         for (i = 0; i < count; i++) {
         1083                 if (!(entry = git_tree_entry_byindex(tree, i)) ||
         1084                     !(entryname = git_tree_entry_name(entry)))
         1085                         return -1;
         1086                 joinpath(entrypath, sizeof(entrypath), path, entryname);
         1087 
         1088                 r = snprintf(filepath, sizeof(filepath), "file/%s.gmi",
         1089                          entrypath);
         1090                 if (r < 0 || (size_t)r >= sizeof(filepath))
         1091                         errx(1, "path truncated: 'file/%s.gmi'", entrypath);
         1092 
         1093                 if (!git_tree_entry_to_object(&obj, repo, entry)) {
         1094                         switch (git_object_type(obj)) {
         1095                         case GIT_OBJ_BLOB:
         1096                                 break;
         1097                         case GIT_OBJ_TREE:
         1098                                 /* NOTE: recurses */
         1099                                 ret = writefilestree(fp, (git_tree *)obj,
         1100                                                      entrypath);
         1101                                 git_object_free(obj);
         1102                                 if (ret)
         1103                                         return ret;
         1104                                 continue;
         1105                         default:
         1106                                 git_object_free(obj);
         1107                                 continue;
         1108                         }
         1109 
         1110                         filesize = git_blob_rawsize((git_blob *)obj);
         1111                         lc = writeblob(obj, filepath, entryname, filesize);
         1112 
         1113                         fprintf(fp, "=> %s/", relpath);
         1114                         gmilink(fp, filepath, strlen(filepath));
         1115                         fputc(' ', fp);
         1116                         fputs(filemode(git_tree_entry_filemode(entry)), fp);
         1117                         fputs("  ", fp);
         1118                         utf8pad(buf, sizeof(buf), entrypath, 50, ' ');
         1119                         gmilink(fp, buf, strlen(buf));
         1120                         fputs("  ", fp);
         1121                         if (lc > 0)
         1122                                 fprintf(fp, "%7zuL", lc);
         1123                         else
         1124                                 fprintf(fp, "%7zuB", filesize);
         1125                                                 fputc('\n', fp);
         1126                         git_object_free(obj);
         1127                 } else if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) {
         1128                         /* commit object in tree is a submodule */        
         1129         git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry));
         1130                         gmilink(fp, oid, strlen(oid));
         1131                         fprintf(fp, "=> %s/file/.gitmodules.gmi ", relpath);
         1132                         fputs("m---------  ", fp);
         1133                         
         1134                         fputc('\n', fp);
         1135         
         1136                         
         1137                         /* NOTE: linecount omitted */
         1138                 }
         1139         }
         1140 
         1141         return 0;
         1142 }
         1143 
         1144 int
         1145 writefiles(FILE *fp, const git_oid *id)
         1146 {
         1147         git_tree *tree = NULL;
         1148         git_commit *commit = NULL;
         1149         int ret = -1;
         1150 
         1151         fprintf(fp, "%-10.10s  ", "Mode");
         1152         fprintf(fp, "%-50.50s  ", "Name");
         1153         fprintf(fp, "%8.8s\n", "Size");
         1154 
         1155         if (!git_commit_lookup(&commit, repo, id) &&
         1156             !git_commit_tree(&tree, commit))
         1157                 ret = writefilestree(fp, tree, "");
         1158 
         1159         git_commit_free(commit);
         1160         git_tree_free(tree);
         1161 
         1162         return ret;
         1163 }
         1164 
         1165 int
         1166 writerefs(FILE *fp)
         1167 {
         1168         struct referenceinfo *ris = NULL;
         1169         struct commitinfo *ci;
         1170         size_t count, i, j, refcount;
         1171         const char *titles[] = { "Branches", "Tags" };
         1172         const char *s;
         1173         char buf[256];
         1174 
         1175         if (getrefs(&ris, &refcount) == -1)
         1176                 return -1;
         1177 
         1178         for (i = 0, j = 0, count = 0; i < refcount; i++) {
         1179                 if (j == 0 && git_reference_is_tag(ris[i].ref)) {
         1180                         /* table footer */
         1181                         if (count)
         1182                                 fputs("\n", fp);
         1183                         count = 0;
         1184                         j = 1;
         1185                 }
         1186 
         1187                 /* print header if it has an entry (first). */
         1188                 if (++count == 1) {
         1189                         fprintf(fp, "%s\n", titles[j]);
         1190                         if (j) 
         1191                         {
         1192                         fprintf(fp, "  %-30.30s", "Download");
         1193                         fprintf(fp, "  %-32.32s", "Name");
         1194                         }
         1195                         else
         1196                         {
         1197                         fprintf(fp, "  %-32.32s", "Name");
         1198                         fprintf(fp, "  %-32.32s", "Commit message");
         1199                         }
         1200                         fprintf(fp, "  %-16.16s", "Last commit date");
         1201                         fprintf(fp, "  %s\n", "Author");
         1202                 }
         1203 
         1204                 ci = ris[i].ci;
         1205                 s = git_reference_shorthand(ris[i].ref);
         1206 
         1207                 if (!j){
         1208                 fputs("  ", fp);
         1209                 utf8pad(buf, sizeof(buf), s, 32, ' ');
         1210                 gmilink(fp, buf, strlen(buf));
         1211                 fputs("  ", fp);
         1212                 }
         1213                 if(j)
         1214                 {
         1215                 
         1216                         char *shortrepo = NULL;
         1217                         char *shorttag = NULL;
         1218                         char link[128];
         1219                         shortrepo = strdup(name);
         1220                         shorttag = strdup(s);
         1221                         
         1222                         if (strstr(shortrepo, ".git"))
         1223                         {
         1224                                 size_t len = strlen(shortrepo);
         1225                                 shortrepo[len-4] = 0;
         1226                         }
         1227                         if (shorttag[0] == 'v') shorttag = shorttag+1;
         1228                         sprintf(link,"=>/snapshot/%s-%s.tar.gz ",shortrepo,shorttag);
         1229                         utf8pad(buf, sizeof(buf), link, 32, ' ');
         1230                         gmilink(fp, buf, strlen(buf));
         1231                         fputs("  ", fp);
         1232                         utf8pad(buf, sizeof(buf), s, 32, ' ');
         1233                         gmilink(fp, buf, strlen(buf));
         1234                 }
         1235                 fputs("  ", fp);
         1236                 utf8pad(buf, sizeof(buf), ci->summary, 32, ' ');
         1237                 gmilink(fp, buf, strlen(buf));
         1238                 
         1239                 if (ci->author)
         1240                         printtimeshort(fp, &(ci->author->when));
         1241                 else
         1242                         fputs("                ", fp);
         1243                 fputs("  ", fp);
         1244                 if (ci->author) {
         1245                         utf8pad(buf, sizeof(buf), ci->author->name, 25, '\0');
         1246                         gmilink(fp, buf, strlen(buf));
         1247                 }
         1248                 fputs("\n", fp);
         1249         }
         1250         /* table footer */
         1251         if (count)
         1252                 fputs("\n", fp);
         1253 
         1254         for (i = 0; i < refcount; i++) {
         1255                 commitinfo_free(ris[i].ci);
         1256                 git_reference_free(ris[i].ref);
         1257         }
         1258         free(ris);
         1259 
         1260         return 0;
         1261 }
         1262 
         1263 void
         1264 usage(char *argv0)
         1265 {
         1266         fprintf(stderr, "usage: %s [-b baseprefix] [-c cachefile | -l commits] "
         1267                 "[-u baseurl] repodir\n", argv0);
         1268         exit(1);
         1269 }
         1270 
         1271 int
         1272 main(int argc, char *argv[])
         1273 {
         1274         git_object *obj = NULL;
         1275         const git_oid *head = NULL;
         1276         mode_t mask;
         1277         FILE *fp, *fpread;
         1278         char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p;
         1279         char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ];
         1280         size_t n;
         1281         int i, fd;
         1282 
         1283         setlocale(LC_CTYPE, "");
         1284 
         1285         for (i = 1; i < argc; i++) {
         1286                 if (argv[i][0] != '-') {
         1287                         if (repodir)
         1288                                 usage(argv[0]);
         1289                         repodir = argv[i];
         1290                 } else if (argv[i][1] == 'b') {
         1291                         if (i + 1 >= argc)
         1292                                 usage(argv[0]);
         1293                         relpath = argv[++i];
         1294                 } else if (argv[i][1] == 'c') {
         1295                         if (nlogcommits > 0 || i + 1 >= argc)
         1296                                 usage(argv[0]);
         1297                         cachefile = argv[++i];
         1298                 } else if (argv[i][1] == 'l') {
         1299                         if (cachefile || i + 1 >= argc)
         1300                                 usage(argv[0]);
         1301                         errno = 0;
         1302                         nlogcommits = strtoll(argv[++i], &p, 10);
         1303                         if (argv[i][0] == '\0' || *p != '\0' ||
         1304                             nlogcommits <= 0 || errno)
         1305                                 usage(argv[0]);
         1306                 } else if (argv[i][1] == 'u') {
         1307                         if (i + 1 >= argc)
         1308                                 usage(argv[0]);
         1309                         baseurl = argv[++i];
         1310                 }
         1311         }
         1312         if (!repodir)
         1313                 usage(argv[0]);
         1314 
         1315         if (!realpath(repodir, repodirabs))
         1316                 err(1, "realpath");
         1317 
         1318         /* do not search outside the git repository:
         1319            GIT_CONFIG_LEVEL_APP is the highest level currently */
         1320         git_libgit2_init();
         1321         for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
         1322                 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
         1323         /* do not require the git repository to be owned by the current user */
         1324         git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
         1325 
         1326 #ifdef __OpenBSD__
         1327         if (unveil(repodir, "r") == -1)
         1328                 err(1, "unveil: %s", repodir);
         1329         if (unveil(".", "rwc") == -1)
         1330                 err(1, "unveil: .");
         1331         if (cachefile && unveil(cachefile, "rwc") == -1)
         1332                 err(1, "unveil: %s", cachefile);
         1333 
         1334         if (cachefile) {
         1335                 if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
         1336                         err(1, "pledge");
         1337         } else {
         1338                 if (pledge("stdio rpath wpath cpath", NULL) == -1)
         1339                         err(1, "pledge");
         1340         }
         1341 #endif
         1342 
         1343         if (git_repository_open_ext(&repo, repodir,
         1344                 GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) {
         1345                 fprintf(stderr, "%s: cannot open repository\n", argv[0]);
         1346                 return 1;
         1347         }
         1348 
         1349         /* find HEAD */
         1350         if (!git_revparse_single(&obj, repo, "HEAD"))
         1351                 head = git_object_id(obj);
         1352         git_object_free(obj);
         1353 
         1354         /* use directory name as name */
         1355         if ((name = strrchr(repodirabs, '/')))
         1356                 name++;
         1357         else
         1358                 name = "";
         1359 
         1360         /* strip .git suffix */
         1361         if (!(strippedname = strdup(name)))
         1362                 err(1, "strdup");
         1363         if ((p = strrchr(strippedname, '.')))
         1364                 if (!strcmp(p, ".git"))
         1365                         *p = '\0';
         1366 
         1367         /* read description or .git/description */
         1368         joinpath(path, sizeof(path), repodir, "description");
         1369         if (!(fpread = fopen(path, "r"))) {
         1370                 joinpath(path, sizeof(path), repodir, ".git/description");
         1371                 fpread = fopen(path, "r");
         1372         }
         1373         if (fpread) {
         1374                 if (!fgets(description, sizeof(description), fpread))
         1375                         description[0] = '\0';
         1376                 checkfileerror(fpread, path, 'r');
         1377                 fclose(fpread);
         1378         }
         1379 
         1380         /* read url or .git/url */
         1381         joinpath(path, sizeof(path), repodir, "url");
         1382         if (!(fpread = fopen(path, "r"))) {
         1383                 joinpath(path, sizeof(path), repodir, ".git/url");
         1384                 fpread = fopen(path, "r");
         1385         }
         1386         if (fpread) {
         1387                 if (!fgets(cloneurl, sizeof(cloneurl), fpread))
         1388                         cloneurl[0] = '\0';
         1389                 checkfileerror(fpread, path, 'r');
         1390                 fclose(fpread);
         1391                 cloneurl[strcspn(cloneurl, "\n")] = '\0';
         1392         }
         1393 
         1394         /* check LICENSE */
         1395         for (i = 0; i < LEN(licensefiles) && !license; i++) {
         1396                 if (!git_revparse_single(&obj, repo, licensefiles[i]) &&
         1397                     git_object_type(obj) == GIT_OBJ_BLOB)
         1398                         license = licensefiles[i] + strlen("HEAD:");
         1399                 git_object_free(obj);
         1400         }
         1401 
         1402         /* check README */
         1403         for (i = 0; i < LEN(readmefiles) && !readme; i++) {
         1404                 if (!git_revparse_single(&obj, repo, readmefiles[i]) &&
         1405                     git_object_type(obj) == GIT_OBJ_BLOB)
         1406                         readme = readmefiles[i] + strlen("HEAD:");
         1407                 git_object_free(obj);
         1408         }
         1409 
         1410         if (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") &&
         1411             git_object_type(obj) == GIT_OBJ_BLOB)
         1412                 submodules = ".gitmodules";
         1413         git_object_free(obj);
         1414 
         1415         /* log for HEAD */
         1416         fp = efopen("log.gmi", "w");
         1417         mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
         1418         writeheader(fp, "Log");
         1419 
         1420         fprintf(fp, "%-16.16s  ", "Date");
         1421         fprintf(fp, "%-40.40s  ", "Commit message");
         1422         fprintf(fp, "%s\n", "Author");
         1423 
         1424         if (cachefile && head) {
         1425                 /* read from cache file (does not need to exist) */
         1426                 if ((rcachefp = fopen(cachefile, "r"))) {
         1427                         if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp))
         1428                                 errx(1, "%s: no object id", cachefile);
         1429                         if (git_oid_fromstr(&lastoid, lastoidstr))
         1430                                 errx(1, "%s: invalid object id", cachefile);
         1431                 }
         1432 
         1433                 /* write log to (temporary) cache */
         1434                 if ((fd = mkstemp(tmppath)) == -1)
         1435                         err(1, "mkstemp");
         1436                 if (!(wcachefp = fdopen(fd, "w")))
         1437                         err(1, "fdopen: '%s'", tmppath);
         1438                 /* write last commit id (HEAD) */
         1439                 git_oid_tostr(buf, sizeof(buf), head);
         1440                 fprintf(wcachefp, "%s\n", buf);
         1441 
         1442                 writelog(fp, head);
         1443 
         1444                 if (rcachefp) {
         1445                         /* append previous log to log.gmi and the new cache */
         1446                         while (!feof(rcachefp)) {
         1447                                 n = fread(buf, 1, sizeof(buf), rcachefp);
         1448                                 if (ferror(rcachefp))
         1449                                         break;
         1450                                 if (fwrite(buf, 1, n, fp) != n ||
         1451                                     fwrite(buf, 1, n, wcachefp) != n)
         1452                                         break;
         1453                         }
         1454                         checkfileerror(rcachefp, cachefile, 'r');
         1455                         fclose(rcachefp);
         1456                 }
         1457                 checkfileerror(wcachefp, tmppath, 'w');
         1458                 fclose(wcachefp);
         1459         } else {
         1460                 if (head)
         1461                         writelog(fp, head);
         1462         }
         1463         fprintf(fp, "\n=> %s/atom.xml Atom Feed\n", relpath);
         1464         writefooter(fp);
         1465         checkfileerror(fp, "log.gmi", 'w');
         1466         fclose(fp);
         1467 
         1468         /* files for HEAD */
         1469         fp = efopen("files.gmi", "w");
         1470         writeheader(fp, "Files");
         1471         if (head)
         1472                 writefiles(fp, head);
         1473         writefooter(fp);
         1474         checkfileerror(fp, "files.gmi", 'w');
         1475         fclose(fp);
         1476 
         1477         /* summary page with branches and tags */
         1478         fp = efopen("refs.gmi", "w");
         1479         writeheader(fp, "Refs");
         1480         writerefs(fp);
         1481         writefooter(fp);
         1482         checkfileerror(fp, "refs.gmi", 'w');
         1483         fclose(fp);
         1484 
         1485         /* Atom feed */
         1486         fp = efopen("atom.xml", "w");
         1487         writeatom(fp, 1);
         1488         checkfileerror(fp, "atom.xml", 'w');
         1489         fclose(fp);
         1490 
         1491         /* Atom feed for tags / releases */
         1492         fp = efopen("tags.xml", "w");
         1493         writeatom(fp, 0);
         1494         checkfileerror(fp, "tags.xml", 'w');
         1495         fclose(fp);
         1496 
         1497         /* rename new cache file on success */
         1498         if (cachefile && head) {
         1499                 if (rename(tmppath, cachefile))
         1500                         err(1, "rename: '%s' to '%s'", tmppath, cachefile);
         1501                 umask((mask = umask(0)));
         1502                 if (chmod(cachefile,
         1503                     (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask))
         1504                         err(1, "chmod: '%s'", cachefile);
         1505         }
         1506 
         1507         /* cleanup */
         1508         git_repository_free(repo);
         1509         git_libgit2_shutdown();
         1510 
         1511         return 0;
         1512 }