summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--Makefile1
-rw-r--r--cgit.c1
-rw-r--r--cgit.h1
-rw-r--r--cmd.c7
-rw-r--r--html.c5
-rw-r--r--html.h1
-rw-r--r--ui-plain.c82
-rw-r--r--ui-plain.h6
-rw-r--r--ui-shared.c2
9 files changed, 106 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index 78aad10..a305894 100644
--- a/Makefile
+++ b/Makefile
@@ -16,96 +16,97 @@ GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
# 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 += shared.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 git 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)"'
cgit: $(OBJECTS) git/libgit.a git/xdiff/lib.a
$(QUIET_CC)$(CC) $(CFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
cgit.o: VERSION
-include $(OBJECTS:.o=.d)
git/libgit.a: git
$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) libgit.a
git/xdiff/lib.a: git
$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a
test: all
$(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
install: all
mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH)
diff --git a/cgit.c b/cgit.c
index f49fffa..497337b 100644
--- a/cgit.c
+++ b/cgit.c
@@ -142,96 +142,97 @@ static void querystring_cb(const char *name, const char *value)
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);
}
}
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);
diff --git a/cgit.h b/cgit.h
index b01fa31..e2af0c2 100644
--- a/cgit.h
+++ b/cgit.h
@@ -120,96 +120,97 @@ struct cgit_query {
int ofs;
};
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);
diff --git a/cmd.c b/cmd.c
index 03e165c..2b34189 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,151 +1,158 @@
/* 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-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 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,
ctx->repo->snapshots);
}
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(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/html.c b/html.c
index 1237076..83fc7a9 100644
--- a/html.c
+++ b/html.c
@@ -1,85 +1,90 @@
/* html.c: helper functions for html output
*
* Copyright (C) 2006 Lars Hjemli
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
int htmlfd = STDOUT_FILENO;
char *fmt(const char *format, ...)
{
static char buf[8][1024];
static int bufidx;
int len;
va_list args;
bufidx++;
bufidx &= 7;
va_start(args, format);
len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args);
va_end(args);
if (len>sizeof(buf[bufidx])) {
fprintf(stderr, "[html.c] string truncated: %s\n", format);
exit(1);
}
return buf[bufidx];
}
+void html_raw(const char *data, size_t size)
+{
+ write(htmlfd, data, size);
+}
+
void html(const char *txt)
{
write(htmlfd, txt, strlen(txt));
}
void htmlf(const char *format, ...)
{
static char buf[65536];
va_list args;
va_start(args, format);
vsnprintf(buf, sizeof(buf), format, args);
va_end(args);
html(buf);
}
void html_status(int code, int more_headers)
{
htmlf("Status: %d\n", code);
if (!more_headers)
html("\n");
}
void html_txt(char *txt)
{
char *t = txt;
while(t && *t){
int c = *t;
if (c=='<' || c=='>' || c=='&') {
write(htmlfd, txt, t - txt);
if (c=='>')
html("&gt;");
else if (c=='<')
html("&lt;");
else if (c=='&')
html("&amp;");
txt = t+1;
}
t++;
}
if (t!=txt)
html(txt);
}
void html_ntxt(int len, char *txt)
{
char *t = txt;
while(t && *t && len--){
diff --git a/html.h b/html.h
index 2bde28d..49462a2 100644
--- a/html.h
+++ b/html.h
@@ -1,21 +1,22 @@
#ifndef HTML_H
#define HTML_H
extern int htmlfd;
+extern void html_raw(const char *txt, size_t size);
extern void html(const char *txt);
extern void htmlf(const char *format,...);
extern void html_status(int code, int more_headers);
extern void html_txt(char *txt);
extern void html_ntxt(int len, char *txt);
extern void html_attr(char *txt);
extern void html_hidden(char *name, char *value);
extern void html_option(char *value, char *text, char *selected_value);
extern void html_link_open(char *url, char *title, char *class);
extern void html_link_close(void);
extern void html_fileperm(unsigned short mode);
extern int html_include(const char *filename);
extern int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value));
#endif /* HTML_H */
diff --git a/ui-plain.c b/ui-plain.c
new file mode 100644
index 0000000..28deae5
--- a/dev/null
+++ b/ui-plain.c
@@ -0,0 +1,82 @@
+/* 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;
+ size_t size;
+
+ type = sha1_object_info(sha1, &size);
+ if (type == OBJ_BAD) {
+ html_status(404, 0);
+ return;
+ }
+
+ buf = read_sha1_file(sha1, &type, &size);
+ if (!buf) {
+ html_status(404, 0);
+ return;
+ }
+ ctx.page.mimetype = "text/plain";
+ ctx.page.filename = fmt("%s", path);
+ ctx.page.size = size;
+ 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)
+{
+ fprintf(stderr, "[cgit] walk_tree.pathname=%s", pathname);
+
+ if (!pathname || strcmp(match_path, pathname))
+ return READ_TREE_RECURSIVE;
+
+ if (S_ISREG(mode))
+ 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, 0);
+ return;
+ }
+ commit = lookup_commit_reference(sha1);
+ if (!commit || parse_commit(commit)) {
+ html_status(404, 0);
+ return;
+ }
+ match_path = ctx->qry.path;
+ fprintf(stderr, "[cgit] match_path=%s", match_path);
+ read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
+ if (!match)
+ html_status(404, 0);
+}
diff --git a/ui-plain.h b/ui-plain.h
new file mode 100644
index 0000000..4373118
--- a/dev/null
+++ b/ui-plain.h
@@ -0,0 +1,6 @@
+#ifndef UI_PLAIN_H
+#define UI_PLAIN_H
+
+extern void cgit_print_plain(struct cgit_context *ctx);
+
+#endif /* UI_PLAIN_H */
diff --git a/ui-shared.c b/ui-shared.c
index 197ee37..4408969 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -373,96 +373,98 @@ 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)
{
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");
}
html("</head>\n");
html("<body>\n");
}
void cgit_print_docend()
{
html("</div>");
if (ctx.cfg.footer)
html_include(ctx.cfg.footer);
else {
html("<div class='footer'>generated ");
cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
htmlf(" by cgit %s", cgit_version);
html("</div>\n");
}
html("</body>\n</html>\n");
}
int print_branch_option(const char *refname, const unsigned char *sha1,
int flags, void *cb_data)
{