URI: 
       stagit-gopher.c - stagit-gopher - A git gopher frontend. (mirror)
  HTML git clone git://bitreich.org/stagit-gopher/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/stagit-gopher/
   DIR Log
   DIR Files
   DIR Refs
   DIR Tags
   DIR README
   DIR LICENSE
       ---
       stagit-gopher.c (35494B)
       ---
            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 .gph format, with newlines */
          460 void
          461 gphtextnl(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                 /* escape '[' with "[|" at the start of a line */
          467                 if (!n && s[i] == '[')
          468                         fputs("[|", fp);
          469 
          470                 switch (s[i]) {
          471                 case '\t': fputs("        ", fp);
          472                 case '\r': break;
          473                 default: putc(s[i], fp);
          474                 }
          475                 n = (s[i] != '\n');
          476         }
          477 }
          478 
          479 /* Escape characters in text in geomyidae .gph format,
          480    newlines are ignored */
          481 void
          482 gphtext(FILE *fp, const char *s, size_t len)
          483 {
          484         size_t i;
          485 
          486         for (i = 0; *s && i < len; s++, i++) {
          487                 switch (*s) {
          488                 case '\r': /* ignore CR */
          489                 case '\n': /* ignore LF */
          490                         break;
          491                 case '\t':
          492                         fputs("        ", fp);
          493                         break;
          494                 default:
          495                         putc(*s, fp);
          496                         break;
          497                 }
          498         }
          499 }
          500 
          501 /* Escape characters in links in geomyidae .gph format */
          502 void
          503 gphlink(FILE *fp, const char *s, size_t len)
          504 {
          505         size_t i;
          506 
          507         for (i = 0; *s && i < len; s++, i++) {
          508                 switch (*s) {
          509                 case '\r': /* ignore CR */
          510                 case '\n': /* ignore LF */
          511                         break;
          512                 case '\t':
          513                         fputs("        ", fp);
          514                         break;
          515                 case '|': /* escape separators */
          516                         fputs("\\|", fp);
          517                         break;
          518                 default:
          519                         putc(*s, fp);
          520                         break;
          521                 }
          522         }
          523 }
          524 
          525 int
          526 mkdirp(const char *path)
          527 {
          528         char tmp[PATH_MAX], *p;
          529 
          530         if (strlcpy(tmp, path, sizeof(tmp)) >= sizeof(tmp))
          531                 errx(1, "path truncated: '%s'", path);
          532         for (p = tmp + (tmp[0] == '/'); *p; p++) {
          533                 if (*p != '/')
          534                         continue;
          535                 *p = '\0';
          536                 if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
          537                         return -1;
          538                 *p = '/';
          539         }
          540         if (mkdir(tmp, S_IRWXU | S_IRWXG | S_IRWXO) < 0 && errno != EEXIST)
          541                 return -1;
          542         return 0;
          543 }
          544 
          545 void
          546 printtimez(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;
          553         if (!(intm = gmtime(&t)))
          554                 return;
          555         strftime(out, sizeof(out), "%Y-%m-%dT%H:%M:%SZ", intm);
          556         fputs(out, fp);
          557 }
          558 
          559 void
          560 printtime(FILE *fp, const git_time *intime)
          561 {
          562         struct tm *intm;
          563         time_t t;
          564         char out[32];
          565 
          566         t = (time_t)intime->time + (intime->offset * 60);
          567         if (!(intm = gmtime(&t)))
          568                 return;
          569         strftime(out, sizeof(out), "%a, %e %b %Y %H:%M:%S", intm);
          570         if (intime->offset < 0)
          571                 fprintf(fp, "%s -%02d%02d", out,
          572                             -(intime->offset) / 60, -(intime->offset) % 60);
          573         else
          574                 fprintf(fp, "%s +%02d%02d", out,
          575                             intime->offset / 60, intime->offset % 60);
          576 }
          577 
          578 void
          579 printtimeshort(FILE *fp, const git_time *intime)
          580 {
          581         struct tm *intm;
          582         time_t t;
          583         char out[32];
          584 
          585         t = (time_t)intime->time;
          586         if (!(intm = gmtime(&t)))
          587                 return;
          588         strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
          589         fputs(out, fp);
          590 }
          591 
          592 void
          593 writeheader(FILE *fp, const char *title)
          594 {
          595         if (title[0] == '[')
          596                 fputs("[|", fp);
          597         gphtext(fp, title, strlen(title));
          598         if (title[0] && strippedname[0])
          599                 fputs(" - ", fp);
          600         gphtext(fp, strippedname, strlen(strippedname));
          601         if (description[0])
          602                 fputs(" - ", fp);
          603         gphtext(fp, description, strlen(description));
          604         fputs("\n", fp);
          605         if (cloneurl[0]) {
          606                 fputs("[h|git clone ", fp);
          607                 gphlink(fp, cloneurl, strlen(cloneurl));
          608                 fputs("|URL:", fp);
          609                 gphlink(fp, cloneurl, strlen(cloneurl));
          610                 fputs("|server|port]\n", fp);
          611         }
          612         fprintf(fp, "[1|Log|%s/log.gph|server|port]\n", relpath);
          613         fprintf(fp, "[1|Files|%s/files.gph|server|port]\n", relpath);
          614         fprintf(fp, "[1|Refs|%s/refs.gph|server|port]\n", relpath);
          615         if (submodules)
          616                 fprintf(fp, "[1|Submodules|%s/file/%s.gph|server|port]\n",
          617                         relpath, submodules);
          618         if (readme)
          619                 fprintf(fp, "[1|README|%s/file/%s.gph|server|port]\n",
          620                         relpath, readme);
          621         if (license)
          622                 fprintf(fp, "[1|LICENSE|%s/file/%s.gph|server|port]\n",
          623                         relpath, license);
          624         fputs("---\n", fp);
          625 }
          626 
          627 void
          628 writefooter(FILE *fp)
          629 {
          630 }
          631 
          632 size_t
          633 writeblobgph(FILE *fp, const git_blob *blob)
          634 {
          635         size_t n = 0, i, j, len, prev;
          636         const char *nfmt = "%6zu ";
          637         const char *s = git_blob_rawcontent(blob);
          638 
          639         len = git_blob_rawsize(blob);
          640         if (len > 0) {
          641                 for (i = 0, prev = 0; i < len; i++) {
          642                         if (s[i] != '\n')
          643                                 continue;
          644                         n++;
          645                         fprintf(fp, nfmt, n, n, n);
          646                         for (j = prev; j <= i && s[j]; j++) {
          647                                 switch (s[j]) {
          648                                 case '\r': break;
          649                                 case '\t': fputs("        ", fp); break;
          650                                 default: putc(s[j], fp);
          651                                 }
          652                         }
          653                         prev = i + 1;
          654                 }
          655                 /* trailing data */
          656                 if ((len - prev) > 0) {
          657                         n++;
          658                         fprintf(fp, nfmt, n, n, n);
          659                         for (j = prev; j < len - prev && s[j]; j++) {
          660                                 switch (s[j]) {
          661                                 case '\r': break;
          662                                 case '\t': fputs("        ", fp); break;
          663                                 default: putc(s[j], fp);
          664                                 }
          665                         }
          666                 }
          667         }
          668 
          669         return n;
          670 }
          671 
          672 void
          673 printcommit(FILE *fp, struct commitinfo *ci)
          674 {
          675         fprintf(fp, "[1|commit %s|%s/commit/%s.gph|server|port]\n",
          676                 ci->oid, relpath, ci->oid);
          677 
          678         if (ci->parentoid[0])
          679                 fprintf(fp, "[1|parent %s|%s/commit/%s.gph|server|port]\n",
          680                         ci->parentoid, relpath, ci->parentoid);
          681 
          682         if (ci->author) {
          683                 fputs("[h|Author: ", fp);
          684                 gphlink(fp, ci->author->name, strlen(ci->author->name));
          685                 fputs(" <", fp);
          686                 gphlink(fp, ci->author->email, strlen(ci->author->email));
          687                 fputs(">|URL:mailto:", fp);
          688                 gphlink(fp, ci->author->email, strlen(ci->author->email));
          689                 fputs("|server|port]\n", fp);
          690                 fputs("Date:   ", fp);
          691                 printtime(fp, &(ci->author->when));
          692                 putc('\n', fp);
          693         }
          694         if (ci->msg) {
          695                 putc('\n', fp);
          696                 gphtextnl(fp, ci->msg, strlen(ci->msg));
          697                 putc('\n', fp);
          698         }
          699 }
          700 
          701 void
          702 printshowfile(FILE *fp, struct commitinfo *ci)
          703 {
          704         const git_diff_delta *delta;
          705         const git_diff_hunk *hunk;
          706         const git_diff_line *line;
          707         git_patch *patch;
          708         size_t nhunks, nhunklines, changed, add, del, total, i, j, k;
          709         char buf[256], filename[256], linestr[32];
          710         int c;
          711 
          712         printcommit(fp, ci);
          713 
          714         if (!ci->deltas)
          715                 return;
          716 
          717         if (ci->filecount > 1000   ||
          718             ci->ndeltas   > 1000   ||
          719             ci->addcount  > 100000 ||
          720             ci->delcount  > 100000) {
          721                 fputs("\nDiff is too large, output suppressed.\n", fp);
          722                 return;
          723         }
          724 
          725         /* diff stat */
          726         fputs("Diffstat:\n", fp);
          727         for (i = 0; i < ci->ndeltas; i++) {
          728                 delta = git_patch_get_delta(ci->deltas[i]->patch);
          729 
          730                 switch (delta->status) {
          731                 case GIT_DELTA_ADDED:      c = 'A'; break;
          732                 case GIT_DELTA_COPIED:     c = 'C'; break;
          733                 case GIT_DELTA_DELETED:    c = 'D'; break;
          734                 case GIT_DELTA_MODIFIED:   c = 'M'; break;
          735                 case GIT_DELTA_RENAMED:    c = 'R'; break;
          736                 case GIT_DELTA_TYPECHANGE: c = 'T'; break;
          737                 default:                   c = ' '; break;
          738                 }
          739 
          740                 if (strcmp(delta->old_file.path, delta->new_file.path)) {
          741                         snprintf(filename, sizeof(filename), "%s -> %s",
          742                                 delta->old_file.path, delta->new_file.path);
          743                         utf8pad(buf, sizeof(buf), filename, 35, ' ');
          744                 } else {
          745                         utf8pad(buf, sizeof(buf), delta->old_file.path, 35, ' ');
          746                 }
          747                 fprintf(fp, "  %c ", c);
          748                 gphtext(fp, buf, strlen(buf));
          749 
          750                 add = ci->deltas[i]->addcount;
          751                 del = ci->deltas[i]->delcount;
          752                 changed = add + del;
          753                 total = sizeof(linestr) - 2;
          754                 if (changed > total) {
          755                         if (add)
          756                                 add = ((float)total / changed * add) + 1;
          757                         if (del)
          758                                 del = ((float)total / changed * del) + 1;
          759                 }
          760                 memset(&linestr, '+', add);
          761                 memset(&linestr[add], '-', del);
          762 
          763                 fprintf(fp, " | %7zu ",
          764                         ci->deltas[i]->addcount + ci->deltas[i]->delcount);
          765                 fwrite(&linestr, 1, add, fp);
          766                 fwrite(&linestr[add], 1, del, fp);
          767                 fputs("\n", fp);
          768         }
          769         fprintf(fp, "\n%zu file%s changed, %zu insertion%s(+), %zu deletion%s(-)\n",
          770                 ci->filecount, ci->filecount == 1 ? "" : "s",
          771                 ci->addcount,  ci->addcount  == 1 ? "" : "s",
          772                 ci->delcount,  ci->delcount  == 1 ? "" : "s");
          773 
          774         fputs("---\n", fp);
          775 
          776         for (i = 0; i < ci->ndeltas; i++) {
          777                 patch = ci->deltas[i]->patch;
          778                 delta = git_patch_get_delta(patch);
          779                 /* NOTE: only links to new path */
          780                 fputs("[1|diff --git a/", fp);
          781                 gphlink(fp, delta->old_file.path, strlen(delta->old_file.path));
          782                 fputs(" b/", fp);
          783                 gphlink(fp, delta->new_file.path, strlen(delta->new_file.path));
          784                 fprintf(fp, "|%s/file/", relpath);
          785                 gphlink(fp, delta->new_file.path, strlen(delta->new_file.path));
          786                 fputs(".gph|server|port]\n", fp);
          787 
          788                 /* check binary data */
          789                 if (delta->flags & GIT_DIFF_FLAG_BINARY) {
          790                         fputs("Binary files differ.\n", fp);
          791                         continue;
          792                 }
          793 
          794                 nhunks = git_patch_num_hunks(patch);
          795                 for (j = 0; j < nhunks; j++) {
          796                         if (git_patch_get_hunk(&hunk, &nhunklines, patch, j))
          797                                 break;
          798 
          799                         if (hunk->header_len > 0 && hunk->header[0] == '[')
          800                                 fputs("[|", fp);
          801                         gphtext(fp, hunk->header, hunk->header_len);
          802                         putc('\n', fp);
          803 
          804                         for (k = 0; ; k++) {
          805                                 if (git_patch_get_line_in_hunk(&line, patch, j, k))
          806                                         break;
          807                                 if (line->old_lineno == -1)
          808                                         fputs("+", fp);
          809                                 else if (line->new_lineno == -1)
          810                                         fputs("-", fp);
          811                                 else
          812                                         fputs(" ", fp);
          813                                 gphtext(fp, line->content, line->content_len);
          814                                 putc('\n', fp);
          815                         }
          816                 }
          817         }
          818 }
          819 
          820 void
          821 writelogline(FILE *fp, struct commitinfo *ci)
          822 {
          823         char buf[256];
          824 
          825         fputs("[1|", fp);
          826         if (ci->author)
          827                 printtimeshort(fp, &(ci->author->when));
          828         else
          829                 fputs("                ", fp);
          830         fputs("  ", fp);
          831         utf8pad(buf, sizeof(buf), ci->summary ? ci->summary : "", 40, ' ');
          832         gphlink(fp, buf, strlen(buf));
          833         fputs("  ", fp);
          834         utf8pad(buf, sizeof(buf), ci->author ? ci->author->name : "", 19, '\0');
          835         gphlink(fp, buf, strlen(buf));
          836         fprintf(fp, "|%s/commit/%s.gph|server|port]\n", relpath, ci->oid);
          837 }
          838 
          839 int
          840 writelog(FILE *fp, const git_oid *oid)
          841 {
          842         struct commitinfo *ci;
          843         git_revwalk *w = NULL;
          844         git_oid id;
          845         char path[PATH_MAX], oidstr[GIT_OID_HEXSZ + 1];
          846         FILE *fpfile;
          847         size_t remcommits = 0;
          848         int r;
          849 
          850         git_revwalk_new(&w, repo);
          851         git_revwalk_push(w, oid);
          852 
          853         while (!git_revwalk_next(&id, w)) {
          854                 if (cachefile && !memcmp(&id, &lastoid, sizeof(id)))
          855                         break;
          856 
          857                 git_oid_tostr(oidstr, sizeof(oidstr), &id);
          858                 r = snprintf(path, sizeof(path), "commit/%s.gph", oidstr);
          859                 if (r < 0 || (size_t)r >= sizeof(path))
          860                         errx(1, "path truncated: 'commit/%s.gph'", oidstr);
          861                 r = access(path, F_OK);
          862 
          863                 /* optimization: if there are no log lines to write and
          864                    the commit file already exists: skip the diffstat */
          865                 if (!nlogcommits) {
          866                         remcommits++;
          867                         if (!r)
          868                                 continue;
          869                 }
          870 
          871                 if (!(ci = commitinfo_getbyoid(&id)))
          872                         break;
          873 
          874                 if (nlogcommits != 0) {
          875                         writelogline(fp, ci);
          876                         if (nlogcommits > 0)
          877                                 nlogcommits--;
          878                 }
          879 
          880                 if (cachefile)
          881                         writelogline(wcachefp, ci);
          882 
          883                 /* check if file exists if so skip it */
          884                 if (r) {
          885                         /* lookup stats: only required here for gopher */
          886                         if (commitinfo_getstats(ci) == -1)
          887                                 goto err;
          888 
          889                         fpfile = efopen(path, "w");
          890                         writeheader(fpfile, ci->summary);
          891                         printshowfile(fpfile, ci);
          892                         writefooter(fpfile);
          893                         checkfileerror(fpfile, path, 'w');
          894                         fclose(fpfile);
          895                 }
          896 err:
          897                 commitinfo_free(ci);
          898         }
          899         git_revwalk_free(w);
          900 
          901         if (nlogcommits == 0 && remcommits != 0) {
          902                 fprintf(fp, "%16.16s  "
          903                         "%zu more commits remaining, fetch the repository\n",
          904                         "", remcommits);
          905         }
          906 
          907         return 0;
          908 }
          909 
          910 void
          911 printcommitatom(FILE *fp, struct commitinfo *ci, const char *tag)
          912 {
          913         fputs("<entry>\n", fp);
          914 
          915         fprintf(fp, "<id>%s</id>\n", ci->oid);
          916         if (ci->author) {
          917                 fputs("<published>", fp);
          918                 printtimez(fp, &(ci->author->when));
          919                 fputs("</published>\n", fp);
          920         }
          921         if (ci->committer) {
          922                 fputs("<updated>", fp);
          923                 printtimez(fp, &(ci->committer->when));
          924                 fputs("</updated>\n", fp);
          925         }
          926         if (ci->summary) {
          927                 fputs("<title>", fp);
          928                 if (tag && tag[0]) {
          929                         fputs("[", fp);
          930                         xmlencode(fp, tag, strlen(tag));
          931                         fputs("] ", fp);
          932                 }
          933                 xmlencode(fp, ci->summary, strlen(ci->summary));
          934                 fputs("</title>\n", fp);
          935         }
          936         fprintf(fp, "<link rel=\"alternate\" href=\"%scommit/%s.gph\" />\n",
          937                 baseurl, ci->oid);
          938 
          939         if (ci->author) {
          940                 fputs("<author>\n<name>", fp);
          941                 xmlencode(fp, ci->author->name, strlen(ci->author->name));
          942                 fputs("</name>\n<email>", fp);
          943                 xmlencode(fp, ci->author->email, strlen(ci->author->email));
          944                 fputs("</email>\n</author>\n", fp);
          945         }
          946 
          947         fputs("<content>", fp);
          948         fprintf(fp, "commit %s\n", ci->oid);
          949         if (ci->parentoid[0])
          950                 fprintf(fp, "parent %s\n", ci->parentoid);
          951         if (ci->author) {
          952                 fputs("Author: ", fp);
          953                 xmlencode(fp, ci->author->name, strlen(ci->author->name));
          954                 fputs(" &lt;", fp);
          955                 xmlencode(fp, ci->author->email, strlen(ci->author->email));
          956                 fputs("&gt;\nDate:   ", fp);
          957                 printtime(fp, &(ci->author->when));
          958                 putc('\n', fp);
          959         }
          960         if (ci->msg) {
          961                 putc('\n', fp);
          962                 xmlencode(fp, ci->msg, strlen(ci->msg));
          963         }
          964         fputs("\n</content>\n</entry>\n", fp);
          965 }
          966 
          967 int
          968 writeatom(FILE *fp, int all)
          969 {
          970         struct referenceinfo *ris = NULL;
          971         size_t refcount = 0;
          972         struct commitinfo *ci;
          973         git_revwalk *w = NULL;
          974         git_oid id;
          975         size_t i, m = 100; /* last 'm' commits */
          976 
          977         fputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
          978               "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n<title>", fp);
          979         xmlencode(fp, strippedname, strlen(strippedname));
          980         fputs(", branch HEAD</title>\n<subtitle>", fp);
          981         xmlencode(fp, description, strlen(description));
          982         fputs("</subtitle>\n", fp);
          983 
          984         /* all commits or only tags? */
          985         if (all) {
          986                 git_revwalk_new(&w, repo);
          987                 git_revwalk_push_head(w);
          988                 for (i = 0; i < m && !git_revwalk_next(&id, w); i++) {
          989                         if (!(ci = commitinfo_getbyoid(&id)))
          990                                 break;
          991                         printcommitatom(fp, ci, "");
          992                         commitinfo_free(ci);
          993                 }
          994                 git_revwalk_free(w);
          995         } else if (getrefs(&ris, &refcount) != -1) {
          996                 /* references: tags */
          997                 for (i = 0; i < refcount; i++) {
          998                         if (git_reference_is_tag(ris[i].ref))
          999                                 printcommitatom(fp, ris[i].ci,
         1000                                                 git_reference_shorthand(ris[i].ref));
         1001 
         1002                         commitinfo_free(ris[i].ci);
         1003                         git_reference_free(ris[i].ref);
         1004                 }
         1005                 free(ris);
         1006         }
         1007 
         1008         fputs("</feed>\n", fp);
         1009 
         1010         return 0;
         1011 }
         1012 
         1013 size_t
         1014 writeblob(git_object *obj, const char *fpath, const char *filename, size_t filesize)
         1015 {
         1016         char tmp[PATH_MAX] = "", *d;
         1017         size_t lc = 0;
         1018         FILE *fp;
         1019 
         1020         if (strlcpy(tmp, fpath, sizeof(tmp)) >= sizeof(tmp))
         1021                 errx(1, "path truncated: '%s'", fpath);
         1022         if (!(d = dirname(tmp)))
         1023                 err(1, "dirname");
         1024         if (mkdirp(d))
         1025                 return -1;
         1026 
         1027         fp = efopen(fpath, "w");
         1028         writeheader(fp, filename);
         1029         if (filename[0] == '[')
         1030                 fputs("[|", fp);
         1031         gphtext(fp, filename, strlen(filename));
         1032         fprintf(fp, " (%zuB)\n", filesize);
         1033         fputs("---\n", fp);
         1034 
         1035         if (git_blob_is_binary((git_blob *)obj))
         1036                 fputs("Binary file.\n", fp);
         1037         else
         1038                 lc = writeblobgph(fp, (git_blob *)obj);
         1039 
         1040         writefooter(fp);
         1041         checkfileerror(fp, fpath, 'w');
         1042         fclose(fp);
         1043 
         1044         return lc;
         1045 }
         1046 
         1047 const char *
         1048 filemode(git_filemode_t m)
         1049 {
         1050         static char mode[11];
         1051 
         1052         memset(mode, '-', sizeof(mode) - 1);
         1053         mode[10] = '\0';
         1054 
         1055         if (S_ISREG(m))
         1056                 mode[0] = '-';
         1057         else if (S_ISBLK(m))
         1058                 mode[0] = 'b';
         1059         else if (S_ISCHR(m))
         1060                 mode[0] = 'c';
         1061         else if (S_ISDIR(m))
         1062                 mode[0] = 'd';
         1063         else if (S_ISFIFO(m))
         1064                 mode[0] = 'p';
         1065         else if (S_ISLNK(m))
         1066                 mode[0] = 'l';
         1067         else if (S_ISSOCK(m))
         1068                 mode[0] = 's';
         1069         else
         1070                 mode[0] = '?';
         1071 
         1072         if (m & S_IRUSR) mode[1] = 'r';
         1073         if (m & S_IWUSR) mode[2] = 'w';
         1074         if (m & S_IXUSR) mode[3] = 'x';
         1075         if (m & S_IRGRP) mode[4] = 'r';
         1076         if (m & S_IWGRP) mode[5] = 'w';
         1077         if (m & S_IXGRP) mode[6] = 'x';
         1078         if (m & S_IROTH) mode[7] = 'r';
         1079         if (m & S_IWOTH) mode[8] = 'w';
         1080         if (m & S_IXOTH) mode[9] = 'x';
         1081 
         1082         if (m & S_ISUID) mode[3] = (mode[3] == 'x') ? 's' : 'S';
         1083         if (m & S_ISGID) mode[6] = (mode[6] == 'x') ? 's' : 'S';
         1084         if (m & S_ISVTX) mode[9] = (mode[9] == 'x') ? 't' : 'T';
         1085 
         1086         return mode;
         1087 }
         1088 
         1089 int
         1090 writefilestree(FILE *fp, git_tree *tree, const char *path)
         1091 {
         1092         const git_tree_entry *entry = NULL;
         1093         git_object *obj = NULL;
         1094         const char *entryname;
         1095         char buf[256], filepath[PATH_MAX], entrypath[PATH_MAX], oid[8];
         1096         size_t count, i, lc, filesize;
         1097         int r, ret;
         1098 
         1099         count = git_tree_entrycount(tree);
         1100         for (i = 0; i < count; i++) {
         1101                 if (!(entry = git_tree_entry_byindex(tree, i)) ||
         1102                     !(entryname = git_tree_entry_name(entry)))
         1103                         return -1;
         1104                 joinpath(entrypath, sizeof(entrypath), path, entryname);
         1105 
         1106                 r = snprintf(filepath, sizeof(filepath), "file/%s.gph",
         1107                          entrypath);
         1108                 if (r < 0 || (size_t)r >= sizeof(filepath))
         1109                         errx(1, "path truncated: 'file/%s.gph'", entrypath);
         1110 
         1111                 if (!git_tree_entry_to_object(&obj, repo, entry)) {
         1112                         switch (git_object_type(obj)) {
         1113                         case GIT_OBJ_BLOB:
         1114                                 break;
         1115                         case GIT_OBJ_TREE:
         1116                                 /* NOTE: recurses */
         1117                                 ret = writefilestree(fp, (git_tree *)obj,
         1118                                                      entrypath);
         1119                                 git_object_free(obj);
         1120                                 if (ret)
         1121                                         return ret;
         1122                                 continue;
         1123                         default:
         1124                                 git_object_free(obj);
         1125                                 continue;
         1126                         }
         1127 
         1128                         filesize = git_blob_rawsize((git_blob *)obj);
         1129                         lc = writeblob(obj, filepath, entryname, filesize);
         1130 
         1131                         fputs("[1|", fp);
         1132                         fputs(filemode(git_tree_entry_filemode(entry)), fp);
         1133                         fputs("  ", fp);
         1134                         utf8pad(buf, sizeof(buf), entrypath, 50, ' ');
         1135                         gphlink(fp, buf, strlen(buf));
         1136                         fputs("  ", fp);
         1137                         if (lc > 0)
         1138                                 fprintf(fp, "%7zuL", lc);
         1139                         else
         1140                                 fprintf(fp, "%7zuB", filesize);
         1141                         fprintf(fp, "|%s/", relpath);
         1142                         gphlink(fp, filepath, strlen(filepath));
         1143                         fputs("|server|port]\n", fp);
         1144                         git_object_free(obj);
         1145                 } else if (git_tree_entry_type(entry) == GIT_OBJ_COMMIT) {
         1146                         /* commit object in tree is a submodule */
         1147                         fputs("[1|m---------  ", fp);
         1148                         gphlink(fp, entrypath, strlen(entrypath));
         1149                         fputs(" @ ", fp);
         1150                         git_oid_tostr(oid, sizeof(oid), git_tree_entry_id(entry));
         1151                         gphlink(fp, oid, strlen(oid));
         1152                         fprintf(fp, "|%s/file/.gitmodules.gph|server|port]\n", relpath);
         1153                         /* NOTE: linecount omitted */
         1154                 }
         1155         }
         1156 
         1157         return 0;
         1158 }
         1159 
         1160 int
         1161 writefiles(FILE *fp, const git_oid *id)
         1162 {
         1163         git_tree *tree = NULL;
         1164         git_commit *commit = NULL;
         1165         int ret = -1;
         1166 
         1167         fprintf(fp, "%-10.10s  ", "Mode");
         1168         fprintf(fp, "%-50.50s  ", "Name");
         1169         fprintf(fp, "%8.8s\n", "Size");
         1170 
         1171         if (!git_commit_lookup(&commit, repo, id) &&
         1172             !git_commit_tree(&tree, commit))
         1173                 ret = writefilestree(fp, tree, "");
         1174 
         1175         git_commit_free(commit);
         1176         git_tree_free(tree);
         1177 
         1178         return ret;
         1179 }
         1180 
         1181 int
         1182 writerefs(FILE *fp)
         1183 {
         1184         struct referenceinfo *ris = NULL;
         1185         struct commitinfo *ci;
         1186         size_t count, i, j, refcount;
         1187         const char *titles[] = { "Branches", "Tags" };
         1188         const char *s;
         1189         char buf[256];
         1190 
         1191         if (getrefs(&ris, &refcount) == -1)
         1192                 return -1;
         1193 
         1194         for (i = 0, j = 0, count = 0; i < refcount; i++) {
         1195                 if (j == 0 && git_reference_is_tag(ris[i].ref)) {
         1196                         /* table footer */
         1197                         if (count)
         1198                                 fputs("\n", fp);
         1199                         count = 0;
         1200                         j = 1;
         1201                 }
         1202 
         1203                 /* print header if it has an entry (first). */
         1204                 if (++count == 1) {
         1205                         fprintf(fp, "%s\n", titles[j]);
         1206                         fprintf(fp, "  %-32.32s", "Name");
         1207                         fprintf(fp, "  %-16.16s", "Last commit date");
         1208                         fprintf(fp, "  %s\n", "Author");
         1209                 }
         1210 
         1211                 ci = ris[i].ci;
         1212                 s = git_reference_shorthand(ris[i].ref);
         1213 
         1214                 fputs("  ", fp);
         1215                 utf8pad(buf, sizeof(buf), s, 32, ' ');
         1216                 gphlink(fp, buf, strlen(buf));
         1217                 fputs("  ", fp);
         1218                 if (ci->author)
         1219                         printtimeshort(fp, &(ci->author->when));
         1220                 else
         1221                         fputs("                ", fp);
         1222                 fputs("  ", fp);
         1223                 if (ci->author) {
         1224                         utf8pad(buf, sizeof(buf), ci->author->name, 25, '\0');
         1225                         gphlink(fp, buf, strlen(buf));
         1226                 }
         1227                 fputs("\n", fp);
         1228         }
         1229         /* table footer */
         1230         if (count)
         1231                 fputs("\n", fp);
         1232 
         1233         for (i = 0; i < refcount; i++) {
         1234                 commitinfo_free(ris[i].ci);
         1235                 git_reference_free(ris[i].ref);
         1236         }
         1237         free(ris);
         1238 
         1239         return 0;
         1240 }
         1241 
         1242 void
         1243 usage(char *argv0)
         1244 {
         1245         fprintf(stderr, "usage: %s [-b baseprefix] [-c cachefile | -l commits] "
         1246                 "[-u baseurl] repodir\n", argv0);
         1247         exit(1);
         1248 }
         1249 
         1250 int
         1251 main(int argc, char *argv[])
         1252 {
         1253         git_object *obj = NULL;
         1254         const git_oid *head = NULL;
         1255         mode_t mask;
         1256         FILE *fp, *fpread;
         1257         char path[PATH_MAX], repodirabs[PATH_MAX + 1], *p;
         1258         char tmppath[64] = "cache.XXXXXXXXXXXX", buf[BUFSIZ];
         1259         size_t n;
         1260         int i, fd;
         1261 
         1262         setlocale(LC_CTYPE, "");
         1263 
         1264         for (i = 1; i < argc; i++) {
         1265                 if (argv[i][0] != '-') {
         1266                         if (repodir)
         1267                                 usage(argv[0]);
         1268                         repodir = argv[i];
         1269                 } else if (argv[i][1] == 'b') {
         1270                         if (i + 1 >= argc)
         1271                                 usage(argv[0]);
         1272                         relpath = argv[++i];
         1273                 } else if (argv[i][1] == 'c') {
         1274                         if (nlogcommits > 0 || i + 1 >= argc)
         1275                                 usage(argv[0]);
         1276                         cachefile = argv[++i];
         1277                 } else if (argv[i][1] == 'l') {
         1278                         if (cachefile || i + 1 >= argc)
         1279                                 usage(argv[0]);
         1280                         errno = 0;
         1281                         nlogcommits = strtoll(argv[++i], &p, 10);
         1282                         if (argv[i][0] == '\0' || *p != '\0' ||
         1283                             nlogcommits <= 0 || errno)
         1284                                 usage(argv[0]);
         1285                 } else if (argv[i][1] == 'u') {
         1286                         if (i + 1 >= argc)
         1287                                 usage(argv[0]);
         1288                         baseurl = argv[++i];
         1289                 }
         1290         }
         1291         if (!repodir)
         1292                 usage(argv[0]);
         1293 
         1294         if (!realpath(repodir, repodirabs))
         1295                 err(1, "realpath");
         1296 
         1297         /* do not search outside the git repository:
         1298            GIT_CONFIG_LEVEL_APP is the highest level currently */
         1299         git_libgit2_init();
         1300         for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
         1301                 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
         1302         /* do not require the git repository to be owned by the current user */
         1303         git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
         1304 
         1305 #ifdef __OpenBSD__
         1306         if (unveil(repodir, "r") == -1)
         1307                 err(1, "unveil: %s", repodir);
         1308         if (unveil(".", "rwc") == -1)
         1309                 err(1, "unveil: .");
         1310         if (cachefile && unveil(cachefile, "rwc") == -1)
         1311                 err(1, "unveil: %s", cachefile);
         1312 
         1313         if (cachefile) {
         1314                 if (pledge("stdio rpath wpath cpath fattr", NULL) == -1)
         1315                         err(1, "pledge");
         1316         } else {
         1317                 if (pledge("stdio rpath wpath cpath", NULL) == -1)
         1318                         err(1, "pledge");
         1319         }
         1320 #endif
         1321 
         1322         if (git_repository_open_ext(&repo, repodir,
         1323                 GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) {
         1324                 fprintf(stderr, "%s: cannot open repository\n", argv[0]);
         1325                 return 1;
         1326         }
         1327 
         1328         /* find HEAD */
         1329         if (!git_revparse_single(&obj, repo, "HEAD"))
         1330                 head = git_object_id(obj);
         1331         git_object_free(obj);
         1332 
         1333         /* use directory name as name */
         1334         if ((name = strrchr(repodirabs, '/')))
         1335                 name++;
         1336         else
         1337                 name = "";
         1338 
         1339         /* strip .git suffix */
         1340         if (!(strippedname = strdup(name)))
         1341                 err(1, "strdup");
         1342         if ((p = strrchr(strippedname, '.')))
         1343                 if (!strcmp(p, ".git"))
         1344                         *p = '\0';
         1345 
         1346         /* read description or .git/description */
         1347         joinpath(path, sizeof(path), repodir, "description");
         1348         if (!(fpread = fopen(path, "r"))) {
         1349                 joinpath(path, sizeof(path), repodir, ".git/description");
         1350                 fpread = fopen(path, "r");
         1351         }
         1352         if (fpread) {
         1353                 if (!fgets(description, sizeof(description), fpread))
         1354                         description[0] = '\0';
         1355                 checkfileerror(fpread, path, 'r');
         1356                 fclose(fpread);
         1357         }
         1358 
         1359         /* read url or .git/url */
         1360         joinpath(path, sizeof(path), repodir, "url");
         1361         if (!(fpread = fopen(path, "r"))) {
         1362                 joinpath(path, sizeof(path), repodir, ".git/url");
         1363                 fpread = fopen(path, "r");
         1364         }
         1365         if (fpread) {
         1366                 if (!fgets(cloneurl, sizeof(cloneurl), fpread))
         1367                         cloneurl[0] = '\0';
         1368                 checkfileerror(fpread, path, 'r');
         1369                 fclose(fpread);
         1370                 cloneurl[strcspn(cloneurl, "\n")] = '\0';
         1371         }
         1372 
         1373         /* check LICENSE */
         1374         for (i = 0; i < LEN(licensefiles) && !license; i++) {
         1375                 if (!git_revparse_single(&obj, repo, licensefiles[i]) &&
         1376                     git_object_type(obj) == GIT_OBJ_BLOB)
         1377                         license = licensefiles[i] + strlen("HEAD:");
         1378                 git_object_free(obj);
         1379         }
         1380 
         1381         /* check README */
         1382         for (i = 0; i < LEN(readmefiles) && !readme; i++) {
         1383                 if (!git_revparse_single(&obj, repo, readmefiles[i]) &&
         1384                     git_object_type(obj) == GIT_OBJ_BLOB)
         1385                         readme = readmefiles[i] + strlen("HEAD:");
         1386                 git_object_free(obj);
         1387         }
         1388 
         1389         if (!git_revparse_single(&obj, repo, "HEAD:.gitmodules") &&
         1390             git_object_type(obj) == GIT_OBJ_BLOB)
         1391                 submodules = ".gitmodules";
         1392         git_object_free(obj);
         1393 
         1394         /* log for HEAD */
         1395         fp = efopen("log.gph", "w");
         1396         mkdir("commit", S_IRWXU | S_IRWXG | S_IRWXO);
         1397         writeheader(fp, "Log");
         1398 
         1399         fprintf(fp, "%-16.16s  ", "Date");
         1400         fprintf(fp, "%-40.40s  ", "Commit message");
         1401         fprintf(fp, "%s\n", "Author");
         1402 
         1403         if (cachefile && head) {
         1404                 /* read from cache file (does not need to exist) */
         1405                 if ((rcachefp = fopen(cachefile, "r"))) {
         1406                         if (!fgets(lastoidstr, sizeof(lastoidstr), rcachefp))
         1407                                 errx(1, "%s: no object id", cachefile);
         1408                         if (git_oid_fromstr(&lastoid, lastoidstr))
         1409                                 errx(1, "%s: invalid object id", cachefile);
         1410                 }
         1411 
         1412                 /* write log to (temporary) cache */
         1413                 if ((fd = mkstemp(tmppath)) == -1)
         1414                         err(1, "mkstemp");
         1415                 if (!(wcachefp = fdopen(fd, "w")))
         1416                         err(1, "fdopen: '%s'", tmppath);
         1417                 /* write last commit id (HEAD) */
         1418                 git_oid_tostr(buf, sizeof(buf), head);
         1419                 fprintf(wcachefp, "%s\n", buf);
         1420 
         1421                 writelog(fp, head);
         1422 
         1423                 if (rcachefp) {
         1424                         /* append previous log to log.gph and the new cache */
         1425                         while (!feof(rcachefp)) {
         1426                                 n = fread(buf, 1, sizeof(buf), rcachefp);
         1427                                 if (ferror(rcachefp))
         1428                                         break;
         1429                                 if (fwrite(buf, 1, n, fp) != n ||
         1430                                     fwrite(buf, 1, n, wcachefp) != n)
         1431                                         break;
         1432                         }
         1433                         checkfileerror(rcachefp, cachefile, 'r');
         1434                         fclose(rcachefp);
         1435                 }
         1436                 checkfileerror(wcachefp, tmppath, 'w');
         1437                 fclose(wcachefp);
         1438         } else {
         1439                 if (head)
         1440                         writelog(fp, head);
         1441         }
         1442         fputs("\n", fp);
         1443         fprintf(fp, "[0|Atom feed|%s/atom.xml|server|port]\n", relpath);
         1444         fprintf(fp, "[0|Atom feed (tags)|%s/tags.xml|server|port]\n", relpath);
         1445         writefooter(fp);
         1446         checkfileerror(fp, "log.gph", 'w');
         1447         fclose(fp);
         1448 
         1449         /* files for HEAD */
         1450         fp = efopen("files.gph", "w");
         1451         writeheader(fp, "Files");
         1452         if (head)
         1453                 writefiles(fp, head);
         1454         writefooter(fp);
         1455         checkfileerror(fp, "files.gph", 'w');
         1456         fclose(fp);
         1457 
         1458         /* summary page with branches and tags */
         1459         fp = efopen("refs.gph", "w");
         1460         writeheader(fp, "Refs");
         1461         writerefs(fp);
         1462         writefooter(fp);
         1463         checkfileerror(fp, "refs.gph", 'w');
         1464         fclose(fp);
         1465 
         1466         /* Atom feed */
         1467         fp = efopen("atom.xml", "w");
         1468         writeatom(fp, 1);
         1469         checkfileerror(fp, "atom.xml", 'w');
         1470         fclose(fp);
         1471 
         1472         /* Atom feed for tags / releases */
         1473         fp = efopen("tags.xml", "w");
         1474         writeatom(fp, 0);
         1475         checkfileerror(fp, "tags.xml", 'w');
         1476         fclose(fp);
         1477 
         1478         /* rename new cache file on success */
         1479         if (cachefile && head) {
         1480                 if (rename(tmppath, cachefile))
         1481                         err(1, "rename: '%s' to '%s'", tmppath, cachefile);
         1482                 umask((mask = umask(0)));
         1483                 if (chmod(cachefile,
         1484                     (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) & ~mask))
         1485                         err(1, "chmod: '%s'", cachefile);
         1486         }
         1487 
         1488         /* cleanup */
         1489         git_repository_free(repo);
         1490         git_libgit2_shutdown();
         1491 
         1492         return 0;
         1493 }