Diff options
Diffstat (limited to '')
-rw-r--r--source/shared.c528
1 file changed, 121 insertions, 407 deletions
diff --git a/source/shared.c b/source/shared.c
index a20773b..1e1bb26 100644
--- a/source/shared.c
+++ b/source/shared.c
@@ -1,225 +1,29 @@
/*
- * The helpers that no single page owns, along with the process wide state
- * every page reads. Repositories are registered here as cgitrc is parsed, the
- * reference and commit records that the renderers list are built and freed
- * here, and git's diff machinery is wrapped so a caller is handed whole
- * filepairs or whole lines instead of xdiff buffers.
+ * The helpers that no single page owns, along with the process-wide state
+ * every page reads. Repositories are registered here as cgitrc is parsed, and
+ * git's diff machinery is wrapped so a caller is handed whole filepairs or
+ * whole lines instead of xdiff buffers.
*/
#define USE_THE_REPOSITORY_VARIABLE
#include "cgit.h"
-#include "parsing.h"
#include "shared.h"
#define MACRO_EXPANSION_BUFSIZE (1024 * 8)
-// How much of a file is read to find its first line.
#define MAX_FIRST_LINE_READ (1024 * 64)
-typedef struct {
- const char *name;
- const char *value;
-} env_var;
-
struct cgit_repolist cgit_repolist;
struct cgit_context ctx;
char cgit_default_repo_desc[] = "[no description]";
-// A diff line that arrived in pieces and is still waiting for its end, see
-// emit_line.
-static char *fragment;
-static int fragment_len;
-
-static void free_refinfo(struct refinfo *ref)
-{
- free((char *)ref->refname);
- switch (ref->object->type) {
- case OBJ_TAG:
- cgit_free_taginfo(ref->tag);
- break;
- case OBJ_COMMIT:
- cgit_free_commitinfo(ref->commit);
- break;
- }
- free(ref);
-}
-
-static struct refinfo *make_refinfo(const char *refname, const struct object_id *oid)
-{
- struct refinfo *ref;
-
- ref = xcalloc(1, sizeof(struct refinfo));
- ref->refname = xstrdup(refname);
- ref->object = parse_object(the_repository, oid);
- if (!ref->object) {
- free((char *)ref->refname);
- free(ref);
- return NULL;
- }
- switch (ref->object->type) {
- case OBJ_TAG:
- ref->tag = cgit_parse_tag((struct tag *)ref->object);
- break;
- case OBJ_COMMIT:
- ref->commit = cgit_parse_commit((struct commit *)ref->object);
- break;
- }
- return ref;
-}
-
-/*
- * Whether a revision from the request may reach git. A hex object id or a name
- * that could be a ref passes. Git's wider syntax is refused, since :/pattern
- * and rev^{/pattern} walk the whole history for a match, and a leading dash
- * reads as an option further down.
- */
-int cgit_valid_rev(const char *rev)
-{
- const char *p = rev;
-
- if (!rev || !*rev || *rev == '-')
- return 0;
- while (isxdigit((unsigned char)*p))
- p++;
- if (!*p)
- return 1;
- return !check_refname_format(rev, REFNAME_ALLOW_ONELEVEL);
-}
-
-static int load_mmfile(mmfile_t *file, const struct object_id *oid)
-{
- enum object_type type;
- size_t size;
-
- // A null oid is the absent side of an add or a delete, and diffs as an
- // empty file rather than as a failure.
- if (is_null_oid(oid)) {
- file->ptr = (char *)"";
- file->size = 0;
- return 1;
- }
-
- file->ptr = odb_read_object(the_repository->objects, oid, &type, &size);
- if (!file->ptr)
- return 0;
- file->size = size;
- return 1;
-}
-
-/*
- * The test is on the oid rather than the size, because load_mmfile uses a
- * literal only for a null oid, while a real but empty blob owns its buffer.
- */
-static void release_mmfile(mmfile_t *file, const struct object_id *oid)
-{
- if (!is_null_oid(oid))
- free(file->ptr);
-}
-
-/*
- * Xdiff emits buffers that need not end on a line boundary, so a trailing
- * fragment is held back and joined with whatever arrives next.
- */
-static int emit_line(void *priv, mmbuffer_t *mb, int nbuf)
-{
- linediff_fn emit = *(linediff_fn *)priv;
- int i;
-
- for (i = 0; i < nbuf; i++) {
- if (mb[i].ptr[mb[i].size - 1] != '\n') {
- fragment = xrealloc(fragment, fragment_len + mb[i].size);
- memcpy(fragment + fragment_len, mb[i].ptr, mb[i].size);
- fragment_len += mb[i].size;
- continue;
- }
-
- if (!fragment) {
- emit(mb[i].ptr, mb[i].size);
- continue;
- }
- fragment = xrealloc(fragment, fragment_len + mb[i].size);
- memcpy(fragment + fragment_len, mb[i].ptr, mb[i].size);
- emit(fragment, fragment_len + mb[i].size);
- free(fragment);
- fragment = NULL;
- fragment_len = 0;
- }
- if (fragment) {
- emit(fragment, fragment_len);
- free(fragment);
- fragment = NULL;
- fragment_len = 0;
- }
- return 0;
-}
-
-/*
- * Takes an unsigned char because a byte over 0x7f is negative where char is
- * signed, which the ctype tests are not defined for.
- */
-static int is_token_char(unsigned char c)
-{
- return isalnum(c) || c == '_';
-}
-
-/*
- * Replace the variable name held at name with its value from the environment,
- * writing at most room bytes including the terminator, and return where the
- * text now ends.
- */
-static char *expand_macro(char *name, size_t room)
-{
- char *value;
- size_t len;
-
- len = 0;
- value = getenv(name);
- if (value) {
- len = strlen(value) + 1;
- if (len > room)
- len = room;
- strlcpy(name, value, len);
- --len;
- }
- return name + len;
-}
-
-int cgit_die_unless_zero(int result, const char *msg)
-{
- if (result != 0)
- die_errno("%s", msg);
- return result;
-}
-
-int cgit_die_unless_positive(int result, const char *msg)
-{
- if (result <= 0)
- die_errno("%s", msg);
- return result;
-}
-
-int cgit_die_unless_non_negative(int result, const char *msg)
-{
- if (result < 0)
- die_errno("%s", msg);
- return result;
-}
-
struct cgit_repo *cgit_add_repo(const char *url)
{
struct cgit_repo *repo;
- if (++cgit_repolist.count > cgit_repolist.length) {
- if (cgit_repolist.length == 0)
- cgit_repolist.length = 8;
- else
- cgit_repolist.length *= 2;
- cgit_repolist.repos =
- xrealloc(cgit_repolist.repos, cgit_repolist.length * sizeof(struct cgit_repo));
- }
-
- repo = &cgit_repolist.repos[cgit_repolist.count - 1];
+ ALLOC_GROW(cgit_repolist.repos, cgit_repolist.count + 1, cgit_repolist.length);
+ repo = &cgit_repolist.repos[cgit_repolist.count++];
memset(repo, 0, sizeof(struct cgit_repo));
repo->url = cgit_trim_end(url, '/');
if (repo->url) {
@@ -232,10 +36,7 @@ struct cgit_repo *cgit_add_repo(const char *url)
repo->ignore = 1;
}
repo->name = repo->url;
- repo->path = NULL;
repo->desc = cgit_default_repo_desc;
- repo->head_content = NULL;
- repo->owner = NULL;
repo->section = ctx.cfg.section;
repo->snapshots = ctx.cfg.snapshots;
repo->enable_blame = ctx.cfg.enable_blame;
@@ -279,108 +80,65 @@ struct cgit_repo *cgit_get_repoinfo(const char *url)
return NULL;
}
-void cgit_free_commitinfo(struct commitinfo *info)
-{
- free(info->author);
- free(info->author_email);
- free(info->committer);
- free(info->committer_email);
- free(info->subject);
- free(info->msg);
- free(info->msg_encoding);
- free(info);
-}
-
-char *cgit_trim_end(const char *str, char c)
-{
- size_t len;
-
- if (!str)
- return NULL;
- len = strlen(str);
- while (len > 0 && str[len - 1] == c)
- len--;
- if (len == 0)
- return NULL;
- return xstrndup(str, len);
-}
-
-char *cgit_ensure_end(const char *str, char c)
+void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options UNUSED, void *data)
{
- size_t len = strlen(str);
- char *result;
-
- if (len && str[len - 1] == c)
- return xstrndup(str, len);
-
- result = xmalloc(len + 2);
- memcpy(result, str, len);
- result[len] = c;
- result[len + 1] = '\0';
- return result;
-}
+ filepair_fn fn = *(filepair_fn *)data;
+ int i;
-void strbuf_ensure_end(struct strbuf *sb, char c)
-{
- if (!sb->len || sb->buf[sb->len - 1] != c)
- strbuf_addch(sb, c);
+ for (i = 0; i < q->nr; i++) {
+ // An unmerged path has no single pair of blobs to show.
+ if (q->queue[i]->status == 'U')
+ continue;
+ fn(q->queue[i]);
+ }
}
-static void add_ref(struct reflist *list, struct refinfo *ref)
+static int load_mmfile(mmfile_t *file, const struct object_id *oid)
{
+ enum object_type type;
size_t size;
- if (list->count >= list->alloc) {
- list->alloc += (list->alloc ? list->alloc : 4);
- size = list->alloc * sizeof(struct refinfo *);
- list->refs = xrealloc(list->refs, size);
+ // A null oid is the absent side of an add or a delete, and diffs as an
+ // empty file rather than as a failure.
+ if (is_null_oid(oid)) {
+ file->ptr = (char *)"";
+ file->size = 0;
+ return 1;
}
- list->refs[list->count++] = ref;
-}
-
-void cgit_free_taginfo(struct taginfo *info)
-{
- // NULL is accepted because cgit_parse_tag returns it for a tag object
- // that cannot be read, and a ref left holding that still gets freed
- // along with the rest of its list.
- if (!info)
- return;
- free(info->tagger);
- free(info->tagger_email);
- free(info->msg);
- free(info);
-}
-
-void cgit_free_reflist_inner(struct reflist *list)
-{
- int i;
- for (i = 0; i < list->count; i++)
- free_refinfo(list->refs[i]);
- free(list->refs);
+ file->ptr = odb_read_object(the_repository->objects, oid, &type, &size);
+ if (!file->ptr)
+ return 0;
+ file->size = size;
+ return 1;
}
-int cgit_refs_cb(const struct reference *ref, void *cb_data)
+/*
+ * The test is on the oid rather than the size, because load_mmfile uses a
+ * literal only for a null oid, while a real but empty blob owns its buffer.
+ */
+static void release_mmfile(mmfile_t *file, const struct object_id *oid)
{
- struct reflist *list = (struct reflist *)cb_data;
- struct refinfo *info = make_refinfo(ref->name, ref->oid);
-
- if (info)
- add_ref(list, info);
- return 0;
+ if (!is_null_oid(oid))
+ free(file->ptr);
}
-void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options UNUSED, void *data)
+/*
+ * Xdiff hands over one line per call, in pieces that are the prefix, the
+ * record and, for a record with no newline, the marker saying so. They are
+ * joined here before the line reaches fn.
+ */
+static int emit_line(void *priv, mmbuffer_t *mb, int nbuf)
{
- filepair_fn fn = *(filepair_fn *)data;
+ linediff_fn emit = *(linediff_fn *)priv;
+ struct strbuf line = STRBUF_INIT;
int i;
- for (i = 0; i < q->nr; i++) {
- // An unmerged path has no single pair of blobs to show.
- if (q->queue[i]->status == 'U')
- continue;
- fn(q->queue[i]);
- }
+ for (i = 0; i < nbuf; i++)
+ strbuf_add(&line, mb[i].ptr, mb[i].size);
+ emit(line.buf, line.len);
+ strbuf_release(&line);
+ return 0;
}
int cgit_diff_files(const struct object_id *old_oid, const struct object_id *new_oid,
@@ -450,23 +208,9 @@ int cgit_diff_files(const struct object_id *old_oid, const struct object_id *new
return 0;
}
-int cgit_blob_too_large(size_t size)
-{
- return ctx.cfg.max_blob_size && size / 1024 > (size_t)ctx.cfg.max_blob_size;
-}
-
void cgit_diff_limit_path(struct diff_options *opt, const char *prefix)
{
- struct pathspec_item *item;
-
- item = xcalloc(1, sizeof(*item));
- item->match = xstrdup(prefix);
- item->len = strlen(prefix);
- // nowildcard_len matching len makes git treat the path as literal
- // rather than as a glob.
- item->nowildcard_len = item->len;
- opt->pathspec.nr = 1;
- opt->pathspec.items = item;
+ cgit_literal_pathspec(&opt->pathspec, xmalloc(sizeof(struct pathspec_item)), xstrdup(prefix));
}
void cgit_diff_tree(const struct object_id *old_oid, const struct object_id *new_oid,
@@ -504,65 +248,37 @@ void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix)
cgit_diff_tree(old_oid, &commit->object.oid, fn, prefix, ctx.qry.ignorews);
}
-/*
- * Git's parse_date_format dies on anything unknown, which would turn a
- * cgitrc typo into a failed request, so only documented formats reach it.
- */
-void cgit_parse_date_format(const char *format, struct date_mode *mode)
+int cgit_blob_too_large(size_t size)
{
- static const char * const names[] = {
- "default", "human", "iso", "iso-strict", "iso8601",
- "iso8601-strict", "raw", "relative", "rfc",
- "rfc2822", "short", "unix",
- };
- const char *rest;
- size_t i;
-
- if (skip_prefix(format, "format:", &rest)) {
- // An empty strftime format would render every date blank.
- if (*rest)
- parse_date_format(format, mode);
- return;
- }
+ return ctx.cfg.max_blob_size && size / 1024 > (size_t)ctx.cfg.max_blob_size;
+}
- // git reads local only as a whole alias of default-local, and dies on
- // it with a suffix of its own.
- if (!strcmp(format, "local")) {
- parse_date_format(format, mode);
- return;
- }
+void cgit_literal_pathspec(struct pathspec *ps, struct pathspec_item *item, const char *path)
+{
+ int len = path ? strlen(path) : 0;
- for (i = 0; i < ARRAY_SIZE(names); i++) {
- if (!skip_prefix(format, names[i], &rest))
- continue;
- // "iso" also prefixes "iso8601", so a partial hit is skipped.
- if (*rest && strcmp(rest, "-local"))
- continue;
- parse_date_format(format, mode);
- return;
- }
+ // nowildcard_len matching len makes git treat the path as literal
+ // rather than as a glob.
+ *item = (struct pathspec_item){ .match = path, .len = len, .nowildcard_len = len };
+ ps->nr = path ? 1 : 0;
+ ps->items = item;
}
-void cgit_prepare_repo_env(struct cgit_repo *repo)
+char *cgit_trim_end(const char *str, char c)
{
- const env_var vars[] = {
- { .name = "CGIT_REPO_URL", .value = repo->url },
- { .name = "CGIT_REPO_NAME", .value = repo->name },
- { .name = "CGIT_REPO_PATH", .value = repo->path },
- { .name = "CGIT_REPO_OWNER", .value = repo->owner },
- { .name = "CGIT_REPO_DEFBRANCH", .value = repo->defbranch },
- { .name = "CGIT_REPO_SECTION", .value = repo->section },
- { .name = "CGIT_REPO_CLONE_URL", .value = repo->clone_url }
- };
- size_t i;
-
- for (i = 0; i < ARRAY_SIZE(vars); i++)
- if (vars[i].value && setenv(vars[i].name, vars[i].value, 1))
- fprintf(stderr, "[cgit] Unable to set %s=%s: %s (%d)\n",
- vars[i].name, vars[i].value, strerror(errno), errno);
+ size_t len;
+
+ if (!str)
+ return NULL;
+ len = strlen(str);
+ while (len > 0 && str[len - 1] == c)
+ len--;
+ if (len == 0)
+ return NULL;
+ return xstrndup(str, len);
}
-int cgit_read_first_line(const char *path, char **buf, size_t *size)
+int cgit_read_first_line(const char *path, char **buf)
{
int fd, err;
ssize_t got;
@@ -590,12 +306,10 @@ int cgit_read_first_line(const char *path, char **buf, size_t *size)
err = errno;
free(*buf);
*buf = NULL;
- *size = 0;
close(fd);
return err;
}
- *size = got;
- (*buf)[*size] = '\0';
+ (*buf)[got] = '\0';
*strchrnul(*buf, '\n') = '\0';
close(fd);
return 0;
@@ -658,6 +372,37 @@ void cgit_utf8_sanitize(char *text)
}
}
+/*
+ * Takes an unsigned char because a byte over 0x7f is negative where char is
+ * signed, which the ctype tests are not defined for.
+ */
+static int is_token_char(unsigned char c)
+{
+ return isalnum(c) || c == '_';
+}
+
+/*
+ * Replace the variable name held at name with its value from the environment,
+ * writing at most room bytes including the terminator, and return where the
+ * text now ends.
+ */
+static char *expand_macro(char *name, size_t room)
+{
+ char *value;
+ size_t len;
+
+ len = 0;
+ value = getenv(name);
+ if (value) {
+ len = strlen(value) + 1;
+ if (len > room)
+ len = room;
+ strlcpy(name, value, len);
+ --len;
+ }
+ return name + len;
+}
+
char *cgit_expand_macros(const char *text)
{
static char result[MACRO_EXPANSION_BUFSIZE];
@@ -701,60 +446,29 @@ char *cgit_expand_macros(const char *text)
return result;
}
-char *cgit_get_mimetype_for_filename(const char *filename)
+int cgit_readme_is_tracked(const char *readme)
{
- const char *ext;
- char *type, line[1024];
- struct string_list fields = STRING_LIST_INIT_NODUP;
- size_t i;
- FILE *file;
- struct string_list_item *entry;
-
- if (!filename)
- return NULL;
-
- ext = strrchr(filename, '.');
- if (!ext)
- return NULL;
- ++ext;
- if (!ext[0])
- return NULL;
- entry = string_list_lookup(&ctx.cfg.mimetypes, ext);
- if (entry)
- return xstrdup(entry->util);
+ const char *colon = strchr(readme, ':');
- if (!ctx.cfg.mimetype_file)
- return NULL;
- file = fopen(ctx.cfg.mimetype_file, "r");
- if (!file)
- return NULL;
- while (fgets(line, sizeof(line), file)) {
- // A line of a mime.types file is one type followed by every
- // extension that maps to it.
- string_list_split_in_place(&fields, line, " \t\r\n", -1);
- string_list_remove_empty_items(&fields, 0);
- if (!fields.nr || fields.items[0].string[0] == '#') {
- string_list_clear(&fields, 0);
- continue;
- }
- type = fields.items[0].string;
- for (i = 1; i < fields.nr; i++) {
- if (!strcasecmp(ext, fields.items[i].string)) {
- fclose(file);
- return xstrdup(type);
- }
- }
- string_list_clear(&fields, 0);
- }
- fclose(file);
- return NULL;
+ return colon && colon[1];
}
-int cgit_readme_is_tracked(const char *readme)
+/*
+ * Git's wider syntax is refused, since :/pattern and rev^{/pattern} walk the
+ * whole history for a match, and a leading dash reads as an option further
+ * down.
+ */
+int cgit_valid_rev(const char *rev)
{
- const char *colon = strchr(readme, ':');
+ const char *p = rev;
- return colon && colon[1];
+ if (!rev || !*rev || *rev == '-')
+ return 0;
+ while (isxdigit((unsigned char)*p))
+ p++;
+ if (!*p)
+ return 1;
+ return !check_refname_format(rev, REFNAME_ALLOW_ONELEVEL);
}
int cgit_notes_ref_usable(const char *notes_ref)