parent
5897d950ec
commit
bc2d48b8cd
@ -0,0 +1,35 @@ |
||||
## cgit-gopher |
||||
|
||||
The plan is to equip cgit with a Gopher (RFC 1436) front-end. This would |
||||
be possible by replacing the functions in ui-*.c with appropriate |
||||
functions to generate Gopher contents. In practice, all the html-related |
||||
stuff should be replaced by simple text, and the output simplified (we |
||||
can't have more than one link per line in Gopher). |
||||
|
||||
It seems that ui-tree.c is a good place to start for a proof-of-concept. |
||||
|
||||
(20180724-16:19) |
||||
|
||||
The PoC works. Now we should produce proper selectors for the stuff in |
||||
the tree page. In particular: |
||||
|
||||
- Distinguish between files and directories/links |
||||
- construct an appropriate selector path (see cgit_tree_link in |
||||
ui-shared.c) |
||||
|
||||
N.B.: We don't need to support all the stuff in cgit. In particular, |
||||
the 'virtual-root' variable might be a bit cumbersome to implement, |
||||
since we need an explicit way to signal the Gopher server that we |
||||
need the script (i.e., the presence of a '?' after the name of the CGI |
||||
script). |
||||
|
||||
(20180725 - 9:30) |
||||
|
||||
The easiest way to inject a Gopher interface seems to be through |
||||
cmd.c, since the functions to be called for each action are defined in |
||||
there. It should be sufficient to provide a gopher-cmd.c and to |
||||
replace all the ui-*.c files with the corresponding ui70-*.c |
||||
counterparts which implement the Gopher interface. |
||||
|
||||
Again, we should start from ui-repolist.c and ui-tree.c, which are the |
||||
two necessary bits for a viable proof-of-concept. |
@ -0,0 +1,149 @@ |
||||
# This Makefile is run in the "git" directory in order to re-use Git's
|
||||
# build variables and operating system detection. Hence all files in
|
||||
# CGit's directory must be prefixed with "../".
|
||||
include Makefile |
||||
|
||||
CGIT_PREFIX = ../
|
||||
|
||||
-include $(CGIT_PREFIX)cgit.conf |
||||
|
||||
# The CGIT_* variables are inherited when this file is called from the
|
||||
# main Makefile - they are defined there.
|
||||
|
||||
$(CGIT_PREFIX)VERSION: force-version |
||||
@cd $(CGIT_PREFIX) && '$(SHELL_PATH_SQ)' ./gen-version.sh "$(CGIT_VERSION)"
|
||||
-include $(CGIT_PREFIX)VERSION |
||||
.PHONY: force-version |
||||
|
||||
# CGIT_CFLAGS is a separate variable so that we can track it separately
|
||||
# and avoid rebuilding all of Git when these variables change.
|
||||
CGIT_CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
|
||||
CGIT_CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
|
||||
CGIT_CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
|
||||
CGIT_CFLAGS += -g
|
||||
|
||||
PKG_CONFIG ?= pkg-config
|
||||
|
||||
ifdef NO_C99_FORMAT |
||||
CFLAGS += -DNO_C99_FORMAT
|
||||
endif |
||||
|
||||
ifdef NO_LUA |
||||
LUA_MESSAGE := linking without specified Lua support
|
||||
CGIT_CFLAGS += -DNO_LUA
|
||||
else |
||||
ifeq ($(LUA_PKGCONFIG),) |
||||
LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \
|
||||
$(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \
|
||||
done)
|
||||
LUA_MODE := autodetected
|
||||
else |
||||
LUA_MODE := specified
|
||||
endif |
||||
ifneq ($(LUA_PKGCONFIG),) |
||||
LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG)
|
||||
LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null)
|
||||
LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null)
|
||||
CGIT_LIBS += $(LUA_LIBS)
|
||||
CGIT_CFLAGS += $(LUA_CFLAGS)
|
||||
else |
||||
LUA_MESSAGE := linking without autodetected Lua support
|
||||
NO_LUA := YesPlease
|
||||
CGIT_CFLAGS += -DNO_LUA
|
||||
endif |
||||
|
||||
endif |
||||
|
||||
# Add -ldl to linker flags on systems that commonly use GNU libc.
|
||||
ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD)) |
||||
CGIT_LIBS += -ldl
|
||||
endif |
||||
|
||||
# glibc 2.1+ offers sendfile which the most common C library on Linux
|
||||
ifeq ($(uname_S),Linux) |
||||
HAVE_LINUX_SENDFILE = YesPlease
|
||||
endif |
||||
|
||||
ifdef HAVE_LINUX_SENDFILE |
||||
CGIT_CFLAGS += -DHAVE_LINUX_SENDFILE
|
||||
endif |
||||
|
||||
#CGIT_OBJ_NAMES += cgit.o
|
||||
CGIT_OBJ_NAMES += cgit_70.o
|
||||
CGIT_OBJ_NAMES += cache.o
|
||||
#CGIT_OBJ_NAMES += cmd.o
|
||||
CGIT_OBJ_NAMES += cmd_70.o
|
||||
CGIT_OBJ_NAMES += configfile.o
|
||||
CGIT_OBJ_NAMES += filter.o
|
||||
CGIT_OBJ_NAMES += html.o
|
||||
CGIT_OBJ_NAMES += parsing.o
|
||||
CGIT_OBJ_NAMES += scan-tree.o
|
||||
CGIT_OBJ_NAMES += shared.o
|
||||
CGIT_OBJ_NAMES += ui-atom.o
|
||||
CGIT_OBJ_NAMES += ui-blame.o
|
||||
CGIT_OBJ_NAMES += ui-blob.o
|
||||
CGIT_OBJ_NAMES += ui-clone.o
|
||||
CGIT_OBJ_NAMES += ui-commit.o
|
||||
CGIT_OBJ_NAMES += ui-diff.o
|
||||
CGIT_OBJ_NAMES += ui-log.o
|
||||
CGIT_OBJ_NAMES += ui-patch.o
|
||||
CGIT_OBJ_NAMES += ui-plain.o
|
||||
##CGIT_OBJ_NAMES += ui-refs.o
|
||||
CGIT_OBJ_NAMES += ui_70-refs.o
|
||||
##CGIT_OBJ_NAMES += ui-repolist.o
|
||||
CGIT_OBJ_NAMES += ui_70-repolist.o
|
||||
##CGIT_OBJ_NAMES += ui-shared.o
|
||||
CGIT_OBJ_NAMES += ui_70-shared.o
|
||||
CGIT_OBJ_NAMES += ui-snapshot.o
|
||||
CGIT_OBJ_NAMES += ui-ssdiff.o
|
||||
CGIT_OBJ_NAMES += ui-stats.o
|
||||
##CGIT_OBJ_NAMES += ui-summary.o
|
||||
CGIT_OBJ_NAMES += ui_70-summary.o
|
||||
CGIT_OBJ_NAMES += ui-tag.o
|
||||
CGIT_OBJ_NAMES += ui-tree.o
|
||||
|
||||
CGIT_OBJS := $(addprefix $(CGIT_PREFIX),$(CGIT_OBJ_NAMES))
|
||||
|
||||
# Only cgit.c reference CGIT_VERSION so we only rebuild its objects when the
|
||||
# version changes.
|
||||
##CGIT_VERSION_OBJS := $(addprefix $(CGIT_PREFIX),cgit.o cgit.sp)
|
||||
CGIT_VERSION_OBJS := $(addprefix $(CGIT_PREFIX),cgit_70.o cgit.sp)
|
||||
$(CGIT_VERSION_OBJS): $(CGIT_PREFIX)VERSION |
||||
$(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \
|
||||
-DCGIT_VERSION='"$(CGIT_VERSION)"'
|
||||
|
||||
# Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not
|
||||
# handled by that and we must handle them ourselves.
|
||||
cgit_dep_files := $(foreach f,$(CGIT_OBJS),$(dir $f).depend/$(notdir $f).d)
|
||||
cgit_dep_files_present := $(wildcard $(cgit_dep_files))
|
||||
ifneq ($(cgit_dep_files_present),) |
||||
include $(cgit_dep_files_present) |
||||
endif |
||||
|
||||
ifeq ($(wildcard $(CGIT_PREFIX).depend),) |
||||
missing_dep_dirs += $(CGIT_PREFIX).depend
|
||||
endif |
||||
|
||||
$(CGIT_PREFIX).depend: |
||||
@mkdir -p $@
|
||||
|
||||
$(CGIT_PREFIX)CGIT-CFLAGS: FORCE |
||||
@FLAGS='$(subst ','\'',$(CGIT_CFLAGS))'; \
|
||||
if test x"$$FLAGS" != x"`cat ../CGIT-CFLAGS 2>/dev/null`" ; then \
|
||||
echo 1>&2 " * new CGit build flags"; \
|
||||
echo "$$FLAGS" >$(CGIT_PREFIX)CGIT-CFLAGS; \
|
||||
fi
|
||||
|
||||
$(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs) |
||||
$(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $<
|
||||
|
||||
$(CGIT_PREFIX)cgit_70: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS) |
||||
@echo 1>&1 " * $(LUA_MESSAGE)"
|
||||
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS)
|
||||
|
||||
CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS))
|
||||
|
||||
$(CGIT_SP_OBJS): %.sp: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS FORCE |
||||
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $(SPARSE_FLAGS) $<
|
||||
|
||||
cgit-sparse: $(CGIT_SP_OBJS) |
@ -0,0 +1,209 @@ |
||||
/* cmd.c: the cgit command dispatcher
|
||||
* |
||||
* Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com> |
||||
* |
||||
* Licensed under GNU General Public License v2 |
||||
* (see COPYING for full license text) |
||||
*/ |
||||
|
||||
#include "cgit.h" |
||||
#include "cmd.h" |
||||
#include "cache.h" |
||||
#include "ui-shared.h" |
||||
#include "ui-atom.h" |
||||
#include "ui-blame.h" |
||||
#include "ui-blob.h" |
||||
#include "ui-clone.h" |
||||
#include "ui-commit.h" |
||||
#include "ui-diff.h" |
||||
#include "ui-log.h" |
||||
#include "ui-patch.h" |
||||
#include "ui-plain.h" |
||||
#include "ui-refs.h" |
||||
#include "ui-repolist.h" |
||||
#include "ui-snapshot.h" |
||||
#include "ui-stats.h" |
||||
#include "ui-summary.h" |
||||
#include "ui-tag.h" |
||||
#include "ui-tree.h" |
||||
|
||||
static void HEAD_fn(void) |
||||
{ |
||||
cgit_clone_head(); |
||||
} |
||||
|
||||
static void atom_fn(void) |
||||
{ |
||||
cgit_print_atom(ctx.qry.head, ctx.qry.path, ctx.cfg.max_atom_items); |
||||
} |
||||
|
||||
static void about_fn(void) |
||||
{ |
||||
if (ctx.repo) { |
||||
size_t path_info_len = ctx.env.path_info ? strlen(ctx.env.path_info) : 0; |
||||
if (!ctx.qry.path && |
||||
ctx.qry.url[strlen(ctx.qry.url) - 1] != '/' && |
||||
(!path_info_len || ctx.env.path_info[path_info_len - 1] != '/')) { |
||||
char *currenturl = cgit_currenturl(); |
||||
char *redirect = fmtalloc("%s/", currenturl); |
||||
cgit_redirect(redirect, true); |
||||
free(currenturl); |
||||
free(redirect); |
||||
} else if (ctx.repo->readme.nr) |
||||
cgit_print_repo_readme(ctx.qry.path); |
||||
else if (ctx.repo->homepage) |
||||
cgit_redirect(ctx.repo->homepage, false); |
||||
else { |
||||
char *currenturl = cgit_currenturl(); |
||||
char *redirect = fmtalloc("%s../", currenturl); |
||||
cgit_redirect(redirect, false); |
||||
free(currenturl); |
||||
free(redirect); |
||||
} |
||||
} else |
||||
cgit_print_site_readme(); |
||||
} |
||||
|
||||
static void blame_fn(void) |
||||
{ |
||||
if (ctx.cfg.enable_blame) |
||||
cgit_print_blame(); |
||||
else |
||||
cgit_print_error_page(403, "Forbidden", "Blame is disabled"); |
||||
} |
||||
|
||||
static void blob_fn(void) |
||||
{ |
||||
cgit_print_blob(ctx.qry.sha1, ctx.qry.path, ctx.qry.head, 0); |
||||
} |
||||
|
||||
static void commit_fn(void) |
||||
{ |
||||
cgit_print_commit(ctx.qry.sha1, ctx.qry.path); |
||||
} |
||||
|
||||
static void diff_fn(void) |
||||
{ |
||||
cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 0); |
||||
} |
||||
|
||||
static void rawdiff_fn(void) |
||||
{ |
||||
cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 1); |
||||
} |
||||
|
||||
static void info_fn(void) |
||||
{ |
||||
cgit_clone_info(); |
||||
} |
||||
|
||||
static void log_fn(void) |
||||
{ |
||||
cgit_print_log(ctx.qry.sha1, ctx.qry.ofs, ctx.cfg.max_commit_count, |
||||
ctx.qry.grep, ctx.qry.search, ctx.qry.path, 1, |
||||
ctx.repo->enable_commit_graph, |
||||
ctx.repo->commit_sort); |
||||
} |
||||
|
||||
static void ls_cache_fn(void) |
||||
{ |
||||
ctx.page.mimetype = "text/plain"; |
||||
ctx.page.filename = "ls-cache.txt"; |
||||
cgit_print_http_headers(); |
||||
cache_ls(ctx.cfg.cache_root); |
||||
} |
||||
|
||||
static void objects_fn(void) |
||||
{ |
||||
cgit_clone_objects(); |
||||
} |
||||
|
||||
static void repolist_fn(void) |
||||
{ |
||||
cgit_print_repolist(); |
||||
} |
||||
|
||||
static void patch_fn(void) |
||||
{ |
||||
cgit_print_patch(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path); |
||||
} |
||||
|
||||
static void plain_fn(void) |
||||
{ |
||||
cgit_print_plain(); |
||||
} |
||||
|
||||
static void refs_fn(void) |
||||
{ |
||||
cgit_print_refs(); |
||||
} |
||||
|
||||
static void snapshot_fn(void) |
||||
{ |
||||
cgit_print_snapshot(ctx.qry.head, ctx.qry.sha1, ctx.qry.path, |
||||
ctx.qry.nohead); |
||||
} |
||||
|
||||
static void stats_fn(void) |
||||
{ |
||||
cgit_show_stats(); |
||||
} |
||||
|
||||
static void summary_fn(void) |
||||
{ |
||||
fprintf(stderr, " ---- selected function: cgit_print_summary\n"); |
||||
cgit_print_summary(); |
||||
} |
||||
|
||||
static void tag_fn(void) |
||||
{ |
||||
cgit_print_tag(ctx.qry.sha1); |
||||
} |
||||
|
||||
static void tree_fn(void) |
||||
{ |
||||
cgit_print_tree(ctx.qry.sha1, ctx.qry.path); |
||||
} |
||||
|
||||
#define def_cmd(name, want_repo, want_vpath, is_clone) \ |
||||
{#name, name##_fn, want_repo, want_vpath, is_clone} |
||||
|
||||
struct cgit_cmd *cgit_get_cmd(void) |
||||
{ |
||||
static struct cgit_cmd cmds[] = { |
||||
def_cmd(HEAD, 1, 0, 1), |
||||
def_cmd(atom, 1, 0, 0), |
||||
def_cmd(about, 0, 0, 0), |
||||
def_cmd(blame, 1, 1, 0), |
||||
def_cmd(blob, 1, 0, 0), |
||||
def_cmd(commit, 1, 1, 0), |
||||
def_cmd(diff, 1, 1, 0), |
||||
def_cmd(info, 1, 0, 1), |
||||
def_cmd(log, 1, 1, 0), |
||||
def_cmd(ls_cache, 0, 0, 0), |
||||
def_cmd(objects, 1, 0, 1), |
||||
def_cmd(patch, 1, 1, 0), |
||||
def_cmd(plain, 1, 0, 0), |
||||
def_cmd(rawdiff, 1, 1, 0), |
||||
def_cmd(refs, 1, 0, 0), |
||||
def_cmd(repolist, 0, 0, 0), |
||||
def_cmd(snapshot, 1, 0, 0), |
||||
def_cmd(stats, 1, 1, 0), |
||||
def_cmd(summary, 1, 0, 0), |
||||
def_cmd(tag, 1, 0, 0), |
||||
def_cmd(tree, 1, 1, 0), |
||||
}; |
||||
int i; |
||||
|
||||
if (ctx.qry.page == NULL) { |
||||
if (ctx.repo) |
||||
ctx.qry.page = "summary"; |
||||
else |
||||
ctx.qry.page = "repolist"; |
||||
} |
||||
|
||||
for (i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) |
||||
if (!strcmp(ctx.qry.page, cmds[i].name)) |
||||
return &cmds[i]; |
||||
return NULL; |
||||
} |
@ -0,0 +1,578 @@ |
||||
/* 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_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(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.outf = 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; |
||||
|
||||
memset(&item, 0, sizeof(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.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); |
||||
|
||||
free(item.match); |
||||
} |
||||
|
||||
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; |
||||
} |
@ -0,0 +1,40 @@ |
||||
/*
|
||||
* |
||||
* Gopher-related functions
|
||||
* |
||||
*/ |
||||
|
||||
#include <stdio.h> |
||||
|
||||
void cgit_gopher_selector(char type, char *str, char *sel, char *host, int port){ |
||||
|
||||
printf("%c%s\t%s\t%s\t%d\r\n", |
||||
type, str, sel, host, port); |
||||
} |
||||
|
||||
|
||||
void cgit_gopher_info(char *msg, char *host, int port){ |
||||
cgit_gopher_selector('i', msg, "", host, port); |
||||
} |
||||
|
||||
void cgit_gopher_menu(char *descr, char *sel, char *host, int port){ |
||||
|
||||
cgit_gopher_selector('1', descr, sel, host, port); |
||||
} |
||||
|
||||
void cgit_gopher_textfile(char *descr, char *sel, char *host, int port){ |
||||
|
||||
cgit_gopher_selector('0', descr, sel, host, port); |
||||
} |
||||
|
||||
void cgit_gopher_error(char *msg, char *host, int port){ |
||||
cgit_gopher_selector('3', msg, "Err", host, port); |
||||
} |
||||
|
||||
void cgit_tree_link_gopher(const char *name, const char *title, const char *class, |
||||
const char *head, const char *rev, const char *path) |
||||
{ |
||||
|
||||
|
||||
|
||||
} |
@ -0,0 +1,13 @@ |
||||
#ifndef GOPHER_H |
||||
#define GOPHER_H |
||||
|
||||
#define CGIT_GOPHER_HOST "localhost" |
||||
#define CGIT_GOPHER_PORT 1500 |
||||
|
||||
void cgit_gopher_selector(char type, char *str, char *sel, char *host, int port); |
||||
void cgit_gopher_info(char *msg, char *host, int port); |
||||
void cgit_gopher_menu(char *descr, char *sel, char *host, int port); |
||||
void cgit_gopher_textfile(char *descr, char *sel, char *host, int port); |
||||
void cgit_gopher_error(char *descr, char *host, int port); |
||||
|
||||
#endif /* GOPHER_H */ |
@ -0,0 +1,224 @@ |
||||
/* ui-refs.c: browse symbolic refs
|
||||
* |
||||
* 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" |
||||
#include "ui-refs.h" |
||||
#include "html.h" |
||||
#include "ui_70-shared.h" |
||||
|
||||
static inline int cmp_age(int age1, int age2) |
||||
{ |
||||
/* age1 and age2 are assumed to be non-negative */ |
||||
return age2 - age1; |
||||
} |
||||
|
||||
static int cmp_ref_name(const void *a, const void *b) |
||||
{ |
||||
struct refinfo *r1 = *(struct refinfo **)a; |
||||
struct refinfo *r2 = *(struct refinfo **)b; |
||||
|
||||
return strcmp(r1->refname, r2->refname); |
||||
} |
||||
|
||||
static int cmp_branch_age(const void *a, const void *b) |
||||
{ |
||||
struct refinfo *r1 = *(struct refinfo **)a; |
||||
struct refinfo *r2 = *(struct refinfo **)b; |
||||
|
||||
return cmp_age(r1->commit->committer_date, r2->commit->committer_date); |
||||
} |
||||
|
||||
static int get_ref_age(struct refinfo *ref) |
||||
{ |
||||
if (!ref->object) |
||||
return 0; |
||||
switch (ref->object->type) { |
||||
case OBJ_TAG: |
||||
return ref->tag ? ref->tag->tagger_date : 0; |
||||
case OBJ_COMMIT: |
||||
return ref->commit ? ref->commit->committer_date : 0; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
static int cmp_tag_age(const void *a, const void *b) |
||||
{ |
||||
struct refinfo *r1 = *(struct refinfo **)a; |
||||
struct refinfo *r2 = *(struct refinfo **)b; |
||||
|
||||
return cmp_age(get_ref_age(r1), get_ref_age(r2)); |
||||
} |
||||
|
||||
static int print_branch(struct refinfo *ref) |
||||
{ |
||||
struct commitinfo *info = ref->commit; |
||||
char *name = (char *)ref->refname; |
||||
|
||||
if (!info) |
||||
return 1; |
||||
cgit_gopher_start_selector(GOPHER_MENU); |
||||
cgit_gopher_text_pad(name, GOPHER_SUMMARY_NAME_LEN); |
||||
if (ref->object->type == OBJ_COMMIT) { |
||||
cgit_gopher_text_pad(info->subject, GOPHER_SUMMARY_DESC_LEN); |
||||
cgit_gopher_text_pad(info->author, GOPHER_SUMMARY_AUTH_LEN); |
||||
cgit_print_age(info->committer_date, info->committer_tz, -1); |
||||
} else { |
||||
html("</td><td></td><td>"); |
||||
cgit_object_link(ref->object); |
||||
} |
||||
cgit_gopher_text("\t");
|
||||
cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, |
||||
ctx.qry.showmsg, 0); |
||||
cgit_gopher_text("\t"); |
||||
cgit_gopher_end_selector(); |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
static int print_tag(struct refinfo *ref) |
||||
{ |
||||
struct tag *tag = NULL; |
||||
struct taginfo *info = NULL; |
||||
char *name = (char *)ref->refname; |
||||
struct object *obj = ref->object; |
||||
|
||||
if (obj->type == OBJ_TAG) { |
||||
tag = (struct tag *)obj; |
||||
obj = tag->tagged; |
||||
info = ref->tag; |
||||
if (!info) |
||||
return 1; |
||||
} |
||||
|
||||
cgit_gopher_start_selector(GOPHER_MENU); |
||||
cgit_gopher_text_pad(name, GOPHER_SUMMARY_NAME_LEN); |
||||
cgit_gopher_text_pad("@@@ commit id here @@@", GOPHER_SUMMARY_DESC_LEN); |
||||
if (info) { |
||||
if (info->tagger) { |
||||
cgit_gopher_text_pad(info->tagger, GOPHER_SUMMARY_AUTH_LEN); |
||||
} |
||||
} else if (ref->object->type == OBJ_COMMIT) { |
||||
cgit_gopher_text_pad(ref->commit->author, GOPHER_SUMMARY_AUTH_LEN); |
||||
} |
||||
if (info) { |
||||
if (info->tagger_date > 0) |
||||
cgit_print_age(info->tagger_date, info->tagger_tz, -1); |
||||
} else if (ref->object->type == OBJ_COMMIT) { |
||||
cgit_print_age(ref->commit->commit->date, 0, -1); |
||||
} |
||||
cgit_gopher_text("\t"); |
||||
cgit_tag_link(name, NULL, NULL, name); |
||||
cgit_gopher_end_selector(); |
||||
return 0; |
||||
} |
||||
|
||||
static void print_refs_link(char *path) |
||||
{ |
||||
html("<tr class='nohover'><td colspan='5'>"); |
||||
cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); |
||||
html("</td></tr>"); |
||||
} |
||||
|
||||
void print_branch_header(void){ |
||||
|
||||
cgit_gopher_start_selector(GOPHER_INFO); |
||||
cgit_gopher_text_pad("Branch", GOPHER_SUMMARY_NAME_LEN); |
||||
cgit_gopher_text_pad("Commit message", GOPHER_SUMMARY_DESC_LEN); |
||||
cgit_gopher_text_pad("Author", GOPHER_SUMMARY_AUTH_LEN); |
||||
cgit_gopher_text_pad("Age", GOPHER_SUMMARY_AGE_LEN); |
||||
cgit_gopher_text("\t"); |
||||
cgit_gopher_selector_link("Err"); |
||||
cgit_gopher_end_selector(); |
||||
|
||||
} |
||||
|
||||
|
||||
void cgit_print_branches(int maxcount) |
||||
{ |
||||
struct reflist list; |
||||
int i; |
||||
|
||||
print_branch_header(); |
||||
list.refs = NULL; |
||||
list.alloc = list.count = 0; |
||||
for_each_branch_ref(cgit_refs_cb, &list); |
||||
if (ctx.repo->enable_remote_branches) |
||||
for_each_remote_ref(cgit_refs_cb, &list); |
||||
|
||||
if (maxcount == 0 || maxcount > list.count) |
||||
maxcount = list.count; |
||||
|
||||
qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); |
||||
if (ctx.repo->branch_sort == 0) |
||||
qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); |
||||
|
||||
for (i = 0; i < maxcount; i++) |
||||
print_branch(list.refs[i]); |
||||
|
||||
if (maxcount < list.count) |
||||
print_refs_link("heads"); |
||||
|
||||
cgit_free_reflist_inner(&list); |
||||
} |
||||
|
||||
static void print_tag_header(void){ |
||||
|
||||
cgit_gopher_start_selector(GOPHER_INFO); |
||||
cgit_gopher_text_pad("Tag", GOPHER_SUMMARY_NAME_LEN); |
||||
cgit_gopher_text_pad("Commit", GOPHER_SUMMARY_DESC_LEN); |
||||
cgit_gopher_text_pad("Author", GOPHER_SUMMARY_AUTH_LEN); |
||||
cgit_gopher_text_pad("Age", GOPHER_SUMMARY_AGE_LEN); |
||||
cgit_gopher_text("\t"); |
||||
cgit_gopher_selector_link("Err"); |
||||
cgit_gopher_end_selector(); |
||||
|
||||
} |
||||
|
||||
|
||||
void cgit_print_tags(int maxcount) |
||||
{ |
||||
struct reflist list; |
||||
int i; |
||||
|
||||
list.refs = NULL; |
||||
list.alloc = list.count = 0; |
||||
for_each_tag_ref(cgit_refs_cb, &list); |
||||
if (list.count == 0) |
||||
return; |
||||
qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); |
||||
if (!maxcount) |
||||
maxcount = list.count; |
||||
else if (maxcount > list.count) |
||||
maxcount = list.count; |
||||
print_tag_header(); |
||||
for (i = 0; i < maxcount; i++) |
||||
print_tag(list.refs[i]); |
||||
|
||||
if (maxcount < list.count) |
||||
print_refs_link("tags"); |
||||
|
||||
cgit_free_reflist_inner(&list); |
||||
} |
||||
|
||||
void cgit_print_refs(void) |
||||
{ |
||||
cgit_print_layout_start(); |
||||
html("<table class='list nowrap'>"); |
||||
|
||||
if (ctx.qry.path && starts_with(ctx.qry.path, "heads")) |
||||
cgit_print_branches(0); |
||||
else if (ctx.qry.path && starts_with(ctx.qry.path, "tags")) |
||||
cgit_print_tags(0); |
||||
else { |
||||
cgit_print_branches(0); |
||||
html("<tr class='nohover'><td colspan='5'> </td></tr>"); |
||||
cgit_print_tags(0); |
||||
} |
||||
html("</table>"); |
||||
cgit_print_layout_end(); |
||||
} |
@ -0,0 +1,309 @@ |
||||
/* ui-repolist.c: functions for generating the repolist page
|
||||
* |
||||
* 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" |
||||
#include "ui-repolist.h" |
||||
#include "html.h" |
||||
#include "ui_70-shared.h" |
||||
|
||||
static time_t read_agefile(char *path) |
||||
{ |
||||
time_t result; |
||||
size_t size; |
||||
char *buf = NULL; |
||||
struct strbuf date_buf = STRBUF_INIT; |
||||
|
||||
if (readfile(path, &buf, &size)) { |
||||
free(buf); |
||||
return -1; |
||||
} |
||||
|
||||
if (parse_date(buf, &date_buf) == 0) |
||||
result = strtoul(date_buf.buf, NULL, 10); |
||||
else |
||||
result = 0; |
||||
free(buf); |
||||
strbuf_release(&date_buf); |
||||
return result; |
||||
} |
||||
|
||||
static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) |
||||
{ |
||||
struct strbuf path = STRBUF_INIT; |
||||
struct stat s; |
||||
struct cgit_repo *r = (struct cgit_repo *)repo; |
||||
|
||||
if (repo->mtime != -1) { |
||||
*mtime = repo->mtime; |
||||
return 1; |
||||
} |
||||
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; |
||||
goto end; |
||||
} |
||||
} |
||||
|
||||
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; |
||||
goto end; |
||||
} |
||||
|
||||
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; |
||||
goto end; |
||||
} |
||||
|
||||
*mtime = 0; |
||||
r->mtime = *mtime; |
||||
end: |
||||
strbuf_release(&path); |
||||
return (r->mtime != 0); |
||||
} |
||||
|
||||
static void print_modtime(struct cgit_repo *repo) |
||||
{ |
||||
time_t t; |
||||
if (get_repo_modtime(repo, &t)) |
||||
cgit_print_age(t, 0, -1); |
||||
else |
||||
cgit_gopher_text_pad("----", GOPHER_SUMMARY_DATE_LEN); |
||||
} |
||||
|
||||
static int is_match(struct cgit_repo *repo) |
||||
{ |
||||
if (!ctx.qry.search) |
||||
return 1; |
||||
if (repo->url && strcasestr(repo->url, ctx.qry.search)) |
||||
return 1; |
||||
if (repo->name && strcasestr(repo->name, ctx.qry.search)) |
||||
return 1; |
||||
if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) |
||||
return 1; |
||||
if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) |
||||
return 1; |
||||
return 0; |
||||
} |
||||
|
||||
static int is_in_url(struct cgit_repo *repo) |
||||
{ |
||||
if (!ctx.qry.url) |
||||
return 1; |
||||
if (repo->url && starts_with(repo->url, ctx.qry.url)) |
||||
return 1; |
||||
return 0; |
||||
} |
||||
|
||||
static int is_visible(struct cgit_repo *repo) |
||||
{ |
||||
if (repo->hide || repo->ignore) |
||||
return 0; |
||||
if (!(is_match(repo) && is_in_url(repo))) |
||||
return 0; |
||||
return 1; |
||||
} |
||||
|
||||
static int any_repos_visible(void) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; i < cgit_repolist.count; i++) { |
||||
if (is_visible(&cgit_repolist.repos[i])) |
||||
return 1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
static void print_header(void) |
||||
{ |
||||
cgit_gopher_info("Repositories"); |
||||
cgit_gopher_info("____________"); |
||||
cgit_gopher_start_selector(GOPHER_INFO); |
||||
cgit_gopher_text_pad("Name", GOPHER_SUMMARY_NAME_LEN); |
||||
cgit_gopher_text_pad("Decription", GOPHER_SUMMARY_DESC_LEN); |
||||
cgit_gopher_text_pad("Modified", GOPHER_SUMMARY_DATE_LEN); |
||||
cgit_gopher_text("\t"); |
||||
cgit_gopher_selector_link("Err"); |
||||
cgit_gopher_end_selector(); |
||||
} |
||||
|
||||
|
||||
|
||||
static int cmp(const char *s1, const char *s2) |
||||
{ |
||||
if (s1 && s2) { |
||||
if (ctx.cfg.case_sensitive_sort) |
||||
return strcmp(s1, s2); |
||||
else |
||||
return strcasecmp(s1, s2); |
||||
} |
||||
if (s1 && !s2) |
||||
return -1; |
||||
if (s2 && !s1) |
||||
return 1; |
||||
return 0; |
||||
} |
||||
|
||||
static int sort_name(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
|
||||
return cmp(r1->name, r2->name); |
||||
} |
||||
|
||||
static int sort_desc(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
|
||||
return cmp(r1->desc, r2->desc); |
||||
} |
||||
|
||||
static int sort_owner(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
|
||||
return cmp(r1->owner, r2->owner); |
||||
} |
||||
|
||||
static int sort_idle(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
time_t t1, t2; |
||||
|
||||
t1 = t2 = 0; |
||||
get_repo_modtime(r1, &t1); |
||||
get_repo_modtime(r2, &t2); |
||||
return t2 - t1; |
||||
} |
||||
|
||||
static int sort_section(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
int result; |
||||
|
||||
result = cmp(r1->section, r2->section); |
||||
if (!result) { |
||||
if (!strcmp(ctx.cfg.repository_sort, "age")) |
||||
result = sort_idle(r1, r2); |
||||
if (!result) |
||||
result = cmp(r1->name, r2->name); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
struct sortcolumn { |
||||
const char *name; |
||||
int (*fn)(const void *a, const void *b); |
||||
}; |
||||
|
||||
static const struct sortcolumn sortcolumn[] = { |
||||
{"section", sort_section}, |
||||
{"name", sort_name}, |
||||
{"desc", sort_desc}, |
||||
{"owner", sort_owner}, |
||||
{"idle", sort_idle}, |
||||
{NULL, NULL} |
||||
}; |
||||
|
||||
static int sort_repolist(char *field) |
||||
{ |
||||
const struct sortcolumn *column; |
||||
|
||||
for (column = &sortcolumn[0]; column->name; column++) { |
||||
if (strcmp(field, column->name)) |
||||
continue; |
||||
qsort(cgit_repolist.repos, cgit_repolist.count, |
||||
sizeof(struct cgit_repo), column->fn); |
||||
return 1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
void cgit_print_repolist(void) |
||||
{ |
||||
int i, columns = 3, hits = 0; |
||||
char *last_section = NULL; |
||||
char *section; |
||||
int sorted = 0; |
||||
|
||||
if (!any_repos_visible()) { |
||||
cgit_gopher_error("No repositories found"); |
||||
return; |
||||
} |
||||
|
||||
if (ctx.cfg.enable_index_links) |
||||
++columns; |
||||
if (ctx.cfg.enable_index_owner) |
||||
++columns; |
||||
|
||||
ctx.page.title = ctx.cfg.root_title; |
||||
|
||||
if (ctx.qry.sort) |
||||
sorted = sort_repolist(ctx.qry.sort); |
||||
else if (ctx.cfg.section_sort) |
||||
sort_repolist("section"); |
||||
print_header(); |
||||
|
||||
for (i = 0; i < cgit_repolist.count; i++) { |
||||
ctx.repo = &cgit_repolist.repos[i]; |
||||
if (!is_visible(ctx.repo)) |
||||
continue; |
||||
hits++; |
||||
if (hits <= ctx.qry.ofs) |
||||
continue; |
||||
if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) |
||||
continue; |
||||
section = ctx.repo->section; |
||||
if (section && !strcmp(section, "")) |
||||
section = NULL; |
||||
if (!sorted && |
||||
((last_section == NULL && section != NULL) || |
||||
(last_section != NULL && section == NULL) || |
||||
(last_section != NULL && section != NULL && |
||||
strcmp(section, last_section)))) { |
||||
cgit_gopher_info(section);
|
||||
last_section = section; |
||||
} |
||||
|
||||
cgit_gopher_start_selector(GOPHER_MENU); |
||||
cgit_gopher_text_pad(ctx.repo->name, GOPHER_SUMMARY_NAME_LEN); |
||||
cgit_gopher_text_pad(ctx.repo->desc, GOPHER_SUMMARY_DESC_LEN); |
||||
print_modtime(ctx.repo); |
||||
cgit_gopher_text("\t");
|
||||
cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); |
||||
cgit_gopher_end_selector(); |
||||
} |
||||
} |
||||
|
||||
void cgit_print_site_readme(void) |
||||
{ |
||||
cgit_print_layout_start(); |
||||
if (!ctx.cfg.root_readme) |
||||
goto done; |
||||
cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme); |
||||
html_include(ctx.cfg.root_readme); |
||||
cgit_close_filter(ctx.cfg.about_filter); |
||||
done: |
||||
cgit_print_layout_end(); |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,130 @@ |
||||
#ifndef UI_70_SHARED_H |
||||
#define UI_70_SHARED_H |
||||
|
||||
#define GOPHER_TXT '0' |
||||
#define GOPHER_MENU '1' |
||||
#define GOPHER_CCSO '2' |
||||
#define GOPHER_ERR '3' |
||||
#define GOPHER_BINHX '4' |
||||
#define GOPHER_DOS '5' |
||||
#define GOPHER_UUENC '6' |
||||
#define GOPHER_SRCH '7' |
||||
#define GOPHER_TELN '8' |
||||
#define GOPHER_BIN '9' |
||||
#define GOPHER_IMG 'I' |
||||
#define GOPHER_3270 'T' |
||||
#define GOPHER_GIF 'g' |
||||
#define GOPHER_HTML 'h' |
||||
#define GOPHER_INFO 'i' |
||||
#define GOPHER_SND 's' |
||||
#define GOPHER_MIRR '+' |
||||
|
||||
#define GOPHER_SUMMARY_NAME_LEN 22 |
||||
#define GOPHER_SUMMARY_DESC_LEN 45 |
||||
#define GOPHER_SUMMARY_DATE_LEN 20 |
||||
#define GOPHER_SUMMARY_AUTH_LEN 20 |
||||
#define GOPHER_SUMMARY_AGE_LEN 10 |
||||
#define GOPHER_PAD_CHAR ' ' |
||||
|
||||
|
||||
|
||||
void cgit_gopher_selector(char type, char *str, char *sel, const char *host, const char * port); |
||||
void cgit_gopher_info(char *msg); |
||||
void cgit_gopher_menu(char *descr, char *sel); |
||||
void cgit_gopher_textfile(char *descr, char *sel); |
||||
void cgit_gopher_error(char *descr); |
||||
|
||||
|
||||
void cgit_gopher_start_selector(char type); |
||||
void cgit_gopher_selector_descr(const char *descr); |
||||
void cgit_gopher_selector_link(const char *sel); |
||||
void cgit_gopher_text(const char *txt); |
||||
void cgit_gopher_text_pad(const char *txt, int len); |
||||
void cgit_gopher_end_selector(); |
||||
|
||||
|
||||
|
||||
extern const char *cgit_httpscheme(void); |
||||
extern char *cgit_hosturl(void); |
||||
extern const char *cgit_rooturl(void); |
||||
extern char *cgit_currenturl(void); |
||||
extern const char *cgit_loginurl(void); |
||||
extern char *cgit_repourl(const char *reponame); |
||||
extern char *cgit_fileurl(const char *reponame, const char *pagename, |
||||
const char *filename, const char *query); |
||||
extern char *cgit_pageurl(const char *reponame, const char *pagename, |
||||
const char *query); |
||||
|
||||
extern void cgit_add_clone_urls(void (*fn)(const char *)); |
||||
|
||||
extern void cgit_index_link(const char *name, const char *title, |
||||
const char *class, const char *pattern, const char *sort, int ofs, int always_root); |
||||
extern void cgit_summary_link(const char *name, const char *title, |
||||
const char *class, const char *head); |
||||
extern void cgit_tag_link(const char *name, const char *title, |
||||
const char *class, const char *tag); |
||||
extern void cgit_tree_link(const char *name, const char *title, |
||||
const char *class, const char *head, |
||||
const char *rev, const char *path); |
||||
extern void cgit_plain_link(const char *name, const char *title, |
||||
const char *class, const char *head, |
||||
const char *rev, const char *path); |
||||
extern void cgit_blame_link(const char *name, const char *title, |
||||
const char *class, const char *head, |
||||
const char *rev, const char *path); |
||||
extern void cgit_log_link(const char *name, const char *title, |
||||
const char *class, const char *head, const char *rev, |
||||
const char *path, int ofs, const char *grep, |
||||
const char *pattern, int showmsg, int follow); |
||||
extern void cgit_commit_link(const char *name, const char *title, |
||||
const char *class, const char *head, |
||||
const char *rev, const char *path); |
||||
extern void cgit_patch_link(const char *name, const char *title, |
||||
const char *class, const char *head, |
||||
const char *rev, const char *path); |
||||
extern void cgit_refs_link(const char *name, const char *title, |
||||
const char *class, const char *head, |
||||
const char *rev, const char *path); |
||||
extern void cgit_snapshot_link(const char *name, const char *title, |
||||
const char *class, const char *head, |
||||
const char *rev, const char *archivename); |
||||
extern void cgit_diff_link(const char *name, const char *title, |
||||
const char *class, const char *head, |
||||
const char *new_rev, const char *old_rev, |
||||
const char *path); |
||||
extern void cgit_stats_link(const char *name, const char *title, |
||||
const char *class, const char *head, |
||||
const char *path); |
||||
extern void cgit_object_link(struct object *obj); |
||||
|
||||
extern void cgit_submodule_link(const char *class, char *path, |
||||
const char *rev); |
||||
|
||||
extern void cgit_print_layout_start(void); |
||||
extern void cgit_print_layout_end(void); |
||||
|
||||
__attribute__((format (printf,1,2))) |
||||
extern void cgit_print_error(const char *fmt, ...); |
||||
__attribute__((format (printf,1,0))) |
||||
extern void cgit_vprint_error(const char *fmt, va_list ap); |
||||
extern const struct date_mode *cgit_date_mode(enum date_mode_type type); |
||||
extern void cgit_print_age(time_t t, int tz, time_t max_relative); |
||||
extern void cgit_print_http_headers(void); |
||||
extern void cgit_redirect(const char *url, bool permanent); |
||||
extern void cgit_print_docstart(void); |
||||
extern void cgit_print_docend(void); |
||||
__attribute__((format (printf,3,4))) |
||||
extern void cgit_print_error_page(int code, const char *msg, const char *fmt, ...); |
||||
extern void cgit_print_pageheader(void); |
||||
extern void cgit_print_filemode(unsigned short mode); |
||||
extern void cgit_compose_snapshot_prefix(struct strbuf *filename, |
||||
const char *base, const char *ref); |
||||
extern void cgit_print_snapshot_links(const struct cgit_repo *repo, |
||||
const char *ref, const char *separator); |
||||
extern const char *cgit_snapshot_prefix(const struct cgit_repo *repo); |
||||
extern void cgit_add_hidden_formfields(int incl_head, int incl_search, |
||||
const char *page); |
||||
|
||||
extern void cgit_set_title_from_path(const char *path); |
||||
#endif /* UI_70_SHARED_H */ |
||||
|
@ -0,0 +1,146 @@ |
||||
/* ui-summary.c: functions for generating repo summary page
|
||||
* |
||||
* 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" |
||||
#include "ui-summary.h" |
||||
#include "html.h" |
||||
#include "ui-blob.h" |
||||
#include "ui-log.h" |
||||
#include "ui-plain.h" |
||||
#include "ui-refs.h" |
||||
#include "ui-shared.h" |
||||
|
||||
static int urls; |
||||
|
||||
static void print_url(const char *url) |
||||
{ |
||||
int columns = 3; |
||||
|
||||
if (ctx.repo->enable_log_filecount) |
||||
columns++; |
||||
if (ctx.repo->enable_log_linecount) |
||||
columns++; |
||||
|
||||
if (urls++ == 0) { |
||||
htmlf("<tr class='nohover'><td colspan='%d'> </td></tr>", columns); |
||||
htmlf("<tr class='nohover'><th class='left' colspan='%d'>Clone</th></tr>\n", columns); |
||||
} |
||||
|
||||
htmlf("<tr><td colspan='%d'><a rel='vcs-git' href='", columns); |
||||
html_url_path(url); |
||||
html("' title='"); |
||||
html_attr(ctx.repo->name); |
||||
html(" Git repository'>"); |
||||
html_txt(url); |
||||
html("</a></td></tr>\n"); |
||||
} |
||||
|
||||
void cgit_print_summary(void) |
||||
{ |
||||
int columns = 3; |
||||
|
||||
if (ctx.repo->enable_log_filecount) |
||||
columns++; |
||||
if (ctx.repo->enable_log_linecount) |
||||
columns++; |
||||
|
||||
cgit_print_branches(ctx.cfg.summary_branches); |
||||
|
||||
cgit_print_tags(ctx.cfg.summary_tags); |
||||
if (ctx.cfg.summary_log > 0) { |
||||
htmlf("<tr class='nohover'><td colspan='%d'> </td></tr>", columns); |
||||
cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, |
||||
NULL, NULL, 0, 0, 0); |
||||
} |
||||
urls = 0; |
||||
cgit_add_clone_urls(print_url); |
||||
html("</table>"); |
||||
cgit_print_layout_end(); |
||||
} |
||||
|
||||
/* The caller must free the return value. */ |
||||
static char* append_readme_path(const char *filename, const char *ref, const char *path) |
||||
{ |
||||
char *file, *base_dir, *full_path, *resolved_base = NULL, *resolved_full = NULL; |
||||
/* If a subpath is specified for the about page, make it relative
|
||||
* to the directory containing the configured readme. */ |
||||
|
||||
file = xstrdup(filename); |
||||
base_dir = dirname(file); |
||||
if (!strcmp(base_dir, ".") || !strcmp(base_dir, "..")) { |
||||
if (!ref) { |
||||
free(file); |
||||
return NULL; |
||||
} |
||||
full_path = xstrdup(path); |
||||
} else |
||||
full_path = fmtalloc("%s/%s", base_dir, path); |
||||
|
||||
if (!ref) { |
||||
resolved_base = realpath(base_dir, NULL); |
||||
resolved_full = realpath(full_path, NULL); |
||||
if (!resolved_base || !resolved_full || !starts_with(resolved_full, resolved_base)) { |
||||
free(full_path); |
||||
full_path = NULL; |
||||
} |
||||
} |
||||
|
||||
free(file); |
||||
free(resolved_base); |
||||
free(resolved_full); |
||||
|
||||
return full_path; |
||||
} |
||||
|
||||
void cgit_print_repo_readme(char *path) |
||||
{ |
||||
char *filename, *ref, *mimetype; |
||||
int free_filename = 0; |
||||
|
||||
mimetype = get_mimetype_for_filename(path); |
||||
if (mimetype && (!strncmp(mimetype, "image/", 6) || !strncmp(mimetype, "video/", 6))) { |
||||
ctx.page.mimetype = mimetype; |
||||
ctx.page.charset = NULL; |
||||
cgit_print_plain(); |
||||
free(mimetype); |
||||
return; |
||||
} |
||||
free(mimetype); |
||||
|
||||
cgit_print_layout_start(); |
||||
if (ctx.repo->readme.nr == 0) |
||||
goto done; |
||||
|
||||
filename = ctx.repo->readme.items[0].string; |
||||
ref = ctx.repo->readme.items[0].util; |
||||
|
||||
if (path) { |
||||
free_filename = 1; |
||||
filename = append_readme_path(filename, ref, path); |
||||
if (!filename) |
||||
goto done; |
||||
} |
||||
|
||||
/* Print the calculated readme, either from the git repo or from the
|
||||
* filesystem, while applying the about-filter. |
||||
*/ |
||||
html("<div id='summary'>"); |
||||
cgit_open_filter(ctx.repo->about_filter, filename); |
||||
if (ref) |
||||
cgit_print_file(filename, ref, 1); |
||||
else |
||||
html_include(filename); |
||||
cgit_close_filter(ctx.repo->about_filter); |
||||
|
||||
html("</div>"); |
||||
if (free_filename) |
||||
free(filename); |
||||
|
||||
done: |
||||
cgit_print_layout_end(); |
||||
} |
@ -0,0 +1,379 @@ |
||||
/* ui-repolist.c: functions for generating the repolist page
|
||||
* |
||||
* 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" |
||||
#include "ui-repolist.h" |
||||
#include "html.h" |
||||
#include "ui-shared.h" |
||||
|
||||
static time_t read_agefile(char *path) |
||||
{ |
||||
time_t result; |
||||
size_t size; |
||||
char *buf = NULL; |
||||
struct strbuf date_buf = STRBUF_INIT; |
||||
|
||||
if (readfile(path, &buf, &size)) { |
||||
free(buf); |
||||
return -1; |
||||
} |
||||
|
||||
if (parse_date(buf, &date_buf) == 0) |
||||
result = strtoul(date_buf.buf, NULL, 10); |
||||
else |
||||
result = 0; |
||||
free(buf); |
||||
strbuf_release(&date_buf); |
||||
return result; |
||||
} |
||||
|
||||
static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) |
||||
{ |
||||
struct strbuf path = STRBUF_INIT; |
||||
struct stat s; |
||||
struct cgit_repo *r = (struct cgit_repo *)repo; |
||||
|
||||
if (repo->mtime != -1) { |
||||
*mtime = repo->mtime; |
||||
return 1; |
||||
} |
||||
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; |
||||
goto end; |
||||
} |
||||
} |
||||
|
||||
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; |
||||
goto end; |
||||
} |
||||
|
||||
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; |
||||
goto end; |
||||
} |
||||
|
||||
*mtime = 0; |
||||
r->mtime = *mtime; |
||||
end: |
||||
strbuf_release(&path); |
||||
return (r->mtime != 0); |
||||
} |
||||
|
||||
static void print_modtime(struct cgit_repo *repo) |
||||
{ |
||||
time_t t; |
||||
if (get_repo_modtime(repo, &t)) |
||||
cgit_print_age(t, 0, -1); |
||||
} |
||||
|
||||
static int is_match(struct cgit_repo *repo) |
||||
{ |
||||
if (!ctx.qry.search) |
||||
return 1; |
||||
if (repo->url && strcasestr(repo->url, ctx.qry.search)) |
||||
return 1; |
||||
if (repo->name && strcasestr(repo->name, ctx.qry.search)) |
||||
return 1; |
||||
if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) |
||||
return 1; |
||||
if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) |
||||
return 1; |
||||
return 0; |
||||
} |
||||
|
||||
static int is_in_url(struct cgit_repo *repo) |
||||
{ |
||||
if (!ctx.qry.url) |
||||
return 1; |
||||
if (repo->url && starts_with(repo->url, ctx.qry.url)) |
||||
return 1; |
||||
return 0; |
||||
} |
||||
|
||||
static int is_visible(struct cgit_repo *repo) |
||||
{ |
||||
if (repo->hide || repo->ignore) |
||||
return 0; |
||||
if (!(is_match(repo) && is_in_url(repo))) |
||||
return 0; |
||||
return 1; |
||||
} |
||||
|
||||
static int any_repos_visible(void) |
||||
{ |
||||
int i; |
||||
|
||||
for (i = 0; i < cgit_repolist.count; i++) { |
||||
if (is_visible(&cgit_repolist.repos[i])) |
||||
return 1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
static void print_sort_header(const char *title, const char *sort) |
||||
{ |
||||
char *currenturl = cgit_currenturl(); |
||||
html("<th class='left'><a href='"); |
||||
html_attr(currenturl); |
||||
htmlf("?s=%s", sort); |
||||
if (ctx.qry.search) { |
||||
html("&q="); |
||||
html_url_arg(ctx.qry.search); |
||||
} |
||||
htmlf("'>%s</a></th>", title); |
||||
free(currenturl); |
||||
} |
||||
|
||||
static void print_header(void) |
||||
{ |
||||
html("<tr class='nohover'>"); |
||||
print_sort_header("Name", "name"); |
||||
print_sort_header("Description", "desc"); |
||||
if (ctx.cfg.enable_index_owner) |
||||
print_sort_header("Owner", "owner"); |
||||
print_sort_header("Idle", "idle"); |
||||
if (ctx.cfg.enable_index_links) |
||||
html("<th class='left'>Links</th>"); |
||||
html("</tr>\n"); |
||||
} |
||||
|
||||
|
||||
static void print_pager(int items, int pagelen, char *search, char *sort) |
||||
{ |
||||
int i, ofs; |
||||
char *class = NULL; |
||||
html("<ul class='pager'>"); |
||||
for (i = 0, ofs = 0; ofs < items; i++, ofs = i * pagelen) { |
||||
class = (ctx.qry.ofs == ofs) ? "current" : NULL; |
||||
html("<li>"); |
||||
cgit_index_link(fmt("[%d]", i + 1), fmt("Page %d", i + 1), |
||||
class, search, sort, ofs, 0); |
||||
html("</li>"); |
||||
} |
||||
html("</ul>"); |
||||
} |
||||
|
||||
static int cmp(const char *s1, const char *s2) |
||||
{ |
||||
if (s1 && s2) { |
||||
if (ctx.cfg.case_sensitive_sort) |
||||
return strcmp(s1, s2); |
||||
else |
||||
return strcasecmp(s1, s2); |
||||
} |
||||
if (s1 && !s2) |
||||
return -1; |
||||
if (s2 && !s1) |
||||
return 1; |
||||
return 0; |
||||
} |
||||
|
||||
static int sort_name(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
|
||||
return cmp(r1->name, r2->name); |
||||
} |
||||
|
||||
static int sort_desc(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
|
||||
return cmp(r1->desc, r2->desc); |
||||
} |
||||
|
||||
static int sort_owner(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
|
||||
return cmp(r1->owner, r2->owner); |
||||
} |
||||
|
||||
static int sort_idle(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
time_t t1, t2; |
||||
|
||||
t1 = t2 = 0; |
||||
get_repo_modtime(r1, &t1); |
||||
get_repo_modtime(r2, &t2); |
||||
return t2 - t1; |
||||
} |
||||
|
||||
static int sort_section(const void *a, const void *b) |
||||
{ |
||||
const struct cgit_repo *r1 = a; |
||||
const struct cgit_repo *r2 = b; |
||||
int result; |
||||
|
||||
result = cmp(r1->section, r2->section); |
||||
if (!result) { |
||||
if (!strcmp(ctx.cfg.repository_sort, "age")) |
||||
result = sort_idle(r1, r2); |
||||
if (!result) |
||||
result = cmp(r1->name, r2->name); |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
struct sortcolumn { |
||||
const char *name; |
||||
int (*fn)(const void *a, const void *b); |
||||
}; |
||||
|
||||
static const struct sortcolumn sortcolumn[] = { |
||||
{"section", sort_section}, |
||||
{"name", sort_name}, |
||||
{"desc", sort_desc}, |
||||
{"owner", sort_owner}, |
||||
{"idle", sort_idle}, |
||||
{NULL, NULL} |
||||
}; |
||||
|
||||
static int sort_repolist(char *field) |
||||
{ |
||||
const struct sortcolumn *column; |
||||
|
||||
for (column = &sortcolumn[0]; column->name; column++) { |
||||
if (strcmp(field, column->name)) |
||||
continue; |
||||
qsort(cgit_repolist.repos, cgit_repolist.count, |
||||
sizeof(struct cgit_repo), column->fn); |
||||
return 1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
|
||||
void cgit_print_repolist(void) |
||||
{ |
||||
int i, columns = 3, hits = 0, header = 0; |
||||
char *last_section = NULL; |
||||
char *section; |
||||
char *repourl; |
||||
int sorted = 0; |
||||
|
||||
if (!any_repos_visible()) { |
||||
cgit_print_error_page(404, "Not found", "No repositories found"); |
||||
return; |
||||
} |
||||
|
||||
if (ctx.cfg.enable_index_links) |
||||
++columns; |
||||
if (ctx.cfg.enable_index_owner) |
||||
++columns; |
||||
|
||||
ctx.page.title = ctx.cfg.root_title; |
||||
cgit_print_http_headers(); |
||||
cgit_print_docstart(); |
||||
cgit_print_pageheader(); |
||||
|
||||
if (ctx.qry.sort) |
||||
sorted = sort_repolist(ctx.qry.sort); |
||||
else if (ctx.cfg.section_sort) |
||||
sort_repolist("section"); |
||||
|
||||
html("<table summary='repository list' class='list nowrap'>"); |
||||
for (i = 0; i < cgit_repolist.count; i++) { |
||||
ctx.repo = &cgit_repolist.repos[i]; |
||||
if (!is_visible(ctx.repo)) |
||||
continue; |
||||
hits++; |
||||
if (hits <= ctx.qry.ofs) |
||||
continue; |
||||
if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) |
||||
continue; |
||||
if (!header++) |
||||
print_header(); |
||||
section = ctx.repo->section; |
||||
if (section && !strcmp(section, "")) |
||||
section = NULL; |
||||
if (!sorted && |
||||
((last_section == NULL && section != NULL) || |
||||
(last_section != NULL && section == NULL) || |
||||
(last_section != NULL && section != NULL && |
||||
strcmp(section, last_section)))) { |
||||
htmlf("<tr class='nohover-highlight'><td colspan='%d' class='reposection'>", |
||||
columns); |
||||
html_txt(section); |
||||
html("</td></tr>"); |
||||
last_section = section; |
||||
} |
||||
htmlf("<tr><td class='%s'>", |
||||
!sorted && section ? "sublevel-repo" : "toplevel-repo"); |
||||
cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); |
||||
html("</td><td>"); |
||||
repourl = cgit_repourl(ctx.repo->url); |
||||
html_link_open(repourl, NULL, NULL); |
||||
free(repourl); |
||||
if (html_ntxt(ctx.repo->desc, ctx.cfg.max_repodesc_len) < 0) |
||||
html("..."); |
||||
html_link_close(); |
||||
html("</td><td>"); |
||||
if (ctx.cfg.enable_index_owner) { |
||||
if (ctx.repo->owner_filter) { |
||||
cgit_open_filter(ctx.repo->owner_filter); |
||||
html_txt(ctx.repo->owner); |
||||
cgit_close_filter(ctx.repo->owner_filter); |
||||
} else { |
||||
char *currenturl = cgit_currenturl(); |
||||
html("<a href='"); |
||||
html_attr(currenturl); |
||||
html("?q="); |
||||
html_url_arg(ctx.repo->owner); |
||||
html("'>"); |
||||
html_txt(ctx.repo->owner); |
||||
html("</a>"); |
||||
free(currenturl); |
||||
} |
||||
html("</td><td>"); |
||||
} |
||||
print_modtime(ctx.repo); |
||||
html("</td>"); |
||||
if (ctx.cfg.enable_index_links) { |
||||
html("<td>"); |
||||
cgit_summary_link("summary", NULL, "button", NULL); |
||||
cgit_log_link("log", NULL, "button", NULL, NULL, NULL, |
||||
0, NULL, NULL, ctx.qry.showmsg, 0); |
||||
cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); |
||||
html("</td>"); |
||||
} |
||||
html("</tr>\n"); |
||||
} |
||||
html("</table>"); |
||||
if (hits > ctx.cfg.max_repo_count) |
||||
print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search, ctx.qry.sort); |
||||
cgit_print_docend(); |
||||
} |
||||
|
||||
void cgit_print_site_readme(void) |
||||
{ |
||||
cgit_print_layout_start(); |
||||
if (!ctx.cfg.root_readme) |
||||
goto done; |
||||
cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme); |
||||
html_include(ctx.cfg.root_readme); |
||||
cgit_close_filter(ctx.cfg.about_filter); |
||||
done: |
||||
cgit_print_layout_end(); |
||||
} |
Loading…
Reference in new issue