stagit-index.c (6390B)
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>
89
#include <git2.h>
1011
static git_repository *repo;
1213
static const char *relpath = "";
1415
static char description[255] = "Repositories";
16static char *name = "";
17static char owner[255];
1819
/* Handle read or write errors for a FILE * stream */
20void
21checkfileerror(FILE *fp, const char *name, int mode)
22{
23if (mode == 'r' && ferror(fp))
24errx(1, "read error: %s", name);
25else if (mode == 'w' && (fflush(fp) || ferror(fp)))
26errx(1, "write error: %s", name);
27}
2829
void
30joinpath(char *buf, size_t bufsiz, const char *path, const char *path2)
31{
32int r;
3334
r = snprintf(buf, bufsiz, "%s%s%s",
35path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
36if (r < 0 || (size_t)r >= bufsiz)
37errx(1, "path truncated: '%s%s%s'",
38path, path[0] && path[strlen(path) - 1] != '/' ? "/" : "", path2);
39}
4041
/* Percent-encode, see RFC3986 section 2.1. */
42void
43percentencode(FILE *fp, const char *s, size_t len)
44{
45static char tab[] = "0123456789ABCDEF";
46unsigned char uc;
47size_t i;
4849
for (i = 0; *s && i < len; s++, i++) {
50uc = *s;
51/* NOTE: do not encode '/' for paths or ",-." */
52if (uc < ',' || uc >= 127 || (uc >= ':' && uc <= '@') ||
53uc == '[' || uc == ']') {
54putc('%', fp);
55putc(tab[(uc >> 4) & 0x0f], fp);
56putc(tab[uc & 0x0f], fp);
57} else {
58putc(uc, fp);
59}
60}
61}
6263
/* Escape characters below as HTML 2.0 / XML 1.0. */
64void
65xmlencode(FILE *fp, const char *s, size_t len)
66{
67size_t i;
6869
for (i = 0; *s && i < len; s++, i++) {
70switch(*s) {
71case '<': fputs("<", fp); break;
72case '>': fputs(">", fp); break;
73case '\'': fputs("'" , fp); break;
74case '&': fputs("&", fp); break;
75case '"': fputs(""", fp); break;
76default: putc(*s, fp);
77}
78}
79}
8081
void
82printtimeshort(FILE *fp, const git_time *intime)
83{
84struct tm *intm;
85time_t t;
86char out[32];
8788
t = (time_t)intime->time;
89if (!(intm = gmtime(&t)))
90return;
91strftime(out, sizeof(out), "%Y-%m-%d %H:%M", intm);
92fputs(out, fp);
93}
9495
void
96writeheader(FILE *fp)
97{
98fputs("<!DOCTYPE html>\n"
99"<html>\n<head>\n"
100"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
101"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n"
102"<title>", fp);
103xmlencode(fp, description, strlen(description));
104fprintf(fp, "</title>\n<link rel=\"icon\" type=\"image/png\" href=\"%sfavicon.png\" />\n", relpath);
105fprintf(fp, "<link rel=\"stylesheet\" type=\"text/css\" href=\"%sstyle.css\" />\n", relpath);
106fputs("</head>\n<body>\n", fp);
107fprintf(fp, "<table>\n<tr><td><img src=\"%slogo.png\" alt=\"\" width=\"32\" height=\"32\" /></td>\n"
108"<td><span class=\"desc\">", relpath);
109xmlencode(fp, description, strlen(description));
110fputs("</span></td></tr><tr><td></td><td>\n"
111"</td></tr>\n</table>\n<hr/>\n<div id=\"content\">\n"
112"<table id=\"index\"><thead>\n"
113"<tr><td><b>Name</b></td><td><b>Description</b></td><td><b>Owner</b></td>"
114"<td><b>Last commit</b></td></tr>"
115"</thead><tbody>\n", fp);
116}
117118
void
119writefooter(FILE *fp)
120{
121fputs("</tbody>\n</table>\n</div>\n</body>\n</html>\n", fp);
122}
123124
int
125writelog(FILE *fp)
126{
127git_commit *commit = NULL;
128const git_signature *author;
129git_revwalk *w = NULL;
130git_oid id;
131char *stripped_name = NULL, *p;
132int ret = 0;
133134
git_revwalk_new(&w, repo);
135git_revwalk_push_head(w);
136137
if (git_revwalk_next(&id, w) ||
138git_commit_lookup(&commit, repo, &id)) {
139ret = -1;
140goto err;
141}
142143
author = git_commit_author(commit);
144145
/* strip .git suffix */
146if (!(stripped_name = strdup(name)))
147err(1, "strdup");
148if ((p = strrchr(stripped_name, '.')))
149if (!strcmp(p, ".git"))
150*p = '\0';
151152
fputs("<tr><td><a href=\"", fp);
153percentencode(fp, stripped_name, strlen(stripped_name));
154/*
155* changing landing page on repo from log.html to files.html
156*/
157//fputs("/log.html\">", fp);
158fputs("/files.html\">", fp);
159160
xmlencode(fp, stripped_name, strlen(stripped_name));
161fputs("</a></td><td>", fp);
162xmlencode(fp, description, strlen(description));
163fputs("</td><td>", fp);
164xmlencode(fp, owner, strlen(owner));
165fputs("</td><td>", fp);
166if (author)
167printtimeshort(fp, &(author->when));
168fputs("</td></tr>", fp);
169170
git_commit_free(commit);
171err:
172git_revwalk_free(w);
173free(stripped_name);
174175
return ret;
176}
177178
int
179main(int argc, char *argv[])
180{
181FILE *fp;
182char path[PATH_MAX], repodirabs[PATH_MAX + 1];
183const char *repodir;
184int i, ret = 0;
185186
if (argc < 2) {
187fprintf(stderr, "usage: %s [repodir...]\n", argv[0]);
188return 1;
189}
190191
/* do not search outside the git repository:
192GIT_CONFIG_LEVEL_APP is the highest level currently */
193git_libgit2_init();
194for (i = 1; i <= GIT_CONFIG_LEVEL_APP; i++)
195git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "");
196/* do not require the git repository to be owned by the current user */
197git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0);
198199
#ifdef __OpenBSD__
200if (pledge("stdio rpath", NULL) == -1)
201err(1, "pledge");
202#endif
203204
writeheader(stdout);
205206
for (i = 1; i < argc; i++) {
207repodir = argv[i];
208if (!realpath(repodir, repodirabs))
209err(1, "realpath");
210211
if (git_repository_open_ext(&repo, repodir,
212GIT_REPOSITORY_OPEN_NO_SEARCH, NULL)) {
213fprintf(stderr, "%s: cannot open repository\n", argv[0]);
214ret = 1;
215continue;
216}
217218
/* use directory name as name */
219if ((name = strrchr(repodirabs, '/')))
220name++;
221else
222name = "";
223224
/* read description or .git/description */
225joinpath(path, sizeof(path), repodir, "description");
226if (!(fp = fopen(path, "r"))) {
227joinpath(path, sizeof(path), repodir, ".git/description");
228fp = fopen(path, "r");
229}
230description[0] = '\0';
231if (fp) {
232if (!fgets(description, sizeof(description), fp))
233description[0] = '\0';
234checkfileerror(fp, "description", 'r');
235fclose(fp);
236}
237238
/* read owner or .git/owner */
239joinpath(path, sizeof(path), repodir, "owner");
240if (!(fp = fopen(path, "r"))) {
241joinpath(path, sizeof(path), repodir, ".git/owner");
242fp = fopen(path, "r");
243}
244owner[0] = '\0';
245if (fp) {
246if (!fgets(owner, sizeof(owner), fp))
247owner[0] = '\0';
248checkfileerror(fp, "owner", 'r');
249fclose(fp);
250owner[strcspn(owner, "\n")] = '\0';
251}
252writelog(stdout);
253}
254writefooter(stdout);
255256
/* cleanup */
257git_repository_free(repo);
258git_libgit2_shutdown();
259260
checkfileerror(stdout, "<stdout>", 'w');
261262
return ret;
263}