git.strcat.st

/strcat/sgit.git/ - summarytreelogarchive

subject
replace markdown patch for compile time flag & other minor improvements
commit
64ba99dd538a5297236784e9deed2eba70b144f8
date
2026-05-04T11:10:55Z
message
diff
 Makefile          |   31 ++
 README.md         |   24 +
 extras/markdown.c |  180 +++++++
 extras/markdown.h |   10 +
 sgit.c            | 1520 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1765 insertions(+)

diff --git a/Makefile b/Makefile
index 3fdf21b..d1911f4 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,4 @@
+<<<<<<< HEAD
 PROG    = sgit
 CFLAGS  = -O2 -Wall -Wextra -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith
 LDFLAGS = -static
@@ -9,3 +10,33 @@ $(PROG): sgit.c
 
 clean:
 	rm -f $(PROG)
+=======
+PROG     = sgit
+CFLAGS   = -O2 -Wall -Wextra -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith
+LDFLAGS  = -static
+MARKDOWN = 0
+
+CFLAGS_MARKDOWN_0 =
+CFLAGS_MARKDOWN_1 = -DUSE_MARKDOWN
+
+OBJS_MARKDOWN_0 =
+OBJS_MARKDOWN_1 = extras/markdown.o
+
+EXTRA_CFLAGS = $(CFLAGS_MARKDOWN_$(MARKDOWN))
+EXTRA_OBJS   = $(OBJS_MARKDOWN_$(MARKDOWN))
+OBJS         = sgit.o $(EXTRA_OBJS)
+
+all: $(PROG)
+
+$(PROG): $(OBJS)
+	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(LDFLAGS) -o $(PROG) $(OBJS)
+
+sgit.o: sgit.c
+	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ sgit.c
+
+extras/markdown.o: extras/markdown.c extras/markdown.h
+	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ extras/markdown.c
+
+clean:
+	rm -f $(PROG) sgit.o extras/markdown.o
+>>>>>>> ee3b035 (init)
diff --git a/README.md b/README.md
index 00cbc2b..fe30c34 100644
--- a/README.md
+++ b/README.md
@@ -5,12 +5,21 @@
 ## building
 
 make dat shit
+<<<<<<< HEAD
 
+=======
+MARKDOWN= is set to 0, pass flag to make if you want markdown
+formatting for readmes
+>>>>>>> ee3b035 (init)
 run dat shit `./sgit repos out`
 
 if `repos` is a repo (`.git/` exists), it generates one site in `out`.
 
+<<<<<<< HEAD
 if `repos` does not have a .git/ directory it will scan direct child directories and generates one 
+=======
+if `repos` does not have a .git/ directory or bare repo it will scan direct child directories and generates one 
+>>>>>>> ee3b035 (init)
 site per repo, plus a top level index
 
 ## output layout
@@ -23,16 +32,28 @@ site per repo, plus a top level index
 
 ## metadata
 
+<<<<<<< HEAD
 it reads meta data from .git/owner or .git/cloneurl
 
 - `owner`     defaults to `unknown` if missing
 - `cloneurl`  shown when present; e.g.:
+=======
+it reads meta data from .git/owner or .git/cloneurl or just repo/<owner/cloneurl> if bare
+
+- `owner`     defaults to `unknown` if missing
+- `cloneurl`  shown when present; e.g.:
+should probably make it so you can specify multiple clone urls neatly..
+>>>>>>> ee3b035 (init)
 ```
 printf '%s\n' "https://git.larp.moe/dd/sgit.git > \
 /path/to/repo/.git/cloneurl
 ```          
 
 ## styling
+<<<<<<< HEAD
+=======
+all pages link to the relative path of your our dir for style.css
+>>>>>>> ee3b035 (init)
 make a style.css in the root of the out directory.
 - `out/style.css`
 
@@ -41,7 +62,10 @@ make a style.css in the root of the out directory.
 each repo output directory stores `.sgit-cache` with the last generated `head`.
 if `head` has not changed, that repo is skipped.
 
+<<<<<<< HEAD
 
+=======
+>>>>>>> ee3b035 (init)
 ## See Also
 
 - [stagit](https://codemadness.org/git/stagit/)
diff --git a/extras/markdown.c b/extras/markdown.c
new file mode 100644
index 0000000..88a9a3f
--- /dev/null
+++ b/extras/markdown.c
@@ -0,0 +1,180 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "markdown.h"
+
+static char *
+md_strdup(const char *s)
+{
+	char *out;
+	size_t len;
+
+	len = strlen(s);
+	out = malloc(len + 1);
+	if (out == NULL)
+		return NULL;
+	memcpy(out, s, len + 1);
+	return out;
+}
+
+static void
+markdown_inline(FILE *fp, const char *line,
+    void (*esc_len)(FILE *, const char *, size_t))
+{
+	const char *code_end, *link_close, *link_end, *p, *url_start;
+	size_t n;
+
+	p = line;
+	while (*p != '\0') {
+		if (*p == '`') {
+			code_end = strchr(p + 1, '`');
+			if (code_end != NULL) {
+				fputs("<code>", fp);
+				esc_len(fp, p + 1,
+				    (size_t)(code_end - (p + 1)));
+				fputs("</code>", fp);
+				p = code_end + 1;
+				continue;
+			}
+		}
+		if (*p == '[') {
+			link_close = strchr(p + 1, ']');
+			if (link_close != NULL && *(link_close + 1) == '(') {
+				url_start = link_close + 2;
+				link_end = strchr(url_start, ')');
+				if (link_end != NULL) {
+					fputs("<a href=\"", fp);
+					esc_len(fp, url_start,
+					    (size_t)(link_end - url_start));
+					fputs("\">", fp);
+					esc_len(fp, p + 1,
+					    (size_t)(link_close - (p + 1)));
+					fputs("</a>", fp);
+					p = link_end + 1;
+					continue;
+				}
+			}
+		}
+		n = strcspn(p, "`[");
+		if (n == 0) {
+			esc_len(fp, p, 1);
+			p++;
+		} else {
+			esc_len(fp, p, n);
+			p += n;
+		}
+	}
+}
+
+void
+markdown_render(FILE *fp, const char *md,
+    void (*esc_len)(FILE *, const char *, size_t))
+{
+	char *line;
+	char *next;
+	char *save;
+	int in_code, in_list, in_p, level;
+
+	save = md_strdup(md);
+	if (save == NULL)
+		return;
+	in_code = 0;
+	in_list = 0;
+	in_p = 0;
+	line = save;
+	while (line != NULL) {
+		next = strchr(line, '\n');
+		if (next != NULL) {
+			*next = '\0';
+			next++;
+		}
+		while (*line == ' ' || *line == '\t')
+			line++;
+		if (strncmp(line, "```", 3) == 0) {
+			if (in_p) {
+				fputs("</p>\n", fp);
+				in_p = 0;
+			}
+			if (in_list) {
+				fputs("</ul>\n", fp);
+				in_list = 0;
+			}
+			if (!in_code)
+				fputs("<pre>", fp);
+			else
+				fputs("</pre>\n", fp);
+			in_code = !in_code;
+			line = next;
+			continue;
+		}
+		if (in_code) {
+			esc_len(fp, line, strlen(line));
+			fputc('\n', fp);
+			line = next;
+			continue;
+		}
+		if (*line == '\0') {
+			if (in_p) {
+				fputs("</p>\n", fp);
+				in_p = 0;
+			}
+			if (in_list) {
+				fputs("</ul>\n", fp);
+				in_list = 0;
+			}
+			line = next;
+			continue;
+		}
+		if (*line == '#') {
+			if (in_p) {
+				fputs("</p>\n", fp);
+				in_p = 0;
+			}
+			if (in_list) {
+				fputs("</ul>\n", fp);
+				in_list = 0;
+			}
+			level = 0;
+			while (*line == '#' && level < 6) {
+				level++;
+				line++;
+			}
+			while (*line == ' ')
+				line++;
+			fprintf(fp, "<h%d>", level == 0 ? 1 : level);
+			markdown_inline(fp, line, esc_len);
+			fprintf(fp, "</h%d>\n", level == 0 ? 1 : level);
+			line = next;
+			continue;
+		}
+		if ((line[0] == '-' || line[0] == '*') && line[1] == ' ') {
+			if (in_p) {
+				fputs("</p>\n", fp);
+				in_p = 0;
+			}
+			if (!in_list) {
+				fputs("<ul>\n", fp);
+				in_list = 1;
+			}
+			fputs("<li>", fp);
+			markdown_inline(fp, line + 2, esc_len);
+			fputs("</li>\n", fp);
+			line = next;
+			continue;
+		}
+		if (!in_p) {
+			fputs("<p>", fp);
+			in_p = 1;
+		} else
+			fputs("<br>\n", fp);
+		markdown_inline(fp, line, esc_len);
+		line = next;
+	}
+	if (in_code)
+		fputs("</pre>\n", fp);
+	if (in_p)
+		fputs("</p>\n", fp);
+	if (in_list)
+		fputs("</ul>\n", fp);
+	free(save);
+}
diff --git a/extras/markdown.h b/extras/markdown.h
new file mode 100644
index 0000000..56e0551
--- /dev/null
+++ b/extras/markdown.h
@@ -0,0 +1,10 @@
+#ifndef MARKDOWN_H
+#define MARKDOWN_H
+
+#include <stddef.h>
+#include <stdio.h>
+
+void	markdown_render(FILE *, const char *,
+	    void (*)(FILE *, const char *, size_t));
+
+#endif
diff --git a/sgit.c b/sgit.c
index d670231..9a16338 100644
--- a/sgit.c
+++ b/sgit.c
@@ -13,11 +13,19 @@
 #include <string.h>
 #include <unistd.h>
 
+<<<<<<< HEAD
+=======
+#ifdef USE_MARKDOWN
+#include "extras/markdown.h"
+#endif
+
+>>>>>>> ee3b035 (init)
 #ifndef PATH_MAX
 #define PATH_MAX 4096
 #endif
 
 #define COMMIT_LIMIT 100
+<<<<<<< HEAD
 #define SEP '\x1f'
 
 struct repo {
@@ -43,6 +51,34 @@ struct tree_node {
 	size_t	 cap;
 	size_t	 nchildren;
 	int	 is_dir;
+=======
+#define CACHE_VERSION "2"
+#define SEP '\x1f'
+
+struct repo {
+  char	*branch;
+  char	*cloneurl;
+  char	*head;
+  char	*last_author;
+  char	*last_date;
+  char	*last_subject;
+  char	*name;
+  char	*owner;
+  char	*outdir;
+  char	*path;
+  char	*rootlink_deep;
+  char	*rootlink_repo;
+  int	 changed;
+};
+
+struct tree_node {
+  struct tree_node **children;
+  char	*name;
+  char	*path;
+  size_t	 cap;
+  size_t	 nchildren;
+  int	 is_dir;
+>>>>>>> ee3b035 (init)
 };
 
 static const char *progname;
@@ -60,20 +96,33 @@ static void	 ensure_parent_dir(const char *);
 static void	 clear_html_files(const char *);
 static void	 free_repos(struct repo *, size_t);
 static char	*join3(const char *, const char *, const char *);
+<<<<<<< HEAD
 static void	 load_repo_meta(struct repo *);
 static void	 make_commit_page(const struct repo *, const char *, const char *,
 	        const char *, const char *, const char *);
 static void	 make_files_page(const struct repo *, const char *);
+=======
+static int	 load_repo_meta(struct repo *);
+static void	 make_commit_page(const struct repo *, const char *, const char *,
+          const char *, const char *, const char *);
+static void	 make_files_page(const struct repo *, const char *);
+static char	*repo_gitdir_path(const struct repo *);
+>>>>>>> ee3b035 (init)
 static char	*read_git_meta_file(const struct repo *, const char *);
 static void	 make_index(const char *, struct repo *, size_t);
 static void	 make_readme_page(const struct repo *, const char *, int);
 static void	 make_repo_page(const struct repo *);
 static char	*file_page_name(const char *);
 static int	 has_suffix_ci(const char *, const char *);
+<<<<<<< HEAD
 static void	 render_markdown(FILE *, const char *);
 static void	 render_markdown_inline(FILE *, const char *);
 static void	 render_tree(FILE *, const struct repo *, struct tree_node *,
 	        int);
+=======
+static void	 render_tree(FILE *, const struct repo *, struct tree_node *,
+          int);
+>>>>>>> ee3b035 (init)
 static int	 repo_cmp(const void *, const void *);
 static size_t	 read_blob_size(const struct repo *, const char *);
 static char	*read_cache_head(const char *);
@@ -105,6 +154,7 @@ static char	*write_shell_quoted(const char *);
 int
 main(int argc, char *argv[])
 {
+<<<<<<< HEAD
 	char *argv0;
 	const char *input, *output;
 	const char *raw;
@@ -176,18 +226,102 @@ main(int argc, char *argv[])
 
 	free_repos(repos, nrepos);
 	return 0;
+=======
+  char *argv0;
+  const char *input, *output;
+  const char *raw;
+  struct repo *repos;
+  size_t cap, i, nrepos;
+  int ch, multi;
+
+  argv0 = argv[0];
+  while ((ch = getopt(argc, argv, "")) != -1) {
+      switch (ch) {
+      default:
+          usage();
+      }
+  }
+  argc -= optind;
+  argv += optind;
+  progname = argv0;
+  raw = getenv("RAW");
+  raw_mode = 0;
+  if (raw != NULL && strcmp(raw, "1") == 0)
+      raw_mode = 1;
+  if (argc == 0) {
+      input = "repos";
+      output = "out";
+  } else if (argc == 2) {
+      input = argv[0];
+      output = argv[1];
+  } else
+      usage();
+
+  repos = NULL;
+  cap = 0;
+  nrepos = 0;
+  discover_repos(input, &repos, &nrepos, &cap);
+  if (nrepos == 0)
+      errx(1, "no git repositories found in %s", input);
+  qsort(repos, nrepos, sizeof(struct repo), repo_cmp);
+
+  ensure_dir(output);
+  multi = nrepos > 1;
+
+  for (i = 0; i < nrepos; i++) {
+      if (multi) {
+          repos[i].outdir = join3(output, "/", repos[i].name);
+          if (repos[i].outdir == NULL)
+              err(1, "malloc");
+      } else {
+          repos[i].outdir = str_dup(output);
+          if (repos[i].outdir == NULL)
+              err(1, "malloc");
+      }
+      if (multi) {
+          repos[i].rootlink_repo = str_dup("../index.html");
+          repos[i].rootlink_deep = str_dup("../../index.html");
+      } else {
+          repos[i].rootlink_repo = str_dup("index.html");
+          repos[i].rootlink_deep = str_dup("../index.html");
+      }
+      if (repos[i].rootlink_repo == NULL || repos[i].rootlink_deep == NULL)
+          err(1, "malloc");
+      if (!load_repo_meta(&repos[i])) {
+          fprintf(stderr, "%s: skipping %s (no commits)\n", progname,
+              repos[i].path);
+          continue;
+      }
+      repos[i].changed = repo_changed(&repos[i]);
+      if (repos[i].changed)
+          make_repo_page(&repos[i]);
+  }
+
+  if (multi) {
+      make_index(output, repos, nrepos);
+  }
+
+  free_repos(repos, nrepos);
+  return 0;
+>>>>>>> ee3b035 (init)
 }
 
 static void
 usage(void)
 {
+<<<<<<< HEAD
 	fprintf(stderr, "usage: %s [repos out]\n", progname);
 	exit(1);
+=======
+  fprintf(stderr, "usage: %s [repos out]\n", progname);
+  exit(1);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 warn(const char *fmt, ...)
 {
+<<<<<<< HEAD
 	va_list ap;
 	int saved;
 
@@ -201,11 +335,27 @@ warn(const char *fmt, ...)
 		fputs("warning", stderr);
 	va_end(ap);
 	fprintf(stderr, ": %s\n", strerror(saved));
+=======
+  va_list ap;
+  int saved;
+
+  saved = errno;
+  if (progname != NULL && *progname != '\0')
+    fprintf(stderr, "%s: ", progname);
+  va_start(ap, fmt);
+  if (fmt != NULL && *fmt != '\0')
+    vfprintf(stderr, fmt, ap);
+  else
+    fputs("warning", stderr);
+  va_end(ap);
+  fprintf(stderr, ": %s\n", strerror(saved));
+>>>>>>> ee3b035 (init)
 }
 
 static void
 errx(int eval, const char *fmt, ...)
 {
+<<<<<<< HEAD
 	va_list ap;
 
 	if (progname != NULL && *progname != '\0')
@@ -218,11 +368,26 @@ errx(int eval, const char *fmt, ...)
 	va_end(ap);
 	fputc('\n', stderr);
 	exit(eval);
+=======
+  va_list ap;
+
+  if (progname != NULL && *progname != '\0')
+    fprintf(stderr, "%s: ", progname);
+  va_start(ap, fmt);
+  if (fmt != NULL && *fmt != '\0')
+    vfprintf(stderr, fmt, ap);
+  else
+    fputs("error", stderr);
+  va_end(ap);
+  fputc('\n', stderr);
+  exit(eval);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 err(int eval, const char *fmt, ...)
 {
+<<<<<<< HEAD
 	va_list ap;
 	int saved;
 
@@ -237,11 +402,28 @@ err(int eval, const char *fmt, ...)
 	va_end(ap);
 	fprintf(stderr, ": %s\n", strerror(saved));
 	exit(eval);
+=======
+  va_list ap;
+  int saved;
+
+  saved = errno;
+  if (progname != NULL && *progname != '\0')
+    fprintf(stderr, "%s: ", progname);
+  va_start(ap, fmt);
+  if (fmt != NULL && *fmt != '\0')
+    vfprintf(stderr, fmt, ap);
+  else
+    fputs("error", stderr);
+  va_end(ap);
+  fprintf(stderr, ": %s\n", strerror(saved));
+  exit(eval);
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 str_dup(const char *s)
 {
+<<<<<<< HEAD
 	char *out;
 	size_t len;
 
@@ -251,11 +433,23 @@ str_dup(const char *s)
 	    return NULL;
 	memcpy(out, s, len + 1);
 	return out;
+=======
+  char *out;
+  size_t len;
+
+  len = strlen(s);
+  out = malloc(len + 1);
+  if (out == NULL)
+      return NULL;
+  memcpy(out, s, len + 1);
+  return out;
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 trim_newline(char *s)
 {
+<<<<<<< HEAD
 	size_t len;
 
 	len = strlen(s);
@@ -264,11 +458,22 @@ trim_newline(char *s)
 	    len--;
 	}
 	return s;
+=======
+  size_t len;
+
+  len = strlen(s);
+  while (len > 0 && (s[len - 1] == '\n' || s[len - 1] == '\r')) {
+      s[len - 1] = '\0';
+      len--;
+  }
+  return s;
+>>>>>>> ee3b035 (init)
 }
 
 static size_t
 text_loc(const char *s)
 {
+<<<<<<< HEAD
 	size_t loc;
 
 	if (*s == '\0')
@@ -279,11 +484,24 @@ text_loc(const char *s)
 	        loc++;
 	}
 	return loc + 1;
+=======
+  size_t loc;
+
+  if (*s == '\0')
+      return 0;
+  loc = 0;
+  for (; *s != '\0'; s++) {
+      if (*s == '\n')
+          loc++;
+  }
+  return loc + 1;
+>>>>>>> ee3b035 (init)
 }
 
 static int
 text_content_p(const char *s, size_t nbytes, size_t blob_size)
 {
+<<<<<<< HEAD
 	size_t i;
 	unsigned char c;
 
@@ -298,11 +516,28 @@ text_content_p(const char *s, size_t nbytes, size_t blob_size)
 	    return 0;
 	}
 	return 1;
+=======
+  size_t i;
+  unsigned char c;
+
+  if (nbytes < blob_size)
+      return 0;
+  for (i = 0; i < nbytes; i++) {
+      c = (unsigned char)s[i];
+      if (c == '\n' || c == '\r' || c == '\t')
+          continue;
+      if (c >= 0x20 || c >= 0x80)
+          continue;
+      return 0;
+  }
+  return 1;
+>>>>>>> ee3b035 (init)
 }
 
 static void
 format_size(size_t nbytes, char *buf, size_t buflen)
 {
+<<<<<<< HEAD
 	const char *units[] = { "bytes", "kb", "mb", "gb", "tb", "pb" };
 	double v;
 	size_t i, nunits;
@@ -324,21 +559,53 @@ format_size(size_t nbytes, char *buf, size_t buflen)
 	    snprintf(buf, buflen, "%.1f %s", v, units[i]);
 	else
 	    snprintf(buf, buflen, "%.2f %s", v, units[i]);
+=======
+  const char *units[] = { "bytes", "kb", "mb", "gb", "tb", "pb" };
+  double v;
+  size_t i, nunits;
+
+  nunits = sizeof(units) / sizeof(units[0]);
+  if (nbytes < 1024) {
+      snprintf(buf, buflen, "%lu bytes", (unsigned long)nbytes);
+      return;
+  }
+  v = (double)nbytes;
+  i = 0;
+  while (v >= 1024.0 && i + 1 < nunits) {
+      v /= 1024.0;
+      i++;
+  }
+  if (v >= 100.0)
+      snprintf(buf, buflen, "%.0f %s", v, units[i]);
+  else if (v >= 10.0)
+      snprintf(buf, buflen, "%.1f %s", v, units[i]);
+  else
+      snprintf(buf, buflen, "%.2f %s", v, units[i]);
+>>>>>>> ee3b035 (init)
 }
 
 static int
 stat_is_dir(const char *path)
 {
+<<<<<<< HEAD
 	struct stat st;
 
 	if (stat(path, &st) == -1)
 	    return 0;
 	return S_ISDIR(st.st_mode);
+=======
+  struct stat st;
+
+  if (stat(path, &st) == -1)
+      return 0;
+  return S_ISDIR(st.st_mode);
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 join3(const char *a, const char *b, const char *c)
 {
+<<<<<<< HEAD
 	char *out;
 	size_t la, lb, lc;
 
@@ -353,11 +620,28 @@ join3(const char *a, const char *b, const char *c)
 	memcpy(out + la + lb, c, lc);
 	out[la + lb + lc] = '\0';
 	return out;
+=======
+  char *out;
+  size_t la, lb, lc;
+
+  la = strlen(a);
+  lb = strlen(b);
+  lc = strlen(c);
+  out = malloc(la + lb + lc + 1);
+  if (out == NULL)
+      return NULL;
+  memcpy(out, a, la);
+  memcpy(out + la, b, lb);
+  memcpy(out + la + lb, c, lc);
+  out[la + lb + lc] = '\0';
+  return out;
+>>>>>>> ee3b035 (init)
 }
 
 static int
 repo_has_git(const char *path)
 {
+<<<<<<< HEAD
 	char *dotgit;
 	int ok;
 
@@ -367,11 +651,55 @@ repo_has_git(const char *path)
 	ok = stat_is_dir(dotgit);
 	free(dotgit);
 	return ok;
+=======
+  char *dotgit, *head, *objects, *out, *packed_refs, *qpath, *refs;
+  struct stat st;
+  int ok;
+
+  dotgit = join3(path, "/", ".git");
+  if (dotgit == NULL)
+      err(1, "malloc");
+  ok = stat(dotgit, &st) == 0 && (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode));
+  free(dotgit);
+  if (ok)
+      return 1;
+
+  /* Bare repository layout fallback: HEAD + objects + (refs or packed-refs). */
+  head = join3(path, "/", "HEAD");
+  objects = join3(path, "/", "objects");
+  refs = join3(path, "/", "refs");
+  packed_refs = join3(path, "/", "packed-refs");
+  if (head == NULL || objects == NULL || refs == NULL || packed_refs == NULL)
+      err(1, "malloc");
+  ok = stat(head, &st) == 0 && S_ISREG(st.st_mode);
+  ok = ok && stat(objects, &st) == 0 && S_ISDIR(st.st_mode);
+  ok = ok && ((stat(refs, &st) == 0 && S_ISDIR(st.st_mode)) ||
+      (stat(packed_refs, &st) == 0 && S_ISREG(st.st_mode)));
+  free(head);
+  free(objects);
+  free(refs);
+  free(packed_refs);
+  if (ok)
+      return 1;
+
+  qpath = write_shell_quoted(path);
+  if (qpath == NULL)
+      err(1, "malloc");
+  out = read_cmd_output("git -c safe.directory='*' -C %s rev-parse --git-dir 2>/dev/null", qpath);
+  free(qpath);
+  if (out == NULL)
+      return 0;
+  trim_newline(out);
+  ok = out[0] != '\0';
+  free(out);
+  return ok;
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 repo_basename(const char *path)
 {
+<<<<<<< HEAD
 	const char *p;
 
 	p = strrchr(path, '/');
@@ -385,11 +713,27 @@ repo_basename(const char *path)
 	    return str_dup(p);
 	}
 	return str_dup(p + 1);
+=======
+  const char *p;
+
+  p = strrchr(path, '/');
+  if (p == NULL)
+      return str_dup(path);
+  if (*(p + 1) == '\0') {
+      for (; p > path && *p == '/'; p--)
+          continue;
+      for (; p > path && *(p - 1) != '/'; p--)
+          continue;
+      return str_dup(p);
+  }
+  return str_dup(p + 1);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 append_char(char **buf, size_t *len, size_t *cap, char c)
 {
+<<<<<<< HEAD
 	char *tmp;
 
 	if (*len + 2 > *cap) {
@@ -405,20 +749,45 @@ append_char(char **buf, size_t *len, size_t *cap, char c)
 	(*buf)[*len] = c;
 	(*len)++;
 	(*buf)[*len] = '\0';
+=======
+  char *tmp;
+
+  if (*len + 2 > *cap) {
+      if (*cap == 0)
+          *cap = 128;
+      else
+          *cap *= 2;
+      tmp = realloc(*buf, *cap);
+      if (tmp == NULL)
+          err(1, "realloc");
+      *buf = tmp;
+  }
+  (*buf)[*len] = c;
+  (*len)++;
+  (*buf)[*len] = '\0';
+>>>>>>> ee3b035 (init)
 }
 
 static void
 append_str(char **buf, size_t *len, size_t *cap, const char *s)
 {
+<<<<<<< HEAD
 	while (*s != '\0') {
 	    append_char(buf, len, cap, *s);
 	    s++;
 	}
+=======
+  while (*s != '\0') {
+      append_char(buf, len, cap, *s);
+      s++;
+  }
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 write_shell_quoted(const char *s)
 {
+<<<<<<< HEAD
 	char *out;
 	size_t cap, len;
 
@@ -435,11 +804,30 @@ write_shell_quoted(const char *s)
 	}
 	append_char(&out, &len, &cap, '\'');
 	return out;
+=======
+  char *out;
+  size_t cap, len;
+
+  out = NULL;
+  cap = 0;
+  len = 0;
+  append_char(&out, &len, &cap, '\'');
+  while (*s != '\0') {
+      if (*s == '\'')
+          append_str(&out, &len, &cap, "'\\''");
+      else
+          append_char(&out, &len, &cap, *s);
+      s++;
+  }
+  append_char(&out, &len, &cap, '\'');
+  return out;
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 read_cmd_output(const char *fmt, ...)
 {
+<<<<<<< HEAD
 	va_list ap;
 	char line[4096], *cmd, *out;
 	size_t cap, len;
@@ -480,11 +868,54 @@ read_cmd_output(const char *fmt, ...)
 	        err(1, "malloc");
 	}
 	return out;
+=======
+  va_list ap;
+  char line[4096], *cmd, *out;
+  size_t cap, len;
+  FILE *fp;
+  int n;
+
+  cmd = NULL;
+  n = 0;
+  va_start(ap, fmt);
+  n = vsnprintf(NULL, 0, fmt, ap);
+  va_end(ap);
+  if (n < 0)
+      return NULL;
+  cmd = malloc((size_t)n + 1);
+  if (cmd == NULL)
+      err(1, "malloc");
+  va_start(ap, fmt);
+  vsnprintf(cmd, (size_t)n + 1, fmt, ap);
+  va_end(ap);
+
+  fp = popen(cmd, "r");
+  free(cmd);
+  if (fp == NULL)
+      return NULL;
+
+  out = NULL;
+  cap = 0;
+  len = 0;
+  while (fgets(line, sizeof(line), fp) != NULL)
+      append_str(&out, &len, &cap, line);
+  if (pclose(fp) == -1) {
+      free(out);
+      return NULL;
+  }
+  if (out == NULL) {
+      out = str_dup("");
+      if (out == NULL)
+          err(1, "malloc");
+  }
+  return out;
+>>>>>>> ee3b035 (init)
 }
 
 static void
 add_repo(struct repo **repos, size_t *nrepos, size_t *cap, const char *path)
 {
+<<<<<<< HEAD
 	struct repo *tmp;
 	char *name;
 
@@ -517,11 +948,46 @@ add_repo(struct repo **repos, size_t *nrepos, size_t *cap, const char *path)
 	    err(1, "malloc");
 	(*repos)[*nrepos].changed = 1;
 	(*nrepos)++;
+=======
+  struct repo *tmp;
+  char *name;
+
+  name = repo_basename(path);
+  if (name == NULL)
+      err(1, "malloc");
+  if (*nrepos + 1 > *cap) {
+      if (*cap == 0)
+          *cap = 8;
+      else
+          *cap *= 2;
+      tmp = realloc(*repos, *cap * sizeof(struct repo));
+      if (tmp == NULL)
+          err(1, "realloc");
+      *repos = tmp;
+  }
+  (*repos)[*nrepos].branch = NULL;
+  (*repos)[*nrepos].cloneurl = NULL;
+  (*repos)[*nrepos].head = NULL;
+  (*repos)[*nrepos].last_author = NULL;
+  (*repos)[*nrepos].last_date = NULL;
+  (*repos)[*nrepos].last_subject = NULL;
+  (*repos)[*nrepos].name = name;
+  (*repos)[*nrepos].owner = NULL;
+  (*repos)[*nrepos].outdir = NULL;
+  (*repos)[*nrepos].path = str_dup(path);
+  (*repos)[*nrepos].rootlink_deep = NULL;
+  (*repos)[*nrepos].rootlink_repo = NULL;
+  if ((*repos)[*nrepos].path == NULL)
+      err(1, "malloc");
+  (*repos)[*nrepos].changed = 1;
+  (*nrepos)++;
+>>>>>>> ee3b035 (init)
 }
 
 static void
 discover_repos(const char *input, struct repo **repos, size_t *nrepos, size_t *cap)
 {
+<<<<<<< HEAD
 	struct dirent *de;
 	char *candidate;
 	DIR *dp;
@@ -548,21 +1014,58 @@ discover_repos(const char *input, struct repo **repos, size_t *nrepos, size_t *c
 	}
 	if (closedir(dp) == -1)
 	    err(1, "closedir %s", input);
+=======
+  struct dirent *de;
+  char *candidate;
+  DIR *dp;
+
+  if (repo_has_git(input)) {
+      add_repo(repos, nrepos, cap, input);
+      return;
+  }
+  if (!stat_is_dir(input))
+      errx(1, "%s is not a directory", input);
+
+  dp = opendir(input);
+  if (dp == NULL)
+      err(1, "opendir %s", input);
+  while ((de = readdir(dp)) != NULL) {
+      if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
+          continue;
+      candidate = join3(input, "/", de->d_name);
+      if (candidate == NULL)
+          err(1, "malloc");
+      if (repo_has_git(candidate))
+          add_repo(repos, nrepos, cap, candidate);
+      free(candidate);
+  }
+  if (closedir(dp) == -1)
+      err(1, "closedir %s", input);
+>>>>>>> ee3b035 (init)
 }
 
 static int
 repo_cmp(const void *a, const void *b)
 {
+<<<<<<< HEAD
 	const struct repo *ra, *rb;
 
 	ra = a;
 	rb = b;
 	return strcmp(ra->name, rb->name);
+=======
+  const struct repo *ra, *rb;
+
+  ra = a;
+  rb = b;
+  return strcmp(ra->name, rb->name);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 ensure_dir(const char *path)
 {
+<<<<<<< HEAD
 	char buf[PATH_MAX];
 	char *p;
 	size_t len;
@@ -584,11 +1087,35 @@ ensure_dir(const char *path)
 	}
 	if (mkdir(buf, 0755) == -1 && errno != EEXIST)
 	    err(1, "mkdir %s", buf);
+=======
+  char buf[PATH_MAX];
+  char *p;
+  size_t len;
+
+  len = strlen(path);
+  if (len == 0)
+      errx(1, "empty path");
+  if (len >= sizeof(buf))
+      errx(1, "path too long: %s", path);
+  memcpy(buf, path, len + 1);
+
+  for (p = buf + 1; *p != '\0'; p++) {
+      if (*p != '/')
+          continue;
+      *p = '\0';
+      if (mkdir(buf, 0755) == -1 && errno != EEXIST)
+          err(1, "mkdir %s", buf);
+      *p = '/';
+  }
+  if (mkdir(buf, 0755) == -1 && errno != EEXIST)
+      err(1, "mkdir %s", buf);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 ensure_parent_dir(const char *path)
 {
+<<<<<<< HEAD
 	char *copy;
 	char *p;
 
@@ -602,11 +1129,27 @@ ensure_parent_dir(const char *path)
 	        ensure_dir(copy);
 	}
 	free(copy);
+=======
+  char *copy;
+  char *p;
+
+  copy = str_dup(path);
+  if (copy == NULL)
+      err(1, "malloc");
+  p = strrchr(copy, '/');
+  if (p != NULL) {
+      *p = '\0';
+      if (*copy != '\0')
+          ensure_dir(copy);
+  }
+  free(copy);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 clear_html_files(const char *dir)
 {
+<<<<<<< HEAD
 	struct dirent *de;
 	char *path;
 	DIR *dp;
@@ -630,11 +1173,37 @@ clear_html_files(const char *dir)
 	}
 	if (closedir(dp) == -1)
 	    warn("closedir %s", dir);
+=======
+  struct dirent *de;
+  char *path;
+  DIR *dp;
+  size_t len;
+
+  dp = opendir(dir);
+  if (dp == NULL)
+      return;
+  while ((de = readdir(dp)) != NULL) {
+      if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
+          continue;
+      len = strlen(de->d_name);
+      if (len < 6 || strcmp(de->d_name + len - 5, ".html") != 0)
+          continue;
+      path = join3(dir, "/", de->d_name);
+      if (path == NULL)
+          err(1, "malloc");
+      if (unlink(path) == -1 && errno != ENOENT)
+          warn("unlink %s", path);
+      free(path);
+  }
+  if (closedir(dp) == -1)
+      warn("closedir %s", dir);
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 read_cache_head(const char *outdir)
 {
+<<<<<<< HEAD
 	char buf[256], *path;
 	FILE *fp;
 
@@ -653,11 +1222,32 @@ read_cache_head(const char *outdir)
 	    warn("fclose cache");
 	trim_newline(buf);
 	return str_dup(buf);
+=======
+  char buf[256], *path;
+  FILE *fp;
+
+  path = join3(outdir, "/", ".sgit-cache");
+  if (path == NULL)
+      err(1, "malloc");
+  fp = fopen(path, "r");
+  free(path);
+  if (fp == NULL)
+      return NULL;
+  if (fgets(buf, sizeof(buf), fp) == NULL) {
+      fclose(fp);
+      return NULL;
+  }
+  if (fclose(fp) == EOF)
+      warn("fclose cache");
+  trim_newline(buf);
+  return str_dup(buf);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 write_cache_head(const char *outdir, const char *head)
 {
+<<<<<<< HEAD
 	char *path;
 	FILE *fp;
 
@@ -728,11 +1318,94 @@ load_repo_meta(struct repo *r)
 
 	free(out);
 	free(qpath);
+=======
+  char *path;
+  FILE *fp;
+
+  path = join3(outdir, "/", ".sgit-cache");
+  if (path == NULL)
+      err(1, "malloc");
+  fp = fopen(path, "w");
+  if (fp == NULL)
+      err(1, "fopen %s", path);
+  fprintf(fp, CACHE_VERSION ":%s\n", head);
+  if (fclose(fp) == EOF)
+      err(1, "fclose %s", path);
+  free(path);
+}
+
+static int
+load_repo_meta(struct repo *r)
+{
+  char *fmt, *out, *p;
+  char *author, *branch, *date, *head, *qpath, *subject;
+
+  qpath = write_shell_quoted(r->path);
+  if (qpath == NULL)
+      err(1, "malloc");
+
+  /* Use latest commit from any ref so bare repos without HEAD still work. */
+  fmt = "%H%x1f%ad%x1f%an%x1f%s";
+  out = read_cmd_output("git -c safe.directory='*' -C %s log -1 --all --date=iso --pretty=format:%s 2>/dev/null",
+      qpath, fmt);
+  if (out == NULL)
+      errx(1, "failed to get last commit for %s", r->path);
+  trim_newline(out);
+  if (out[0] == '\0') {
+      free(out);
+      free(qpath);
+      return 0;
+  }
+
+  p = out;
+  if ((head = split_field(&p)) == NULL || (date = split_field(&p)) == NULL ||
+      (author = split_field(&p)) == NULL || (subject = split_field(&p)) == NULL) {
+      free(out);
+      free(qpath);
+      return 0;
+  }
+  r->head = str_dup(head);
+  if (r->head == NULL)
+      err(1, "malloc");
+
+  branch = read_cmd_output("git -c safe.directory='*' -C %s rev-parse --abbrev-ref HEAD 2>/dev/null", qpath);
+  if (branch == NULL)
+      errx(1, "failed to get branch for %s", r->path);
+  trim_newline(branch);
+  if (branch[0] == '\0' || strcmp(branch, "HEAD") == 0) {
+      free(branch);
+      branch = str_dup("(no HEAD)");
+      if (branch == NULL)
+          err(1, "malloc");
+  }
+  r->branch = branch;
+
+  r->last_date = str_dup(date);
+  if (r->last_date == NULL)
+      err(1, "malloc");
+  r->last_author = str_dup(author);
+  if (r->last_author == NULL)
+      err(1, "malloc");
+  r->last_subject = str_dup(subject);
+  if (r->last_subject == NULL)
+      err(1, "malloc");
+  r->owner = read_git_meta_file(r, "owner");
+  if (r->owner == NULL)
+      err(1, "malloc");
+  r->cloneurl = read_git_meta_file(r, "cloneurl");
+  if (r->cloneurl == NULL)
+      err(1, "malloc");
+
+  free(out);
+  free(qpath);
+  return 1;
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 split_field(char **s)
 {
+<<<<<<< HEAD
 	char *field, *p;
 
 	if (*s == NULL)
@@ -746,11 +1419,27 @@ split_field(char **s)
 	*p = '\0';
 	*s = p + 1;
 	return field;
+=======
+  char *field, *p;
+
+  if (*s == NULL)
+      return NULL;
+  field = *s;
+  p = strchr(field, SEP);
+  if (p == NULL) {
+      *s = NULL;
+      return field;
+  }
+  *p = '\0';
+  *s = p + 1;
+  return field;
+>>>>>>> ee3b035 (init)
 }
 
 static int
 repo_changed(struct repo *r)
 {
+<<<<<<< HEAD
 	char *cached;
 	int changed;
 
@@ -761,17 +1450,38 @@ repo_changed(struct repo *r)
 	changed = strcmp(cached, r->head) != 0;
 	free(cached);
 	return changed;
+=======
+  char *cached, *expect;
+  int changed;
+
+  ensure_dir(r->outdir);
+  cached = read_cache_head(r->outdir);
+  if (cached == NULL)
+      return 1;
+  expect = join3(CACHE_VERSION ":", "", r->head);
+  if (expect == NULL)
+      err(1, "malloc");
+  changed = strcmp(cached, expect) != 0;
+  free(cached);
+  free(expect);
+  return changed;
+>>>>>>> ee3b035 (init)
 }
 
 static void
 write_html_escaped(FILE *fp, const char *s)
 {
+<<<<<<< HEAD
 	write_html_escaped_len(fp, s, strlen(s));
+=======
+  write_html_escaped_len(fp, s, strlen(s));
+>>>>>>> ee3b035 (init)
 }
 
 static void
 write_html_escaped_len(FILE *fp, const char *s, size_t len)
 {
+<<<<<<< HEAD
 	size_t i;
 
 	for (i = 0; i < len; i++) {
@@ -794,12 +1504,37 @@ write_html_escaped_len(FILE *fp, const char *s, size_t len)
 	    }
 	    s++;
 	}
+=======
+  size_t i;
+
+  for (i = 0; i < len; i++) {
+      switch (*s) {
+      case '&':
+          fputs("&amp;", fp);
+          break;
+      case '<':
+          fputs("&lt;", fp);
+          break;
+      case '>':
+          fputs("&gt;", fp);
+          break;
+      case '\"':
+          fputs("&quot;", fp);
+          break;
+      default:
+          fputc((unsigned char)*s, fp);
+          break;
+      }
+      s++;
+  }
+>>>>>>> ee3b035 (init)
 }
 
 static void
 make_commit_page(const struct repo *r, const char *hash, const char *short_hash,
     const char *date, const char *author, const char *subject)
 {
+<<<<<<< HEAD
 	char *dir, *html, *patch, *qhash, *qpath, *tmp;
 	FILE *fp;
 
@@ -865,11 +1600,79 @@ make_commit_page(const struct repo *r, const char *hash, const char *short_hash,
 	free(patch);
 	free(qhash);
 	free(qpath);
+=======
+  char *dir, *html, *patch, *qhash, *qpath, *tmp;
+  FILE *fp;
+
+  dir = join3(r->outdir, "/", "commits");
+  if (dir == NULL)
+      err(1, "malloc");
+  ensure_dir(dir);
+  tmp = join3(dir, "/", hash);
+  if (tmp == NULL)
+      err(1, "malloc");
+  html = join3(tmp, "", ".html");
+  if (html == NULL)
+      err(1, "malloc");
+  free(tmp);
+
+  qpath = write_shell_quoted(r->path);
+  qhash = write_shell_quoted(hash);
+  if (qpath == NULL || qhash == NULL)
+      err(1, "malloc");
+  patch = read_cmd_output("git -c safe.directory='*' -C %s show --date=short --pretty=format: "
+      "--patch --stat %s 2>/dev/null", qpath, qhash);
+  if (patch == NULL)
+      patch = str_dup("");
+  if (patch == NULL)
+      err(1, "malloc");
+
+  fp = fopen(html, "w");
+  if (fp == NULL)
+      err(1, "fopen %s", html);
+
+  fputs("<!doctype html>\n<html lang=\"en\">\n<head>\n", fp);
+  fputs("<meta charset=\"utf-8\">\n", fp);
+  fputs("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n", fp);
+  fputs("<link rel=\"stylesheet\" href=\"../../style.css\">\n", fp);
+  fputs("<title>", fp);
+  write_html_escaped(fp, short_hash);
+  fputs(" - ", fp);
+  write_html_escaped(fp, r->name);
+  fputs("</title>\n", fp);
+  fputs("</head>\n<body>\n<h1>", fp);
+  write_html_escaped(fp, short_hash);
+  fputs("</h1>\n<p><a href=\"../index.html\">summary</a> | "
+      "<a href=\"../files.html\">files</a> | "
+      "<a href=\"../history.html\">history</a> | ", fp);
+  write_html_escaped(fp, r->name);
+  fputs("</p>\n<p>commit: <code>", fp);
+  write_html_escaped(fp, hash);
+  fputs("</code><br>author: ", fp);
+  write_html_escaped(fp, author);
+  fputs("<br>date: ", fp);
+  write_html_escaped(fp, date);
+  fputs("<br>subject: ", fp);
+  write_html_escaped(fp, subject);
+  fputs("</p>\n", fp);
+  fputs("<h2>Diff</h2>\n<pre>", fp);
+  write_html_escaped(fp, patch);
+  fputs("</pre>\n</body>\n</html>\n", fp);
+
+  if (fclose(fp) == EOF)
+      err(1, "fclose %s", html);
+  free(dir);
+  free(html);
+  free(patch);
+  free(qhash);
+  free(qpath);
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 read_readme(const struct repo *r, int *is_md)
 {
+<<<<<<< HEAD
 	const char *names[] = { "README.md", "README", "README.txt", "readme.md",
 	    "readme", NULL };
 	char *out, *qname, *qpath;
@@ -897,11 +1700,46 @@ read_readme(const struct repo *r, int *is_md)
 	}
 	free(qpath);
 	return str_dup("");
+=======
+  const char *names[] = { "README.md", "README", "README.txt", "readme.md",
+      "readme", NULL };
+  char *out, *qhead, *qname, *qpath;
+  size_t i;
+
+  *is_md = 0;
+  qpath = write_shell_quoted(r->path);
+  qhead = write_shell_quoted(r->head);
+  if (qpath == NULL)
+      err(1, "malloc");
+  if (qhead == NULL)
+      err(1, "malloc");
+  for (i = 0; names[i] != NULL; i++) {
+      qname = write_shell_quoted(names[i]);
+      if (qname == NULL)
+          err(1, "malloc");
+      out = read_cmd_output("git -c safe.directory='*' -C %s show %s:%s 2>/dev/null", qpath, qhead,
+          qname);
+      free(qname);
+      if (out == NULL)
+          continue;
+      if (out[0] != '\0') {
+          *is_md = has_suffix_ci(names[i], ".md");
+          free(qhead);
+          free(qpath);
+          return out;
+      }
+      free(out);
+  }
+  free(qhead);
+  free(qpath);
+  return str_dup("");
+>>>>>>> ee3b035 (init)
 }
 
 static int
 has_suffix_ci(const char *s, const char *suffix)
 {
+<<<<<<< HEAD
 	size_t i, ls, lsf;
 
 	ls = strlen(s);
@@ -1074,11 +1912,53 @@ render_markdown(FILE *fp, const char *md)
 	if (in_list)
 	    fputs("</ul>\n", fp);
 	free(save);
+=======
+  size_t i, ls, lsf;
+
+  ls = strlen(s);
+  lsf = strlen(suffix);
+  if (lsf > ls)
+      return 0;
+  for (i = 0; i < lsf; i++) {
+      if (tolower((unsigned char)s[ls - lsf + i]) !=
+          tolower((unsigned char)suffix[i]))
+          return 0;
+  }
+  return 1;
+}
+
+static char *
+repo_gitdir_path(const struct repo *r)
+{
+  char *gitdir, *qpath, *tmp;
+
+  qpath = write_shell_quoted(r->path);
+  if (qpath == NULL)
+      err(1, "malloc");
+  gitdir = read_cmd_output("git -c safe.directory='*' -C %s rev-parse --git-dir 2>/dev/null",
+      qpath);
+  free(qpath);
+  if (gitdir == NULL)
+      return NULL;
+  trim_newline(gitdir);
+  if (gitdir[0] == '\0') {
+      free(gitdir);
+      return NULL;
+  }
+  if (gitdir[0] == '/')
+      return gitdir;
+  tmp = join3(r->path, "/", gitdir);
+  free(gitdir);
+  if (tmp == NULL)
+      err(1, "malloc");
+  return tmp;
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 read_git_meta_file(const struct repo *r, const char *name)
 {
+<<<<<<< HEAD
 	char *file, *gitdir, *out;
 	FILE *fp;
 	size_t n;
@@ -1108,11 +1988,43 @@ read_git_meta_file(const struct repo *r, const char *name)
 	    warn("fclose metadata");
 	trim_newline(out);
 	return out;
+=======
+  char *file, *gitdir, *out;
+  FILE *fp;
+  size_t n;
+
+  out = NULL;
+  n = 0;
+  gitdir = repo_gitdir_path(r);
+  if (gitdir == NULL)
+      return str_dup("");
+  file = join3(gitdir, "/", name);
+  if (file == NULL)
+      err(1, "malloc");
+  free(gitdir);
+
+  fp = fopen(file, "r");
+  free(file);
+  if (fp == NULL)
+      return str_dup("");
+
+  if (getline(&out, &n, fp) == -1) {
+      if (fclose(fp) == EOF)
+          warn("fclose metadata");
+      free(out);
+      return str_dup("");
+  }
+  if (fclose(fp) == EOF)
+      warn("fclose metadata");
+  trim_newline(out);
+  return out;
+>>>>>>> ee3b035 (init)
 }
 
 static void
 make_readme_page(const struct repo *r, const char *readme, int readme_md)
 {
+<<<<<<< HEAD
 	char *html;
 	FILE *fp;
 
@@ -1167,10 +2079,73 @@ make_readme_page(const struct repo *r, const char *readme, int readme_md)
 	if (fclose(fp) == EOF)
 	    err(1, "fclose %s", html);
 	free(html);
+=======
+  char *html;
+  FILE *fp;
+
+  html = join3(r->outdir, "/", "index.html");
+  if (html == NULL)
+      err(1, "malloc");
+  fp = fopen(html, "w");
+  if (fp == NULL)
+      err(1, "fopen %s", html);
+
+  fputs("<!doctype html>\n<html lang=\"en\">\n<head>\n", fp);
+  fputs("<meta charset=\"utf-8\">\n", fp);
+  fputs("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n", fp);
+  fputs("<link rel=\"stylesheet\" href=\"../style.css\">\n", fp);
+  fputs("<title>", fp);
+  write_html_escaped(fp, r->name);
+  fputs(" summary</title>\n", fp);
+  fputs("</head>\n<body>\n", fp);
+  if (strcmp(r->rootlink_repo, "index.html") != 0)
+      fputs("<p><a class=\"index-link\" href=\"../index.html\">index</a></p>\n", fp);
+  fputs("<h1>", fp);
+  write_html_escaped(fp, r->name);
+  fputs("</h1>\n<p><a href=\"index.html\">summary</a> | <a href=\"files.html\">files</a>", fp);
+  fputs(" | <a href=\"history.html\">history</a></p>\n<p class=\"meta\">", fp);
+  fputs("<span class=\"meta-k\">branch</span> : ", fp);
+  write_html_escaped(fp, r->branch);
+  fputs("<br><span class=\"meta-k\">head</span> : ", fp);
+  write_html_escaped(fp, r->head);
+  fputs("<br><span class=\"meta-k\">owner</span> : ", fp);
+  if (r->owner[0] == '\0')
+      fputs("unknown", fp);
+  else
+      write_html_escaped(fp, r->owner);
+  if (r->cloneurl[0] != '\0') {
+      fputs("<br><span class=\"meta-k\">clone</span> : git clone ", fp);
+      write_html_escaped(fp, r->cloneurl);
+  }
+  fputs("</p>\n", fp);
+
+  if (readme[0] != '\0') {
+      fputs("<h2>readme</h2>\n", fp);
+      if (readme_md) {
+#ifdef USE_MARKDOWN
+          markdown_render(fp, readme, write_html_escaped_len);
+#else
+          fputs("<pre>", fp);
+          write_html_escaped(fp, readme);
+          fputs("</pre>\n", fp);
+#endif
+      } else {
+          fputs("<pre>", fp);
+          write_html_escaped(fp, readme);
+          fputs("</pre>\n", fp);
+      }
+  }
+  fputs("</body>\n</html>\n", fp);
+
+  if (fclose(fp) == EOF)
+      err(1, "fclose %s", html);
+  free(html);
+>>>>>>> ee3b035 (init)
 }
 static char *
 file_page_name(const char *path)
 {
+<<<<<<< HEAD
 	const char *base;
 	char *out;
 
@@ -1183,11 +2158,41 @@ file_page_name(const char *path)
 	if (out == NULL)
 	    err(1, "malloc");
 	return out;
+=======
+  const unsigned char *p;
+  char *out;
+  size_t cap, len;
+  static const char hex[] = "0123456789abcdef";
+
+  out = NULL;
+  cap = 0;
+  len = 0;
+  for (p = (const unsigned char *)path; *p != '\0'; p++) {
+      if ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z') ||
+          (*p >= '0' && *p <= '9') || *p == '.' || *p == '_' ||
+          *p == '-') {
+          append_char(&out, &len, &cap, (char)*p);
+          continue;
+      }
+      /*
+       * Avoid percent-encoding in URL path segments. Some servers decode
+       * %2f into '/' before path lookup, which breaks nested file links.
+       */
+      append_char(&out, &len, &cap, '_');
+      append_char(&out, &len, &cap, hex[*p >> 4]);
+      append_char(&out, &len, &cap, hex[*p & 0x0f]);
+  }
+  append_str(&out, &len, &cap, ".html");
+  if (out == NULL)
+      err(1, "malloc");
+  return out;
+>>>>>>> ee3b035 (init)
 }
 
 static char *
 read_file_head(const struct repo *r, const char *path)
 {
+<<<<<<< HEAD
 	char *qpath, *qspec, *spec, *out;
 
 	spec = join3("HEAD:", "", path);
@@ -1206,11 +2211,32 @@ read_file_head(const struct repo *r, const char *path)
 	free(qpath);
 	free(qspec);
 	return out;
+=======
+  char *qpath, *qspec, *spec, *out;
+
+  spec = join3(r->head, ":", path);
+  if (spec == NULL)
+      err(1, "malloc");
+  qpath = write_shell_quoted(r->path);
+  qspec = write_shell_quoted(spec);
+  free(spec);
+  if (qpath == NULL || qspec == NULL)
+      err(1, "malloc");
+  out = read_cmd_output("git -c safe.directory='*' -C %s show %s 2>/dev/null", qpath, qspec);
+  if (out == NULL)
+      out = str_dup("");
+  if (out == NULL)
+      err(1, "malloc");
+  free(qpath);
+  free(qspec);
+  return out;
+>>>>>>> ee3b035 (init)
 }
 
 static size_t
 read_blob_size(const struct repo *r, const char *path)
 {
+<<<<<<< HEAD
 	char *end, *out, *qpath, *qspec, *spec;
 	unsigned long n;
 
@@ -1235,11 +2261,40 @@ read_blob_size(const struct repo *r, const char *path)
 	if (errno != 0 || end == NULL || *end != '\0')
 	    return 0;
 	return (size_t)n;
+=======
+  char *end, *out, *qpath, *qspec, *spec;
+  unsigned long n;
+
+  spec = join3(r->head, ":", path);
+  if (spec == NULL)
+      err(1, "malloc");
+  qpath = write_shell_quoted(r->path);
+  qspec = write_shell_quoted(spec);
+  free(spec);
+  if (qpath == NULL || qspec == NULL)
+      err(1, "malloc");
+  out = read_cmd_output("git -c safe.directory='*' -C %s cat-file -s %s 2>/dev/null", qpath,
+      qspec);
+  free(qpath);
+  free(qspec);
+  if (out == NULL)
+      return 0;
+  trim_newline(out);
+  errno = 0;
+  n = strtoul(out, &end, 10);
+  if (errno != 0 || end == NULL || *end != '\0') {
+      free(out);
+      return 0;
+  }
+  free(out);
+  return (size_t)n;
+>>>>>>> ee3b035 (init)
 }
 
 static void
 write_raw_blob(const struct repo *r, const char *path)
 {
+<<<<<<< HEAD
 	char *cmd, *dst, *qdst, *qpath, *qspec, *spec;
 	int n, rc;
 
@@ -1273,11 +2328,47 @@ write_raw_blob(const struct repo *r, const char *path)
 	free(qdst);
 	free(qpath);
 	free(qspec);
+=======
+  char *cmd, *dst, *qdst, *qpath, *qspec, *spec;
+  int n, rc;
+
+  dst = join3(r->outdir, "/raw/", path);
+  if (dst == NULL)
+      err(1, "malloc");
+  ensure_parent_dir(dst);
+  qpath = write_shell_quoted(r->path);
+  spec = join3(r->head, ":", path);
+  if (qpath == NULL || spec == NULL)
+      err(1, "malloc");
+  qspec = write_shell_quoted(spec);
+  qdst = write_shell_quoted(dst);
+  free(spec);
+  if (qspec == NULL || qdst == NULL)
+      err(1, "malloc");
+  n = snprintf(NULL, 0, "git -c safe.directory='*' -C %s cat-file blob %s > %s 2>/dev/null",
+      qpath, qspec, qdst);
+  if (n < 0)
+      errx(1, "snprintf");
+  cmd = malloc((size_t)n + 1);
+  if (cmd == NULL)
+      err(1, "malloc");
+  snprintf(cmd, (size_t)n + 1, "git -c safe.directory='*' -C %s cat-file blob %s > %s 2>/dev/null",
+      qpath, qspec, qdst);
+  rc = system(cmd);
+  if (rc != 0)
+      warn("write raw blob %s", path);
+  free(cmd);
+  free(dst);
+  free(qdst);
+  free(qpath);
+  free(qspec);
+>>>>>>> ee3b035 (init)
 }
 
 static struct tree_node *
 tree_new(const char *name, const char *path, int is_dir)
 {
+<<<<<<< HEAD
 	struct tree_node *n;
 
 	n = calloc(1, sizeof(*n));
@@ -1291,11 +2382,27 @@ tree_new(const char *name, const char *path, int is_dir)
 	    err(1, "malloc");
 	n->is_dir = is_dir;
 	return n;
+=======
+  struct tree_node *n;
+
+  n = calloc(1, sizeof(*n));
+  if (n == NULL)
+      err(1, "calloc");
+  n->name = str_dup(name);
+  if (n->name == NULL)
+      err(1, "malloc");
+  n->path = str_dup(path);
+  if (n->path == NULL)
+      err(1, "malloc");
+  n->is_dir = is_dir;
+  return n;
+>>>>>>> ee3b035 (init)
 }
 
 static int
 tree_cmp(const void *a, const void *b)
 {
+<<<<<<< HEAD
 	const struct tree_node *na, *nb;
 	const struct tree_node *const *pa, *const *pb;
 
@@ -1306,11 +2413,24 @@ tree_cmp(const void *a, const void *b)
 	if (na->is_dir != nb->is_dir)
 	    return nb->is_dir - na->is_dir;
 	return strcmp(na->name, nb->name);
+=======
+  const struct tree_node *na, *nb;
+  const struct tree_node *const *pa, *const *pb;
+
+  pa = a;
+  pb = b;
+  na = *pa;
+  nb = *pb;
+  if (na->is_dir != nb->is_dir)
+      return nb->is_dir - na->is_dir;
+  return strcmp(na->name, nb->name);
+>>>>>>> ee3b035 (init)
 }
 
 static struct tree_node *
 tree_add_path(struct tree_node *root, const char *path)
 {
+<<<<<<< HEAD
 	char *copy, *curpath, *part, *slash;
 	struct tree_node *cur, *next;
 	size_t i;
@@ -1378,11 +2498,81 @@ tree_add_path(struct tree_node *root, const char *path)
 	free(curpath);
 	free(copy);
 	return root;
+=======
+  char *copy, *curpath, *part, *slash;
+  struct tree_node *cur, *next;
+  size_t i;
+
+  copy = str_dup(path);
+  curpath = str_dup("");
+  if (copy == NULL || curpath == NULL)
+      err(1, "malloc");
+  cur = root;
+  part = copy;
+
+  while (part != NULL && *part != '\0') {
+      int is_dir;
+
+      slash = strchr(part, '/');
+      is_dir = slash != NULL;
+      if (slash != NULL)
+          *slash = '\0';
+
+      if (curpath[0] == '\0')
+          next = tree_new(part, part, is_dir);
+      else {
+          char *tmp;
+
+          tmp = join3(curpath, "/", part);
+          if (tmp == NULL)
+              err(1, "malloc");
+          next = tree_new(part, tmp, is_dir);
+          free(tmp);
+      }
+
+      for (i = 0; i < cur->nchildren; i++) {
+          if (strcmp(cur->children[i]->name, next->name) == 0 &&
+              cur->children[i]->is_dir == next->is_dir)
+              break;
+      }
+      if (i == cur->nchildren) {
+          struct tree_node **tmp_children;
+
+          if (cur->nchildren + 1 > cur->cap) {
+              if (cur->cap == 0)
+                  cur->cap = 8;
+              else
+                  cur->cap *= 2;
+              tmp_children = realloc(cur->children,
+                  cur->cap * sizeof(*cur->children));
+              if (tmp_children == NULL)
+                  err(1, "realloc");
+              cur->children = tmp_children;
+          }
+          cur->children[cur->nchildren++] = next;
+          cur = next;
+      } else {
+          tree_free(next);
+          cur = cur->children[i];
+      }
+
+      free(curpath);
+      curpath = str_dup(cur->path);
+      if (curpath == NULL)
+          err(1, "malloc");
+      part = slash == NULL ? NULL : slash + 1;
+  }
+
+  free(curpath);
+  free(copy);
+  return root;
+>>>>>>> ee3b035 (init)
 }
 
 static struct tree_node *
 tree_root_from_files(const char *files)
 {
+<<<<<<< HEAD
 	char *copy, *line;
 	struct tree_node *root;
 
@@ -1397,11 +2587,28 @@ tree_root_from_files(const char *files)
 	}
 	free(copy);
 	return root;
+=======
+  char *copy, *line;
+  struct tree_node *root;
+
+  root = tree_new("", "", 1);
+  copy = str_dup(files);
+  if (copy == NULL)
+      err(1, "malloc");
+  line = strtok(copy, "\n");
+  while (line != NULL) {
+      tree_add_path(root, line);
+      line = strtok(NULL, "\n");
+  }
+  free(copy);
+  return root;
+>>>>>>> ee3b035 (init)
 }
 
 static void
 tree_free(struct tree_node *n)
 {
+<<<<<<< HEAD
 	size_t i;
 
 	if (n == NULL)
@@ -1412,11 +2619,24 @@ tree_free(struct tree_node *n)
 	free(n->name);
 	free(n->path);
 	free(n);
+=======
+  size_t i;
+
+  if (n == NULL)
+      return;
+  for (i = 0; i < n->nchildren; i++)
+      tree_free(n->children[i]);
+  free(n->children);
+  free(n->name);
+  free(n->path);
+  free(n);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 render_tree(FILE *fp, const struct repo *r, struct tree_node *n, int depth)
 {
+<<<<<<< HEAD
 	char *blob, *html, *name;
 	char *content;
 	char hsize[64];
@@ -1513,11 +2733,110 @@ render_tree(FILE *fp, const struct repo *r, struct tree_node *n, int depth)
 	    fputs("</ul>\n", fp);
 	else
 	    fputs("</ul>\n</li>\n", fp);
+=======
+  char *blob, *html, *name;
+  char *content;
+  char hsize[64];
+  FILE *bfp;
+  size_t blob_size, i, loc, nbytes;
+
+  if (!n->is_dir) {
+      name = file_page_name(n->path);
+      if (raw_mode)
+          write_raw_blob(r, n->path);
+      blob_size = read_blob_size(r, n->path);
+      content = read_file_head(r, n->path);
+      nbytes = strlen(content);
+      if (!text_content_p(content, nbytes, blob_size)) {
+          if (raw_mode) {
+              fputs("<li><a href=\"raw/", fp);
+              write_html_escaped(fp, n->path);
+              fputs("\"><code>", fp);
+              write_html_escaped(fp, n->path);
+              fputs("</code></a></li>\n", fp);
+          } else {
+              fputs("<li><code>", fp);
+              write_html_escaped(fp, n->path);
+              fputs("</code></li>\n", fp);
+          }
+          free(content);
+          free(name);
+          return;
+      }
+      fputs("<li><a href=\"file/", fp);
+      write_html_escaped(fp, name);
+      fputs("\"><code>", fp);
+      write_html_escaped(fp, n->path);
+      fputs("</code></a></li>\n", fp);
+
+      blob = join3(r->outdir, "/file/", name);
+      if (blob == NULL)
+          err(1, "malloc");
+      html = blob;
+      loc = text_loc(content);
+      bfp = fopen(html, "w");
+      if (bfp == NULL)
+          err(1, "fopen %s", html);
+      fputs("<!doctype html>\n<html lang=\"en\">\n<head>\n", bfp);
+      fputs("<meta charset=\"utf-8\">\n", bfp);
+      fputs("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n", bfp);
+      fputs("<link rel=\"stylesheet\" href=\"../../style.css\">\n", bfp);
+      fputs("<title>", bfp);
+      write_html_escaped(bfp, n->path);
+      fputs(" - ", bfp);
+      write_html_escaped(bfp, r->name);
+      fputs("</title>\n", bfp);
+      fputs("</head>\n<body>\n<p><a href=\"../index.html\">summary</a> | "
+          "<a href=\"../files.html\">files</a> | "
+          "<a href=\"../history.html\">history</a></p>\n<h1><code>",
+          bfp);
+      write_html_escaped(bfp, n->path);
+      fputs("</code></h1>\n<p class=\"meta\">", bfp);
+      fputs("<span class=\"meta-k\">file</span> : ", bfp);
+      write_html_escaped(bfp, n->path);
+      fputs("<br><span class=\"meta-k\">loc</span> : ", bfp);
+      fprintf(bfp, "%lu", (unsigned long)loc);
+      fputs("<br><span class=\"meta-k\">size</span> : ", bfp);
+      format_size(blob_size, hsize, sizeof(hsize));
+      fprintf(bfp, "%lu bytes (%s)", (unsigned long)blob_size, hsize);
+      if (raw_mode) {
+          fputs("<br><span class=\"meta-k\">raw</span> : <a href=\"../raw/",
+              bfp);
+          write_html_escaped(bfp, n->path);
+          fputs("\">raw</a>", bfp);
+      }
+      fputs("</p>\n<pre>", bfp);
+      write_html_escaped(bfp, content);
+      fputs("</pre>\n</body>\n</html>\n", bfp);
+      if (fclose(bfp) == EOF)
+          err(1, "fclose %s", html);
+      free(content);
+      free(name);
+      free(html);
+      return;
+  }
+
+  if (depth == 0)
+      fputs("<ul>\n", fp);
+  else {
+      fputs("<li><code>", fp);
+      write_html_escaped(fp, n->name);
+      fputs("/</code>\n<ul>\n", fp);
+  }
+  qsort(n->children, n->nchildren, sizeof(*n->children), tree_cmp);
+  for (i = 0; i < n->nchildren; i++)
+      render_tree(fp, r, n->children[i], depth + 1);
+  if (depth == 0)
+      fputs("</ul>\n", fp);
+  else
+      fputs("</ul>\n</li>\n", fp);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 make_files_page(const struct repo *r, const char *files)
 {
+<<<<<<< HEAD
 	struct tree_node *root;
 	char *dir, *html;
 	FILE *fp;
@@ -1558,11 +2877,54 @@ make_files_page(const struct repo *r, const char *files)
 	    err(1, "fclose %s", html);
 	free(html);
 	tree_free(root);
+=======
+  struct tree_node *root;
+  char *dir, *html;
+  FILE *fp;
+
+  root = tree_root_from_files(files);
+  dir = join3(r->outdir, "/", "file");
+  if (dir == NULL)
+      err(1, "malloc");
+  ensure_dir(dir);
+  clear_html_files(dir);
+  free(dir);
+
+  html = join3(r->outdir, "/", "files.html");
+  if (html == NULL)
+      err(1, "malloc");
+  fp = fopen(html, "w");
+  if (fp == NULL)
+      err(1, "fopen %s", html);
+
+  fputs("<!doctype html>\n<html lang=\"en\">\n<head>\n", fp);
+  fputs("<meta charset=\"utf-8\">\n", fp);
+  fputs("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n", fp);
+  fputs("<link rel=\"stylesheet\" href=\"../style.css\">\n", fp);
+  fputs("<title>", fp);
+  write_html_escaped(fp, r->name);
+  fputs(" files</title>\n", fp);
+  fputs("</head>\n<body>\n", fp);
+  if (strcmp(r->rootlink_repo, "index.html") != 0)
+      fputs("<p><a class=\"index-link\" href=\"../index.html\">index</a></p>\n", fp);
+  fputs("<h1>", fp);
+  write_html_escaped(fp, r->name);
+  fputs("</h1>\n<p><a href=\"index.html\">summary</a> | <a href=\"files.html\">files</a>", fp);
+  fputs(" | <a href=\"history.html\">history</a></p>\n<h2>Files</h2>\n", fp);
+  render_tree(fp, r, root, 0);
+  fputs("</body>\n</html>\n", fp);
+
+  if (fclose(fp) == EOF)
+      err(1, "fclose %s", html);
+  free(html);
+  tree_free(root);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 make_repo_page(const struct repo *r)
 {
+<<<<<<< HEAD
 	char *commits, *files, *full_hash, *history, *line, *qpath, *readme;
 	char *short_hash, *author, *date, *subject;
 	FILE *fp;
@@ -1649,11 +3011,104 @@ make_repo_page(const struct repo *r)
 	free(commits);
 	free(files);
 	free(readme);
+=======
+  char *commits, *files, *full_hash, *history, *line, *qhead, *qpath, *readme;
+  char *short_hash, *author, *date, *subject;
+  FILE *fp;
+  int readme_md;
+
+  qpath = write_shell_quoted(r->path);
+  if (qpath == NULL)
+      err(1, "malloc");
+  commits = read_cmd_output("git -c safe.directory='*' -C %s log -n %d --date=short "
+      "--pretty=format:%%H%%x1f%%h%%x1f%%ad%%x1f%%an%%x1f%%s 2>/dev/null",
+      qpath, COMMIT_LIMIT);
+  if (commits == NULL)
+      errx(1, "failed to read commits for %s", r->path);
+  qhead = write_shell_quoted(r->head);
+  if (qhead == NULL)
+      err(1, "malloc");
+  files = read_cmd_output("git -c safe.directory='*' -C %s ls-tree -r --name-only %s 2>/dev/null",
+      qpath, qhead);
+  if (files == NULL)
+      files = str_dup("");
+  if (files == NULL)
+      err(1, "malloc");
+  free(qhead);
+  free(qpath);
+
+  readme = read_readme(r, &readme_md);
+  if (readme == NULL)
+      err(1, "malloc");
+
+  make_readme_page(r, readme, readme_md);
+  make_files_page(r, files);
+
+  history = join3(r->outdir, "/", "history.html");
+  if (history == NULL)
+      err(1, "malloc");
+  fp = fopen(history, "w");
+  if (fp == NULL)
+      err(1, "fopen %s", history);
+
+  fputs("<!doctype html>\n<html lang=\"en\">\n<head>\n", fp);
+  fputs("<meta charset=\"utf-8\">\n", fp);
+  fputs("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n", fp);
+  fputs("<link rel=\"stylesheet\" href=\"../style.css\">\n", fp);
+  fputs("<title>", fp);
+  write_html_escaped(fp, r->name);
+  fputs(" history</title>\n</head>\n<body>\n", fp);
+  if (strcmp(r->rootlink_repo, "index.html") != 0)
+      fputs("<p><a class=\"index-link\" href=\"../index.html\">index</a></p>\n", fp);
+  fputs("<h1>", fp);
+  write_html_escaped(fp, r->name);
+  fputs("</h1>\n<p><a href=\"index.html\">summary</a> | <a href=\"files.html\">files</a>", fp);
+  fputs(" | <a href=\"history.html\">history</a></p>\n", fp);
+  fputs("<h2>History</h2>\n", fp);
+  fputs("<table>\n<thead><tr><th>hash</th><th>date</th><th>author</th><th>subject</th></tr></thead>\n<tbody>\n", fp);
+
+  line = strtok(commits, "\n");
+  while (line != NULL) {
+      full_hash = split_field(&line);
+      short_hash = split_field(&line);
+      date = split_field(&line);
+      author = split_field(&line);
+      subject = line == NULL ? "" : line;
+      if (full_hash != NULL && short_hash != NULL && date != NULL &&
+          author != NULL && subject != NULL) {
+          make_commit_page(r, full_hash, short_hash, date, author,
+              subject);
+          fputs("<tr><td><a href=\"commits/", fp);
+          write_html_escaped(fp, full_hash);
+          fputs(".html\"><code>", fp);
+          write_html_escaped(fp, short_hash);
+          fputs("</code></a></td><td>", fp);
+          write_html_escaped(fp, date);
+          fputs("</td><td>", fp);
+          write_html_escaped(fp, author);
+          fputs("</td><td>", fp);
+          write_html_escaped(fp, subject);
+          fputs("</td></tr>\n", fp);
+      }
+      line = strtok(NULL, "\n");
+  }
+  fputs("</tbody>\n</table>\n</body>\n</html>\n", fp);
+
+  if (fclose(fp) == EOF)
+      err(1, "fclose %s", history);
+  write_cache_head(r->outdir, r->head);
+
+  free(history);
+  free(commits);
+  free(files);
+  free(readme);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 make_index(const char *outdir, struct repo *repos, size_t nrepos)
 {
+<<<<<<< HEAD
 	FILE *fp;
 	char *path;
 	size_t i;
@@ -1695,11 +3150,57 @@ make_index(const char *outdir, struct repo *repos, size_t nrepos)
 	if (fclose(fp) == EOF)
 	    err(1, "fclose %s", path);
 	free(path);
+=======
+  FILE *fp;
+  char *path;
+  size_t i;
+
+  path = join3(outdir, "/", "index.html");
+  if (path == NULL)
+      err(1, "malloc");
+  fp = fopen(path, "w");
+  if (fp == NULL)
+      err(1, "fopen %s", path);
+
+  fputs("<!doctype html>\n<html lang=\"en\">\n<head>\n", fp);
+  fputs("<meta charset=\"utf-8\">\n", fp);
+  fputs("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n", fp);
+  fputs("<link rel=\"stylesheet\" href=\"style.css\">\n", fp);
+  fputs("<title>git index</title>\n", fp);
+  fputs("</head>\n<body>\n<h1>Repositories</h1>\n", fp);
+  fputs("<table>\n<thead><tr><th>repo</th><th>owner</th><th>branch</th><th>head</th><th>last commit</th></tr></thead>\n<tbody>\n", fp);
+  for (i = 0; i < nrepos; i++) {
+      if (repos[i].head == NULL)
+          continue;
+      fputs("<tr><td><a href=\"", fp);
+      write_html_escaped(fp, repos[i].name);
+      fputs("/\">", fp);
+      write_html_escaped(fp, repos[i].name);
+      fputs("</a></td><td>", fp);
+      if (repos[i].owner[0] == '\0')
+          fputs("unknown", fp);
+      else
+          write_html_escaped(fp, repos[i].owner);
+      fputs("</td><td>", fp);
+      write_html_escaped(fp, repos[i].branch);
+      fputs("</td><td><code>", fp);
+      write_html_escaped(fp, repos[i].head);
+      fputs("</code></td><td>", fp);
+      write_html_escaped(fp, repos[i].last_date);
+      fputs("</td></tr>\n", fp);
+  }
+  fputs("</tbody>\n</table>\n</body>\n</html>\n", fp);
+
+  if (fclose(fp) == EOF)
+      err(1, "fclose %s", path);
+  free(path);
+>>>>>>> ee3b035 (init)
 }
 
 static void
 free_repos(struct repo *repos, size_t nrepos)
 {
+<<<<<<< HEAD
 	size_t i;
 
 	for (i = 0; i < nrepos; i++) {
@@ -1717,4 +3218,23 @@ free_repos(struct repo *repos, size_t nrepos)
 	    free(repos[i].rootlink_repo);
 	}
 	free(repos);
+=======
+  size_t i;
+
+  for (i = 0; i < nrepos; i++) {
+      free(repos[i].branch);
+      free(repos[i].cloneurl);
+      free(repos[i].head);
+      free(repos[i].last_author);
+      free(repos[i].last_date);
+      free(repos[i].last_subject);
+      free(repos[i].name);
+      free(repos[i].owner);
+      free(repos[i].outdir);
+      free(repos[i].path);
+      free(repos[i].rootlink_deep);
+      free(repos[i].rootlink_repo);
+  }
+  free(repos);
+>>>>>>> ee3b035 (init)
 }