dotfiles
My PC Dotfiles & Websitegit clone https://github.com/maydayv7/dotfiles
site/git/stagit/stagit-index.c (8305B)
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
11static char *sitename = "Git";
12static const char *relpath = "";
13
14struct repoinfo {
15 char *name;
16 char *strippedname;
17 char *description;
18 char *owner;
19 git_time time;
20};
21
22/* Handle read or write errors for a FILE * stream */
23void checkfileerror(FILE *fp, const char *name, int mode) {
24 if (mode == 'r' && ferror(fp))
25 errx(1, "read error: %s", name);
26 else if (mode == 'w' && (fflush(fp) || ferror(fp)))
27 errx(1, "write error: %s", name);
28}
29
30void joinpath(char *buf, size_t bufsiz, const char *path, const char *path2) {
31 int r;
32
33 r = snprintf(buf, bufsiz, "%s%s%s", path,
34 path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
35 if (r < 0 || (size_t)r >= bufsiz)
36 errx(1, "path truncated: '%s%s%s'", path,
37 path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
38}
39
40/* Percent-encode, see RFC3986 section 2.1. */
41void percentencode(FILE *fp, const char *s, size_t len) {
42 static char tab[] = "0123456789ABCDEF";
43 unsigned char uc;
44 size_t i;
45
46 for (i = 0; *s && i < len; s++, i++) {
47 uc = *s;
48 /* NOTE: do not encode '/' for paths or ",-." */
49 if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') || uc == '[' ||
50 uc == ']') {
51 putc('%', fp);
52 putc(tab[(uc >> 4) & 0x0f], fp);
53 putc(tab[uc & 0x0f], fp);
54 } else {
55 putc(uc, fp);
56 }
57 }
58}
59
60/* Escape characters below as HTML 2.0 / XML 1.0. */
61void xmlencode(FILE *fp, const char *s, size_t len) {
62 size_t i;
63
64 for (i = 0; *s && i < len; s++, i++) {
65 switch (*s) {
66 case '<':
67 fputs("<", fp);
68 break;
69 case '>':
70 fputs(">", fp);
71 break;
72 case '\'':
73 fputs("'", fp);
74 break;
75 case '&':
76 fputs("&", fp);
77 break;
78 case '"':
79 fputs(""", fp);
80 break;
81 default:
82 putc(*s, fp);
83 }
84 }
85}
86
87void printtimeshort(FILE *fp, const git_time *intime) {
88 struct tm *intm;
89 time_t t;
90 char out[32];
91
92 t = (time_t)intime->time;
93 if (!(intm = localtime(&t)))
94 return;
95 strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
96 fputs(out, fp);
97}
98
99void writeheader(FILE *fp) {
100 fputs("<!DOCTYPE html>\n"
101 "<html lang=\"en\">\n<head>\n"
102 "<meta http-equiv=\"Content-Type\" content=\"text/html; "
103 "charset=UTF-8\" />\n"
104 "<meta name=\"viewport\" content=\"width=device-width, "
105 "initial-scale=1\" />\n"
106 "<title>",
107 fp);
108 xmlencode(fp, sitename, strlen(sitename));
109 fprintf(fp,
110 "</title>\n<link rel=\"icon\" type=\"image/png\" "
111 "href=\"%sfavicon.png\" />\n",
112 relpath);
113 fputs("<link rel=\"stylesheet\" type=\"text/css\" href=\"/style.css\" />\n",
114 fp);
115 fputs("</head>\n<body>\n<header><a href=\"./\">", fp);
116 xmlencode(fp, sitename, strlen(sitename));
117 fputs(
118 "</a></header>\n"
119 "<main id=\"main-content\" class=\"post git-content\" tabindex=\"-1\">\n"
120 "<h1 class=\"post-title\">Git repositories</h1>\n"
121 "<div id=\"content\">\n",
122 fp);
123
124 fputs(
125 "<table id=\"index\"><thead>\n"
126 "<tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Owner</b></td>"
127 "<td><b>Last commit</b></td></tr>"
128 "</thead><tbody>\n",
129 fp);
130}
131
132void writefooter(FILE *fp) {
133 fputs("</tbody>\n</table>\n</div>\n</main>\n</body>\n</html>\n", fp);
134}
135
136int getlastcommit(git_repository *repo, git_time *t) {
137 git_revwalk *w = NULL;
138 git_oid id;
139 git_commit *c = NULL;
140 const git_signature *author;
141 int ret = -1;
142
143 if (git_revwalk_new(&w, repo))
144 return -1;
145 if (git_revwalk_push_head(w))
146 goto err;
147 if (git_revwalk_next(&id, w))
148 goto err;
149 if (git_commit_lookup(&c, repo, &id))
150 goto err;
151
152 author = git_commit_author(c);
153 *t = author->when;
154 ret = 0;
155
156err:
157 git_commit_free(c);
158 git_revwalk_free(w);
159 return ret;
160}
161
162int repocompare(const void *a, const void *b) {
163 const struct repoinfo *ra = a;
164 const struct repoinfo *rb = b;
165
166 /* sort by time descending (newest first) */
167 if (ra->time.time > rb->time.time)
168 return -1;
169 if (ra->time.time < rb->time.time)
170 return 1;
171 /* fallback to name */
172 return strcmp(ra->name, rb->name);
173}
174
175void printrepo(FILE *fp, struct repoinfo *ri) {
176 fputs("<tr><td><a href=\"", fp);
177 percentencode(fp, ri->strippedname, strlen(ri->strippedname));
178 fputs("/log.html\">", fp);
179 xmlencode(fp, ri->strippedname, strlen(ri->strippedname));
180 fputs("</a></td><td>", fp);
181 xmlencode(fp, ri->description, strlen(ri->description));
182 fputs("</td><td>", fp);
183 xmlencode(fp, ri->owner, strlen(ri->owner));
184 fputs("</td><td>", fp);
185 if (ri->time.time)
186 printtimeshort(fp, &(ri->time));
187 fputs("</td></tr>\n", fp);
188}
189
190void usage(char *argv0) {
191 fprintf(stderr, "usage: %s [-n sitename] [repodir...]\n", argv0);
192 exit(1);
193}
194
195int main(int argc, char *argv[]) {
196 git_repository *repo = NULL;
197 struct repoinfo *repos = NULL;
198 size_t nrepos = 0;
199 char path[PATH_MAX], repodirabs[PATH_MAX + 1];
200 char description[255], owner[255];
201 char *name, *p;
202 FILE *fp;
203 int i, ret = 0;
204
205 for (i = 1; i < argc; i++) {
206 if (argv[i][0] == '-') {
207 if (!strcmp(argv[i], "-n")) {
208 if (i + 1 >= argc)
209 usage(argv[0]);
210 sitename = argv[++i];
211 } else {
212 usage(argv[0]);
213 }
214 }
215 }
216
217 if (argc < 2)
218 usage(argv[0]);
219
220 /* do not search outside the git repository:
221 GIT_CONFIG_LEVEL_APP is the highest level currently */
222 git_libgit2_init();
223 for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
224 git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
225 /* do not require the git repository to be owned by the current user */
226 git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
227
228#ifdef __OpenBSD__
229 if (pledge("stdio rpath", NULL) == -1)
230 err(1, "pledge");
231#endif
232
233 for (i = 1; i < argc; i++) {
234 if (argv[i][0] == '-') {
235 if (argv[i][1] == 'n')
236 i++;
237 continue;
238 }
239
240 if (!realpath(argv[i], repodirabs)) {
241 fprintf(stderr, "realpath error for %s\n", argv[i]);
242 ret = 1;
243 continue;
244 }
245
246 if (git_repository_open_ext(&repo, argv[i], GIT_REPOSITORY_OPEN_NO_SEARCH,
247 NULL)) {
248 fprintf(stderr, "%s: cannot open repository\n", argv[i]);
249 ret = 1;
250 continue;
251 }
252
253 repos = realloc(repos, (nrepos + 1) * sizeof(*repos));
254 if (!repos)
255 err(1, "realloc");
256
257 /* set defaults */
258 memset(&repos[nrepos], 0, sizeof(struct repoinfo));
259 repos[nrepos].name = strdup(argv[i]);
260 description[0] = '\0';
261 owner[0] = '\0';
262
263 /* calculate stripped name */
264 if ((name = strrchr(repodirabs, '/')))
265 name++;
266 else
267 name = "";
268 if (!(repos[nrepos].strippedname = strdup(name)))
269 err(1, "strdup");
270 if ((p = strrchr(repos[nrepos].strippedname, '.')))
271 if (!strcmp(p, ".git"))
272 *p = '\0';
273
274 /* description */
275 joinpath(path, sizeof(path), argv[i], "description");
276 if (!(fp = fopen(path, "r"))) {
277 joinpath(path, sizeof(path), argv[i], ".git/description");
278 fp = fopen(path, "r");
279 }
280 if (fp) {
281 if (!fgets(description, sizeof(description), fp))
282 description[0] = '\0';
283 fclose(fp);
284 }
285 repos[nrepos].description = strdup(description);
286
287 /* owner */
288 joinpath(path, sizeof(path), argv[i], "owner");
289 if (!(fp = fopen(path, "r"))) {
290 joinpath(path, sizeof(path), argv[i], ".git/owner");
291 fp = fopen(path, "r");
292 }
293 if (fp) {
294 if (!fgets(owner, sizeof(owner), fp))
295 owner[0] = '\0';
296 owner[strcspn(owner, "\n")] = '\0';
297 fclose(fp);
298 }
299 repos[nrepos].owner = strdup(owner);
300
301 /* last commit */
302 getlastcommit(repo, &repos[nrepos].time);
303
304 git_repository_free(repo);
305 repo = NULL;
306 nrepos++;
307 }
308
309 /* sort by last commit time */
310 qsort(repos, nrepos, sizeof(struct repoinfo), repocompare);
311
312 /* output */
313 writeheader(stdout);
314 for (i = 0; i < nrepos; i++) {
315 printrepo(stdout, &repos[i]);
316 free(repos[i].name);
317 free(repos[i].strippedname);
318 free(repos[i].description);
319 free(repos[i].owner);
320 }
321 writefooter(stdout);
322 free(repos);
323
324 checkfileerror(stdout, "stdout", 'w');
325 git_libgit2_shutdown();
326
327 return ret;
328}