summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile2
-rw-r--r--cgit.c4
-rw-r--r--cgit.css15
-rw-r--r--cgit.h3
-rw-r--r--cmd.c3
m---------git0
-rw-r--r--shared.c5
-rw-r--r--ui-log.c35
-rw-r--r--ui-refs.c40
-rw-r--r--ui-repolist.c137
-rw-r--r--ui-shared.c29
-rw-r--r--ui-shared.h2
-rw-r--r--ui-snapshot.c94
-rw-r--r--ui-snapshot.h3
-rw-r--r--ui-tree.c2
15 files changed, 285 insertions, 89 deletions
diff --git a/Makefile b/Makefile
index 2e51c31..3c7ec07 100644
--- a/Makefile
+++ b/Makefile
@@ -1,152 +1,152 @@
CGIT_VERSION = v0.8.1
CGIT_SCRIPT_NAME = cgit.cgi
CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
CGIT_CONFIG = /etc/cgitrc
CACHE_ROOT = /var/cache/cgit
SHA1_HEADER = <openssl/sha.h>
-GIT_VER = 1.6.0.3
+GIT_VER = 1.6.1
GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
# 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
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-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
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) libgit.a
$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a
test: all
$(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
install: all
mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH)
install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
install -m 0644 cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css
install -m 0644 cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png
uninstall:
rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
rm -f $(CGIT_SCRIPT_PATH)/cgit.css
rm -f $(CGIT_SCRIPT_PATH)/cgit.png
clean:
rm -f cgit VERSION *.o *.d
get-git:
curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cgit.c b/cgit.c
index c82587b..166fbc6 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,412 +1,416 @@
/* 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 "scan-tree.h"
const char *cgit_version = CGIT_VERSION;
void config_cb(const char *name, const char *value)
{
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, "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")) {
ctx.cfg.virtual_root = trim_end(value, '/');
if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
ctx.cfg.virtual_root = "";
} else if (!strcmp(name, "nocache"))
ctx.cfg.nocache = atoi(value);
else if (!strcmp(name, "snapshots"))
ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
else if (!strcmp(name, "enable-index-links"))
ctx.cfg.enable_index_links = atoi(value);
else if (!strcmp(name, "enable-log-filecount"))
ctx.cfg.enable_log_filecount = atoi(value);
else if (!strcmp(name, "enable-log-linecount"))
ctx.cfg.enable_log_linecount = atoi(value);
else if (!strcmp(name, "cache-size"))
ctx.cfg.cache_size = atoi(value);
else if (!strcmp(name, "cache-root"))
ctx.cfg.cache_root = xstrdup(value);
else if (!strcmp(name, "cache-root-ttl"))
ctx.cfg.cache_root_ttl = atoi(value);
else if (!strcmp(name, "cache-repo-ttl"))
ctx.cfg.cache_repo_ttl = atoi(value);
else if (!strcmp(name, "cache-static-ttl"))
ctx.cfg.cache_static_ttl = atoi(value);
else if (!strcmp(name, "cache-dynamic-ttl"))
ctx.cfg.cache_dynamic_ttl = atoi(value);
else if (!strcmp(name, "max-message-length"))
ctx.cfg.max_msg_len = atoi(value);
else if (!strcmp(name, "max-repodesc-length"))
ctx.cfg.max_repodesc_len = atoi(value);
else if (!strcmp(name, "max-repo-count"))
ctx.cfg.max_repo_count = atoi(value);
else if (!strcmp(name, "max-commit-count"))
ctx.cfg.max_commit_count = atoi(value);
else if (!strcmp(name, "summary-log"))
ctx.cfg.summary_log = atoi(value);
else if (!strcmp(name, "summary-branches"))
ctx.cfg.summary_branches = atoi(value);
else if (!strcmp(name, "summary-tags"))
ctx.cfg.summary_tags = atoi(value);
else if (!strcmp(name, "agefile"))
ctx.cfg.agefile = xstrdup(value);
else if (!strcmp(name, "renamelimit"))
ctx.cfg.renamelimit = atoi(value);
else if (!strcmp(name, "robots"))
ctx.cfg.robots = xstrdup(value);
else if (!strcmp(name, "clone-prefix"))
ctx.cfg.clone_prefix = xstrdup(value);
else if (!strcmp(name, "local-time"))
ctx.cfg.local_time = atoi(value);
else if (!strcmp(name, "repo.group"))
ctx.cfg.repo_group = xstrdup(value);
else if (!strcmp(name, "repo.url"))
ctx.repo = cgit_add_repo(value);
else if (!strcmp(name, "repo.name"))
ctx.repo->name = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.path"))
ctx.repo->path = trim_end(value, '/');
else if (ctx.repo && !strcmp(name, "repo.clone-url"))
ctx.repo->clone_url = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.desc"))
ctx.repo->desc = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.owner"))
ctx.repo->owner = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.defbranch"))
ctx.repo->defbranch = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.snapshots"))
ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
else if (ctx.repo && !strcmp(name, "repo.module-link"))
ctx.repo->module_link= xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
if (*value == '/')
ctx.repo->readme = xstrdup(value);
else
ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
} else if (!strcmp(name, "include"))
parse_configfile(value, config_cb);
}
static void querystring_cb(const char *name, const char *value)
{
if (!strcmp(name,"r")) {
ctx.qry.repo = xstrdup(value);
ctx.repo = cgit_get_repoinfo(value);
} else if (!strcmp(name, "p")) {
ctx.qry.page = xstrdup(value);
} else if (!strcmp(name, "url")) {
ctx.qry.url = xstrdup(value);
cgit_parse_url(value);
} else if (!strcmp(name, "qt")) {
ctx.qry.grep = xstrdup(value);
} else if (!strcmp(name, "q")) {
ctx.qry.search = xstrdup(value);
} else if (!strcmp(name, "h")) {
ctx.qry.head = xstrdup(value);
ctx.qry.has_symref = 1;
} else if (!strcmp(name, "id")) {
ctx.qry.sha1 = xstrdup(value);
ctx.qry.has_sha1 = 1;
} else if (!strcmp(name, "id2")) {
ctx.qry.sha2 = xstrdup(value);
ctx.qry.has_sha1 = 1;
} else if (!strcmp(name, "ofs")) {
ctx.qry.ofs = atoi(value);
} else if (!strcmp(name, "path")) {
ctx.qry.path = trim_end(value, '/');
} else if (!strcmp(name, "name")) {
ctx.qry.name = xstrdup(value);
} else if (!strcmp(name, "mimetype")) {
ctx.qry.mimetype = xstrdup(value);
+ } else if (!strcmp(name, "s")){
+ ctx.qry.sort = xstrdup(value);
+ } else if (!strcmp(name, "showmsg")) {
+ ctx.qry.showmsg = atoi(value);
}
}
static void prepare_context(struct cgit_context *ctx)
{
memset(ctx, 0, sizeof(ctx));
ctx->cfg.agefile = "info/web/last-modified";
ctx->cfg.nocache = 0;
ctx->cfg.cache_size = 0;
ctx->cfg.cache_dynamic_ttl = 5;
ctx->cfg.cache_max_create_time = 5;
ctx->cfg.cache_repo_ttl = 5;
ctx->cfg.cache_root = CGIT_CACHE_ROOT;
ctx->cfg.cache_root_ttl = 5;
ctx->cfg.cache_static_ttl = -1;
ctx->cfg.css = "/cgit.css";
ctx->cfg.logo = "/git-logo.png";
ctx->cfg.local_time = 0;
ctx->cfg.max_repo_count = 50;
ctx->cfg.max_commit_count = 50;
ctx->cfg.max_lock_attempts = 5;
ctx->cfg.max_msg_len = 80;
ctx->cfg.max_repodesc_len = 80;
ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
ctx->cfg.renamelimit = -1;
ctx->cfg.robots = "index, nofollow";
ctx->cfg.root_title = "Git repository browser";
ctx->cfg.root_desc = "a fast webinterface for the git dscm";
ctx->cfg.script_name = CGIT_SCRIPT_NAME;
ctx->cfg.summary_branches = 10;
ctx->cfg.summary_log = 10;
ctx->cfg.summary_tags = 10;
ctx->page.mimetype = "text/html";
ctx->page.charset = PAGE_ENCODING;
ctx->page.filename = NULL;
ctx->page.size = 0;
ctx->page.modified = time(NULL);
ctx->page.expires = ctx->page.modified;
}
struct refmatch {
char *req_ref;
char *first_ref;
int match;
};
int find_current_ref(const char *refname, const unsigned char *sha1,
int flags, void *cb_data)
{
struct refmatch *info;
info = (struct refmatch *)cb_data;
if (!strcmp(refname, info->req_ref))
info->match = 1;
if (!info->first_ref)
info->first_ref = xstrdup(refname);
return info->match;
}
char *find_default_branch(struct cgit_repo *repo)
{
struct refmatch info;
char *ref;
info.req_ref = repo->defbranch;
info.first_ref = NULL;
info.match = 0;
for_each_branch_ref(find_current_ref, &info);
if (info.match)
ref = info.req_ref;
else
ref = info.first_ref;
if (ref)
ref = xstrdup(ref);
return ref;
}
static int prepare_repo_cmd(struct cgit_context *ctx)
{
char *tmp;
unsigned char sha1[20];
int nongit = 0;
setenv("GIT_DIR", ctx->repo->path, 1);
setup_git_directory_gently(&nongit);
if (nongit) {
ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
"config error");
tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
ctx->repo = NULL;
cgit_print_http_headers(ctx);
cgit_print_docstart(ctx);
cgit_print_pageheader(ctx);
cgit_print_error(tmp);
cgit_print_docend();
return 1;
}
ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
if (!ctx->qry.head) {
ctx->qry.nohead = 1;
ctx->qry.head = find_default_branch(ctx->repo);
ctx->repo->defbranch = ctx->qry.head;
}
if (!ctx->qry.head) {
cgit_print_http_headers(ctx);
cgit_print_docstart(ctx);
cgit_print_pageheader(ctx);
cgit_print_error("Repository seems to be empty");
cgit_print_docend();
return 1;
}
if (get_sha1(ctx->qry.head, sha1)) {
tmp = xstrdup(ctx->qry.head);
ctx->qry.head = ctx->repo->defbranch;
cgit_print_http_headers(ctx);
cgit_print_docstart(ctx);
cgit_print_pageheader(ctx);
cgit_print_error(fmt("Invalid branch: %s", tmp));
cgit_print_docend();
return 1;
}
return 0;
}
static void process_request(void *cbdata)
{
struct cgit_context *ctx = cbdata;
struct cgit_cmd *cmd;
cmd = cgit_get_cmd(ctx);
if (!cmd) {
ctx->page.title = "cgit error";
ctx->repo = NULL;
cgit_print_http_headers(ctx);
cgit_print_docstart(ctx);
cgit_print_pageheader(ctx);
cgit_print_error("Invalid request");
cgit_print_docend();
return;
}
if (cmd->want_repo && !ctx->repo) {
cgit_print_http_headers(ctx);
cgit_print_docstart(ctx);
cgit_print_pageheader(ctx);
cgit_print_error(fmt("No repository selected"));
cgit_print_docend();
return;
}
if (ctx->repo && prepare_repo_cmd(ctx))
return;
if (cmd->want_layout) {
cgit_print_http_headers(ctx);
cgit_print_docstart(ctx);
cgit_print_pageheader(ctx);
}
cmd->fn(ctx);
if (cmd->want_layout)
cgit_print_docend();
}
int cmp_repos(const void *a, const void *b)
{
const struct cgit_repo *ra = a, *rb = b;
return strcmp(ra->url, rb->url);
}
void print_repo(struct cgit_repo *repo)
{
printf("repo.url=%s\n", repo->url);
printf("repo.name=%s\n", repo->name);
printf("repo.path=%s\n", repo->path);
if (repo->owner)
printf("repo.owner=%s\n", repo->owner);
if (repo->desc)
printf("repo.desc=%s\n", repo->desc);
if (repo->readme)
printf("repo.readme=%s\n", repo->readme);
printf("\n");
}
void print_repolist(struct cgit_repolist *list)
{
int i;
for(i = 0; i < list->count; i++)
print_repo(&list->repos[i]);
}
static void cgit_parse_args(int argc, const char **argv)
{
int i;
int scan = 0;
for (i = 1; i < argc; i++) {
if (!strncmp(argv[i], "--cache=", 8)) {
ctx.cfg.cache_root = xstrdup(argv[i]+8);
}
if (!strcmp(argv[i], "--nocache")) {
ctx.cfg.nocache = 1;
}
if (!strncmp(argv[i], "--query=", 8)) {
ctx.qry.raw = xstrdup(argv[i]+8);
}
if (!strncmp(argv[i], "--repo=", 7)) {
ctx.qry.repo = xstrdup(argv[i]+7);
}
if (!strncmp(argv[i], "--page=", 7)) {
ctx.qry.page = xstrdup(argv[i]+7);
}
if (!strncmp(argv[i], "--head=", 7)) {
ctx.qry.head = xstrdup(argv[i]+7);
ctx.qry.has_symref = 1;
}
if (!strncmp(argv[i], "--sha1=", 7)) {
ctx.qry.sha1 = xstrdup(argv[i]+7);
ctx.qry.has_sha1 = 1;
}
if (!strncmp(argv[i], "--ofs=", 6)) {
ctx.qry.ofs = atoi(argv[i]+6);
}
if (!strncmp(argv[i], "--scan-tree=", 12)) {
scan++;
scan_tree(argv[i] + 12);
}
}
if (scan) {
qsort(cgit_repolist.repos, cgit_repolist.count,
sizeof(struct cgit_repo), cmp_repos);
print_repolist(&cgit_repolist);
exit(0);
}
}
static int calc_ttl()
{
if (!ctx.repo)
return ctx.cfg.cache_root_ttl;
if (!ctx.qry.page)
return ctx.cfg.cache_repo_ttl;
if (ctx.qry.has_symref)
return ctx.cfg.cache_dynamic_ttl;
if (ctx.qry.has_sha1)
return ctx.cfg.cache_static_ttl;
diff --git a/cgit.css b/cgit.css
index a37d218..7928c2f 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,401 +1,416 @@
body, table, form {
padding: 0em;
margin: 0em;
}
body {
font-family: sans-serif;
font-size: 10pt;
color: #333;
background: white;
padding: 4px;
}
a {
color: blue;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
table {
border-collapse: collapse;
}
table#header {
width: 100%;
margin-bottom: 1em;
}
table#header td.logo {
width: 96px;
}
table#header td.main {
font-size: 250%;
padding-left: 10px;
white-space: nowrap;
}
table#header td.main a {
color: #000;
}
table#header td.form {
text-align: right;
vertical-align: bottom;
padding-right: 1em;
padding-bottom: 2px;
white-space: nowrap;
}
table#header td.form form,
table#header td.form input,
table#header td.form select {
font-size: 90%;
}
table#header td.sub {
color: #777;
border-top: solid 1px #ccc;
padding-left: 10px;
}
table.tabs {
/* border-bottom: solid 2px #ccc; */
border-collapse: collapse;
margin-top: 2em;
margin-bottom: 0px;
width: 100%;
}
table.tabs td {
padding: 0px 1em;
vertical-align: bottom;
}
table.tabs td a {
padding: 2px 0.75em;
color: #777;
font-size: 110%;
}
table.tabs td a.active {
color: #000;
background-color: #ccc;
}
table.tabs td.form {
text-align: right;
}
table.tabs td.form form {
padding-bottom: 2px;
font-size: 90%;
white-space: nowrap;
}
table.tabs td.form input,
table.tabs td.form select {
font-size: 90%;
}
div.content {
margin: 0px;
padding: 2em;
border-top: solid 3px #ccc;
border-bottom: solid 3px #ccc;
}
table.list {
width: 100%;
border: none;
border-collapse: collapse;
}
table.list tr {
background: white;
}
+table.list tr.logheader {
+ background: #eee;
+}
+
table.list tr:hover {
background: #eee;
}
table.list tr.nohover:hover {
background: white;
}
table.list th {
font-weight: bold;
/* color: #888;
border-top: dashed 1px #888;
border-bottom: dashed 1px #888;
*/
padding: 0.1em 0.5em 0.05em 0.5em;
vertical-align: baseline;
}
table.list td {
border: none;
padding: 0.1em 0.5em 0.1em 0.5em;
}
+table.list td.logsubject {
+ font-family: monospace;
+ font-weight: bold;
+}
+
+table.list td.logmsg {
+ font-family: monospace;
+ white-space: pre;
+ padding: 1em 0em 2em 0em;
+}
+
table.list td a {
color: black;
}
table.list td a:hover {
color: #00f;
}
img {
border: none;
}
input#switch-btn {
margin: 2px 0px 0px 0px;
}
td#sidebar input.txt {
width: 100%;
margin: 2px 0px 0px 0px;
}
table#grid {
margin: 0px;
}
td#content {
vertical-align: top;
padding: 1em 2em 1em 1em;
border: none;
}
div#summary {
vertical-align: top;
margin-bottom: 1em;
}
table#downloads {
float: right;
border-collapse: collapse;
border: solid 1px #777;
margin-left: 0.5em;
margin-bottom: 0.5em;
}
table#downloads th {
background-color: #ccc;
}
div#blob {
border: solid 1px black;
}
div.error {
color: red;
font-weight: bold;
margin: 1em 2em;
}
a.ls-blob, a.ls-dir, a.ls-mod {
font-family: monospace;
}
td.ls-size {
text-align: right;
font-family: monospace;
width: 10em;
}
td.ls-mode {
font-family: monospace;
width: 10em;
}
table.blob {
margin-top: 0.5em;
border-top: solid 1px black;
}
table.blob td.no {
border-right: solid 1px black;
color: black;
background-color: #eee;
text-align: right;
}
table.blob td.no a {
color: black;
}
table.blob td.no a:hover {
color: black;
text-decoration: none;
}
table.blob td.txt {
white-space: pre;
font-family: monospace;
padding-left: 0.5em;
}
table.nowrap td {
white-space: nowrap;
}
table.commit-info {
border-collapse: collapse;
margin-top: 1.5em;
}
table.commit-info th {
text-align: left;
font-weight: normal;
padding: 0.1em 1em 0.1em 0.1em;
vertical-align: top;
}
table.commit-info td {
font-weight: normal;
padding: 0.1em 1em 0.1em 0.1em;
}
div.commit-subject {
font-weight: bold;
font-size: 125%;
margin: 1.5em 0em 0.5em 0em;
padding: 0em;
}
div.commit-msg {
white-space: pre;
font-family: monospace;
}
div.diffstat-header {
font-weight: bold;
padding-top: 1.5em;
}
table.diffstat {
border-collapse: collapse;
border: solid 1px #aaa;
background-color: #eee;
}
table.diffstat th {
font-weight: normal;
text-align: left;
text-decoration: underline;
padding: 0.1em 1em 0.1em 0.1em;
font-size: 100%;
}
table.diffstat td {
padding: 0.2em 0.2em 0.1em 0.1em;
font-size: 100%;
border: none;
}
table.diffstat td.mode {
white-space: nowrap;
}
table.diffstat td span.modechange {
padding-left: 1em;
color: red;
}
table.diffstat td.add a {
color: green;
}
table.diffstat td.del a {
color: red;
}
table.diffstat td.upd a {
color: blue;
}
table.diffstat td.graph {
width: 500px;
vertical-align: middle;
}
table.diffstat td.graph table {
border: none;
}
table.diffstat td.graph td {
padding: 0px;
border: 0px;
height: 7pt;
}
table.diffstat td.graph td.add {
background-color: #5c5;
}
table.diffstat td.graph td.rem {
background-color: #c55;
}
div.diffstat-summary {
color: #888;
padding-top: 0.5em;
}
table.diff {
width: 100%;
}
table.diff td {
font-family: monospace;
white-space: pre;
}
table.diff td div.head {
font-weight: bold;
margin-top: 1em;
color: black;
}
table.diff td div.hunk {
color: #009;
}
table.diff td div.add {
color: green;
}
table.diff td div.del {
color: red;
}
.sha1 {
font-family: monospace;
font-size: 90%;
}
.left {
text-align: left;
}
.right {
text-align: right;
}
table.list td.repogroup {
font-style: italic;
color: #888;
}
a.button {
font-size: 80%;
padding: 0em 0.5em;
}
diff --git a/cgit.h b/cgit.h
index 92f0c5a..cb2f176 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,237 +1,240 @@
#ifndef CGIT_H
#define CGIT_H
#include <git-compat-util.h>
#include <cache.h>
#include <grep.h>
#include <object.h>
#include <tree.h>
#include <commit.h>
#include <tag.h>
#include <diff.h>
#include <diffcore.h>
#include <refs.h>
#include <revision.h>
#include <log-tree.h>
#include <archive.h>
#include <xdiff/xdiff.h>
#include <utf8.h>
/*
* Dateformats used on misc. pages
*/
#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
#define FMT_SHORTDATE "%Y-%m-%d"
#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
/*
* Limits used for relative dates
*/
#define TM_MIN 60
#define TM_HOUR (TM_MIN * 60)
#define TM_DAY (TM_HOUR * 24)
#define TM_WEEK (TM_DAY * 7)
#define TM_YEAR (TM_DAY * 365)
#define TM_MONTH (TM_YEAR / 12.0)
/*
* Default encoding
*/
#define PAGE_ENCODING "UTF-8"
typedef void (*configfn)(const char *name, const char *value);
typedef void (*filepair_fn)(struct diff_filepair *pair);
typedef void (*linediff_fn)(char *line, int len);
struct cgit_repo {
char *url;
char *name;
char *path;
char *desc;
char *owner;
char *defbranch;
char *group;
char *module_link;
char *readme;
char *clone_url;
int snapshots;
int enable_log_filecount;
int enable_log_linecount;
+ time_t mtime;
};
struct cgit_repolist {
int length;
int count;
struct cgit_repo *repos;
};
struct commitinfo {
struct commit *commit;
char *author;
char *author_email;
unsigned long author_date;
char *committer;
char *committer_email;
unsigned long committer_date;
char *subject;
char *msg;
char *msg_encoding;
};
struct taginfo {
char *tagger;
char *tagger_email;
unsigned long tagger_date;
char *msg;
};
struct refinfo {
const char *refname;
struct object *object;
union {
struct taginfo *tag;
struct commitinfo *commit;
};
};
struct reflist {
struct refinfo **refs;
int alloc;
int count;
};
struct cgit_query {
int has_symref;
int has_sha1;
char *raw;
char *repo;
char *page;
char *search;
char *grep;
char *head;
char *sha1;
char *sha2;
char *path;
char *name;
char *mimetype;
char *url;
int ofs;
int nohead;
+ char *sort;
+ int showmsg;
};
struct cgit_config {
char *agefile;
char *cache_root;
char *clone_prefix;
char *css;
char *favicon;
char *footer;
char *index_header;
char *index_info;
char *logo;
char *logo_link;
char *module_link;
char *repo_group;
char *robots;
char *root_title;
char *root_desc;
char *root_readme;
char *script_name;
char *virtual_root;
int cache_size;
int cache_dynamic_ttl;
int cache_max_create_time;
int cache_repo_ttl;
int cache_root_ttl;
int cache_static_ttl;
int enable_index_links;
int enable_log_filecount;
int enable_log_linecount;
int local_time;
int max_repo_count;
int max_commit_count;
int max_lock_attempts;
int max_msg_len;
int max_repodesc_len;
int nocache;
int renamelimit;
int snapshots;
int summary_branches;
int summary_log;
int summary_tags;
};
struct cgit_page {
time_t modified;
time_t expires;
size_t size;
char *mimetype;
char *charset;
char *filename;
char *title;
};
struct cgit_context {
struct cgit_query qry;
struct cgit_config cfg;
struct cgit_repo *repo;
struct cgit_page page;
};
struct cgit_snapshot_format {
const char *suffix;
const char *mimetype;
write_archive_fn_t write_func;
int bit;
};
extern const char *cgit_version;
extern struct cgit_repolist cgit_repolist;
extern struct cgit_context ctx;
extern const struct cgit_snapshot_format cgit_snapshot_formats[];
extern struct cgit_repo *cgit_add_repo(const char *url);
extern struct cgit_repo *cgit_get_repoinfo(const char *url);
extern void cgit_repo_config_cb(const char *name, const char *value);
extern int chk_zero(int result, char *msg);
extern int chk_positive(int result, char *msg);
extern int chk_non_negative(int result, char *msg);
extern char *trim_end(const char *str, char c);
extern char *strlpart(char *txt, int maxlen);
extern char *strrpart(char *txt, int maxlen);
extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
int flags, void *cb_data);
extern void *cgit_free_commitinfo(struct commitinfo *info);
extern int cgit_diff_files(const unsigned char *old_sha1,
const unsigned char *new_sha1,
linediff_fn fn);
extern void cgit_diff_tree(const unsigned char *old_sha1,
const unsigned char *new_sha1,
filepair_fn fn, const char *prefix);
extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
extern char *fmt(const char *format,...);
extern struct commitinfo *cgit_parse_commit(struct commit *commit);
extern struct taginfo *cgit_parse_tag(struct tag *tag);
extern void cgit_parse_url(const char *url);
extern const char *cgit_repobasename(const char *reponame);
extern int cgit_parse_snapshots_mask(const char *str);
#endif /* CGIT_H */
diff --git a/cmd.c b/cmd.c
index 5b3c14c..8914fa5 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,165 +1,164 @@
/* cmd.c: the cgit command dispatcher
*
* Copyright (C) 2008 Lars Hjemli
*
* 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-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-summary.h"
#include "ui-tag.h"
#include "ui-tree.h"
static void HEAD_fn(struct cgit_context *ctx)
{
cgit_clone_head(ctx);
}
static void atom_fn(struct cgit_context *ctx)
{
cgit_print_atom(ctx->qry.head, ctx->qry.path, 10);
}
static void about_fn(struct cgit_context *ctx)
{
if (ctx->repo)
cgit_print_repo_readme();
else
cgit_print_site_readme();
}
static void blob_fn(struct cgit_context *ctx)
{
cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
}
static void commit_fn(struct cgit_context *ctx)
{
cgit_print_commit(ctx->qry.sha1);
}
static void diff_fn(struct cgit_context *ctx)
{
cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
}
static void info_fn(struct cgit_context *ctx)
{
cgit_clone_info(ctx);
}
static void log_fn(struct cgit_context *ctx)
{
cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1);
}
static void ls_cache_fn(struct cgit_context *ctx)
{
ctx->page.mimetype = "text/plain";
ctx->page.filename = "ls-cache.txt";
cgit_print_http_headers(ctx);
cache_ls(ctx->cfg.cache_root);
}
static void objects_fn(struct cgit_context *ctx)
{
cgit_clone_objects(ctx);
}
static void repolist_fn(struct cgit_context *ctx)
{
cgit_print_repolist();
}
static void patch_fn(struct cgit_context *ctx)
{
cgit_print_patch(ctx->qry.sha1);
}
static void plain_fn(struct cgit_context *ctx)
{
cgit_print_plain(ctx);
}
static void refs_fn(struct cgit_context *ctx)
{
cgit_print_refs();
}
static void snapshot_fn(struct cgit_context *ctx)
{
- cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1,
- cgit_repobasename(ctx->repo->url), ctx->qry.path,
+ cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
ctx->repo->snapshots, ctx->qry.nohead);
}
static void summary_fn(struct cgit_context *ctx)
{
cgit_print_summary();
}
static void tag_fn(struct cgit_context *ctx)
{
cgit_print_tag(ctx->qry.sha1);
}
static void tree_fn(struct cgit_context *ctx)
{
cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
}
#define def_cmd(name, want_repo, want_layout) \
{#name, name##_fn, want_repo, want_layout}
struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
{
static struct cgit_cmd cmds[] = {
def_cmd(HEAD, 1, 0),
def_cmd(atom, 1, 0),
def_cmd(about, 0, 1),
def_cmd(blob, 1, 0),
def_cmd(commit, 1, 1),
def_cmd(diff, 1, 1),
def_cmd(info, 1, 0),
def_cmd(log, 1, 1),
def_cmd(ls_cache, 0, 0),
def_cmd(objects, 1, 0),
def_cmd(patch, 1, 0),
def_cmd(plain, 1, 0),
def_cmd(refs, 1, 1),
def_cmd(repolist, 0, 0),
def_cmd(snapshot, 1, 0),
def_cmd(summary, 1, 1),
def_cmd(tag, 1, 1),
def_cmd(tree, 1, 1),
};
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;
}
diff --git a/git b/git
-Subproject 031e6c898f61db1ae0c0be641eac6532c1000d5
+Subproject 8104ebfe8276657ee803cca7eb8665a78cf3ef8
diff --git a/shared.c b/shared.c
index f5875e4..a764c4d 100644
--- a/shared.c
+++ b/shared.c
@@ -1,344 +1,347 @@
/* shared.c: global vars + some callback functions
*
* Copyright (C) 2006 Lars Hjemli
*
* 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 cgit_cmd;
int chk_zero(int result, char *msg)
{
if (result != 0)
die("%s: %s", msg, strerror(errno));
return result;
}
int chk_positive(int result, char *msg)
{
if (result <= 0)
die("%s: %s", msg, strerror(errno));
return result;
}
int chk_non_negative(int result, char *msg)
{
if (result < 0)
die("%s: %s",msg, strerror(errno));
return result;
}
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];
ret->url = trim_end(url, '/');
ret->name = ret->url;
ret->path = NULL;
ret->desc = "[no description]";
ret->owner = NULL;
ret->group = ctx.cfg.repo_group;
ret->defbranch = "master";
ret->snapshots = ctx.cfg.snapshots;
ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
ret->module_link = ctx.cfg.module_link;
ret->readme = NULL;
+ ret->mtime = -1;
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 (!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);
return NULL;
}
char *trim_end(const char *str, char c)
{
int len;
char *s, *t;
if (str == NULL)
return NULL;
t = (char *)str;
len = strlen(t);
while(len > 0 && t[len - 1] == c)
len--;
if (len == 0)
return NULL;
c = t[len];
t[len] = '\0';
s = xstrdup(t);
t[len] = c;
return s;
}
char *strlpart(char *txt, int maxlen)
{
char *result;
if (!txt)
return txt;
if (strlen(txt) <= maxlen)
return txt;
result = xmalloc(maxlen + 1);
memcpy(result, txt, maxlen - 3);
result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
result[maxlen] = '\0';
return result;
}
char *strrpart(char *txt, int maxlen)
{
char *result;
if (!txt)
return txt;
if (strlen(txt) <= maxlen)
return txt;
result = xmalloc(maxlen + 1);
memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
result[0] = result[1] = result[2] = '.';
return result;
}
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;
}
struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
{
struct refinfo *ref;
ref = xmalloc(sizeof (struct refinfo));
ref->refname = xstrdup(refname);
ref->object = parse_object(sha1);
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;
}
int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
void *cb_data)
{
struct reflist *list = (struct reflist *)cb_data;
struct refinfo *info = cgit_mk_refinfo(refname, sha1);
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 unsigned char *sha1)
{
enum object_type type;
if (is_null_sha1(sha1)) {
file->ptr = (char *)"";
file->size = 0;
} else {
file->ptr = read_sha1_file(sha1, &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.
*/
char *diffbuf = NULL;
int buflen = 0;
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 unsigned char *old_sha1,
const unsigned char *new_sha1,
linediff_fn fn)
{
mmfile_t file1, file2;
xpparam_t diff_params;
xdemitconf_t emit_params;
xdemitcb_t emit_cb;
if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
return 1;
+ 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;
emit_params.ctxlen = 3;
emit_params.flags = XDL_EMIT_FUNCNAMES;
- emit_params.find_func = NULL;
emit_cb.outf = filediff_cb;
emit_cb.priv = fn;
xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
return 0;
}
void cgit_diff_tree(const unsigned char *old_sha1,
const unsigned char *new_sha1,
filepair_fn fn, const char *prefix)
{
struct diff_options opt;
int ret;
int prefixlen;
diff_setup(&opt);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.detect_rename = 1;
opt.rename_limit = ctx.cfg.renamelimit;
DIFF_OPT_SET(&opt, RECURSIVE);
opt.format_callback = cgit_diff_tree_cb;
opt.format_callback_data = fn;
if (prefix) {
opt.nr_paths = 1;
opt.paths = &prefix;
prefixlen = strlen(prefix);
opt.pathlens = &prefixlen;
}
diff_setup_done(&opt);
if (old_sha1 && !is_null_sha1(old_sha1))
ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
else
ret = diff_root_tree_sha1(new_sha1, "", &opt);
diffcore_std(&opt);
diff_flush(&opt);
}
void cgit_diff_commit(struct commit *commit, filepair_fn fn)
{
unsigned char *old_sha1 = NULL;
if (commit->parents)
old_sha1 = commit->parents->item->object.sha1;
cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL);
}
int cgit_parse_snapshots_mask(const char *str)
{
const struct cgit_snapshot_format *f;
static const char *delim = " \t,:/|;";
int tl, sl, rv = 0;
/* favor legacy setting */
if(atoi(str))
return 1;
for(;;) {
str += strspn(str,delim);
tl = strcspn(str,delim);
if (!tl)
break;
for (f = cgit_snapshot_formats; f->suffix; f++) {
sl = strlen(f->suffix);
if((tl == sl && !strncmp(f->suffix, str, tl)) ||
(tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
rv |= f->bit;
break;
}
}
str += tl;
}
return rv;
}
diff --git a/ui-log.c b/ui-log.c
index d212984..2f90778 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -1,168 +1,189 @@
/* ui-log.c: functions for log output
*
* Copyright (C) 2006 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"
int files, add_lines, rem_lines;
void count_lines(char *line, int size)
{
if (size <= 0)
return;
if (line[0] == '+')
add_lines++;
else if (line[0] == '-')
rem_lines++;
}
void inspect_files(struct diff_filepair *pair)
{
files++;
if (ctx.repo->enable_log_linecount)
cgit_diff_files(pair->one->sha1, pair->two->sha1, count_lines);
}
void print_commit(struct commit *commit)
{
struct commitinfo *info;
char *tmp;
+ int cols = 2;
info = cgit_parse_commit(commit);
- html("<tr><td>");
+ htmlf("<tr%s><td>",
+ ctx.qry.showmsg ? " class='logheader'" : "");
tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
html_link_open(tmp, NULL, NULL);
cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
html_link_close();
- html("</td><td>");
+ htmlf("</td><td%s>",
+ ctx.qry.showmsg ? " class='logsubject'" : "");
cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
sha1_to_hex(commit->object.sha1));
html("</td><td>");
html_txt(info->author);
if (ctx.repo->enable_log_filecount) {
files = 0;
add_lines = 0;
rem_lines = 0;
cgit_diff_commit(commit, inspect_files);
html("</td><td>");
htmlf("%d", files);
if (ctx.repo->enable_log_linecount) {
html("</td><td>");
htmlf("-%d/+%d", rem_lines, add_lines);
}
}
html("</td></tr>\n");
+ if (ctx.qry.showmsg) {
+ if (ctx.repo->enable_log_filecount) {
+ cols++;
+ if (ctx.repo->enable_log_linecount)
+ cols++;
+ }
+ htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
+ cols);
+ html_txt(info->msg);
+ html("</td></tr>\n");
+ }
cgit_free_commitinfo(info);
}
static const char *disambiguate_ref(const char *ref)
{
unsigned char sha1[20];
const char *longref;
longref = fmt("refs/heads/%s", ref);
if (get_sha1(longref, sha1) == 0)
return longref;
return ref;
}
void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
char *path, int pager)
{
struct rev_info rev;
struct commit *commit;
const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
int argc = 2;
int i, columns = 3;
if (!tip)
tip = ctx.qry.head;
argv[1] = disambiguate_ref(tip);
if (grep && pattern && (!strcmp(grep, "grep") ||
!strcmp(grep, "author") ||
!strcmp(grep, "committer")))
argv[argc++] = fmt("--%s=%s", grep, pattern);
if (path) {
argv[argc++] = "--";
argv[argc++] = path;
}
init_revisions(&rev, NULL);
rev.abbrev = DEFAULT_ABBREV;
rev.commit_format = CMIT_FMT_DEFAULT;
rev.verbose_header = 1;
rev.show_root_diff = 0;
setup_revisions(argc, argv, &rev, NULL);
rev.grep_filter.regflags |= REG_ICASE;
compile_grep_patterns(&rev.grep_filter);
prepare_revision_walk(&rev);
if (pager)
html("<table class='list nowrap'>");
html("<tr class='nohover'><th class='left'>Age</th>"
- "<th class='left'>Commit message</th>"
- "<th class='left'>Author</th>");
+ "<th class='left'>Commit message");
+ if (pager) {
+ html(" (");
+ cgit_log_link("toggle", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
+ ctx.qry.path, ctx.qry.ofs, ctx.qry.grep,
+ ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
+ html(")");
+ }
+ html("</th><th class='left'>Author</th>");
if (ctx.repo->enable_log_filecount) {
html("<th class='left'>Files</th>");
columns++;
if (ctx.repo->enable_log_linecount) {
html("<th class='left'>Lines</th>");
columns++;
}
}
html("</tr>\n");
if (ofs<0)
ofs = 0;
for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) {
free(commit->buffer);
commit->buffer = NULL;
free_commit_list(commit->parents);
commit->parents = NULL;
}
for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) {
print_commit(commit);
free(commit->buffer);
commit->buffer = NULL;
free_commit_list(commit->parents);
commit->parents = NULL;
}
if (pager) {
htmlf("</table><div class='pager'>",
columns);
if (ofs > 0) {
cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
ctx.qry.sha1, ctx.qry.path,
ofs - cnt, ctx.qry.grep,
- ctx.qry.search);
+ ctx.qry.search, ctx.qry.showmsg);
html("&nbsp;");
}
if ((commit = get_revision(&rev)) != NULL) {
cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
ctx.qry.sha1, ctx.qry.path,
ofs + cnt, ctx.qry.grep,
- ctx.qry.search);
+ ctx.qry.search, ctx.qry.showmsg);
}
html("</div>");
} else if ((commit = get_revision(&rev)) != NULL) {
html("<tr class='nohover'><td colspan='3'>");
cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0,
- NULL, NULL);
+ NULL, NULL, ctx.qry.showmsg);
html("</td></tr>\n");
}
}
diff --git a/ui-refs.c b/ui-refs.c
index 32e0429..d61ee7c 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -1,196 +1,228 @@
/* ui-refs.c: browse symbolic refs
*
* Copyright (C) 2006 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"
static int header;
static int cmp_age(int age1, int age2)
{
if (age1 != 0 && age2 != 0)
return age2 - age1;
if (age1 == 0 && age2 == 0)
return 0;
if (age1 == 0)
return +1;
return -1;
}
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 cmp_tag_age(const void *a, const void *b)
{
struct refinfo *r1 = *(struct refinfo **)a;
struct refinfo *r2 = *(struct refinfo **)b;
return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date);
}
static int print_branch(struct refinfo *ref)
{
struct commitinfo *info = ref->commit;
char *name = (char *)ref->refname;
if (!info)
return 1;
html("<tr><td>");
- cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL);
+ cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
+ ctx.qry.showmsg);
html("</td><td>");
if (ref->object->type == OBJ_COMMIT) {
cgit_commit_link(info->subject, NULL, NULL, name, NULL);
html("</td><td>");
html_txt(info->author);
html("</td><td colspan='2'>");
cgit_print_age(info->commit->date, -1, NULL);
} else {
html("</td><td></td><td>");
cgit_object_link(ref->object);
}
html("</td></tr>\n");
return 0;
}
static void print_tag_header()
{
html("<tr class='nohover'><th class='left'>Tag</th>"
- "<th class='left'>Reference</th>"
+ "<th class='left'>Download</th>"
"<th class='left'>Author</th>"
"<th class='left' colspan='2'>Age</th></tr>\n");
header = 1;
}
+static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
+{
+ const struct cgit_snapshot_format* f;
+ char *filename;
+ const char *basename;
+
+ if (!ref || strlen(ref) < 2)
+ return;
+
+ basename = cgit_repobasename(repo->url);
+ if (prefixcmp(ref, basename) != 0) {
+ if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
+ ref++;
+ if (isdigit(ref[0]))
+ ref = xstrdup(fmt("%s-%s", basename, ref));
+ }
+
+ for (f = cgit_snapshot_formats; f->suffix; f++) {
+ if (!(repo->snapshots & f->bit))
+ continue;
+ filename = fmt("%s%s", ref, f->suffix);
+ cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
+ html("&nbsp;&nbsp;");
+ }
+}
static int print_tag(struct refinfo *ref)
{
struct tag *tag;
struct taginfo *info;
char *name = (char *)ref->refname;
if (ref->object->type == OBJ_TAG) {
tag = (struct tag *)ref->object;
info = ref->tag;
if (!tag || !info)
return 1;
html("<tr><td>");
cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
html("</td><td>");
- cgit_object_link(tag->tagged);
+ if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
+ print_tag_downloads(ctx.repo, name);
+ else
+ cgit_object_link(tag->tagged);
html("</td><td>");
if (info->tagger)
html(info->tagger);
html("</td><td colspan='2'>");
if (info->tagger_date > 0)
cgit_print_age(info->tagger_date, -1, NULL);
html("</td></tr>\n");
} else {
if (!header)
print_tag_header();
html("<tr><td>");
html_txt(name);
html("</td><td>");
- cgit_object_link(ref->object);
+ if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
+ print_tag_downloads(ctx.repo, name);
+ else
+ cgit_object_link(ref->object);
html("</td></tr>\n");
}
return 0;
}
static void print_refs_link(char *path)
{
html("<tr class='nohover'><td colspan='4'>");
cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
html("</td></tr>");
}
void cgit_print_branches(int maxcount)
{
struct reflist list;
int i;
html("<tr class='nohover'><th class='left'>Branch</th>"
"<th class='left'>Commit message</th>"
"<th class='left'>Author</th>"
"<th class='left' colspan='2'>Age</th></tr>\n");
list.refs = NULL;
list.alloc = list.count = 0;
for_each_branch_ref(cgit_refs_cb, &list);
if (maxcount == 0 || maxcount > list.count)
maxcount = list.count;
if (maxcount < list.count) {
qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
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");
}
void cgit_print_tags(int maxcount)
{
struct reflist list;
int i;
header = 0;
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");
}
void cgit_print_refs()
{
html("<table class='list nowrap'>");
if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5))
cgit_print_branches(0);
else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4))
cgit_print_tags(0);
else {
cgit_print_branches(0);
html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
cgit_print_tags(0);
}
html("</table>");
}
diff --git a/ui-repolist.c b/ui-repolist.c
index 2324273..2c13d50 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -1,171 +1,278 @@
/* ui-repolist.c: functions for generating the repolist page
*
* Copyright (C) 2006 Lars Hjemli
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
/* This is needed for strcasestr to be defined by <string.h> */
#define _GNU_SOURCE 1
#include <string.h>
#include <time.h>
#include "cgit.h"
#include "html.h"
#include "ui-shared.h"
time_t read_agefile(char *path)
{
FILE *f;
static char buf[64], buf2[64];
if (!(f = fopen(path, "r")))
return -1;
if (fgets(buf, sizeof(buf), f) == NULL)
return -1;
fclose(f);
if (parse_date(buf, buf2, sizeof(buf2)))
return strtoul(buf2, NULL, 10);
else
return 0;
}
-static void print_modtime(struct cgit_repo *repo)
+static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
{
char *path;
struct stat s;
+ struct cgit_repo *r = (struct cgit_repo *)repo;
+ if (repo->mtime != -1) {
+ *mtime = repo->mtime;
+ return 1;
+ }
path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
if (stat(path, &s) == 0) {
- cgit_print_age(read_agefile(path), -1, NULL);
- return;
+ *mtime = read_agefile(path);
+ r->mtime = *mtime;
+ return 1;
}
path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch);
- if (stat(path, &s) != 0)
- return;
- cgit_print_age(s.st_mtime, -1, NULL);
+ if (stat(path, &s) == 0)
+ *mtime = s.st_mtime;
+ else
+ *mtime = 0;
+
+ r->mtime = *mtime;
+ 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, -1, NULL);
}
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;
}
int is_in_url(struct cgit_repo *repo)
{
if (!ctx.qry.url)
return 1;
if (repo->url && !prefixcmp(repo->url, ctx.qry.url))
return 1;
return 0;
}
+void print_sort_header(const char *title, const char *sort)
+{
+ htmlf("<th class='left'><a href='./?s=%s", sort);
+ if (ctx.qry.search) {
+ html("&q=");
+ html_url_arg(ctx.qry.search);
+ }
+ htmlf("'>%s</a></th>", title);
+}
+
void print_header(int columns)
{
- html("<tr class='nohover'>"
- "<th class='left'>Name</th>"
- "<th class='left'>Description</th>"
- "<th class='left'>Owner</th>"
- "<th class='left'>Idle</th>");
+ html("<tr class='nohover'>");
+ print_sort_header("Name", "name");
+ print_sort_header("Description", "desc");
+ 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");
}
void print_pager(int items, int pagelen, char *search)
{
int i;
html("<div class='pager'>");
for(i = 0; i * pagelen < items; i++)
cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL,
search, i * pagelen);
html("</div>");
}
+static int cmp(const char *s1, const char *s2)
+{
+ if (s1 && s2)
+ return strcmp(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;
+}
+
+struct sortcolumn {
+ const char *name;
+ int (*fn)(const void *a, const void *b);
+};
+
+struct sortcolumn sortcolumn[] = {
+ {"name", sort_name},
+ {"desc", sort_desc},
+ {"owner", sort_owner},
+ {"idle", sort_idle},
+ {NULL, NULL}
+};
+
+int sort_repolist(char *field)
+{
+ 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()
{
int i, columns = 4, hits = 0, header = 0;
char *last_group = NULL;
+ int sorted = 0;
if (ctx.cfg.enable_index_links)
columns++;
ctx.page.title = ctx.cfg.root_title;
cgit_print_http_headers(&ctx);
cgit_print_docstart(&ctx);
cgit_print_pageheader(&ctx);
if (ctx.cfg.index_header)
html_include(ctx.cfg.index_header);
+ if(ctx.qry.sort)
+ sorted = sort_repolist(ctx.qry.sort);
+
html("<table summary='repository list' class='list nowrap'>");
for (i=0; i<cgit_repolist.count; i++) {
ctx.repo = &cgit_repolist.repos[i];
if (!(is_match(ctx.repo) && is_in_url(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(columns);
- if ((last_group == NULL && ctx.repo->group != NULL) ||
+ if (!sorted &&
+ ((last_group == NULL && ctx.repo->group != NULL) ||
(last_group != NULL && ctx.repo->group == NULL) ||
(last_group != NULL && ctx.repo->group != NULL &&
- strcmp(ctx.repo->group, last_group))) {
+ strcmp(ctx.repo->group, last_group)))) {
htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>",
columns);
html_txt(ctx.repo->group);
html("</td></tr>");
last_group = ctx.repo->group;
}
htmlf("<tr><td class='%s'>",
- ctx.repo->group ? "sublevel-repo" : "toplevel-repo");
+ !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo");
cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
html("</td><td>");
html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc);
html_link_close();
html("</td><td>");
html_txt(ctx.repo->owner);
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);
+ 0, NULL, NULL, ctx.qry.showmsg);
cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
html("</td>");
}
html("</tr>\n");
}
html("</table>");
if (!hits)
cgit_print_error("No repositories found");
else if (hits > ctx.cfg.max_repo_count)
print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search);
cgit_print_docend();
}
void cgit_print_site_readme()
{
if (ctx.cfg.root_readme)
html_include(ctx.cfg.root_readme);
}
diff --git a/ui-shared.c b/ui-shared.c
index 224e5f3..95dfeb4 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -28,687 +28,696 @@ static char *http_date(time_t t)
}
void cgit_print_error(char *msg)
{
html("<div class='error'>");
html_txt(msg);
html("</div>\n");
}
char *cgit_hosturl()
{
char *host, *port;
host = getenv("HTTP_HOST");
if (host) {
host = xstrdup(host);
} else {
host = getenv("SERVER_NAME");
if (!host)
return NULL;
port = getenv("SERVER_PORT");
if (port && atoi(port) != 80)
host = xstrdup(fmt("%s:%d", host, atoi(port)));
else
host = xstrdup(host);
}
return host;
}
char *cgit_rooturl()
{
if (ctx.cfg.virtual_root)
return fmt("%s/", ctx.cfg.virtual_root);
else
return ctx.cfg.script_name;
}
char *cgit_repourl(const char *reponame)
{
if (ctx.cfg.virtual_root) {
return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
} else {
return fmt("?r=%s", reponame);
}
}
char *cgit_fileurl(const char *reponame, const char *pagename,
const char *filename, const char *query)
{
char *tmp;
char *delim;
if (ctx.cfg.virtual_root) {
tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
pagename, (filename ? filename:""));
delim = "?";
} else {
tmp = fmt("?url=%s/%s/%s", reponame, pagename,
(filename ? filename : ""));
delim = "&";
}
if (query)
tmp = fmt("%s%s%s", tmp, delim, query);
return tmp;
}
char *cgit_pageurl(const char *reponame, const char *pagename,
const char *query)
{
return cgit_fileurl(reponame,pagename,0,query);
}
const char *cgit_repobasename(const char *reponame)
{
/* I assume we don't need to store more than one repo basename */
static char rvbuf[1024];
int p;
const char *rv;
strncpy(rvbuf,reponame,sizeof(rvbuf));
if(rvbuf[sizeof(rvbuf)-1])
die("cgit_repobasename: truncated repository name '%s'", reponame);
p = strlen(rvbuf)-1;
/* strip trailing slashes */
while(p && rvbuf[p]=='/') rvbuf[p--]=0;
/* strip trailing .git */
if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
p -= 3; rvbuf[p--] = 0;
}
/* strip more trailing slashes if any */
while( p && rvbuf[p]=='/') rvbuf[p--]=0;
/* find last slash in the remaining string */
rv = strrchr(rvbuf,'/');
if(rv)
return ++rv;
return rvbuf;
}
char *cgit_currurl()
{
if (!ctx.cfg.virtual_root)
return ctx.cfg.script_name;
else if (ctx.qry.page)
return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
else if (ctx.qry.repo)
return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
else
return fmt("%s/", ctx.cfg.virtual_root);
}
static void site_url(char *page, char *search, int ofs)
{
char *delim = "?";
if (ctx.cfg.virtual_root) {
html_attr(ctx.cfg.virtual_root);
if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
html("/");
} else
html(ctx.cfg.script_name);
if (page) {
htmlf("?p=%s", page);
delim = "&";
}
if (search) {
html(delim);
html("q=");
html_attr(search);
delim = "&";
}
if (ofs) {
html(delim);
htmlf("ofs=%d", ofs);
}
}
static void site_link(char *page, char *name, char *title, char *class,
char *search, int ofs)
{
html("<a");
if (title) {
html(" title='");
html_attr(title);
html("'");
}
if (class) {
html(" class='");
html_attr(class);
html("'");
}
html(" href='");
site_url(page, search, ofs);
html("'>");
html_txt(name);
html("</a>");
}
void cgit_index_link(char *name, char *title, char *class, char *pattern,
int ofs)
{
site_link(NULL, name, title, class, pattern, ofs);
}
static char *repolink(char *title, char *class, char *page, char *head,
char *path)
{
char *delim = "?";
html("<a");
if (title) {
html(" title='");
html_attr(title);
html("'");
}
if (class) {
html(" class='");
html_attr(class);
html("'");
}
html(" href='");
if (ctx.cfg.virtual_root) {
html_url_path(ctx.cfg.virtual_root);
if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
html("/");
html_url_path(ctx.repo->url);
if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
html("/");
if (page) {
html_url_path(page);
html("/");
if (path)
html_url_path(path);
}
} else {
html(ctx.cfg.script_name);
html("?url=");
html_url_arg(ctx.repo->url);
if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
html("/");
if (page) {
html_url_arg(page);
html("/");
if (path)
html_url_arg(path);
}
delim = "&amp;";
}
if (head && strcmp(head, ctx.repo->defbranch)) {
html(delim);
html("h=");
html_url_arg(head);
delim = "&amp;";
}
return fmt("%s", delim);
}
static void reporevlink(char *page, char *name, char *title, char *class,
char *head, char *rev, char *path)
{
char *delim;
delim = repolink(title, class, page, head, path);
if (rev && strcmp(rev, ctx.qry.head)) {
html(delim);
html("id=");
html_url_arg(rev);
}
html("'>");
html_txt(name);
html("</a>");
}
void cgit_summary_link(char *name, char *title, char *class, char *head)
{
reporevlink(NULL, name, title, class, head, NULL, NULL);
}
void cgit_tag_link(char *name, char *title, char *class, char *head,
char *rev)
{
reporevlink("tag", name, title, class, head, rev, NULL);
}
void cgit_tree_link(char *name, char *title, char *class, char *head,
char *rev, char *path)
{
reporevlink("tree", name, title, class, head, rev, path);
}
void cgit_plain_link(char *name, char *title, char *class, char *head,
char *rev, char *path)
{
reporevlink("plain", name, title, class, head, rev, path);
}
void cgit_log_link(char *name, char *title, char *class, char *head,
- char *rev, char *path, int ofs, char *grep, char *pattern)
+ char *rev, char *path, int ofs, char *grep, char *pattern,
+ int showmsg)
{
char *delim;
delim = repolink(title, class, "log", head, path);
if (rev && strcmp(rev, ctx.qry.head)) {
html(delim);
html("id=");
html_url_arg(rev);
delim = "&";
}
if (grep && pattern) {
html(delim);
html("qt=");
html_url_arg(grep);
delim = "&";
html(delim);
html("q=");
html_url_arg(pattern);
}
if (ofs > 0) {
html(delim);
html("ofs=");
htmlf("%d", ofs);
+ delim = "&";
+ }
+ if (showmsg) {
+ html(delim);
+ html("showmsg=1");
}
html("'>");
html_txt(name);
html("</a>");
}
void cgit_commit_link(char *name, char *title, char *class, char *head,
char *rev)
{
if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
name[ctx.cfg.max_msg_len] = '\0';
name[ctx.cfg.max_msg_len - 1] = '.';
name[ctx.cfg.max_msg_len - 2] = '.';
name[ctx.cfg.max_msg_len - 3] = '.';
}
reporevlink("commit", name, title, class, head, rev, NULL);
}
void cgit_refs_link(char *name, char *title, char *class, char *head,
char *rev, char *path)
{
reporevlink("refs", name, title, class, head, rev, path);
}
void cgit_snapshot_link(char *name, char *title, char *class, char *head,
char *rev, char *archivename)
{
reporevlink("snapshot", name, title, class, head, rev, archivename);
}
void cgit_diff_link(char *name, char *title, char *class, char *head,
char *new_rev, char *old_rev, char *path)
{
char *delim;
delim = repolink(title, class, "diff", head, path);
if (new_rev && strcmp(new_rev, ctx.qry.head)) {
html(delim);
html("id=");
html_url_arg(new_rev);
delim = "&amp;";
}
if (old_rev) {
html(delim);
html("id2=");
html_url_arg(old_rev);
}
html("'>");
html_txt(name);
html("</a>");
}
void cgit_patch_link(char *name, char *title, char *class, char *head,
char *rev)
{
reporevlink("patch", name, title, class, head, rev, NULL);
}
void cgit_object_link(struct object *obj)
{
- char *page, *rev, *name;
+ char *page, *shortrev, *fullrev, *name;
+ fullrev = sha1_to_hex(obj->sha1);
+ shortrev = xstrdup(fullrev);
+ shortrev[10] = '\0';
if (obj->type == OBJ_COMMIT) {
- cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
- ctx.qry.head, sha1_to_hex(obj->sha1));
+ cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
+ ctx.qry.head, fullrev);
return;
} else if (obj->type == OBJ_TREE)
page = "tree";
else if (obj->type == OBJ_TAG)
page = "tag";
else
page = "blob";
- rev = sha1_to_hex(obj->sha1);
- name = fmt("%s %s", typename(obj->type), rev);
- reporevlink(page, name, NULL, NULL, ctx.qry.head, rev, NULL);
+ name = fmt("%s %s...", typename(obj->type), shortrev);
+ reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
}
void cgit_print_date(time_t secs, char *format, int local_time)
{
char buf[64];
struct tm *time;
if (!secs)
return;
if(local_time)
time = localtime(&secs);
else
time = gmtime(&secs);
strftime(buf, sizeof(buf)-1, format, time);
html_txt(buf);
}
void cgit_print_age(time_t t, time_t max_relative, char *format)
{
time_t now, secs;
if (!t)
return;
time(&now);
secs = now - t;
if (secs > max_relative && max_relative >= 0) {
cgit_print_date(t, format, ctx.cfg.local_time);
return;
}
if (secs < TM_HOUR * 2) {
htmlf("<span class='age-mins'>%.0f min.</span>",
secs * 1.0 / TM_MIN);
return;
}
if (secs < TM_DAY * 2) {
htmlf("<span class='age-hours'>%.0f hours</span>",
secs * 1.0 / TM_HOUR);
return;
}
if (secs < TM_WEEK * 2) {
htmlf("<span class='age-days'>%.0f days</span>",
secs * 1.0 / TM_DAY);
return;
}
if (secs < TM_MONTH * 2) {
htmlf("<span class='age-weeks'>%.0f weeks</span>",
secs * 1.0 / TM_WEEK);
return;
}
if (secs < TM_YEAR * 2) {
htmlf("<span class='age-months'>%.0f months</span>",
secs * 1.0 / TM_MONTH);
return;
}
htmlf("<span class='age-years'>%.0f years</span>",
secs * 1.0 / TM_YEAR);
}
void cgit_print_http_headers(struct cgit_context *ctx)
{
if (ctx->page.mimetype && ctx->page.charset)
htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
ctx->page.charset);
else if (ctx->page.mimetype)
htmlf("Content-Type: %s\n", ctx->page.mimetype);
if (ctx->page.size)
htmlf("Content-Length: %ld\n", ctx->page.size);
if (ctx->page.filename)
htmlf("Content-Disposition: inline; filename=\"%s\"\n",
ctx->page.filename);
htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
htmlf("Expires: %s\n", http_date(ctx->page.expires));
html("\n");
}
void cgit_print_docstart(struct cgit_context *ctx)
{
char *host = cgit_hosturl();
html(cgit_doctype);
html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
html("<head>\n");
html("<title>");
html_txt(ctx->page.title);
html("</title>\n");
htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
if (ctx->cfg.robots && *ctx->cfg.robots)
htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
html("<link rel='stylesheet' type='text/css' href='");
html_attr(ctx->cfg.css);
html("'/>\n");
if (ctx->cfg.favicon) {
html("<link rel='shortcut icon' href='");
html_attr(ctx->cfg.favicon);
html("'/>\n");
}
if (host && ctx->repo) {
html("<link rel='alternate' title='Atom feed' href='http://");
html_attr(cgit_hosturl());
html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
fmt("h=%s", ctx->qry.head)));
html("' type='application/atom+xml'/>");
}
html("</head>\n");
html("<body>\n");
}
void cgit_print_docend()
{
html("</div>");
if (ctx.cfg.footer)
html_include(ctx.cfg.footer);
else {
htmlf("<div class='footer'>generated by cgit %s at ",
cgit_version);
cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
html("</div>\n");
}
html("</body>\n</html>\n");
}
int print_branch_option(const char *refname, const unsigned char *sha1,
int flags, void *cb_data)
{
char *name = (char *)refname;
html_option(name, name, ctx.qry.head);
return 0;
}
int print_archive_ref(const char *refname, const unsigned char *sha1,
int flags, void *cb_data)
{
struct tag *tag;
struct taginfo *info;
struct object *obj;
char buf[256], *url;
unsigned char fileid[20];
int *header = (int *)cb_data;
if (prefixcmp(refname, "refs/archives"))
return 0;
strncpy(buf, refname+14, sizeof(buf));
obj = parse_object(sha1);
if (!obj)
return 1;
if (obj->type == OBJ_TAG) {
tag = lookup_tag(sha1);
if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
return 0;
hashcpy(fileid, tag->tagged->sha1);
} else if (obj->type != OBJ_BLOB) {
return 0;
} else {
hashcpy(fileid, sha1);
}
if (!*header) {
html("<h1>download</h1>\n");
*header = 1;
}
url = cgit_pageurl(ctx.qry.repo, "blob",
fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
buf));
html_link_open(url, NULL, "menu");
html_txt(strlpart(buf, 20));
html_link_close();
return 0;
}
void add_hidden_formfields(int incl_head, int incl_search, char *page)
{
char *url;
if (!ctx.cfg.virtual_root) {
url = fmt("%s/%s", ctx.qry.repo, page);
if (ctx.qry.path)
url = fmt("%s/%s", url, ctx.qry.path);
html_hidden("url", url);
}
if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
strcmp(ctx.qry.head, ctx.repo->defbranch))
html_hidden("h", ctx.qry.head);
if (ctx.qry.sha1)
html_hidden("id", ctx.qry.sha1);
if (ctx.qry.sha2)
html_hidden("id2", ctx.qry.sha2);
+ if (ctx.qry.showmsg)
+ html_hidden("showmsg", "1");
if (incl_search) {
if (ctx.qry.grep)
html_hidden("qt", ctx.qry.grep);
if (ctx.qry.search)
html_hidden("q", ctx.qry.search);
}
}
char *hc(struct cgit_cmd *cmd, const char *page)
{
return (strcmp(cmd->name, page) ? NULL : "active");
}
void cgit_print_pageheader(struct cgit_context *ctx)
{
struct cgit_cmd *cmd = cgit_get_cmd(ctx);
html("<table id='header'>\n");
html("<tr>\n");
html("<td class='logo' rowspan='2'><a href='");
if (ctx->cfg.logo_link)
html_attr(ctx->cfg.logo_link);
else
html_attr(cgit_rooturl());
html("'><img src='");
html_attr(ctx->cfg.logo);
html("' alt='cgit logo'/></a></td>\n");
html("<td class='main'>");
if (ctx->repo) {
cgit_index_link("index", NULL, NULL, NULL, 0);
html(" : ");
cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
html("</td><td class='form'>");
html("<form method='get' action=''>\n");
add_hidden_formfields(0, 1, ctx->qry.page);
html("<select name='h' onchange='this.form.submit();'>\n");
for_each_branch_ref(print_branch_option, ctx->qry.head);
html("</select> ");
html("<input type='submit' name='' value='switch'/>");
html("</form>");
} else
html_txt(ctx->cfg.root_title);
html("</td></tr>\n");
html("<tr><td class='sub'>");
if (ctx->repo) {
html_txt(ctx->repo->desc);
html("</td><td class='sub right'>");
html_txt(ctx->repo->owner);
} else {
if (ctx->cfg.root_desc)
html_txt(ctx->cfg.root_desc);
else if (ctx->cfg.index_info)
html_include(ctx->cfg.index_info);
}
html("</td></tr></table>\n");
html("<table class='tabs'><tr><td>\n");
if (ctx->repo) {
cgit_summary_link("summary", NULL, hc(cmd, "summary"),
ctx->qry.head);
cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
ctx->qry.sha1, NULL);
cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
- NULL, NULL, 0, NULL, NULL);
+ NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
ctx->qry.sha1, NULL);
cgit_commit_link("commit", NULL, hc(cmd, "commit"),
ctx->qry.head, ctx->qry.sha1);
cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
ctx->qry.sha1, ctx->qry.sha2, NULL);
if (ctx->repo->readme)
reporevlink("about", "about", NULL,
hc(cmd, "about"), ctx->qry.head, NULL,
NULL);
html("</td><td class='form'>");
html("<form class='right' method='get' action='");
if (ctx->cfg.virtual_root)
html_url_path(cgit_fileurl(ctx->qry.repo, "log",
ctx->qry.path, NULL));
html("'>\n");
add_hidden_formfields(1, 0, "log");
html("<select name='qt'>\n");
html_option("grep", "log msg", ctx->qry.grep);
html_option("author", "author", ctx->qry.grep);
html_option("committer", "committer", ctx->qry.grep);
html("</select>\n");
html("<input class='txt' type='text' size='10' name='q' value='");
html_attr(ctx->qry.search);
html("'/>\n");
html("<input type='submit' value='search'/>\n");
html("</form>\n");
} else {
site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
if (ctx->cfg.root_readme)
site_link("about", "about", NULL, hc(cmd, "about"),
NULL, 0);
html("</td><td class='form'>");
html("<form method='get' action='");
html_attr(cgit_rooturl());
html("'>\n");
html("<input type='text' name='q' size='10' value='");
html_attr(ctx->qry.search);
html("'/>\n");
html("<input type='submit' value='search'/>\n");
html("</form>");
}
html("</td></tr></table>\n");
html("<div class='content'>");
}
void cgit_print_filemode(unsigned short mode)
{
if (S_ISDIR(mode))
html("d");
else if (S_ISLNK(mode))
html("l");
else if (S_ISGITLINK(mode))
html("m");
else
html("-");
html_fileperm(mode >> 6);
html_fileperm(mode >> 3);
html_fileperm(mode);
}
void cgit_print_snapshot_links(const char *repo, const char *head,
const char *hex, int snapshots)
{
const struct cgit_snapshot_format* f;
char *filename;
for (f = cgit_snapshot_formats; f->suffix; f++) {
if (!(snapshots & f->bit))
continue;
filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
f->suffix);
- cgit_snapshot_link(filename, NULL, NULL, (char *)head,
- (char *)hex, filename);
+ cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
html("<br/>");
}
}
diff --git a/ui-shared.h b/ui-shared.h
index 3c8a6d0..2ab53ae 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,46 +1,46 @@
#ifndef UI_SHARED_H
#define UI_SHARED_H
extern char *cgit_hosturl();
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_index_link(char *name, char *title, char *class,
char *pattern, int ofs);
extern void cgit_summary_link(char *name, char *title, char *class, char *head);
extern void cgit_tag_link(char *name, char *title, char *class, char *head,
char *rev);
extern void cgit_tree_link(char *name, char *title, char *class, char *head,
char *rev, char *path);
extern void cgit_plain_link(char *name, char *title, char *class, char *head,
char *rev, char *path);
extern void cgit_log_link(char *name, char *title, char *class, char *head,
char *rev, char *path, int ofs, char *grep,
- char *pattern);
+ char *pattern, int showmsg);
extern void cgit_commit_link(char *name, char *title, char *class, char *head,
char *rev);
extern void cgit_patch_link(char *name, char *title, char *class, char *head,
char *rev);
extern void cgit_refs_link(char *name, char *title, char *class, char *head,
char *rev, char *path);
extern void cgit_snapshot_link(char *name, char *title, char *class,
char *head, char *rev, char *archivename);
extern void cgit_diff_link(char *name, char *title, char *class, char *head,
char *new_rev, char *old_rev, char *path);
extern void cgit_object_link(struct object *obj);
extern void cgit_print_error(char *msg);
extern void cgit_print_date(time_t secs, char *format, int local_time);
extern void cgit_print_age(time_t t, time_t max_relative, char *format);
extern void cgit_print_http_headers(struct cgit_context *ctx);
extern void cgit_print_docstart(struct cgit_context *ctx);
extern void cgit_print_docend();
extern void cgit_print_pageheader(struct cgit_context *ctx);
extern void cgit_print_filemode(unsigned short mode);
extern void cgit_print_snapshot_links(const char *repo, const char *head,
const char *hex, int snapshots);
#endif /* UI_SHARED_H */
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 9c4d086..6f09151 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -1,188 +1,192 @@
/* ui-snapshot.c: generate snapshot of a commit
*
* Copyright (C) 2006 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"
static int write_compressed_tar_archive(struct archiver_args *args,const char *filter)
{
int rw[2];
pid_t gzpid;
int stdout2;
int status;
int rv;
stdout2 = chk_non_negative(dup(STDIN_FILENO), "Preserving STDOUT before compressing");
chk_zero(pipe(rw), "Opening pipe from compressor subprocess");
gzpid = chk_non_negative(fork(), "Forking compressor subprocess");
if(gzpid==0) {
/* child */
chk_zero(close(rw[1]), "Closing write end of pipe in child");
chk_zero(close(STDIN_FILENO), "Closing STDIN");
chk_non_negative(dup2(rw[0],STDIN_FILENO), "Redirecting compressor input to stdin");
execlp(filter,filter,NULL);
_exit(-1);
}
/* parent */
chk_zero(close(rw[0]), "Closing read end of pipe");
chk_non_negative(dup2(rw[1],STDOUT_FILENO), "Redirecting output to compressor");
rv = write_tar_archive(args);
chk_zero(close(STDOUT_FILENO), "Closing STDOUT redirected to compressor");
chk_non_negative(dup2(stdout2,STDOUT_FILENO), "Restoring uncompressed STDOUT");
chk_zero(close(stdout2), "Closing uncompressed STDOUT");
chk_zero(close(rw[1]), "Closing write end of pipe in parent");
chk_positive(waitpid(gzpid,&status,0), "Waiting on compressor process");
if(! ( WIFEXITED(status) && WEXITSTATUS(status)==0 ) )
cgit_print_error("Failed to compress archive");
return rv;
}
static int write_tar_gzip_archive(struct archiver_args *args)
{
return write_compressed_tar_archive(args,"gzip");
}
static int write_tar_bzip2_archive(struct archiver_args *args)
{
return write_compressed_tar_archive(args,"bzip2");
}
const struct cgit_snapshot_format cgit_snapshot_formats[] = {
{ ".zip", "application/x-zip", write_zip_archive, 0x1 },
{ ".tar.gz", "application/x-tar", write_tar_gzip_archive, 0x2 },
{ ".tar.bz2", "application/x-tar", write_tar_bzip2_archive, 0x4 },
{ ".tar", "application/x-tar", write_tar_archive, 0x8 },
{}
};
static const struct cgit_snapshot_format *get_format(const char *filename)
{
const struct cgit_snapshot_format *fmt;
int fl, sl;
fl = strlen(filename);
for(fmt = cgit_snapshot_formats; fmt->suffix; fmt++) {
sl = strlen(fmt->suffix);
if (sl >= fl)
continue;
if (!strcmp(fmt->suffix, filename + fl - sl))
return fmt;
}
return NULL;
}
static int make_snapshot(const struct cgit_snapshot_format *format,
const char *hex, const char *prefix,
const char *filename)
{
struct archiver_args args;
struct commit *commit;
unsigned char sha1[20];
if(get_sha1(hex, sha1)) {
cgit_print_error(fmt("Bad object id: %s", hex));
return 1;
}
commit = lookup_commit_reference(sha1);
if(!commit) {
cgit_print_error(fmt("Not a commit reference: %s", hex));
return 1;
}
memset(&args, 0, sizeof(args));
if (prefix) {
args.base = fmt("%s/", prefix);
args.baselen = strlen(prefix) + 1;
} else {
args.base = "";
args.baselen = 0;
}
args.tree = commit->tree;
args.time = commit->date;
ctx.page.mimetype = xstrdup(format->mimetype);
ctx.page.filename = xstrdup(filename);
cgit_print_http_headers(&ctx);
format->write_func(&args);
return 0;
}
-char *dwim_filename = NULL;
-const char *dwim_refname = NULL;
-
-static int ref_cb(const char *refname, const unsigned char *sha1, int flags,
- void *cb_data)
-{
- const char *r = refname;
- while (r && *r) {
- fprintf(stderr, " cmp %s with %s:", dwim_filename, r);
- if (!strcmp(dwim_filename, r)) {
- fprintf(stderr, "MATCH!\n");
- dwim_refname = refname;
- return 1;
- }
- fprintf(stderr, "no match\n");
- if (isdigit(*r))
- break;
- r++;
- }
- return 0;
-}
-
-/* Try to guess the requested revision by combining repo name and tag name
- * and comparing this to the requested snapshot name. E.g. the requested
- * snapshot is "cgit-0.7.2.tar.gz" while repo name is "cgit" and tag name
- * is "v0.7.2". First, the reponame is stripped off, leaving "-0.7.2.tar.gz".
- * Next, any '-' and '_' characters are stripped, leaving "0.7.2.tar.gz".
- * Finally, the requested format suffix is removed and we end up with "0.7.2".
- * Then we test each tag against this dwimmed filename, and for each tag
- * we even try to remove any leading characters which are non-digits. I.e.
- * we first compare with "v0.7.2", then with "0.7.2" and we've got a match.
+/* Try to guess the requested revision from the requested snapshot name.
+ * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become
+ * "cgit-0.7.2". If this is a valid commit object name we've got a winner.
+ * Otherwise, if the snapshot name has a prefix matching the result from
+ * repo_basename(), we strip the basename and any following '-' and '_'
+ * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once
+ * more. If this still isn't a valid commit object name, we check if pre-
+ * pending a 'v' to the remaining snapshot name ("0.7.2" -> "v0.7.2") gives
+ * us something valid.
*/
static const char *get_ref_from_filename(const char *url, const char *filename,
- const struct cgit_snapshot_format *fmt)
+ const struct cgit_snapshot_format *format)
{
- const char *reponame = cgit_repobasename(url);
- fprintf(stderr, "reponame=%s, filename=%s\n", reponame, filename);
- if (prefixcmp(filename, reponame))
- return NULL;
- filename += strlen(reponame);
- while (filename && (*filename == '-' || *filename == '_'))
- filename++;
- dwim_filename = xstrdup(filename);
- dwim_filename[strlen(filename) - strlen(fmt->suffix)] = '\0';
- for_each_tag_ref(ref_cb, NULL);
- return dwim_refname;
+ const char *reponame;
+ unsigned char sha1[20];
+ char *snapshot;
+
+ snapshot = xstrdup(filename);
+ snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0';
+ fprintf(stderr, "snapshot=%s\n", snapshot);
+
+ if (get_sha1(snapshot, sha1) == 0)
+ return snapshot;
+
+ reponame = cgit_repobasename(url);
+ fprintf(stderr, "reponame=%s\n", reponame);
+ if (prefixcmp(snapshot, reponame) == 0) {
+ snapshot += strlen(reponame);
+ while (snapshot && (*snapshot == '-' || *snapshot == '_'))
+ snapshot++;
+ }
+
+ if (get_sha1(snapshot, sha1) == 0)
+ return snapshot;
+
+ snapshot = fmt("v%s", snapshot);
+ if (get_sha1(snapshot, sha1) == 0)
+ return snapshot;
+
+ return NULL;
}
-void cgit_print_snapshot(const char *head, const char *hex, const char *prefix,
+void cgit_print_snapshot(const char *head, const char *hex,
const char *filename, int snapshots, int dwim)
{
const struct cgit_snapshot_format* f;
+ char *prefix = NULL;
f = get_format(filename);
if (!f) {
ctx.page.mimetype = "text/html";
cgit_print_http_headers(&ctx);
cgit_print_docstart(&ctx);
cgit_print_pageheader(&ctx);
cgit_print_error(fmt("Unsupported snapshot format: %s", filename));
cgit_print_docend();
return;
}
- if (!hex && dwim)
+ if (!hex && dwim) {
hex = get_ref_from_filename(ctx.repo->url, filename, f);
+ if (hex != NULL) {
+ prefix = xstrdup(filename);
+ prefix[strlen(filename) - strlen(f->suffix)] = '\0';
+ }
+ }
if (!hex)
hex = head;
+ if (!prefix)
+ prefix = xstrdup(cgit_repobasename(ctx.repo->url));
+
make_snapshot(f, hex, prefix, filename);
+ free(prefix);
}
diff --git a/ui-snapshot.h b/ui-snapshot.h
index 3540303..b6ede52 100644
--- a/ui-snapshot.h
+++ b/ui-snapshot.h
@@ -1,8 +1,7 @@
#ifndef UI_SNAPSHOT_H
#define UI_SNAPSHOT_H
extern void cgit_print_snapshot(const char *head, const char *hex,
- const char *prefix, const char *filename,
- int snapshot, int dwim);
+ const char *filename, int snapshot, int dwim);
#endif /* UI_SNAPSHOT_H */
diff --git a/ui-tree.c b/ui-tree.c
index 79332fc..051db7c 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,223 +1,223 @@
/* ui-tree.c: functions for tree output
*
* Copyright (C) 2006 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 header = 0;
static void print_object(const unsigned char *sha1, char *path)
{
enum object_type type;
char *buf;
unsigned long size, lineno, start, idx;
const char *linefmt = "<tr><td class='no'><a id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a></td><td class='txt'>";
type = sha1_object_info(sha1, &size);
if (type == OBJ_BAD) {
cgit_print_error(fmt("Bad object name: %s",
sha1_to_hex(sha1)));
return;
}
buf = read_sha1_file(sha1, &type, &size);
if (!buf) {
cgit_print_error(fmt("Error reading object %s",
sha1_to_hex(sha1)));
return;
}
html(" (");
cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
curr_rev, path);
htmlf(")<br/>blob: %s", sha1_to_hex(sha1));
html("<table summary='blob content' class='blob'>\n");
idx = 0;
start = 0;
lineno = 0;
while(idx < size) {
if (buf[idx] == '\n') {
buf[idx] = '\0';
htmlf(linefmt, ++lineno);
html_txt(buf + start);
html("</td></tr>\n");
start = idx + 1;
}
idx++;
}
htmlf(linefmt, ++lineno);
html_txt(buf + start);
html("</td></tr>\n");
html("</table>\n");
}
static int ls_item(const unsigned char *sha1, const char *base, int baselen,
const char *pathname, unsigned int mode, int stage,
void *cbdata)
{
char *name;
char *fullpath;
enum object_type type;
unsigned long size = 0;
name = xstrdup(pathname);
fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
ctx.qry.path ? "/" : "", name);
if (!S_ISGITLINK(mode)) {
type = sha1_object_info(sha1, &size);
if (type == OBJ_BAD) {
htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
name,
sha1_to_hex(sha1));
return 0;
}
}
html("<tr><td class='ls-mode'>");
cgit_print_filemode(mode);
html("</td><td>");
if (S_ISGITLINK(mode)) {
htmlf("<a class='ls-mod' href='");
html_attr(fmt(ctx.repo->module_link,
name,
sha1_to_hex(sha1)));
html("'>");
html_txt(name);
html("</a>");
} else if (S_ISDIR(mode)) {
cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
curr_rev, fullpath);
} else {
cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head,
curr_rev, fullpath);
}
htmlf("</td><td class='ls-size'>%li</td>", size);
html("<td>");
cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
- fullpath, 0, NULL, NULL);
+ fullpath, 0, NULL, NULL, ctx.qry.showmsg);
html("</td></tr>\n");
free(name);
return 0;
}
static void ls_head()
{
html("<table summary='tree listing' class='list'>\n");
html("<tr class='nohover'>");
html("<th class='left'>Mode</th>");
html("<th class='left'>Name</th>");
html("<th class='right'>Size</th>");
html("<th/>");
html("</tr>\n");
header = 1;
}
static void ls_tail()
{
if (!header)
return;
html("</table>\n");
header = 0;
}
static void ls_tree(const unsigned char *sha1, char *path)
{
struct tree *tree;
tree = parse_tree_indirect(sha1);
if (!tree) {
cgit_print_error(fmt("Not a tree object: %s",
sha1_to_hex(sha1)));
return;
}
ls_head();
read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
ls_tail();
}
static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
const char *pathname, unsigned mode, int stage,
void *cbdata)
{
static int state;
static char buffer[PATH_MAX];
char *url;
if (state == 0) {
memcpy(buffer, base, baselen);
strcpy(buffer+baselen, pathname);
url = cgit_pageurl(ctx.qry.repo, "tree",
fmt("h=%s&amp;path=%s", curr_rev, buffer));
html("/");
cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
curr_rev, buffer);
if (strcmp(match_path, buffer))
return READ_TREE_RECURSIVE;
if (S_ISDIR(mode)) {
state = 1;
ls_head();
return READ_TREE_RECURSIVE;
} else {
print_object(sha1, buffer);
return 0;
}
}
ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
return 0;
}
/*
* Show a tree or a blob
* rev: the commit pointing at the root tree object
* path: path to tree or blob
*/
void cgit_print_tree(const char *rev, char *path)
{
unsigned char sha1[20];
struct commit *commit;
const char *paths[] = {path, NULL};
if (!rev)
rev = ctx.qry.head;
curr_rev = xstrdup(rev);
if (get_sha1(rev, sha1)) {
cgit_print_error(fmt("Invalid revision name: %s", rev));
return;
}
commit = lookup_commit_reference(sha1);
if (!commit || parse_commit(commit)) {
cgit_print_error(fmt("Invalid commit reference: %s", rev));
return;
}
html("path: <a href='");
html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
html("'>root</a>");
if (path == NULL) {
ls_tree(commit->tree->object.sha1, NULL);
return;
}
match_path = path;
read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
ls_tail();
}