stagit-index.c 7.2 KB

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