]> git.armaanb.net Git - stagit.git/blob - src/stagit-index.c
do not simplify the history by first-parent
[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("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 }