logo

stagit

STAtic GIT web view generator (in C) git clone https://hacktivis.me/git/stagit.git

stagit-index.c (6913B)


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