stagit-index.c (4836B)
1 #include <sys/stat.h> 2 3 #include <err.h> 4 #include <errno.h> 5 #include <inttypes.h> 6 #include <limits.h> 7 #include <stdio.h> 8 #include <stdlib.h> 9 #include <string.h> 10 #include <unistd.h> 11 12 #include <git2.h> 13 14 #include "compat.h" 15 16 static git_repository *repo; 17 18 static const char *relpath = ""; 19 20 static char description[255] = "Repositories"; 21 static char *name = ""; 22 23 #ifndef USE_PLEDGE 24 #define pledge(p1,p2) 0 25 #endif 26 27 void 28 joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) 29 { 30 int r; 31 32 r = snprintf(buf, bufsiz, "%s%s%s", 33 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); 34 if (r == -1 || (size_t)r >= bufsiz) 35 errx(1, "path truncated: '%s%s%s'", 36 path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2); 37 } 38 39 /* Escape characters below as HTML 2.0 / XML 1.0. */ 40 void 41 xmlencode(FILE *fp, const char *s, size_t len) 42 { 43 size_t i; 44 45 for (i = 0; *s && i < len; s++, i++) { 46 switch(*s) { 47 case '<': fputs("<", fp); break; 48 case '>': fputs(">", fp); break; 49 case '\'': fputs("'" , fp); break; 50 case '&': fputs("&", fp); break; 51 case '"': fputs(""", fp); break; 52 default: fputc(*s, fp); 53 } 54 } 55 } 56 57 void 58 printtimeshort(FILE *fp, const git_time *intime) 59 { 60 struct tm *intm; 61 time_t t; 62 char out[32]; 63 64 t = (time_t)intime->time; 65 if (!(intm = gmtime(&t))) 66 return; 67 strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm); 68 fputs(out, fp); 69 } 70 71 void 72 writeheader(FILE *fp) 73 { 74 fputs("<!DOCTYPE html>\n" 75 "<html>\n<head>\n" 76 "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" 77 "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n" 78 "<title>", fp); 79 xmlencode(fp, description, strlen(description)); 80 fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"/favicon.png\" />\n", relpath); 81 fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"/stagit_style.css\" />\n", relpath); 82 fputs("</head>\n<body>\n", fp); 83 fprintf(fp, "<table>\n<tr><td><a href=\"../\">" 84 "<img src=\"/stagit_logo.png\" alt=\"\" width=\"32\" height=\"32\" /></a></td>\n" 85 "<td><span class=\"desc\">", relpath); 86 xmlencode(fp, description, strlen(description)); 87 fputs("</span></td></tr><tr><td></td><td>\n" 88 "</td></tr>\n</table>\n<hr/>\n<div id=\"content\">\n" 89 "<table id=\"index\"><thead>\n" 90 "<tr><th>Name</th><th>Description</th>" 91 "<th>Last commit</th></tr>" 92 "</thead><tbody>\n", fp); 93 } 94 95 void 96 writefooter(FILE *fp) 97 { 98 fputs("</tbody>\n</table>\n</div>\n</body>\n</html>\n", fp); 99 } 100 101 int 102 writelog(FILE *fp) 103 { 104 git_commit *commit = NULL; 105 const git_signature *author; 106 git_revwalk *w = NULL; 107 git_oid id; 108 char *stripped_name = NULL, *p; 109 int ret = 0; 110 111 git_revwalk_new(&w, repo); 112 git_revwalk_push_head(w); 113 git_revwalk_sorting(w, GIT_SORT_TIME); 114 git_revwalk_simplify_first_parent(w); 115 116 if (git_revwalk_next(&id, w) || 117 git_commit_lookup(&commit, repo, &id)) { 118 ret = -1; 119 goto err; 120 } 121 122 author = git_commit_author(commit); 123 124 /* strip .git suffix */ 125 if (!(stripped_name = strdup(name))) 126 err(1, "strdup"); 127 if ((p = strrchr(stripped_name, '.'))) 128 if (!strcmp(p, ".git")) 129 *p = '\0'; 130 131 fputs("<tr><td><a href=\"", fp); 132 xmlencode(fp, stripped_name, strlen(stripped_name)); 133 fputs("/\">", fp); 134 xmlencode(fp, stripped_name, strlen(stripped_name)); 135 fputs("</a></td><td>", fp); 136 xmlencode(fp, description, strlen(description)); 137 fputs("</td><td>", fp); 138 if (author) 139 printtimeshort(fp, &(author->when)); 140 fputs("</td></tr>", fp); 141 142 git_commit_free(commit); 143 err: 144 git_revwalk_free(w); 145 free(stripped_name); 146 147 return ret; 148 } 149 150 int 151 main(int argc, char *argv[]) 152 { 153 const git_error *e = NULL; 154 FILE *fp; 155 char path[PATH_MAX], repodirabs[PATH_MAX + 1]; 156 const char *repodir; 157 int i, ret = 0; 158 159 if (argc < 2) { 160 fprintf(stderr, "%s [repodir...]\n", argv[0]); 161 return 1; 162 } 163 164 git_libgit2_init(); 165 166 if (pledge("stdio rpath", NULL) == -1) 167 err(1, "pledge"); 168 169 writeheader(stdout); 170 171 for (i = 1; i < argc; i++) { 172 repodir = argv[i]; 173 if (!realpath(repodir, repodirabs)) 174 err(1, "realpath"); 175 176 if (git_repository_open_ext(&repo, repodir, 177 GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) { 178 e = giterr_last(); 179 fprintf(stderr, "%s: %s\n", argv[0], e->message); 180 ret = 1; 181 continue; 182 } 183 184 /* use directory name as name */ 185 if ((name = strrchr(repodirabs, '/'))) 186 name++; 187 else 188 name = ""; 189 190 /* read description or .git/description */ 191 joinpath(path, sizeof(path), repodir, "description"); 192 if (!(fp = fopen(path, "r"))) { 193 joinpath(path, sizeof(path), repodir, ".git/description"); 194 fp = fopen(path, "r"); 195 } 196 description[0] = '\0'; 197 if (fp) { 198 if (!fgets(description, sizeof(description), fp)) 199 description[0] = '\0'; 200 fclose(fp); 201 } 202 203 writelog(stdout); 204 } 205 writefooter(stdout); 206 207 /* cleanup */ 208 git_repository_free(repo); 209 git_libgit2_shutdown(); 210 211 return ret; 212 }