stagit-index.c (5993B) - raw
1 #include <err.h> 2 #include <limits.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <time.h> 7 #include <unistd.h> 8 9 #include <git2.h> 10 11 #ifdef HAS_CMARK 12 #include <cmark-gfm.h> 13 #endif 14 15 #include "cp.h" 16 17 static git_repository *repo; 18 19 static const char *relpath = ""; 20 21 static char description[255] = "Repositories"; 22 static char *name = ""; 23 static char owner[255]; 24 25 void 26 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) 27 { 28 int r; 29 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 37 /* Escape characters below as HTML 2.0 / XML 1.0. */ 38 void 39 xmlencode(FILE *fp, const char *s, size_t len) 40 { 41 size_t i; 42 43 for (i = 0; *s && i < len; s++, i++) { 44 switch(*s) { 45 case '<': fputs("<", fp); break; 46 case '>': fputs(">", fp); break; 47 case '\'': fputs("'" , fp); break; 48 case '&': fputs("&", fp); break; 49 case '"': fputs(""", fp); break; 50 default: putc(*s, fp); 51 } 52 } 53 } 54 55 void 56 printtimeshort(FILE *fp, const git_time *intime) 57 { 58 struct tm *intm; 59 time_t t; 60 char out[32]; 61 62 t = (time_t)intime->time; 63 if (!(intm = gmtime(&t))) 64 return; 65 strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm); 66 fputs(out, fp); 67 } 68 69 void 70 writeheader(char *path) 71 { 72 FILE *fp = fopen(path, "w"); 73 fputs("<!DOCTYPE html>\n" 74 "<html lang=\"en\">\n<head>\n" 75 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" 76 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n" 77 "<title>", fp); 78 xmlencode(fp, description, strlen(description)); 79 fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath); 80 fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath); 81 fputs("</head>\n<body>\n", fp); 82 fprintf(fp, "<img src=\"%slogo.png\" class=\"logo\" width=\"32\" height=\"32\" /></td>\n" 83 "<h1>", relpath); 84 xmlencode(fp, description, strlen(description)); 85 fputs("</h1>\n", fp); 86 87 FILE *longdesc = fopen("description.md", "r"); 88 if (longdesc == NULL) longdesc = fopen("description", "r"); 89 if (longdesc != NULL) { 90 char c = fgetc(longdesc); 91 #ifdef HAS_CMARK 92 char buf[2048]; 93 while (c != EOF) { 94 strncat(buf, &c, 1); 95 c = fgetc(longdesc); 96 } 97 char *md = cmark_markdown_to_html(buf, strlen(buf), CMARK_OPT_DEFAULT); 98 fprintf(fp, md, relpath); 99 free(md); 100 #else 101 fputs("<p>\n", fp); 102 while (c != EOF) { 103 fprintf(fp, &c, relpath); 104 c = fgetc(longdesc); 105 } 106 #endif 107 fclose(longdesc); 108 } 109 110 fputs("</p>\n<hr/>\n<div id=\"content\">\n" 111 "<table id=\"index\"><thead>\n" 112 "<tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Owner</b></td>" 113 "<td><b>Last commit</b></td></tr>" 114 "</thead><tbody>\n", fp); 115 fclose(fp); 116 } 117 118 void 119 writefooter(char *path) 120 { 121 FILE *fp = fopen(path, "a"); 122 fputs("</tbody>\n</table>\n</div>\n</body>\n</html>\n", fp); 123 fclose(fp); 124 } 125 126 int 127 writelog(char *path) 128 { 129 FILE *fp = fopen(path, "a"); 130 git_commit *commit = NULL; 131 const git_signature *author; 132 git_revwalk *w = NULL; 133 git_oid id; 134 char *stripped_name = NULL, *p; 135 int ret = 0; 136 137 git_revwalk_new(&w, repo); 138 git_revwalk_push_head(w); 139 140 if (git_revwalk_next(&id, w) || 141 git_commit_lookup(&commit, repo, &id)) { 142 ret = -1; 143 goto err; 144 } 145 146 author = git_commit_author(commit); 147 148 /* strip .git suffix */ 149 if (!(stripped_name = strdup(name))) 150 err(1, "strdup"); 151 if ((p = strrchr(stripped_name, '.'))) 152 if (!strcmp(p, ".git")) 153 *p = '\0'; 154 155 fputs("<tr><td><a href=\"", fp); 156 xmlencode(fp, stripped_name, strlen(stripped_name)); 157 fputs("/log.html\">", fp); 158 xmlencode(fp, stripped_name, strlen(stripped_name)); 159 fputs("</a></td><td>", fp); 160 xmlencode(fp, description, strlen(description)); 161 fputs("</td><td>", fp); 162 xmlencode(fp, owner, strlen(owner)); 163 fputs("</td><td>", fp); 164 if (author) 165 printtimeshort(fp, &(author->when)); 166 fputs("</td></tr>", fp); 167 168 git_commit_free(commit); 169 err: 170 git_revwalk_free(w); 171 free(stripped_name); 172 fclose(fp); 173 174 return ret; 175 } 176 177 int 178 main(int argc, char *argv[]) 179 { 180 FILE *fp; 181 char path[PATH_MAX], repodirabs[PATH_MAX + 1]; 182 const char *repodir; 183 int i, ret = 0; 184 185 if (argc < 2) { 186 fprintf(stderr, "%s [repodir...]\n", argv[0]); 187 return 1; 188 } 189 190 git_libgit2_init(); 191 192 #ifdef __OpenBSD__ 193 for (i = 1; i < argc; i++) 194 if (unveil(argv[i], "r") == -1) 195 err(1, "unveil: %s", argv[i]); 196 197 if (pledge("stdio rpath", NULL) == -1) 198 err(1, "pledge"); 199 #endif 200 201 writeheader("index.html"); 202 203 for (i = 1; i < argc; i++) { 204 repodir = argv[i]; 205 if (!realpath(repodir, repodirabs)) 206 err(1, "realpath"); 207 208 if (git_repository_open_ext(&repo, repodir, 209 GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) { 210 fprintf(stderr, "%s: cannot open repository\n", argv[0]); 211 ret = 1; 212 continue; 213 } 214 215 /* use directory name as name */ 216 if ((name = strrchr(repodirabs, '/'))) 217 name++; 218 else 219 name = ""; 220 221 /* read description or .git/description */ 222 joinpath(path, sizeof(path), repodir, "description"); 223 if (!(fp = fopen(path, "r"))) { 224 joinpath(path, sizeof(path), repodir, ".git/description"); 225 fp = fopen(path, "r"); 226 } 227 description[0] = '\0'; 228 if (fp) { 229 if (!fgets(description, sizeof(description), fp)) 230 description[0] = '\0'; 231 fclose(fp); 232 } 233 234 /* read owner or .git/owner */ 235 joinpath(path, sizeof(path), repodir, "owner"); 236 if (!(fp = fopen(path, "r"))) { 237 joinpath(path, sizeof(path), repodir, ".git/owner"); 238 fp = fopen(path, "r"); 239 } 240 owner[0] = '\0'; 241 if (fp) { 242 if (!fgets(owner, sizeof(owner), fp)) 243 owner[0] = '\0'; 244 owner[strcspn(owner, "\n")] = '\0'; 245 fclose(fp); 246 } 247 writelog("index.html"); 248 } 249 writefooter("index.html"); 250 251 /* copy css */ 252 char cwd[PATH_MAX]; 253 strcpy(cwd, getcwd(cwd, sizeof(cwd))); 254 cp("/usr/local/share/stagit/style.css", strcat(cwd, "/style.css")); 255 256 /* cleanup */ 257 git_repository_free(repo); 258 git_libgit2_shutdown(); 259 260 return ret; 261 }