]> git.armaanb.net Git - stagit.git/blob - src/stagit-index.c
bad91d196ad4db01a37e06beb8eebd8763e85842
[stagit.git] / src / stagit-index.c
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("&lt;",   fp); break;
46                 case '>':  fputs("&gt;",   fp); break;
47                 case '\'': fputs("&#39;" , fp); break;
48                 case '&':  fputs("&amp;",  fp); break;
49                 case '"':  fputs("&quot;", 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("desc.md", "r");
88         if (longdesc == NULL) longdesc = fopen("desc", "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         }
108         fclose(longdesc);
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         git_revwalk_simplify_first_parent(w);
140
141         if (git_revwalk_next(&id, w) ||
142             git_commit_lookup(&commit, repo, &id)) {
143                 ret = -1;
144                 goto err;
145         }
146
147         author = git_commit_author(commit);
148
149         /* strip .git suffix */
150         if (!(stripped_name = strdup(name)))
151                 err(1, "strdup");
152         if ((p = strrchr(stripped_name, '.')))
153                 if (!strcmp(p, ".git"))
154                         *p = '\0';
155
156         fputs("<tr><td><a href=\"", fp);
157         xmlencode(fp, stripped_name, strlen(stripped_name));
158         fputs("/log.html\">", fp);
159         xmlencode(fp, stripped_name, strlen(stripped_name));
160         fputs("</a></td><td>", fp);
161         xmlencode(fp, description, strlen(description));
162         fputs("</td><td>", fp);
163         xmlencode(fp, owner, strlen(owner));
164         fputs("</td><td>", fp);
165         if (author)
166                 printtimeshort(fp, &(author->when));
167         fputs("</td></tr>", fp);
168
169         git_commit_free(commit);
170 err:
171         git_revwalk_free(w);
172         free(stripped_name);
173         fclose(fp);
174
175         return ret;
176 }
177
178 int
179 main(int argc, char *argv[])
180 {
181         FILE *fp;
182         char path[PATH_MAX], repodirabs[PATH_MAX + 1];
183         const char *repodir;
184         int i, ret = 0;
185
186         if (argc < 2) {
187                 fprintf(stderr, "%s [repodir...]\n", argv[0]);
188                 return 1;
189         }
190
191         git_libgit2_init();
192
193 #ifdef __OpenBSD__
194         for (i = 1; i < argc; i++)
195                 if (unveil(argv[i], "r") == -1)
196                         err(1, "unveil: %s", argv[i]);
197
198         if (pledge("stdio rpath", NULL) == -1)
199                 err(1, "pledge");
200 #endif
201
202         writeheader("index.html");
203
204         for (i = 1; i < argc; i++) {
205                 repodir = argv[i];
206                 if (!realpath(repodir, repodirabs))
207                         err(1, "realpath");
208
209                 if (git_repository_open_ext(&repo, repodir,
210                     GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) {
211                         fprintf(stderr, "%s: cannot open repository\n", argv[0]);
212                         ret = 1;
213                         continue;
214                 }
215
216                 /* use directory name as name */
217                 if ((name = strrchr(repodirabs, '/')))
218                         name++;
219                 else
220                         name = "";
221
222                 /* read description or .git/description */
223                 joinpath(path, sizeof(path), repodir, "description");
224                 if (!(fp = fopen(path, "r"))) {
225                         joinpath(path, sizeof(path), repodir, ".git/description");
226                         fp = fopen(path, "r");
227                 }
228                 description[0] = '\0';
229                 if (fp) {
230                         if (!fgets(description, sizeof(description), fp))
231                                 description[0] = '\0';
232                         fclose(fp);
233                 }
234
235                 /* read owner or .git/owner */
236                 joinpath(path, sizeof(path), repodir, "owner");
237                 if (!(fp = fopen(path, "r"))) {
238                         joinpath(path, sizeof(path), repodir, ".git/owner");
239                         fp = fopen(path, "r");
240                 }
241                 owner[0] = '\0';
242                 if (fp) {
243                         if (!fgets(owner, sizeof(owner), fp))
244                                 owner[0] = '\0';
245                         owner[strcspn(owner, "\n")] = '\0';
246                         fclose(fp);
247                 }
248                 writelog("index.html");
249         }
250         writefooter("index.html");
251
252         /* copy css */
253         char cwd[PATH_MAX];
254         strcpy(cwd, getcwd(cwd, sizeof(cwd)));
255         cp("/usr/local/share/stagit/style.css", strcat(cwd, "/style.css"));
256
257         /* cleanup */
258         git_repository_free(repo);
259         git_libgit2_shutdown();
260
261         return ret;
262 }