snix/shared.c
sterni 0c86e2ac3f Squashed 'third_party/cgit/' changes from 8fc0c81bb..26ac84466
26ac84466 Bump version to 1.4.1
cc167887f Fix bad free in cgit_diff_tree
4a30da89c Bump version to 1.4.0
695515801 Tolerate short writes in print_slot
069d15447 git: update to v2.36.1
3374b0e7a git: update to v2.36.0
400fd7abb Use release_commit_memory()
93ce8fe3b Reset font size for blame oid
28c0cb6f7 Add a link to the parent commit in blame
c5809a429 Fix fmt() off-by-one error
129d8a158 Allow to give readme head from query
44a004809 Bump version to 1.3.0
76d9396e4 Update information for fork
d993e4be6 Remove Lua support
e4ce4fa74 Merge remote-tracking branch 'ch/git-2-35'
065ee253a Silence owner-info error
f92ba7ea8 Remove default favicon
03519c7bc Show about path in page title
20f0352d9 Generate valid Atom feeds
516175469 Merge up to git v2.32.0
12d91b897 Fix crash trying to print "this commit" on 404s
957855ffd Use owner-filter for repo page headers
f9c417f24 Fix tests for diff spans
be6a526a7 Use <pre> and <span> to print diffs
a8e11db65 Use buffered stdio
b2511f295 Remove redundant title on repo anchors
2d4c893db Improve button spacing for browsers w/o CSS
1e9f26ffb Improve decoration display for browsers without CSS
2843093cb Use <pre> for commit-msg
122f7a9be Improve pageheader display on text-based browsers
298f7d15f Use git raw note format
6524fe671 Add "this commit" option to switch form
8f9034884 Show subject in commit page title
41a17adaf Show symlink targets in tree listing
437d0b416 Disallow blame in robots.txt
e75fa957e Don't link to blame for binary blobs
8bf017ec5 Bail from blame if blob is binary
fa86ef6a3 Remove dependency on memrchr
bbbaa29a9 git: update to v2.35.1
73e98c16e git: update to v2.35.0
11be5b818 git: update to v2.34.1
b8f2b675d git: update to v2.34.0
45eff4065 git: update to v2.33.0
5258c297b git: update to v2.32.0
6dbbffe01 git: update to v2.31.1
62eb8db45 md2html: use proper formatting for hr
d889cae81 git: update to v2.31.0
4ffadc1e0 git: update to v2.30.1
bd6f5683f tests: t0107: support older and/or non-GNU tar
f69626c68 md2html: use sane_lists extension
cef27b670 git: update to v2.30.0
b1739247b git: update to v2.29.2
fe99c76ee git: update to v2.29.1
adcc4f822 tests: try with commit-graph
a1039ab17 tests: do not copy snapshots to /tmp/
a4de0e810 global: replace hard coded hash length
779631c6d global: replace references to 'sha1' with 'oid'
629659d2c git: update to v2.29.0
205837d46 git: update to v2.28.0
f780396c0 git: update to v2.27.0
0462f08d8 git: update to v2.26.0
55fa25adb Bump version
6a8d6d4b5 global: use proper accessors for maybe_tree
892ba8c3c ui-snapshot: add support for zstd compression
cc230bf04 tests: add tests for xz compressed snapshots
06671f4b2 ui-snapshot: add support for lzip compression
fde897b81 git: update to v2.25.1
5e49023b0 tests: allow to skip git version tests
fa146ccab Bump version
bd68c9887 git: update to v2.25.0
ca98c9e7b tests: skip tests if strace is not functional
d8e5dd25a git: update to v2.24.1
583aa5d80 ui-repolist: do not return unsigned (negative) value
bfabd4519 git: update to v2.24.0

git-subtree-dir: third_party/cgit
git-subtree-split: 26ac844661e95ee1ff3ddf4f8691de9904dad783
2022-05-19 10:23:37 +02:00

577 lines
13 KiB
C

/* shared.c: global vars + some callback functions
*
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
#include "cgit.h"
struct cgit_repolist cgit_repolist;
struct cgit_context ctx;
int chk_zero(int result, char *msg)
{
if (result != 0)
die_errno("%s", msg);
return result;
}
int chk_positive(int result, char *msg)
{
if (result <= 0)
die_errno("%s", msg);
return result;
}
int chk_non_negative(int result, char *msg)
{
if (result < 0)
die_errno("%s", msg);
return result;
}
char *cgit_default_repo_desc = "[no description]";
struct cgit_repo *cgit_add_repo(const char *url)
{
struct cgit_repo *ret;
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));
}
ret = &cgit_repolist.repos[cgit_repolist.count-1];
memset(ret, 0, sizeof(struct cgit_repo));
ret->url = trim_end(url, '/');
ret->name = ret->url;
ret->path = NULL;
ret->desc = cgit_default_repo_desc;
ret->extra_head_content = NULL;
ret->owner = NULL;
ret->homepage = NULL;
ret->section = ctx.cfg.section;
ret->snapshots = ctx.cfg.snapshots;
ret->enable_blame = ctx.cfg.enable_blame;
ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
ret->enable_subject_links = ctx.cfg.enable_subject_links;
ret->enable_html_serving = ctx.cfg.enable_html_serving;
ret->max_stats = ctx.cfg.max_stats;
ret->branch_sort = ctx.cfg.branch_sort;
ret->commit_sort = ctx.cfg.commit_sort;
ret->module_link = ctx.cfg.module_link;
ret->readme = ctx.cfg.readme;
ret->mtime = -1;
ret->about_filter = ctx.cfg.about_filter;
ret->commit_filter = ctx.cfg.commit_filter;
ret->source_filter = ctx.cfg.source_filter;
ret->email_filter = ctx.cfg.email_filter;
ret->owner_filter = ctx.cfg.owner_filter;
ret->clone_url = ctx.cfg.clone_url;
ret->submodules.strdup_strings = 1;
ret->hide = ret->ignore = 0;
return ret;
}
struct cgit_repo *cgit_get_repoinfo(const char *url)
{
int i;
struct cgit_repo *repo;
for (i = 0; i < cgit_repolist.count; i++) {
repo = &cgit_repolist.repos[i];
if (repo->ignore)
continue;
if (!strcmp(repo->url, url))
return repo;
}
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 *trim_end(const char *str, char c)
{
int len;
if (str == NULL)
return NULL;
len = strlen(str);
while (len > 0 && str[len - 1] == c)
len--;
if (len == 0)
return NULL;
return xstrndup(str, len);
}
char *ensure_end(const char *str, char c)
{
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] = '/';
result[len + 1] = '\0';
return result;
}
void strbuf_ensure_end(struct strbuf *sb, char c)
{
if (!sb->len || sb->buf[sb->len - 1] != c)
strbuf_addch(sb, c);
}
void cgit_add_ref(struct reflist *list, struct refinfo *ref)
{
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);
}
list->refs[list->count++] = ref;
}
static struct refinfo *cgit_mk_refinfo(const char *refname, const struct object_id *oid)
{
struct refinfo *ref;
ref = xmalloc(sizeof (struct refinfo));
ref->refname = xstrdup(refname);
ref->object = parse_object(the_repository, oid);
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;
}
void cgit_free_taginfo(struct taginfo *tag)
{
if (tag->tagger)
free(tag->tagger);
if (tag->tagger_email)
free(tag->tagger_email);
if (tag->msg)
free(tag->msg);
free(tag);
}
static void cgit_free_refinfo(struct refinfo *ref)
{
if (ref->refname)
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);
}
void cgit_free_reflist_inner(struct reflist *list)
{
int i;
for (i = 0; i < list->count; i++) {
cgit_free_refinfo(list->refs[i]);
}
free(list->refs);
}
int cgit_refs_cb(const char *refname, const struct object_id *oid, int flags,
void *cb_data)
{
struct reflist *list = (struct reflist *)cb_data;
struct refinfo *info = cgit_mk_refinfo(refname, oid);
if (info)
cgit_add_ref(list, info);
return 0;
}
void cgit_diff_tree_cb(struct diff_queue_struct *q,
struct diff_options *options, void *data)
{
int i;
for (i = 0; i < q->nr; i++) {
if (q->queue[i]->status == 'U')
continue;
((filepair_fn)data)(q->queue[i]);
}
}
static int load_mmfile(mmfile_t *file, const struct object_id *oid)
{
enum object_type type;
if (is_null_oid(oid)) {
file->ptr = (char *)"";
file->size = 0;
} else {
file->ptr = read_object_file(oid, &type,
(unsigned long *)&file->size);
}
return 1;
}
/*
* Receive diff-buffers from xdiff and concatenate them as
* needed across multiple callbacks.
*
* This is basically a copy of xdiff-interface.c/xdiff_outf(),
* ripped from git and modified to use globals instead of
* a special callback-struct.
*/
static char *diffbuf = NULL;
static int buflen = 0;
static int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
{
int i;
for (i = 0; i < nbuf; i++) {
if (mb[i].ptr[mb[i].size-1] != '\n') {
/* Incomplete line */
diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
buflen += mb[i].size;
continue;
}
/* we have a complete line */
if (!diffbuf) {
((linediff_fn)priv)(mb[i].ptr, mb[i].size);
continue;
}
diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
free(diffbuf);
diffbuf = NULL;
buflen = 0;
}
if (diffbuf) {
((linediff_fn)priv)(diffbuf, buflen);
free(diffbuf);
diffbuf = NULL;
buflen = 0;
}
return 0;
}
int cgit_diff_files(const struct object_id *old_oid,
const struct object_id *new_oid, unsigned long *old_size,
unsigned long *new_size, int *binary, int context,
int ignorews, linediff_fn fn)
{
mmfile_t file1, file2;
xpparam_t diff_params;
xdemitconf_t emit_params;
xdemitcb_t emit_cb;
if (!load_mmfile(&file1, old_oid) || !load_mmfile(&file2, new_oid))
return 1;
*old_size = file1.size;
*new_size = file2.size;
if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
(file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
*binary = 1;
if (file1.size)
free(file1.ptr);
if (file2.size)
free(file2.ptr);
return 0;
}
memset(&diff_params, 0, sizeof(diff_params));
memset(&emit_params, 0, sizeof(emit_params));
memset(&emit_cb, 0, sizeof(emit_cb));
diff_params.flags = XDF_NEED_MINIMAL;
if (ignorews)
diff_params.flags |= XDF_IGNORE_WHITESPACE;
emit_params.ctxlen = context > 0 ? context : 3;
emit_params.flags = XDL_EMIT_FUNCNAMES;
emit_cb.out_line = filediff_cb;
emit_cb.priv = fn;
xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
if (file1.size)
free(file1.ptr);
if (file2.size)
free(file2.ptr);
return 0;
}
void cgit_diff_tree(const struct object_id *old_oid,
const struct object_id *new_oid,
filepair_fn fn, const char *prefix, int ignorews)
{
struct diff_options opt;
struct pathspec_item *item;
diff_setup(&opt);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.detect_rename = 1;
opt.rename_limit = ctx.cfg.renamelimit;
opt.flags.recursive = 1;
if (ignorews)
DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
opt.format_callback = cgit_diff_tree_cb;
opt.format_callback_data = fn;
if (prefix) {
item = xcalloc(1, sizeof(*item));
item->match = xstrdup(prefix);
item->len = strlen(prefix);
opt.pathspec.nr = 1;
opt.pathspec.items = item;
}
diff_setup_done(&opt);
if (old_oid && !is_null_oid(old_oid))
diff_tree_oid(old_oid, new_oid, "", &opt);
else
diff_root_tree_oid(new_oid, "", &opt);
diffcore_std(&opt);
diff_flush(&opt);
}
void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix)
{
const struct object_id *old_oid = NULL;
if (commit->parents)
old_oid = &commit->parents->item->object.oid;
cgit_diff_tree(old_oid, &commit->object.oid, fn, prefix,
ctx.qry.ignorews);
}
int cgit_parse_snapshots_mask(const char *str)
{
struct string_list tokens = STRING_LIST_INIT_DUP;
struct string_list_item *item;
const struct cgit_snapshot_format *f;
int rv = 0;
/* favor legacy setting */
if (atoi(str))
return 1;
if (strcmp(str, "all") == 0)
return INT_MAX;
string_list_split(&tokens, str, ' ', -1);
string_list_remove_empty_items(&tokens, 0);
for_each_string_list_item(item, &tokens) {
for (f = cgit_snapshot_formats; f->suffix; f++) {
if (!strcmp(item->string, f->suffix) ||
!strcmp(item->string, f->suffix + 1)) {
rv |= cgit_snapshot_format_bit(f);
break;
}
}
}
string_list_clear(&tokens, 0);
return rv;
}
typedef struct {
char * name;
char * value;
} cgit_env_var;
void cgit_prepare_repo_env(struct cgit_repo * repo)
{
cgit_env_var env_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 }
};
int env_var_count = ARRAY_SIZE(env_vars);
cgit_env_var *p, *q;
static char *warn = "cgit warning: failed to set env: %s=%s\n";
p = env_vars;
q = p + env_var_count;
for (; p < q; p++)
if (p->value && setenv(p->name, p->value, 1))
fprintf(stderr, warn, p->name, p->value);
}
/* Read the content of the specified file into a newly allocated buffer,
* zeroterminate the buffer and return 0 on success, errno otherwise.
*/
int readfile(const char *path, char **buf, size_t *size)
{
int fd, e;
struct stat st;
fd = open(path, O_RDONLY);
if (fd == -1)
return errno;
if (fstat(fd, &st)) {
e = errno;
close(fd);
return e;
}
if (!S_ISREG(st.st_mode)) {
close(fd);
return EISDIR;
}
*buf = xmalloc(st.st_size + 1);
*size = read_in_full(fd, *buf, st.st_size);
e = errno;
(*buf)[*size] = '\0';
close(fd);
return (*size == st.st_size ? 0 : e);
}
static int is_token_char(char c)
{
return isalnum(c) || c == '_';
}
/* Replace name with getenv(name), return pointer to zero-terminating char
*/
static char *expand_macro(char *name, int maxlength)
{
char *value;
size_t len;
len = 0;
value = getenv(name);
if (value) {
len = strlen(value) + 1;
if (len > maxlength)
len = maxlength;
strlcpy(name, value, len);
--len;
}
return name + len;
}
#define EXPBUFSIZE (1024 * 8)
/* Replace all tokens prefixed by '$' in the specified text with the
* value of the named environment variable.
* NB: the return value is a static buffer, i.e. it must be strdup'd
* by the caller.
*/
char *expand_macros(const char *txt)
{
static char result[EXPBUFSIZE];
char *p, *start;
int len;
p = result;
start = NULL;
while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
*p = *txt;
if (start) {
if (!is_token_char(*txt)) {
if (p - start > 0) {
*p = '\0';
len = result + EXPBUFSIZE - start - 1;
p = expand_macro(start, len) - 1;
}
start = NULL;
txt--;
}
p++;
txt++;
continue;
}
if (*txt == '$') {
start = p;
txt++;
continue;
}
p++;
txt++;
}
*p = '\0';
if (start && p - start > 0) {
len = result + EXPBUFSIZE - start - 1;
p = expand_macro(start, len);
*p = '\0';
}
return result;
}
char *get_mimetype_for_filename(const char *filename)
{
char *ext, *mimetype, *token, line[1024], *saveptr;
FILE *file;
struct string_list_item *mime;
if (!filename)
return NULL;
ext = strrchr(filename, '.');
if (!ext)
return NULL;
++ext;
if (!ext[0])
return NULL;
mime = string_list_lookup(&ctx.cfg.mimetypes, ext);
if (mime)
return xstrdup(mime->util);
if (!ctx.cfg.mimetype_file)
return NULL;
file = fopen(ctx.cfg.mimetype_file, "r");
if (!file)
return NULL;
while (fgets(line, sizeof(line), file)) {
if (!line[0] || line[0] == '#')
continue;
mimetype = strtok_r(line, " \t\r\n", &saveptr);
while ((token = strtok_r(NULL, " \t\r\n", &saveptr))) {
if (!strcasecmp(ext, token)) {
fclose(file);
return xstrdup(mimetype);
}
}
}
fclose(file);
return NULL;
}