stagit

static git page - forked from git.codemadness.org/stagit
git clone git://src.gearsix.net/stagitstagit.zip
Log | Files | Refs | Atom | README | LICENSE

stagit-index.c (raw) (7575B)


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