about summary refs log tree commit
diff options
context:
space:
mode:
-rw-r--r--cache.c57
-rw-r--r--cgit.c72
-rw-r--r--scan-tree.c160
-rw-r--r--ui-log.c33
-rw-r--r--ui-plain.c6
-rw-r--r--ui-refs.c10
-rw-r--r--ui-repolist.c28
-rw-r--r--ui-shared.c63
-rw-r--r--ui-snapshot.c60
-rw-r--r--ui-summary.c12
-rw-r--r--ui-tag.c14
-rw-r--r--ui-tree.c33
12 files changed, 305 insertions, 243 deletions
diff --git a/cache.c b/cache.c
index 3127fc2..c1d777b 100644
--- a/cache.c
+++ b/cache.c
@@ -312,9 +312,9 @@ int cache_process(int size, const char *path, const char *key, int ttl,
                   cache_fill_fn fn, void *cbdata)
 {
         unsigned long hash;
-        int len, i;
-        char filename[1024];
-        char lockname[1024 + 5];  /* 5 = ".lock" */
+        int i;
+        struct strbuf filename = STRBUF_INIT;
+        struct strbuf lockname = STRBUF_INIT;
         struct cache_slot slot;
 
         /* If the cache is disabled, just generate the content */
@@ -329,32 +329,22 @@ int cache_process(int size, const char *path, const char *key, int ttl,
                 fn(cbdata);
                 return 0;
         }
-        len = strlen(path);
-        if (len > sizeof(filename) - 10) { /* 10 = "/01234567\0" */
-                cache_log("[cgit] Cache path too long, caching is disabled: %s\n",
-                          path);
-                fn(cbdata);
-                return 0;
-        }
         if (!key)
                 key = "";
         hash = hash_str(key) % size;
-        strcpy(filename, path);
-        if (filename[len - 1] != '/')
-                filename[len++] = '/';
+        strbuf_addstr(&filename, path);
+        strbuf_ensure_end(&filename, '/');
         for (i = 0; i < 8; i++) {
-                sprintf(filename + len++, "%x",
-                        (unsigned char)(hash & 0xf));
+                strbuf_addf(&filename, "%x", (unsigned char)(hash & 0xf));
                 hash >>= 4;
         }
-        filename[len] = '\0';
-        strcpy(lockname, filename);
-        strcpy(lockname + len, ".lock");
+        strbuf_addbuf(&lockname, &filename);
+        strbuf_addstr(&lockname, ".lock");
         slot.fn = fn;
         slot.cbdata = cbdata;
         slot.ttl = ttl;
-        slot.cache_name = filename;
-        slot.lock_name = lockname;
+        slot.cache_name = strbuf_detach(&filename, NULL);
+        slot.lock_name = strbuf_detach(&lockname, NULL);
         slot.key = key;
         slot.keylen = strlen(key);
         return process_slot(&slot);
@@ -381,18 +371,13 @@ int cache_ls(const char *path)
         struct dirent *ent;
         int err = 0;
         struct cache_slot slot;
-        char fullname[1024];
-        char *name;
+        struct strbuf fullname = STRBUF_INIT;
+        size_t prefixlen;
 
         if (!path) {
                 cache_log("[cgit] cache path not specified\n");
                 return -1;
         }
-        if (strlen(path) > 1024 - 10) {
-                cache_log("[cgit] cache path too long: %s\n",
-                          path);
-                return -1;
-        }
         dir = opendir(path);
         if (!dir) {
                 err = errno;
@@ -400,30 +385,28 @@ int cache_ls(const char *path)
                           path, strerror(err), err);
                 return err;
         }
-        strcpy(fullname, path);
-        name = fullname + strlen(path);
-        if (*(name - 1) != '/') {
-                *name++ = '/';
-                *name = '\0';
-        }
-        slot.cache_name = fullname;
+        strbuf_addstr(&fullname, path);
+        strbuf_ensure_end(&fullname, '/');
+        prefixlen = fullname.len;
         while ((ent = readdir(dir)) != NULL) {
                 if (strlen(ent->d_name) != 8)
                         continue;
-                strcpy(name, ent->d_name);
+                strbuf_setlen(&fullname, prefixlen);
+                strbuf_addstr(&fullname, ent->d_name);
                 if ((err = open_slot(&slot)) != 0) {
                         cache_log("[cgit] unable to open path %s: %s (%d)\n",
-                                  fullname, strerror(err), err);
+                                  fullname.buf, strerror(err), err);
                         continue;
                 }
                 printf("%s %s %10"PRIuMAX" %s\n",
-                       name,
+                       fullname.buf,
                        sprintftime("%Y-%m-%d %H:%M:%S",
                                    slot.cache_st.st_mtime),
                        (uintmax_t)slot.cache_st.st_size,
                        slot.buf);
                 close_slot(&slot);
         }
+        slot.cache_name = strbuf_detach(&fullname, NULL);
         closedir(dir);
         return 0;
 }
diff --git a/cgit.c b/cgit.c
index 4e51283..f73c7b0 100644
--- a/cgit.c
+++ b/cgit.c
@@ -468,8 +468,8 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
         if (nongit) {
                 const char *name = ctx->repo->name;
                 rc = errno;
-                ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
-                                      "config error");
+                ctx->page.title = fmtalloc("%s - %s", ctx->cfg.root_title,
+                                                "config error");
                 ctx->repo = NULL;
                 cgit_print_http_headers(ctx);
                 cgit_print_docstart(ctx);
@@ -479,7 +479,7 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
                 cgit_print_docend();
                 return 1;
         }
-        ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
+        ctx->page.title = fmtalloc("%s - %s", ctx->repo->name, ctx->repo->desc);
 
         if (!ctx->repo->defbranch)
                 ctx->repo->defbranch = guess_defbranch();
@@ -577,21 +577,16 @@ static int cmp_repos(const void *a, const void *b)
 static char *build_snapshot_setting(int bitmap)
 {
         const struct cgit_snapshot_format *f;
-        char *result = xstrdup("");
-        char *tmp;
-        int len;
+        struct strbuf result = STRBUF_INIT;
 
         for (f = cgit_snapshot_formats; f->suffix; f++) {
                 if (f->bit & bitmap) {
-                        tmp = result;
-                        result = xstrdup(fmt("%s%s ", tmp, f->suffix));
-                        free(tmp);
+                        if (result.len)
+                                strbuf_addch(&result, ' ');
+                        strbuf_addstr(&result, f->suffix);
                 }
         }
-        len = strlen(result);
-        if (len)
-                result[len - 1] = '\0';
-        return result;
+        return strbuf_detach(&result, NULL);
 }
 
 static char *get_first_line(char *txt)
@@ -639,7 +634,7 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
                 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
         if (repo->snapshots != ctx.cfg.snapshots) {
                 char *tmp = build_snapshot_setting(repo->snapshots);
-                fprintf(f, "repo.snapshots=%s\n", tmp);
+                fprintf(f, "repo.snapshots=%s\n", tmp ? tmp : "");
                 free(tmp);
         }
         if (repo->max_stats != ctx.cfg.max_stats)
@@ -661,20 +656,22 @@ static void print_repolist(FILE *f, struct cgit_repolist *list, int start)
  */
 static int generate_cached_repolist(const char *path, const char *cached_rc)
 {
-        char *locked_rc;
+        struct strbuf locked_rc = STRBUF_INIT;
+        int result = 0;
         int idx;
         FILE *f;
 
-        locked_rc = xstrdup(fmt("%s.lock", cached_rc));
-        f = fopen(locked_rc, "wx");
+        strbuf_addf(&locked_rc, "%s.lock", cached_rc);
+        f = fopen(locked_rc.buf, "wx");
         if (!f) {
                 /* Inform about the error unless the lockfile already existed,
                  * since that only means we've got concurrent requests.
                  */
-                if (errno != EEXIST)
+                result = errno;
+                if (result != EEXIST)
                         fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
-                                locked_rc, strerror(errno), errno);
-                return errno;
+                                locked_rc.buf, strerror(result), result);
+                goto out;
         }
         idx = cgit_repolist.count;
         if (ctx.cfg.project_list)
@@ -682,55 +679,59 @@ static int generate_cached_repolist(const char *path, const char *cached_rc)
         else
                 scan_tree(path, repo_config);
         print_repolist(f, &cgit_repolist, idx);
-        if (rename(locked_rc, cached_rc))
+        if (rename(locked_rc.buf, cached_rc))
                 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
-                        locked_rc, cached_rc, strerror(errno), errno);
+                        locked_rc.buf, cached_rc, strerror(errno), errno);
         fclose(f);
-        return 0;
+out:
+        strbuf_release(&locked_rc);
+        return result;
 }
 
 static void process_cached_repolist(const char *path)
 {
         struct stat st;
-        char *cached_rc;
+        struct strbuf cached_rc = STRBUF_INIT;
         time_t age;
         unsigned long hash;
 
         hash = hash_str(path);
         if (ctx.cfg.project_list)
                 hash += hash_str(ctx.cfg.project_list);
-        cached_rc = xstrdup(fmt("%s/rc-%8lx", ctx.cfg.cache_root, hash));
+        strbuf_addf(&cached_rc, "%s/rc-%8lx", ctx.cfg.cache_root, hash);
 
-        if (stat(cached_rc, &st)) {
+        if (stat(cached_rc.buf, &st)) {
                 /* Nothing is cached, we need to scan without forking. And
                  * if we fail to generate a cached repolist, we need to
                  * invoke scan_tree manually.
                  */
-                if (generate_cached_repolist(path, cached_rc)) {
+                if (generate_cached_repolist(path, cached_rc.buf)) {
                         if (ctx.cfg.project_list)
                                 scan_projects(path, ctx.cfg.project_list,
                                               repo_config);
                         else
                                 scan_tree(path, repo_config);
                 }
-                return;
+                goto out;
         }
 
-        parse_configfile(cached_rc, config_cb);
+        parse_configfile(cached_rc.buf, config_cb);
 
         /* If the cached configfile hasn't expired, lets exit now */
         age = time(NULL) - st.st_mtime;
         if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
-                return;
+                goto out;
 
         /* The cached repolist has been parsed, but it was old. So lets
          * rescan the specified path and generate a new cached repolist
          * in a child-process to avoid latency for the current request.
          */
         if (fork())
-                return;
+                goto out;
 
-        exit(generate_cached_repolist(path, cached_rc));
+        exit(generate_cached_repolist(path, cached_rc.buf));
+out:
+        strbuf_release(&cached_rc);
 }
 
 static void cgit_parse_args(int argc, const char **argv)
@@ -812,7 +813,6 @@ static int calc_ttl()
 int main(int argc, const char **argv)
 {
         const char *path;
-        char *qry;
         int err, ttl;
 
         prepare_context(&ctx);
@@ -843,9 +843,9 @@ int main(int argc, const char **argv)
                         path++;
                 ctx.qry.url = xstrdup(path);
                 if (ctx.qry.raw) {
-                        qry = ctx.qry.raw;
-                        ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
-                        free(qry);
+                        char *newqry = fmtalloc("%s?%s", path, ctx.qry.raw);
+                        free(ctx.qry.raw);
+                        ctx.qry.raw = newqry;
                 } else
                         ctx.qry.raw = xstrdup(ctx.qry.url);
                 cgit_parse_url(ctx.qry.url);
diff --git a/scan-tree.c b/scan-tree.c
index 05caba5..beb584b 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -12,38 +12,38 @@
 #include "configfile.h"
 #include "html.h"
 
-#define MAX_PATH 4096
-
 /* return 1 if path contains a objects/ directory and a HEAD file */
 static int is_git_dir(const char *path)
 {
         struct stat st;
-        static char buf[MAX_PATH];
+        struct strbuf pathbuf = STRBUF_INIT;
+        int result = 0;
 
-        if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) {
-                fprintf(stderr, "Insanely long path: %s\n", path);
-                return 0;
-        }
-        if (stat(buf, &st)) {
+        strbuf_addf(&pathbuf, "%s/objects", path);
+        if (stat(pathbuf.buf, &st)) {
                 if (errno != ENOENT)
                         fprintf(stderr, "Error checking path %s: %s (%d)\n",
                                 path, strerror(errno), errno);
-                return 0;
+                goto out;
         }
         if (!S_ISDIR(st.st_mode))
-                return 0;
+                goto out;
 
-        sprintf(buf, "%s/HEAD", path);
-        if (stat(buf, &st)) {
+        strbuf_reset(&pathbuf);
+        strbuf_addf(&pathbuf, "%s/HEAD", path);
+        if (stat(pathbuf.buf, &st)) {
                 if (errno != ENOENT)
                         fprintf(stderr, "Error checking path %s: %s (%d)\n",
                                 path, strerror(errno), errno);
-                return 0;
+                goto out;
         }
         if (!S_ISREG(st.st_mode))
-                return 0;
+                goto out;
 
-        return 1;
+        result = 1;
+out:
+        strbuf_release(&pathbuf);
+        return result;
 }
 
 struct cgit_repo *repo;
@@ -75,47 +75,61 @@ static char *xstrrchr(char *s, char *from, int c)
         return from < s ? NULL : from;
 }
 
-static void add_repo(const char *base, const char *path, repo_config_fn fn)
+static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
 {
         struct stat st;
         struct passwd *pwd;
-        char *rel, *p, *slash;
+        size_t pathlen;
+        struct strbuf rel = STRBUF_INIT;
+        char *p, *slash;
         int n;
         size_t size;
 
-        if (stat(path, &st)) {
+        if (stat(path->buf, &st)) {
                 fprintf(stderr, "Error accessing %s: %s (%d)\n",
-                        path, strerror(errno), errno);
+                        path->buf, strerror(errno), errno);
                 return;
         }
 
-        if (ctx.cfg.strict_export && stat(fmt("%s/%s", path, ctx.cfg.strict_export), &st))
-                return;
+        strbuf_addch(path, '/');
+        pathlen = path->len;
 
-        if (!stat(fmt("%s/noweb", path), &st))
+        if (ctx.cfg.strict_export) {
+                strbuf_addstr(path, ctx.cfg.strict_export);
+                if(stat(path->buf, &st))
+                        return;
+                strbuf_setlen(path, pathlen);
+        }
+
+        strbuf_addstr(path, "noweb");
+        if (!stat(path->buf, &st))
                 return;
+        strbuf_setlen(path, pathlen);
 
-        if (base == path)
-                rel = xstrdup(path);
+        if (strncmp(base, path->buf, strlen(base)))
+                strbuf_addbuf(&rel, path);
         else
-                rel = xstrdup(path + strlen(base) + 1);
+                strbuf_addstr(&rel, path->buf + strlen(base) + 1);
 
-        if (!strcmp(rel + strlen(rel) - 5, "/.git"))
-                rel[strlen(rel) - 5] = '\0';
+        if (!strcmp(rel.buf + rel.len - 5, "/.git"))
+                strbuf_setlen(&rel, rel.len - 5);
 
-        repo = cgit_add_repo(rel);
+        repo = cgit_add_repo(rel.buf);
         config_fn = fn;
-        if (ctx.cfg.enable_git_config)
-                git_config_from_file(gitconfig_config, fmt("%s/config", path), NULL);
+        if (ctx.cfg.enable_git_config) {
+                strbuf_addstr(path, "config");
+                git_config_from_file(gitconfig_config, path->buf, NULL);
+                strbuf_setlen(path, pathlen);
+        }
 
         if (ctx.cfg.remove_suffix)
                 if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git"))
                         *p = '\0';
-        repo->path = xstrdup(path);
+        repo->path = xstrdup(path->buf);
         while (!repo->owner) {
                 if ((pwd = getpwuid(st.st_uid)) == NULL) {
                         fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
-                                path, strerror(errno), errno);
+                                path->buf, strerror(errno), errno);
                         break;
                 }
                 if (pwd->pw_gecos)
@@ -125,30 +139,32 @@ static void add_repo(const char *base, const char *path, repo_config_fn fn)
         }
 
         if (repo->desc == cgit_default_repo_desc || !repo->desc) {
-                p = fmt("%s/description", path);
-                if (!stat(p, &st))
-                        readfile(p, &repo->desc, &size);
+                strbuf_addstr(path, "description");
+                if (!stat(path->buf, &st))
+                        readfile(path->buf, &repo->desc, &size);
+                strbuf_setlen(path, pathlen);
         }
 
         if (!repo->readme) {
-                p = fmt("%s/README.html", path);
-                if (!stat(p, &st))
+                strbuf_addstr(path, "README.html");
+                if (!stat(path->buf, &st))
                         repo->readme = "README.html";
+                strbuf_setlen(path, pathlen);
         }
         if (ctx.cfg.section_from_path) {
                 n  = ctx.cfg.section_from_path;
                 if (n > 0) {
-                        slash = rel;
+                        slash = rel.buf;
                         while (slash && n && (slash = strchr(slash, '/')))
                                 n--;
                 } else {
-                        slash = rel + strlen(rel);
-                        while (slash && n && (slash = xstrrchr(rel, slash, '/')))
+                        slash = rel.buf + rel.len;
+                        while (slash && n && (slash = xstrrchr(rel.buf, slash, '/')))
                                 n++;
                 }
                 if (slash && !n) {
                         *slash = '\0';
-                        repo->section = xstrdup(rel);
+                        repo->section = xstrdup(rel.buf);
                         *slash = '/';
                         if (!prefixcmp(repo->name, repo->section)) {
                                 repo->name += strlen(repo->section);
@@ -158,19 +174,19 @@ static void add_repo(const char *base, const char *path, repo_config_fn fn)
                 }
         }
 
-        p = fmt("%s/cgitrc", path);
-        if (!stat(p, &st))
-                parse_configfile(xstrdup(p), &repo_config);
-
+        strbuf_addstr(path, "cgitrc");
+        if (!stat(path->buf, &st))
+                parse_configfile(xstrdup(path->buf), &repo_config);
 
-        free(rel);
+        strbuf_release(&rel);
 }
 
 static void scan_path(const char *base, const char *path, repo_config_fn fn)
 {
         DIR *dir = opendir(path);
         struct dirent *ent;
-        char *buf;
+        struct strbuf pathbuf = STRBUF_INIT;
+        size_t pathlen = strlen(path);
         struct stat st;
 
         if (!dir) {
@@ -178,14 +194,22 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
                         path, strerror(errno), errno);
                 return;
         }
-        if (is_git_dir(path)) {
-                add_repo(base, path, fn);
+
+        strbuf_add(&pathbuf, path, strlen(path));
+        if (is_git_dir(pathbuf.buf)) {
+                add_repo(base, &pathbuf, fn);
                 goto end;
         }
-        if (is_git_dir(fmt("%s/.git", path))) {
-                add_repo(base, fmt("%s/.git", path), fn);
+        strbuf_addstr(&pathbuf, "/.git");
+        if (is_git_dir(pathbuf.buf)) {
+                add_repo(base, &pathbuf, fn);
                 goto end;
         }
+        /*
+         * Add one because we don't want to lose the trailing '/' when we
+         * reset the length of pathbuf in the loop below.
+         */
+        pathlen++;
         while ((ent = readdir(dir)) != NULL) {
                 if (ent->d_name[0] == '.') {
                         if (ent->d_name[1] == '\0')
@@ -195,24 +219,18 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
                         if (!ctx.cfg.scan_hidden_path)
                                 continue;
                 }
-                buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
-                if (!buf) {
-                        fprintf(stderr, "Alloc error on %s: %s (%d)\n",
-                                path, strerror(errno), errno);
-                        exit(1);
-                }
-                sprintf(buf, "%s/%s", path, ent->d_name);
-                if (stat(buf, &st)) {
+                strbuf_setlen(&pathbuf, pathlen);
+                strbuf_addstr(&pathbuf, ent->d_name);
+                if (stat(pathbuf.buf, &st)) {
                         fprintf(stderr, "Error checking path %s: %s (%d)\n",
-                                buf, strerror(errno), errno);
-                        free(buf);
+                                pathbuf.buf, strerror(errno), errno);
                         continue;
                 }
                 if (S_ISDIR(st.st_mode))
-                        scan_path(base, buf, fn);
-                free(buf);
+                        scan_path(base, pathbuf.buf, fn);
         }
 end:
+        strbuf_release(&pathbuf);
         closedir(dir);
 }
 
@@ -220,7 +238,7 @@ end:
 
 void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
 {
-        char line[MAX_PATH * 2], *z;
+        struct strbuf line = STRBUF_INIT;
         FILE *projects;
         int err;
 
@@ -230,19 +248,19 @@ void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn
                         projectsfile, strerror(errno), errno);
                 return;
         }
-        while (fgets(line, sizeof(line), projects) != NULL) {
-                for (z = &lastc(line);
-                     strlen(line) && strchr("\n\r", *z);
-                     z = &lastc(line))
-                        *z = '\0';
-                if (strlen(line))
-                        scan_path(path, fmt("%s/%s", path, line), fn);
+        while (strbuf_getline(&line, projects, '\n') != EOF) {
+                if (!line.len)
+                        continue;
+                strbuf_insert(&line, 0, "/", 1);
+                strbuf_insert(&line, 0, path, strlen(path));
+                scan_path(path, line.buf, fn);
         }
         if ((err = ferror(projects))) {
                 fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
                         projectsfile, strerror(err), err);
         }
         fclose(projects);
+        strbuf_release(&line);
 }
 
 void scan_tree(const char *path, repo_config_fn fn)
diff --git a/ui-log.c b/ui-log.c
index 8592843..93af0ce 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -243,15 +243,19 @@ static void print_commit(struct commit *commit, struct rev_info *revs)
         cgit_free_commitinfo(info);
 }
 
-static const char *disambiguate_ref(const char *ref)
+static const char *disambiguate_ref(const char *ref, int *must_free_result)
 {
         unsigned char sha1[20];
-        const char *longref;
+        struct strbuf longref = STRBUF_INIT;
 
-        longref = fmt("refs/heads/%s", ref);
-        if (get_sha1(longref, sha1) == 0)
-                return longref;
+        strbuf_addf(&longref, "refs/heads/%s", ref);
+        if (get_sha1(longref.buf, sha1) == 0) {
+                *must_free_result = 1;
+                return strbuf_detach(&longref, NULL);
+        }
 
+        *must_free_result = 0;
+        strbuf_release(&longref);
         return ref;
 }
 
@@ -284,24 +288,26 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
         struct commit *commit;
         struct vector vec = VECTOR_INIT(char *);
         int i, columns = commit_graph ? 4 : 3;
-        char *arg;
+        int must_free_tip = 0;
+        struct strbuf argbuf = STRBUF_INIT;
 
         /* First argv is NULL */
         vector_push(&vec, NULL, 0);
 
         if (!tip)
                 tip = ctx.qry.head;
-        tip = disambiguate_ref(tip);
+        tip = disambiguate_ref(tip, &must_free_tip);
         vector_push(&vec, &tip, 0);
 
         if (grep && pattern && *pattern) {
                 pattern = xstrdup(pattern);
                 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
                     !strcmp(grep, "committer")) {
-                        arg = fmt("--%s=%s", grep, pattern);
-                        vector_push(&vec, &arg, 0);
+                        strbuf_addf(&argbuf, "--%s=%s", grep, pattern);
+                        vector_push(&vec, &argbuf.buf, 0);
                 }
                 if (!strcmp(grep, "range")) {
+                        char *arg;
                         /* Split the pattern at whitespace and add each token
                          * as a revision expression. Do not accept other
                          * rev-list options. Also, replace the previously
@@ -336,8 +342,8 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
         }
 
         if (path) {
-                arg = "--";
-                vector_push(&vec, &arg, 0);
+                static const char *double_dash_arg = "--";
+                vector_push(&vec, &double_dash_arg, 0);
                 vector_push(&vec, &path, 0);
         }
 
@@ -430,4 +436,9 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
                               ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
                 html("</td></tr>\n");
         }
+
+        /* If we allocated tip then it is safe to cast away const. */
+        if (must_free_tip)
+                free((char*) tip);
+        strbuf_release(&argbuf);
 }
diff --git a/ui-plain.c b/ui-plain.c
index 6b0d84b..9c86542 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -109,9 +109,9 @@ static int print_object(const unsigned char *sha1, const char *path)
 static char *buildpath(const char *base, int baselen, const char *path)
 {
         if (path[0])
-                return fmt("%.*s%s/", baselen, base, path);
+                return fmtalloc("%.*s%s/", baselen, base, path);
         else
-                return fmt("%.*s/", baselen, base);
+                return fmtalloc("%.*s/", baselen, base);
 }
 
 static void print_dir(const unsigned char *sha1, const char *base,
@@ -142,6 +142,7 @@ static void print_dir(const unsigned char *sha1, const char *base,
                                 fullpath);
                 html("</li>\n");
         }
+        free(fullpath);
 }
 
 static void print_dir_entry(const unsigned char *sha1, const char *base,
@@ -159,6 +160,7 @@ static void print_dir_entry(const unsigned char *sha1, const char *base,
                 cgit_plain_link(path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
                                 fullpath);
         html("</li>\n");
+        free(fullpath);
 }
 
 static void print_dir_tail(void)
diff --git a/ui-refs.c b/ui-refs.c
index 7406478..3fbaad0 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -99,7 +99,7 @@ static void print_tag_header()
 static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
 {
         const struct cgit_snapshot_format* f;
-            char *filename;
+        struct strbuf filename = STRBUF_INIT;
         const char *basename;
         int free_ref = 0;
 
@@ -111,7 +111,7 @@ static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
                 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
                         ref++;
                 if (isdigit(ref[0])) {
-                        ref = xstrdup(fmt("%s-%s", basename, ref));
+                        ref = fmtalloc("%s-%s", basename, ref);
                         free_ref = 1;
                 }
         }
@@ -119,13 +119,15 @@ static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
         for (f = cgit_snapshot_formats; f->suffix; f++) {
                 if (!(repo->snapshots & f->bit))
                         continue;
-                filename = fmt("%s%s", ref, f->suffix);
-                cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
+                strbuf_reset(&filename);
+                strbuf_addf(&filename, "%s%s", ref, f->suffix);
+                cgit_snapshot_link(filename.buf, NULL, NULL, NULL, NULL, filename.buf);
                 html("&nbsp;&nbsp;");
         }
 
         if (free_ref)
                 free((char *)ref);
+        strbuf_release(&filename);
 }
 
 static int print_tag(struct refinfo *ref)
diff --git a/ui-repolist.c b/ui-repolist.c
index 76fe71a..47ca997 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -33,7 +33,7 @@ static time_t read_agefile(char *path)
 
 static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
 {
-        char *path;
+        struct strbuf path = STRBUF_INIT;
         struct stat s;
         struct cgit_repo *r = (struct cgit_repo *)repo;
 
@@ -41,32 +41,36 @@ static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
                 *mtime = repo->mtime;
                 return 1;
         }
-        path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
-        if (stat(path, &s) == 0) {
-                *mtime = read_agefile(path);
+        strbuf_addf(&path, "%s/%s", repo->path, ctx.cfg.agefile);
+        if (stat(path.buf, &s) == 0) {
+                *mtime = read_agefile(path.buf);
                 if (*mtime) {
                         r->mtime = *mtime;
-                        return 1;
+                        goto end;
                 }
         }
 
-        path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch ?
-                   repo->defbranch : "master");
-        if (stat(path, &s) == 0) {
+        strbuf_reset(&path);
+        strbuf_addf(&path, "%s/refs/heads/%s", repo->path,
+                    repo->defbranch ? repo->defbranch : "master");
+        if (stat(path.buf, &s) == 0) {
                 *mtime = s.st_mtime;
                 r->mtime = *mtime;
-                return 1;
+                goto end;
         }
 
-        path = fmt("%s/%s", repo->path, "packed-refs");
-        if (stat(path, &s) == 0) {
+        strbuf_reset(&path);
+        strbuf_addf(&path, "%s/%s", repo->path, "packed-refs");
+        if (stat(path.buf, &s) == 0) {
                 *mtime = s.st_mtime;
                 r->mtime = *mtime;
-                return 1;
+                goto end;
         }
 
         *mtime = 0;
         r->mtime = *mtime;
+end:
+        strbuf_release(&path);
         return (r->mtime != 0);
 }
 
diff --git a/ui-shared.c b/ui-shared.c
index b93b77a..519eef7 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -62,7 +62,7 @@ const char *cgit_hosturl()
                 return NULL;
         if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
                 return ctx.env.server_name;
-        return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
+        return fmtalloc("%s:%s", ctx.env.server_name, ctx.env.server_port);
 }
 
 const char *cgit_rooturl()
@@ -75,31 +75,30 @@ const char *cgit_rooturl()
 
 char *cgit_repourl(const char *reponame)
 {
-        if (ctx.cfg.virtual_root) {
-                return fmt("%s%s/", ctx.cfg.virtual_root, reponame);
-        } else {
-                return fmt("?r=%s", reponame);
-        }
+        if (ctx.cfg.virtual_root)
+                return fmtalloc("%s%s/", ctx.cfg.virtual_root, reponame);
+        else
+                return fmtalloc("?r=%s", reponame);
 }
 
 char *cgit_fileurl(const char *reponame, const char *pagename,
                    const char *filename, const char *query)
 {
-        char *tmp;
+        struct strbuf sb = STRBUF_INIT;
         char *delim;
 
         if (ctx.cfg.virtual_root) {
-                tmp = fmt("%s%s/%s/%s", ctx.cfg.virtual_root, reponame,
-                          pagename, (filename ? filename:""));
+                strbuf_addf(&sb, "%s%s/%s/%s", ctx.cfg.virtual_root, reponame,
+                            pagename, (filename ? filename:""));
                 delim = "?";
         } else {
-                tmp = fmt("?url=%s/%s/%s", reponame, pagename,
-                          (filename ? filename : ""));
+                strbuf_addf(&sb, "?url=%s/%s/%s", reponame, pagename,
+                            (filename ? filename : ""));
                 delim = "&amp;";
         }
         if (query)
-                tmp = fmt("%s%s%s", tmp, delim, query);
-        return tmp;
+                strbuf_addf(&sb, "%s%s", delim, query);
+        return strbuf_detach(&sb, NULL);
 }
 
 char *cgit_pageurl(const char *reponame, const char *pagename,
@@ -548,21 +547,21 @@ void cgit_submodule_link(const char *class, char *path, const char *rev)
                 htmlf("class='%s' ", class);
         html("href='");
         if (item) {
-                html_attr(fmt(item->util, rev));
+                html_attrf(item->util, rev);
         } else if (ctx.repo->module_link) {
                 dir = strrchr(path, '/');
                 if (dir)
                         dir++;
                 else
                         dir = path;
-                html_attr(fmt(ctx.repo->module_link, dir, rev));
+                html_attrf(ctx.repo->module_link, dir, rev);
         } else {
                 html("#");
         }
         html("'>");
         html_txt(path);
         html("</a>");
-        html_txt(fmt(" @ %.7s", rev));
+        html_txtf(" @ %.7s", rev);
         if (item && tail)
                 path[len - 1] = tail;
 }
@@ -678,12 +677,16 @@ void cgit_print_docstart(struct cgit_context *ctx)
                 html("'/>\n");
         }
         if (host && ctx->repo && ctx->qry.head) {
+                struct strbuf sb = STRBUF_INIT;
+                strbuf_addf(&sb, "h=%s", ctx->qry.head);
+
                 html("<link rel='alternate' title='Atom feed' href='");
                 html(cgit_httpscheme());
                 html_attr(cgit_hosturl());
                 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
-                                       fmt("h=%s", ctx->qry.head)));
+                                       sb.buf));
                 html("' type='application/atom+xml'/>\n");
+                strbuf_release(&sb);
         }
         if (ctx->cfg.head_include)
                 html_include(ctx->cfg.head_include);
@@ -725,13 +728,14 @@ static int print_branch_option(const char *refname, const unsigned char *sha1,
 void cgit_add_hidden_formfields(int incl_head, int incl_search,
                                 const char *page)
 {
-        char *url;
-
         if (!ctx.cfg.virtual_root) {
-                url = fmt("%s/%s", ctx.qry.repo, page);
+                struct strbuf url = STRBUF_INIT;
+
+                strbuf_addf(&url, "%s/%s", ctx.qry.repo, page);
                 if (ctx.qry.vpath)
-                        url = fmt("%s/%s", url, ctx.qry.vpath);
-                html_hidden("url", url);
+                        strbuf_addf(&url, "/%s", ctx.qry.vpath);
+                html_hidden("url", url.buf);
+                strbuf_release(&url);
         }
 
         if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
@@ -926,20 +930,23 @@ void cgit_print_snapshot_links(const char *repo, const char *head,
                                const char *hex, int snapshots)
 {
         const struct cgit_snapshot_format* f;
-        char *prefix;
-        char *filename;
+        struct strbuf filename = STRBUF_INIT;
+        size_t prefixlen;
         unsigned char sha1[20];
 
         if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
             (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
                 hex++;
-        prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
+        strbuf_addf(&filename, "%s-%s", cgit_repobasename(repo), hex);
+        prefixlen = filename.len;
         for (f = cgit_snapshot_formats; f->suffix; f++) {
                 if (!(snapshots & f->bit))
                         continue;
-                filename = fmt("%s%s", prefix, f->suffix);
-                cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
+                strbuf_setlen(&filename, prefixlen);
+                strbuf_addstr(&filename, f->suffix);
+                cgit_snapshot_link(filename.buf, NULL, NULL, NULL, NULL,
+                                   filename.buf);
                 html("<br/>");
         }
-        free(prefix);
+        strbuf_release(&filename);
 }
diff --git a/ui-snapshot.c b/ui-snapshot.c
index a47884e..8e76977 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -15,14 +15,33 @@
 static int write_archive_type(const char *format, const char *hex, const char *prefix)
 {
         struct argv_array argv = ARGV_ARRAY_INIT;
+        const char **nargv;
+        int result;
         argv_array_push(&argv, "snapshot");
         argv_array_push(&argv, format);
         if (prefix) {
+                struct strbuf buf = STRBUF_INIT;
+                strbuf_addstr(&buf, prefix);
+                strbuf_addch(&buf, '/');
                 argv_array_push(&argv, "--prefix");
-                argv_array_push(&argv, fmt("%s/", prefix));
+                argv_array_push(&argv, buf.buf);
+                strbuf_release(&buf);
         }
         argv_array_push(&argv, hex);
-        return write_archive(argv.argc, argv.argv, NULL, 1, NULL, 0);
+        /*
+         * Now we need to copy the pointers to arguments into a new
+         * structure because write_archive will rearrange its arguments
+         * which may result in duplicated/missing entries causing leaks
+         * or double-frees in argv_array_clear.
+         */
+        nargv = xmalloc(sizeof(char *) * (argv.argc + 1));
+        /* argv_array guarantees a trailing NULL entry. */
+        memcpy(nargv, argv.argv, sizeof(char *) * (argv.argc + 1));
+
+        result = write_archive(argv.argc, nargv, NULL, 1, NULL, 0);
+        argv_array_clear(&argv);
+        free(nargv);
+        return result;
 }
 
 static int write_tar_archive(const char *hex, const char *prefix)
@@ -129,29 +148,36 @@ static const char *get_ref_from_filename(const char *url, const char *filename,
 {
         const char *reponame;
         unsigned char sha1[20];
-        char *snapshot;
+        struct strbuf snapshot = STRBUF_INIT;
+        int result = 1;
 
-        snapshot = xstrdup(filename);
-        snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0';
+        strbuf_addstr(&snapshot, filename);
+        strbuf_setlen(&snapshot, snapshot.len - strlen(format->suffix));
 
-        if (get_sha1(snapshot, sha1) == 0)
-                return snapshot;
+        if (get_sha1(snapshot.buf, sha1) == 0)
+                goto out;
 
         reponame = cgit_repobasename(url);
-        if (prefixcmp(snapshot, reponame) == 0) {
-                snapshot += strlen(reponame);
-                while (snapshot && (*snapshot == '-' || *snapshot == '_'))
-                        snapshot++;
+        if (prefixcmp(snapshot.buf, reponame) == 0) {
+                const char *new_start = snapshot.buf;
+                new_start += strlen(reponame);
+                while (new_start && (*new_start == '-' || *new_start == '_'))
+                        new_start++;
+                strbuf_splice(&snapshot, 0, new_start - snapshot.buf, "", 0);
         }
 
-        if (get_sha1(snapshot, sha1) == 0)
-                return snapshot;
+        if (get_sha1(snapshot.buf, sha1) == 0)
+                goto out;
 
-        snapshot = fmt("v%s", snapshot);
-        if (get_sha1(snapshot, sha1) == 0)
-                return snapshot;
+        strbuf_insert(&snapshot, 0, "v", 1);
+        if (get_sha1(snapshot.buf, sha1) == 0)
+                goto out;
 
-        return NULL;
+        result = 0;
+        strbuf_release(&snapshot);
+
+out:
+        return result ? strbuf_detach(&snapshot, NULL) : NULL;
 }
 
 __attribute__((format (printf, 1, 2)))
diff --git a/ui-summary.c b/ui-summary.c
index bd123ef..f965b32 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -17,6 +17,7 @@
 static void print_url(char *base, char *suffix)
 {
         int columns = 3;
+        struct strbuf basebuf = STRBUF_INIT;
 
         if (ctx.repo->enable_log_filecount)
                 columns++;
@@ -25,13 +26,16 @@ static void print_url(char *base, char *suffix)
 
         if (!base || !*base)
                 return;
-        if (suffix && *suffix)
-                base = fmt("%s/%s", base, suffix);
+        if (suffix && *suffix) {
+                strbuf_addf(&basebuf, "%s/%s", base, suffix);
+                base = basebuf.buf;
+        }
         htmlf("<tr><td colspan='%d'><a href='", columns);
         html_url_path(base);
         html("'>");
         html_txt(base);
         html("</a></td></tr>\n");
+        strbuf_release(&basebuf);
 }
 
 static void print_urls(char *txt, char *suffix)
@@ -112,8 +116,8 @@ void cgit_print_repo_readme(char *path)
 
         /* Prepend repo path to relative readme path unless tracked. */
         if (!ref && *ctx.repo->readme != '/')
-                ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path,
-                                               ctx.repo->readme));
+                ctx.repo->readme = fmtalloc("%s/%s", ctx.repo->path,
+                                                ctx.repo->readme);
 
         /* If a subpath is specified for the about page, make it relative
          * to the directory containing the configured readme.
diff --git a/ui-tag.c b/ui-tag.c
index 397e15b..aea7958 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -41,6 +41,7 @@ static void print_download_links(char *revname)
 
 void cgit_print_tag(char *revname)
 {
+        struct strbuf fullref = STRBUF_INIT;
         unsigned char sha1[20];
         struct object *obj;
         struct tag *tag;
@@ -49,20 +50,21 @@ void cgit_print_tag(char *revname)
         if (!revname)
                 revname = ctx.qry.head;
 
-        if (get_sha1(fmt("refs/tags/%s", revname), sha1)) {
+        strbuf_addf(&fullref, "refs/tags/%s", revname);
+        if (get_sha1(fullref.buf, sha1)) {
                 cgit_print_error("Bad tag reference: %s", revname);
-                return;
+                goto cleanup;
         }
         obj = parse_object(sha1);
         if (!obj) {
                 cgit_print_error("Bad object id: %s", sha1_to_hex(sha1));
-                return;
+                goto cleanup;
         }
         if (obj->type == OBJ_TAG) {
                 tag = lookup_tag(sha1);
                 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
                         cgit_print_error("Bad tag object: %s", revname);
-                        return;
+                        goto cleanup;
                 }
                 html("<table class='commit-info'>\n");
                 htmlf("<tr><td>tag name</td><td>");
@@ -101,5 +103,7 @@ void cgit_print_tag(char *revname)
                         print_download_links(revname);
                 html("</table>\n");
         }
-        return;
+
+cleanup:
+        strbuf_release(&fullref);
 }
diff --git a/ui-tree.c b/ui-tree.c
index aebe145..aa5dee9 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -129,14 +129,14 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
 {
         struct walk_tree_context *walk_tree_ctx = cbdata;
         char *name;
-        char *fullpath;
-        char *class;
+        struct strbuf fullpath = STRBUF_INIT;
+        struct strbuf class = STRBUF_INIT;
         enum object_type type;
         unsigned long size = 0;
 
         name = xstrdup(pathname);
-        fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
-                       ctx.qry.path ? "/" : "", name);
+        strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "",
+                    ctx.qry.path ? "/" : "", name);
 
         if (!S_ISGITLINK(mode)) {
                 type = sha1_object_info(sha1, &size);
@@ -152,33 +152,34 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
         cgit_print_filemode(mode);
         html("</td><td>");
         if (S_ISGITLINK(mode)) {
-                cgit_submodule_link("ls-mod", fullpath, sha1_to_hex(sha1));
+                cgit_submodule_link("ls-mod", fullpath.buf, sha1_to_hex(sha1));
         } else if (S_ISDIR(mode)) {
                 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
-                               walk_tree_ctx->curr_rev, fullpath);
+                               walk_tree_ctx->curr_rev, fullpath.buf);
         } else {
-                class = strrchr(name, '.');
-                if (class != NULL) {
-                        class = fmt("ls-blob %s", class + 1);
-                } else
-                        class = "ls-blob";
-                cgit_tree_link(name, NULL, class, ctx.qry.head,
-                               walk_tree_ctx->curr_rev, fullpath);
+                char *ext = strrchr(name, '.');
+                strbuf_addstr(&class, "ls-blob");
+                if (ext)
+                        strbuf_addf(&class, " %s", ext + 1);
+                cgit_tree_link(name, NULL, class.buf, ctx.qry.head,
+                               walk_tree_ctx->curr_rev, fullpath.buf);
         }
         htmlf("</td><td class='ls-size'>%li</td>", size);
 
         html("<td>");
         cgit_log_link("log", NULL, "button", ctx.qry.head,
-                      walk_tree_ctx->curr_rev, fullpath, 0, NULL, NULL,
+                      walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL,
                       ctx.qry.showmsg);
         if (ctx.repo->max_stats)
                 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
-                                fullpath);
+                                fullpath.buf);
         if (!S_ISGITLINK(mode))
                 cgit_plain_link("plain", NULL, "button", ctx.qry.head,
-                                walk_tree_ctx->curr_rev, fullpath);
+                                walk_tree_ctx->curr_rev, fullpath.buf);
         html("</td></tr>\n");
         free(name);
+        strbuf_release(&fullpath);
+        strbuf_release(&class);
         return 0;
 }