summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--Makefile4
-rw-r--r--cgit.c2
m---------git0
-rw-r--r--ui-plain.c2
-rw-r--r--ui-stats.c8
5 files changed, 8 insertions, 8 deletions
diff --git a/Makefile b/Makefile
index 5162020..0349639 100644
--- a/Makefile
+++ b/Makefile
@@ -1,167 +1,167 @@
CGIT_VERSION = v0.8.3.3
CGIT_SCRIPT_NAME = cgit.cgi
CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
CGIT_CONFIG = /etc/cgitrc
CACHE_ROOT = /var/cache/cgit
SHA1_HEADER = <openssl/sha.h>
-GIT_VER = 1.6.4.3
+GIT_VER = 1.7.3
GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
INSTALL = install
# Define NO_STRCASESTR if you don't have strcasestr.
#
# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
#
#-include config.mak
#
# Platform specific tweaks
#
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
ifeq ($(uname_O),Cygwin)
NO_STRCASESTR = YesPlease
NEEDS_LIBICONV = YesPlease
endif
#
# Let the user override the above settings.
#
-include cgit.conf
#
# Define a way to invoke make in subdirs quietly, shamelessly ripped
# from git.git
#
QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
QUIET_SUBDIR1 =
ifneq ($(findstring $(MAKEFLAGS),w),w)
PRINT_DIR = --no-print-directory
else # "make -w"
NO_SUBDIR = :
endif
ifndef V
QUIET_CC = @echo ' ' CC $@;
QUIET_MM = @echo ' ' MM $@;
QUIET_SUBDIR0 = +@subdir=
QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
$(MAKE) $(PRINT_DIR) -C $$subdir
endif
#
# Define a pattern rule for automatic dependency building
#
%.d: %.c
$(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@
#
# Define a pattern rule for silent object building
#
%.o: %.c
$(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
-EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto
+EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto -lpthread
OBJECTS =
OBJECTS += cache.o
OBJECTS += cgit.o
OBJECTS += cmd.o
OBJECTS += configfile.o
OBJECTS += html.o
OBJECTS += parsing.o
OBJECTS += scan-tree.o
OBJECTS += shared.o
OBJECTS += ui-atom.o
OBJECTS += ui-blob.o
OBJECTS += ui-clone.o
OBJECTS += ui-commit.o
OBJECTS += ui-diff.o
OBJECTS += ui-log.o
OBJECTS += ui-patch.o
OBJECTS += ui-plain.o
OBJECTS += ui-refs.o
OBJECTS += ui-repolist.o
OBJECTS += ui-shared.o
OBJECTS += ui-snapshot.o
OBJECTS += ui-stats.o
OBJECTS += ui-summary.o
OBJECTS += ui-tag.o
OBJECTS += ui-tree.o
ifdef NEEDS_LIBICONV
EXTLIBS += -liconv
endif
.PHONY: all libgit test install uninstall clean force-version get-git \
doc man-doc html-doc clean-doc
all: cgit
VERSION: force-version
@./gen-version.sh "$(CGIT_VERSION)"
-include VERSION
CFLAGS += -g -Wall -Igit
CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
ifdef NO_ICONV
CFLAGS += -DNO_ICONV
endif
ifdef NO_STRCASESTR
CFLAGS += -DNO_STRCASESTR
endif
cgit: $(OBJECTS) libgit
$(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
cgit.o: VERSION
-include $(OBJECTS:.o=.d)
libgit:
$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a
$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a
test: all
$(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
install: all
$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
$(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
$(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
$(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
uninstall:
rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
rm -f $(CGIT_DATA_PATH)/cgit.css
rm -f $(CGIT_DATA_PATH)/cgit.png
doc: man-doc html-doc pdf-doc
man-doc: cgitrc.5.txt
a2x -f manpage cgitrc.5.txt
html-doc: cgitrc.5.txt
a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt
pdf-doc: cgitrc.5.txt
a2x -f pdf cgitrc.5.txt
clean: clean-doc
rm -f cgit VERSION *.o *.d
clean-doc:
diff --git a/cgit.c b/cgit.c
index 6c7e811..ad62d10 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,120 +1,120 @@
/* cgit.c: cgi for the git scm
*
* Copyright (C) 2006 Lars Hjemli
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
#include "cgit.h"
#include "cache.h"
#include "cmd.h"
#include "configfile.h"
#include "html.h"
#include "ui-shared.h"
#include "ui-stats.h"
#include "scan-tree.h"
const char *cgit_version = CGIT_VERSION;
void add_mimetype(const char *name, const char *value)
{
struct string_list_item *item;
- item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
+ item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name));
item->util = xstrdup(value);
}
struct cgit_filter *new_filter(const char *cmd, int extra_args)
{
struct cgit_filter *f;
if (!cmd || !cmd[0])
return NULL;
f = xmalloc(sizeof(struct cgit_filter));
f->cmd = xstrdup(cmd);
f->argv = xmalloc((2 + extra_args) * sizeof(char *));
f->argv[0] = f->cmd;
f->argv[1] = NULL;
return f;
}
static void process_cached_repolist(const char *path);
void repo_config(struct cgit_repo *repo, const char *name, const char *value)
{
if (!strcmp(name, "name"))
repo->name = xstrdup(value);
else if (!strcmp(name, "clone-url"))
repo->clone_url = xstrdup(value);
else if (!strcmp(name, "desc"))
repo->desc = xstrdup(value);
else if (!strcmp(name, "owner"))
repo->owner = xstrdup(value);
else if (!strcmp(name, "defbranch"))
repo->defbranch = xstrdup(value);
else if (!strcmp(name, "snapshots"))
repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
else if (!strcmp(name, "enable-log-filecount"))
repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
else if (!strcmp(name, "enable-log-linecount"))
repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
else if (!strcmp(name, "max-stats"))
repo->max_stats = cgit_find_stats_period(value, NULL);
else if (!strcmp(name, "module-link"))
repo->module_link= xstrdup(value);
else if (!strcmp(name, "section"))
repo->section = xstrdup(value);
else if (!strcmp(name, "readme") && value != NULL) {
if (*value == '/')
repo->readme = xstrdup(value);
else
repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
} else if (ctx.cfg.enable_filter_overrides) {
if (!strcmp(name, "about-filter"))
repo->about_filter = new_filter(value, 0);
else if (!strcmp(name, "commit-filter"))
repo->commit_filter = new_filter(value, 0);
else if (!strcmp(name, "source-filter"))
repo->source_filter = new_filter(value, 1);
}
}
void config_cb(const char *name, const char *value)
{
if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
ctx.cfg.section = xstrdup(value);
else if (!strcmp(name, "repo.url"))
ctx.repo = cgit_add_repo(value);
else if (ctx.repo && !strcmp(name, "repo.path"))
ctx.repo->path = trim_end(value, '/');
else if (ctx.repo && !prefixcmp(name, "repo."))
repo_config(ctx.repo, name + 5, value);
else if (!strcmp(name, "root-title"))
ctx.cfg.root_title = xstrdup(value);
else if (!strcmp(name, "root-desc"))
ctx.cfg.root_desc = xstrdup(value);
else if (!strcmp(name, "root-readme"))
ctx.cfg.root_readme = xstrdup(value);
else if (!strcmp(name, "css"))
ctx.cfg.css = xstrdup(value);
else if (!strcmp(name, "favicon"))
ctx.cfg.favicon = xstrdup(value);
else if (!strcmp(name, "footer"))
ctx.cfg.footer = xstrdup(value);
else if (!strcmp(name, "head-include"))
ctx.cfg.head_include = xstrdup(value);
else if (!strcmp(name, "header"))
ctx.cfg.header = xstrdup(value);
else if (!strcmp(name, "logo"))
ctx.cfg.logo = xstrdup(value);
else if (!strcmp(name, "index-header"))
ctx.cfg.index_header = xstrdup(value);
else if (!strcmp(name, "index-info"))
ctx.cfg.index_info = xstrdup(value);
else if (!strcmp(name, "logo-link"))
ctx.cfg.logo_link = xstrdup(value);
else if (!strcmp(name, "module-link"))
ctx.cfg.module_link = xstrdup(value);
else if (!strcmp(name, "virtual-root")) {
diff --git a/git b/git
-Subproject 7fb6bcff2dece2ff9fbc5ebfe526d9b2a7e764c
+Subproject 87b50542a08ac6caa083ddc376e674424e37940
diff --git a/ui-plain.c b/ui-plain.c
index 66cb19c..5569a7c 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -1,94 +1,94 @@
/* ui-plain.c: functions for output of plain blobs by path
*
* Copyright (C) 2008 Lars Hjemli
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
#include "cgit.h"
#include "html.h"
#include "ui-shared.h"
char *curr_rev;
char *match_path;
int match;
static void print_object(const unsigned char *sha1, const char *path)
{
enum object_type type;
char *buf, *ext;
unsigned long size;
struct string_list_item *mime;
type = sha1_object_info(sha1, &size);
if (type == OBJ_BAD) {
html_status(404, "Not found", 0);
return;
}
buf = read_sha1_file(sha1, &type, &size);
if (!buf) {
html_status(404, "Not found", 0);
return;
}
ctx.page.mimetype = NULL;
ext = strrchr(path, '.');
if (ext && *(++ext)) {
- mime = string_list_lookup(ext, &ctx.cfg.mimetypes);
+ mime = string_list_lookup(&ctx.cfg.mimetypes, ext);
if (mime)
ctx.page.mimetype = (char *)mime->util;
}
if (!ctx.page.mimetype) {
if (buffer_is_binary(buf, size))
ctx.page.mimetype = "application/octet-stream";
else
ctx.page.mimetype = "text/plain";
}
ctx.page.filename = fmt("%s", path);
ctx.page.size = size;
ctx.page.etag = sha1_to_hex(sha1);
cgit_print_http_headers(&ctx);
html_raw(buf, size);
match = 1;
}
static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
const char *pathname, unsigned mode, int stage,
void *cbdata)
{
if (S_ISDIR(mode))
return READ_TREE_RECURSIVE;
if (S_ISREG(mode) && !strncmp(base, match_path, baselen) &&
!strcmp(pathname, match_path + baselen))
print_object(sha1, pathname);
return 0;
}
void cgit_print_plain(struct cgit_context *ctx)
{
const char *rev = ctx->qry.sha1;
unsigned char sha1[20];
struct commit *commit;
const char *paths[] = {ctx->qry.path, NULL};
if (!rev)
rev = ctx->qry.head;
curr_rev = xstrdup(rev);
if (get_sha1(rev, sha1)) {
html_status(404, "Not found", 0);
return;
}
commit = lookup_commit_reference(sha1);
if (!commit || parse_commit(commit)) {
html_status(404, "Not found", 0);
return;
}
match_path = ctx->qry.path;
read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
if (!match)
html_status(404, "Not found", 0);
}
diff --git a/ui-stats.c b/ui-stats.c
index bdaf9cc..50c2540 100644
--- a/ui-stats.c
+++ b/ui-stats.c
@@ -82,337 +82,337 @@ static void trunc_quarter(struct tm *tm)
while(tm->tm_mon % 3 != 0)
dec_month(tm);
}
static void dec_quarter(struct tm *tm)
{
dec_month(tm);
dec_month(tm);
dec_month(tm);
}
static void inc_quarter(struct tm *tm)
{
inc_month(tm);
inc_month(tm);
inc_month(tm);
}
static char *pretty_quarter(struct tm *tm)
{
return fmt("Q%d %d", tm->tm_mon / 3 + 1, tm->tm_year + 1900);
}
static void trunc_year(struct tm *tm)
{
trunc_month(tm);
tm->tm_mon = 0;
}
static void dec_year(struct tm *tm)
{
tm->tm_year--;
}
static void inc_year(struct tm *tm)
{
tm->tm_year++;
}
static char *pretty_year(struct tm *tm)
{
return fmt("%d", tm->tm_year + 1900);
}
struct cgit_period periods[] = {
{'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week},
{'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month},
{'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter},
{'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year},
};
/* Given a period code or name, return a period index (1, 2, 3 or 4)
* and update the period pointer to the correcsponding struct.
* If no matching code is found, return 0.
*/
int cgit_find_stats_period(const char *expr, struct cgit_period **period)
{
int i;
char code = '\0';
if (!expr)
return 0;
if (strlen(expr) == 1)
code = expr[0];
for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++)
if (periods[i].code == code || !strcmp(periods[i].name, expr)) {
if (period)
*period = &periods[i];
return i+1;
}
return 0;
}
const char *cgit_find_stats_periodname(int idx)
{
if (idx > 0 && idx < 4)
return periods[idx - 1].name;
else
return "";
}
static void add_commit(struct string_list *authors, struct commit *commit,
struct cgit_period *period)
{
struct commitinfo *info;
struct string_list_item *author, *item;
struct authorstat *authorstat;
struct string_list *items;
char *tmp;
struct tm *date;
time_t t;
info = cgit_parse_commit(commit);
tmp = xstrdup(info->author);
- author = string_list_insert(tmp, authors);
+ author = string_list_insert(authors, tmp);
if (!author->util)
author->util = xcalloc(1, sizeof(struct authorstat));
else
free(tmp);
authorstat = author->util;
items = &authorstat->list;
t = info->committer_date;
date = gmtime(&t);
period->trunc(date);
tmp = xstrdup(period->pretty(date));
- item = string_list_insert(tmp, items);
+ item = string_list_insert(items, tmp);
if (item->util)
free(tmp);
item->util++;
authorstat->total++;
cgit_free_commitinfo(info);
}
static int cmp_total_commits(const void *a1, const void *a2)
{
const struct string_list_item *i1 = a1;
const struct string_list_item *i2 = a2;
const struct authorstat *auth1 = i1->util;
const struct authorstat *auth2 = i2->util;
return auth2->total - auth1->total;
}
/* Walk the commit DAG and collect number of commits per author per
* timeperiod into a nested string_list collection.
*/
struct string_list collect_stats(struct cgit_context *ctx,
struct cgit_period *period)
{
struct string_list authors;
struct rev_info rev;
struct commit *commit;
const char *argv[] = {NULL, ctx->qry.head, NULL, NULL, NULL, NULL};
int argc = 3;
time_t now;
long i;
struct tm *tm;
char tmp[11];
time(&now);
tm = gmtime(&now);
period->trunc(tm);
for (i = 1; i < period->count; i++)
period->dec(tm);
strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm);
argv[2] = xstrdup(fmt("--since=%s", tmp));
if (ctx->qry.path) {
argv[3] = "--";
argv[4] = ctx->qry.path;
argc += 2;
}
init_revisions(&rev, NULL);
rev.abbrev = DEFAULT_ABBREV;
rev.commit_format = CMIT_FMT_DEFAULT;
rev.no_merges = 1;
rev.verbose_header = 1;
rev.show_root_diff = 0;
setup_revisions(argc, argv, &rev, NULL);
prepare_revision_walk(&rev);
memset(&authors, 0, sizeof(authors));
while ((commit = get_revision(&rev)) != NULL) {
add_commit(&authors, commit, period);
free(commit->buffer);
free_commit_list(commit->parents);
}
return authors;
}
void print_combined_authorrow(struct string_list *authors, int from, int to,
const char *name, const char *leftclass, const char *centerclass,
const char *rightclass, struct cgit_period *period)
{
struct string_list_item *author;
struct authorstat *authorstat;
struct string_list *items;
struct string_list_item *date;
time_t now;
long i, j, total, subtotal;
struct tm *tm;
char *tmp;
time(&now);
tm = gmtime(&now);
period->trunc(tm);
for (i = 1; i < period->count; i++)
period->dec(tm);
total = 0;
htmlf("<tr><td class='%s'>%s</td>", leftclass,
fmt(name, to - from + 1));
for (j = 0; j < period->count; j++) {
tmp = period->pretty(tm);
period->inc(tm);
subtotal = 0;
for (i = from; i <= to; i++) {
author = &authors->items[i];
authorstat = author->util;
items = &authorstat->list;
- date = string_list_lookup(tmp, items);
+ date = string_list_lookup(items, tmp);
if (date)
subtotal += (size_t)date->util;
}
htmlf("<td class='%s'>%d</td>", centerclass, subtotal);
total += subtotal;
}
htmlf("<td class='%s'>%d</td></tr>", rightclass, total);
}
void print_authors(struct string_list *authors, int top,
struct cgit_period *period)
{
struct string_list_item *author;
struct authorstat *authorstat;
struct string_list *items;
struct string_list_item *date;
time_t now;
long i, j, total;
struct tm *tm;
char *tmp;
time(&now);
tm = gmtime(&now);
period->trunc(tm);
for (i = 1; i < period->count; i++)
period->dec(tm);
html("<table class='stats'><tr><th>Author</th>");
for (j = 0; j < period->count; j++) {
tmp = period->pretty(tm);
htmlf("<th>%s</th>", tmp);
period->inc(tm);
}
html("<th>Total</th></tr>\n");
if (top <= 0 || top > authors->nr)
top = authors->nr;
for (i = 0; i < top; i++) {
author = &authors->items[i];
html("<tr><td class='left'>");
html_txt(author->string);
html("</td>");
authorstat = author->util;
items = &authorstat->list;
total = 0;
for (j = 0; j < period->count; j++)
period->dec(tm);
for (j = 0; j < period->count; j++) {
tmp = period->pretty(tm);
period->inc(tm);
- date = string_list_lookup(tmp, items);
+ date = string_list_lookup(items, tmp);
if (!date)
html("<td>0</td>");
else {
htmlf("<td>%d</td>", date->util);
total += (size_t)date->util;
}
}
htmlf("<td class='sum'>%d</td></tr>", total);
}
if (top < authors->nr)
print_combined_authorrow(authors, top, authors->nr - 1,
"Others (%d)", "left", "", "sum", period);
print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
"total", "sum", "sum", period);
html("</table>");
}
/* Create a sorted string_list with one entry per author. The util-field
* for each author is another string_list which is used to calculate the
* number of commits per time-interval.
*/
void cgit_show_stats(struct cgit_context *ctx)
{
struct string_list authors;
struct cgit_period *period;
int top, i;
const char *code = "w";
if (ctx->qry.period)
code = ctx->qry.period;
i = cgit_find_stats_period(code, &period);
if (!i) {
cgit_print_error(fmt("Unknown statistics type: %c", code));
return;
}
if (i > ctx->repo->max_stats) {
cgit_print_error(fmt("Statistics type disabled: %s",
period->name));
return;
}
authors = collect_stats(ctx, period);
qsort(authors.items, authors.nr, sizeof(struct string_list_item),
cmp_total_commits);
top = ctx->qry.ofs;
if (!top)
top = 10;
htmlf("<h2>Commits per author per %s", period->name);
if (ctx->qry.path) {
html(" (path '");
html_txt(ctx->qry.path);
html("')");
}
html("</h2>");
html("<form method='get' action='' style='float: right; text-align: right;'>");
cgit_add_hidden_formfields(1, 0, "stats");
if (ctx->repo->max_stats > 1) {
html("Period: ");
html("<select name='period' onchange='this.form.submit();'>");
for (i = 0; i < ctx->repo->max_stats; i++)
htmlf("<option value='%c'%s>%s</option>",
periods[i].code,
period == &periods[i] ? " selected" : "",
periods[i].name);
html("</select><br/><br/>");
}
html("Authors: ");
html("");
html("<select name='ofs' onchange='this.form.submit();'>");
htmlf("<option value='10'%s>10</option>", top == 10 ? " selected" : "");
htmlf("<option value='25'%s>25</option>", top == 25 ? " selected" : "");
htmlf("<option value='50'%s>50</option>", top == 50 ? " selected" : "");
htmlf("<option value='100'%s>100</option>", top == 100 ? " selected" : "");
htmlf("<option value='-1'%s>All</option>", top == -1 ? " selected" : "");
html("</select>");
html("<noscript>&nbsp;&nbsp;<input type='submit' value='Reload'/></noscript>");
html("</form>");
print_authors(&authors, top, period);
}