summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c16
-rw-r--r--cgit.h2
-rw-r--r--cmd.c9
-rw-r--r--html.c6
-rw-r--r--ui-commit.c146
-rw-r--r--ui-diff.c151
-rw-r--r--ui-diff.h3
-rw-r--r--ui-repolist.c15
-rw-r--r--ui-repolist.h1
-rw-r--r--ui-shared.c58
-rw-r--r--ui-summary.c14
-rw-r--r--ui-summary.h1
12 files changed, 257 insertions, 165 deletions
diff --git a/cgit.c b/cgit.c
index 4dc8eec..ccd61f4 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,369 +1,383 @@
/* 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"
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, "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-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, "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")) {
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);
}
}
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.max_commit_count = 50;
ctx->cfg.max_lock_attempts = 5;
ctx->cfg.max_msg_len = 60;
ctx->cfg.max_repodesc_len = 60;
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->page.mimetype = "text/html";
ctx->page.charset = PAGE_ENCODING;
ctx->page.filename = NULL;
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;
info.req_ref = repo->defbranch;
info.first_ref = NULL;
info.match = 0;
for_each_branch_ref(find_current_ref, &info);
if (info.match)
return info.req_ref;
else
return info.first_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.head = xstrdup(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 && prepare_repo_cmd(ctx))
+ 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();
}
static void cgit_parse_args(int argc, const char **argv)
{
int i;
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);
}
}
}
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;
return ctx.cfg.cache_repo_ttl;
}
int main(int argc, const char **argv)
{
const char *cgit_config_env = getenv("CGIT_CONFIG");
int err, ttl;
prepare_context(&ctx);
cgit_repolist.length = 0;
cgit_repolist.count = 0;
cgit_repolist.repos = NULL;
parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
config_cb);
ctx.repo = NULL;
if (getenv("SCRIPT_NAME"))
ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
if (getenv("QUERY_STRING"))
ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
cgit_parse_args(argc, argv);
http_parse_querystring(ctx.qry.raw, querystring_cb);
ttl = calc_ttl();
ctx.page.expires += ttl*60;
if (ctx.cfg.nocache)
ctx.cfg.cache_size = 0;
err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
ctx.qry.raw, ttl, process_request, &ctx);
if (err)
cache_log("[cgit] error %d - %s\n",
err, strerror(err));
return err;
}
diff --git a/cgit.h b/cgit.h
index f04d227..bbb404e 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,232 +1,234 @@
#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"
#define FMT_SHORTDATE "%Y-%m-%d"
/*
* 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;
};
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;
int 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;
int ofs;
};
struct cgit_config {
char *agefile;
char *cache_root;
char *clone_prefix;
char *css;
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 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;
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);
/* libgit.a either links against or compiles its own implementation of
* strcasestr(), and we'd like to reuse it. Simply re-declaring it
* seems to do the trick.
*/
extern char *strcasestr(const char *haystack, const char *needle);
#endif /* CGIT_H */
diff --git a/cmd.c b/cmd.c
index 07f4707..4edca6b 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,123 +1,132 @@
/* 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-commit.h"
#include "ui-diff.h"
#include "ui-log.h"
#include "ui-patch.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 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);
}
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 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 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 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(about, 0, 1),
def_cmd(blob, 1, 0),
def_cmd(commit, 1, 1),
def_cmd(diff, 1, 1),
def_cmd(log, 1, 1),
def_cmd(ls_cache, 0, 0),
def_cmd(patch, 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 937b5e7..bddb04d 100644
--- a/html.c
+++ b/html.c
@@ -1,241 +1,245 @@
/* 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(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_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--){
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)
write(htmlfd, txt, t - txt);
if (len<0)
html("...");
}
void html_attr(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("&quote;");
txt = t+1;
}
t++;
}
if (t!=txt)
html(txt);
}
void html_hidden(char *name, char *value)
{
html("<input type='hidden' name='");
html_attr(name);
html("' value='");
html_attr(value);
html("'/>");
}
void html_option(char *value, char *text, char *selected_value)
{
html("<option value='");
html_attr(value);
html("'");
if (selected_value && !strcmp(selected_value, value))
html(" selected='selected'");
html(">");
html_txt(text);
html("</option>\n");
}
void html_link_open(char *url, char *title, char *class)
{
html("<a href='");
html_attr(url);
if (title) {
html("' title='");
html_attr(title);
}
if (class) {
html("' class='");
html_attr(class);
}
html("'>");
}
void html_link_close(void)
{
html("</a>");
}
void html_fileperm(unsigned short mode)
{
htmlf("%c%c%c", (mode & 4 ? 'r' : '-'),
(mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-'));
}
int html_include(const char *filename)
{
FILE *f;
char buf[4096];
size_t len;
- if (!(f = fopen(filename, "r")))
+ if (!(f = fopen(filename, "r"))) {
+ fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n",
+ filename, strerror(errno), errno);
return -1;
+ }
while((len = fread(buf, 1, 4096, f)) > 0)
write(htmlfd, buf, len);
fclose(f);
return 0;
}
int hextoint(char c)
{
if (c >= 'a' && c <= 'f')
return 10 + c - 'a';
else if (c >= 'A' && c <= 'F')
return 10 + c - 'A';
else if (c >= '0' && c <= '9')
return c - '0';
else
return -1;
}
char *convert_query_hexchar(char *txt)
{
int d1, d2;
if (strlen(txt) < 3) {
*txt = '\0';
return txt-1;
}
d1 = hextoint(*(txt+1));
d2 = hextoint(*(txt+2));
if (d1<0 || d2<0) {
strcpy(txt, txt+3);
return txt-1;
} else {
*txt = d1 * 16 + d2;
strcpy(txt+1, txt+3);
return txt;
}
}
int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value))
{
char *t, *value = NULL, c;
if (!txt)
return 0;
t = txt = strdup(txt);
if (t == NULL) {
printf("Out of memory\n");
exit(1);
}
while((c=*t) != '\0') {
if (c=='=') {
*t = '\0';
value = t+1;
} else if (c=='+') {
*t = ' ';
} else if (c=='%') {
t = convert_query_hexchar(t);
} else if (c=='&') {
*t = '\0';
(*fn)(txt, value);
txt = t+1;
value = NULL;
}
t++;
}
if (t!=txt)
(*fn)(txt, value);
return 0;
}
diff --git a/ui-commit.c b/ui-commit.c
index 12a96fb..1aa5d34 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,236 +1,98 @@
/* ui-commit.c: generate commit view
*
* 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"
#include "ui-diff.h"
-static int files, slots;
-static int total_adds, total_rems, max_changes;
-static int lines_added, lines_removed;
-static char *curr_rev;
-
-static struct fileinfo {
- char status;
- unsigned char old_sha1[20];
- unsigned char new_sha1[20];
- unsigned short old_mode;
- unsigned short new_mode;
- char *old_path;
- char *new_path;
- unsigned int added;
- unsigned int removed;
-} *items;
-
-
-void print_fileinfo(struct fileinfo *info)
-{
- char *class;
-
- switch (info->status) {
- case DIFF_STATUS_ADDED:
- class = "add";
- break;
- case DIFF_STATUS_COPIED:
- class = "cpy";
- break;
- case DIFF_STATUS_DELETED:
- class = "del";
- break;
- case DIFF_STATUS_MODIFIED:
- class = "upd";
- break;
- case DIFF_STATUS_RENAMED:
- class = "mov";
- break;
- case DIFF_STATUS_TYPE_CHANGED:
- class = "typ";
- break;
- case DIFF_STATUS_UNKNOWN:
- class = "unk";
- break;
- case DIFF_STATUS_UNMERGED:
- class = "stg";
- break;
- default:
- die("bug: unhandled diff status %c", info->status);
- }
-
- html("<tr>");
- htmlf("<td class='mode'>");
- if (is_null_sha1(info->new_sha1)) {
- cgit_print_filemode(info->old_mode);
- } else {
- cgit_print_filemode(info->new_mode);
- }
-
- if (info->old_mode != info->new_mode &&
- !is_null_sha1(info->old_sha1) &&
- !is_null_sha1(info->new_sha1)) {
- html("<span class='modechange'>[");
- cgit_print_filemode(info->old_mode);
- html("]</span>");
- }
- htmlf("</td><td class='%s'>", class);
- cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, curr_rev,
- NULL, info->new_path);
- if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
- htmlf(" (%s from %s)",
- info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
- info->old_path);
- html("</td><td class='right'>");
- htmlf("%d", info->added + info->removed);
- html("</td><td class='graph'>");
- htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
- htmlf("<td class='add' style='width: %.1f%%;'/>",
- info->added * 100.0 / max_changes);
- htmlf("<td class='rem' style='width: %.1f%%;'/>",
- info->removed * 100.0 / max_changes);
- htmlf("<td class='none' style='width: %.1f%%;'/>",
- (max_changes - info->removed - info->added) * 100.0 / max_changes);
- html("</tr></table></td></tr>\n");
-}
-
-void cgit_count_diff_lines(char *line, int len)
-{
- if (line && (len > 0)) {
- if (line[0] == '+')
- lines_added++;
- else if (line[0] == '-')
- lines_removed++;
- }
-}
-
-void inspect_filepair(struct diff_filepair *pair)
-{
- files++;
- lines_added = 0;
- lines_removed = 0;
- cgit_diff_files(pair->one->sha1, pair->two->sha1, cgit_count_diff_lines);
- if (files >= slots) {
- if (slots == 0)
- slots = 4;
- else
- slots = slots * 2;
- items = xrealloc(items, slots * sizeof(struct fileinfo));
- }
- items[files-1].status = pair->status;
- hashcpy(items[files-1].old_sha1, pair->one->sha1);
- hashcpy(items[files-1].new_sha1, pair->two->sha1);
- items[files-1].old_mode = pair->one->mode;
- items[files-1].new_mode = pair->two->mode;
- items[files-1].old_path = xstrdup(pair->one->path);
- items[files-1].new_path = xstrdup(pair->two->path);
- items[files-1].added = lines_added;
- items[files-1].removed = lines_removed;
- if (lines_added + lines_removed > max_changes)
- max_changes = lines_added + lines_removed;
- total_adds += lines_added;
- total_rems += lines_removed;
-}
-
-
void cgit_print_commit(char *hex)
{
struct commit *commit, *parent;
struct commitinfo *info;
struct commit_list *p;
unsigned char sha1[20];
char *tmp;
- int i;
if (!hex)
hex = ctx.qry.head;
- curr_rev = hex;
if (get_sha1(hex, sha1)) {
cgit_print_error(fmt("Bad object id: %s", hex));
return;
}
commit = lookup_commit_reference(sha1);
if (!commit) {
cgit_print_error(fmt("Bad commit reference: %s", hex));
return;
}
info = cgit_parse_commit(commit);
html("<table summary='commit info' class='commit-info'>\n");
html("<tr><th>author</th><td>");
html_txt(info->author);
html(" ");
html_txt(info->author_email);
html("</td><td class='right'>");
cgit_print_date(info->author_date, FMT_LONGDATE);
html("</td></tr>\n");
html("<tr><th>committer</th><td>");
html_txt(info->committer);
html(" ");
html_txt(info->committer_email);
html("</td><td class='right'>");
cgit_print_date(info->committer_date, FMT_LONGDATE);
html("</td></tr>\n");
html("<tr><th>commit</th><td colspan='2' class='sha1'>");
tmp = sha1_to_hex(commit->object.sha1);
cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp);
html(" (");
cgit_patch_link("patch", NULL, NULL, NULL, tmp);
html(")</td></tr>\n");
html("<tr><th>tree</th><td colspan='2' class='sha1'>");
tmp = xstrdup(hex);
cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
ctx.qry.head, tmp, NULL);
html("</td></tr>\n");
for (p = commit->parents; p ; p = p->next) {
parent = lookup_commit_reference(p->item->object.sha1);
if (!parent) {
html("<tr><td colspan='3'>");
cgit_print_error("Error reading parent commit");
html("</td></tr>");
continue;
}
html("<tr><th>parent</th>"
"<td colspan='2' class='sha1'>");
cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
ctx.qry.head, sha1_to_hex(p->item->object.sha1));
html(" (");
cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
sha1_to_hex(p->item->object.sha1), NULL);
html(")</td></tr>");
}
if (ctx.repo->snapshots) {
html("<tr><th>download</th><td colspan='2' class='sha1'>");
cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
hex, ctx.repo->snapshots);
html("</td></tr>");
}
html("</table>\n");
html("<div class='commit-subject'>");
html_txt(info->subject);
html("</div>");
html("<div class='commit-msg'>");
html_txt(info->msg);
html("</div>");
- if (!(commit->parents && commit->parents->next && commit->parents->next->next)) {
- html("<div class='diffstat-header'>Diffstat</div>");
- html("<table summary='diffstat' class='diffstat'>");
- max_changes = 0;
- cgit_diff_commit(commit, inspect_filepair);
- for(i = 0; i<files; i++)
- print_fileinfo(&items[i]);
- html("</table>");
- html("<div class='diffstat-summary'>");
- htmlf("%d files changed, %d insertions, %d deletions",
- files, total_adds, total_rems);
- html("</div>");
- cgit_print_diff(ctx.qry.sha1,
- sha1_to_hex(commit->parents->item->object.sha1),
- NULL);
+ if (!(commit->parents && commit->parents->next &&
+ commit->parents->next->next)) {
+ tmp = sha1_to_hex(commit->parents->item->object.sha1);
+ cgit_print_diff(ctx.qry.sha1, tmp, NULL);
}
cgit_free_commitinfo(info);
}
diff --git a/ui-diff.c b/ui-diff.c
index 2a22009..12e78b1 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,150 +1,289 @@
/* ui-diff.c: show diff between two blobs
*
* 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"
unsigned char old_rev_sha1[20];
unsigned char new_rev_sha1[20];
+static int files, slots;
+static int total_adds, total_rems, max_changes;
+static int lines_added, lines_removed;
+static char *curr_rev;
+
+static struct fileinfo {
+ char status;
+ unsigned char old_sha1[20];
+ unsigned char new_sha1[20];
+ unsigned short old_mode;
+ unsigned short new_mode;
+ char *old_path;
+ char *new_path;
+ unsigned int added;
+ unsigned int removed;
+} *items;
+
+
+static void print_fileinfo(struct fileinfo *info)
+{
+ char *class;
+
+ switch (info->status) {
+ case DIFF_STATUS_ADDED:
+ class = "add";
+ break;
+ case DIFF_STATUS_COPIED:
+ class = "cpy";
+ break;
+ case DIFF_STATUS_DELETED:
+ class = "del";
+ break;
+ case DIFF_STATUS_MODIFIED:
+ class = "upd";
+ break;
+ case DIFF_STATUS_RENAMED:
+ class = "mov";
+ break;
+ case DIFF_STATUS_TYPE_CHANGED:
+ class = "typ";
+ break;
+ case DIFF_STATUS_UNKNOWN:
+ class = "unk";
+ break;
+ case DIFF_STATUS_UNMERGED:
+ class = "stg";
+ break;
+ default:
+ die("bug: unhandled diff status %c", info->status);
+ }
+
+ html("<tr>");
+ htmlf("<td class='mode'>");
+ if (is_null_sha1(info->new_sha1)) {
+ cgit_print_filemode(info->old_mode);
+ } else {
+ cgit_print_filemode(info->new_mode);
+ }
+
+ if (info->old_mode != info->new_mode &&
+ !is_null_sha1(info->old_sha1) &&
+ !is_null_sha1(info->new_sha1)) {
+ html("<span class='modechange'>[");
+ cgit_print_filemode(info->old_mode);
+ html("]</span>");
+ }
+ htmlf("</td><td class='%s'>", class);
+ cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, curr_rev,
+ NULL, info->new_path);
+ if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
+ htmlf(" (%s from %s)",
+ info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
+ info->old_path);
+ html("</td><td class='right'>");
+ htmlf("%d", info->added + info->removed);
+ html("</td><td class='graph'>");
+ htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
+ htmlf("<td class='add' style='width: %.1f%%;'/>",
+ info->added * 100.0 / max_changes);
+ htmlf("<td class='rem' style='width: %.1f%%;'/>",
+ info->removed * 100.0 / max_changes);
+ htmlf("<td class='none' style='width: %.1f%%;'/>",
+ (max_changes - info->removed - info->added) * 100.0 / max_changes);
+ html("</tr></table></td></tr>\n");
+}
+
+static void count_diff_lines(char *line, int len)
+{
+ if (line && (len > 0)) {
+ if (line[0] == '+')
+ lines_added++;
+ else if (line[0] == '-')
+ lines_removed++;
+ }
+}
+
+static void inspect_filepair(struct diff_filepair *pair)
+{
+ files++;
+ lines_added = 0;
+ lines_removed = 0;
+ cgit_diff_files(pair->one->sha1, pair->two->sha1, count_diff_lines);
+ if (files >= slots) {
+ if (slots == 0)
+ slots = 4;
+ else
+ slots = slots * 2;
+ items = xrealloc(items, slots * sizeof(struct fileinfo));
+ }
+ items[files-1].status = pair->status;
+ hashcpy(items[files-1].old_sha1, pair->one->sha1);
+ hashcpy(items[files-1].new_sha1, pair->two->sha1);
+ items[files-1].old_mode = pair->one->mode;
+ items[files-1].new_mode = pair->two->mode;
+ items[files-1].old_path = xstrdup(pair->one->path);
+ items[files-1].new_path = xstrdup(pair->two->path);
+ items[files-1].added = lines_added;
+ items[files-1].removed = lines_removed;
+ if (lines_added + lines_removed > max_changes)
+ max_changes = lines_added + lines_removed;
+ total_adds += lines_added;
+ total_rems += lines_removed;
+}
+
+void cgit_print_diffstat(const unsigned char *old_sha1,
+ const unsigned char *new_sha1)
+{
+ int i;
+
+ html("<div class='diffstat-header'>Diffstat</div>");
+ html("<table summary='diffstat' class='diffstat'>");
+ max_changes = 0;
+ cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL);
+ for(i = 0; i<files; i++)
+ print_fileinfo(&items[i]);
+ html("</table>");
+ html("<div class='diffstat-summary'>");
+ htmlf("%d files changed, %d insertions, %d deletions",
+ files, total_adds, total_rems);
+ html("</div>");
+}
+
+
/*
* print a single line returned from xdiff
*/
static void print_line(char *line, int len)
{
char *class = "ctx";
char c = line[len-1];
if (line[0] == '+')
class = "add";
else if (line[0] == '-')
class = "del";
else if (line[0] == '@')
class = "hunk";
htmlf("<div class='%s'>", class);
line[len-1] = '\0';
html_txt(line);
html("</div>");
line[len-1] = c;
}
static void header(unsigned char *sha1, char *path1, int mode1,
unsigned char *sha2, char *path2, int mode2)
{
char *abbrev1, *abbrev2;
int subproject;
subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
html("<div class='head'>");
html("diff --git a/");
html_txt(path1);
html(" b/");
html_txt(path2);
if (is_null_sha1(sha1))
path1 = "dev/null";
if (is_null_sha1(sha2))
path2 = "dev/null";
if (mode1 == 0)
htmlf("<br/>new file mode %.6o", mode2);
if (mode2 == 0)
htmlf("<br/>deleted file mode %.6o", mode1);
if (!subproject) {
abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
htmlf("<br/>index %s..%s", abbrev1, abbrev2);
free(abbrev1);
free(abbrev2);
if (mode1 != 0 && mode2 != 0) {
htmlf(" %.6o", mode1);
if (mode2 != mode1)
htmlf("..%.6o", mode2);
}
html("<br/>--- a/");
if (mode1 != 0)
cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
sha1_to_hex(old_rev_sha1), path1);
else
html_txt(path1);
html("<br/>+++ b/");
if (mode2 != 0)
cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
sha1_to_hex(new_rev_sha1), path2);
else
html_txt(path2);
}
html("</div>");
}
static void filepair_cb(struct diff_filepair *pair)
{
header(pair->one->sha1, pair->one->path, pair->one->mode,
pair->two->sha1, pair->two->path, pair->two->mode);
if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
if (S_ISGITLINK(pair->one->mode))
print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
if (S_ISGITLINK(pair->two->mode))
print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
return;
}
if (cgit_diff_files(pair->one->sha1, pair->two->sha1, print_line))
cgit_print_error("Error running diff");
}
void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
{
enum object_type type;
unsigned long size;
struct commit *commit, *commit2;
if (!new_rev)
new_rev = ctx.qry.head;
get_sha1(new_rev, new_rev_sha1);
type = sha1_object_info(new_rev_sha1, &size);
if (type == OBJ_BAD) {
cgit_print_error(fmt("Bad object name: %s", new_rev));
return;
}
- if (type != OBJ_COMMIT) {
- cgit_print_error(fmt("Unhandled object type: %s",
- typename(type)));
- return;
- }
-
commit = lookup_commit_reference(new_rev_sha1);
if (!commit || parse_commit(commit))
cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
if (old_rev)
get_sha1(old_rev, old_rev_sha1);
else if (commit->parents && commit->parents->item)
hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
else
hashclr(old_rev_sha1);
if (!is_null_sha1(old_rev_sha1)) {
type = sha1_object_info(old_rev_sha1, &size);
if (type == OBJ_BAD) {
cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
return;
}
commit2 = lookup_commit_reference(old_rev_sha1);
if (!commit2 || parse_commit(commit2))
cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
}
+ cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
+
html("<table summary='diff' class='diff'>");
html("<tr><td>");
cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
html("</td></tr>");
html("</table>");
}
diff --git a/ui-diff.h b/ui-diff.h
index 2307322..70b2926 100644
--- a/ui-diff.h
+++ b/ui-diff.h
@@ -1,7 +1,10 @@
#ifndef UI_DIFF_H
#define UI_DIFF_H
+extern void cgit_print_diffstat(const unsigned char *old_sha1,
+ const unsigned char *new_sha1);
+
extern void cgit_print_diff(const char *new_hex, const char *old_hex,
const char *prefix);
#endif /* UI_DIFF_H */
diff --git a/ui-repolist.c b/ui-repolist.c
index 98009c0..3f78e28 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -1,141 +1,144 @@
/* 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)
*/
#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;
fgets(buf, sizeof(buf), f);
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)
{
char *path;
struct stat s;
path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
if (stat(path, &s) == 0) {
cgit_print_age(read_agefile(path), -1, NULL);
return;
}
path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch);
if (stat(path, &s) != 0)
return;
cgit_print_age(s.st_mtime, -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;
}
void print_header(int columns)
{
- if (ctx.cfg.index_header) {
- htmlf("<tr class='nohover'><td colspan='%d' class='include-block'>",
- columns);
- html_include(ctx.cfg.index_header);
- html("</td></tr>");
- }
html("<tr class='nohover'>"
"<th class='left'>Name</th>"
"<th class='left'>Description</th>"
"<th class='left'>Owner</th>"
"<th class='left'>Idle</th>");
if (ctx.cfg.enable_index_links)
html("<th class='left'>Links</th>");
html("</tr>\n");
}
void cgit_print_repolist()
{
int i, columns = 4, hits = 0, header = 0;
char *last_group = NULL;
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);
+
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))
continue;
if (!header++)
print_header(columns);
hits++;
if ((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))) {
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");
html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
html_txt(ctx.repo->name);
html_link_close();
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>");
html_link_open(cgit_repourl(ctx.repo->url),
NULL, "button");
html("summary</a>");
cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
0, NULL, NULL);
cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
html("</td>");
}
html("</tr>\n");
}
html("</table>");
if (!hits)
cgit_print_error("No repositories found");
cgit_print_docend();
}
+
+void cgit_print_site_readme()
+{
+ if (ctx.cfg.root_readme)
+ html_include(ctx.cfg.root_readme);
+}
diff --git a/ui-repolist.h b/ui-repolist.h
index c23e5d2..5b1e542 100644
--- a/ui-repolist.h
+++ b/ui-repolist.h
@@ -1,6 +1,7 @@
#ifndef UI_REPOLIST_H
#define UI_REPOLIST_H
extern void cgit_print_repolist();
+extern void cgit_print_site_readme();
#endif /* UI_REPOLIST_H */
diff --git a/ui-shared.c b/ui-shared.c
index 8a804c2..d08ede9 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,596 +1,646 @@
/* ui-shared.c: common web output functions
*
* Copyright (C) 2006 Lars Hjemli
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
#include "cgit.h"
#include "cmd.h"
#include "html.h"
const char cgit_doctype[] =
"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
static char *http_date(time_t t)
{
static char day[][4] =
{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
static char month[][4] =
{"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
struct tm *tm = gmtime(&t);
return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
tm->tm_hour, tm->tm_min, tm->tm_sec);
}
void cgit_print_error(char *msg)
{
html("<div class='error'>");
html_txt(msg);
html("</div>\n");
}
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)
+{
+ 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);
+ }
+}
+
+static void site_link(char *page, char *name, char *title, char *class,
+ char *search)
+{
+ html("<a");
+ if (title) {
+ html(" title='");
+ html_attr(title);
+ html("'");
+ }
+ if (class) {
+ html(" class='");
+ html_attr(class);
+ html("'");
+ }
+ html(" href='");
+ site_url(page, search);
+ html("'>");
+ html_txt(name);
+ html("</a>");
+}
+
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_attr(ctx.cfg.virtual_root);
if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
html("/");
html_attr(ctx.repo->url);
if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
html("/");
if (page) {
html(page);
html("/");
if (path)
html_attr(path);
}
} else {
html(ctx.cfg.script_name);
html("?url=");
html_attr(ctx.repo->url);
if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
html("/");
if (page) {
html(page);
html("/");
if (path)
html_attr(path);
}
delim = "&amp;";
}
if (head && strcmp(head, ctx.repo->defbranch)) {
html(delim);
html("h=");
html_attr(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_attr(rev);
}
html("'>");
html_txt(name);
html("</a>");
}
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_log_link(char *name, char *title, char *class, char *head,
char *rev, char *path, int ofs, char *grep, char *pattern)
{
char *delim;
delim = repolink(title, class, "log", head, path);
if (rev && strcmp(rev, ctx.qry.head)) {
html(delim);
html("id=");
html_attr(rev);
delim = "&";
}
if (grep && pattern) {
html(delim);
html("qt=");
html_attr(grep);
delim = "&";
html(delim);
html("q=");
html_attr(pattern);
}
if (ofs > 0) {
html(delim);
html("ofs=");
htmlf("%d", ofs);
}
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_attr(new_rev);
delim = "&amp;";
}
if (old_rev) {
html(delim);
html("id2=");
html_attr(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, *arg, *url;
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));
return;
} else if (obj->type == OBJ_TREE) {
page = "tree";
arg = "id";
} else if (obj->type == OBJ_TAG) {
page = "tag";
arg = "id";
} else {
page = "blob";
arg = "id";
}
url = cgit_pageurl(ctx.qry.repo, page,
fmt("%s=%s", arg, sha1_to_hex(obj->sha1)));
html_link_open(url, NULL, NULL);
htmlf("%s %s", typename(obj->type),
sha1_to_hex(obj->sha1));
html_link_close();
}
void cgit_print_date(time_t secs, char *format)
{
char buf[64];
struct tm *time;
if (!secs)
return;
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);
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.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");
html("</head>\n");
html("<body>\n");
}
void cgit_print_docend()
{
html("</div>\n</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 && 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 (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) {
reporevlink(NULL, ctx->repo->name, NULL, hc(cmd, "summary"),
ctx->qry.head, NULL, NULL);
html(" : ");
html_txt(ctx->qry.page);
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(" colspan='2'>");
html_txt(ctx->repo->desc);
} else {
html(">");
- html_txt("a fast webinterface for the git dscm");
+ 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) {
reporevlink(NULL, "summary", NULL, hc(cmd, "summary"),
ctx->qry.head, NULL, NULL);
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);
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_attr(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 {
- html("<a class='active' href='");
- html_attr(cgit_rooturl());
- html("'>index</a>\n");
+ site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL);
+ if (ctx->cfg.root_readme)
+ site_link("about", "about", NULL, hc(cmd, "about"), NULL);
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);
html("<br/>");
}
}
diff --git a/ui-summary.c b/ui-summary.c
index 318148a..ad0b4a7 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -1,31 +1,35 @@
/* ui-summary.c: functions for generating repo summary page
*
* 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-log.h"
#include "ui-refs.h"
void cgit_print_summary()
{
- if (ctx.repo->readme) {
- html("<div id='summary'>");
- html_include(ctx.repo->readme);
- html("</div>");
- }
html("<table summary='repository info' class='list nowrap'>");
cgit_print_branches(ctx.cfg.summary_branches);
html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
cgit_print_tags(ctx.cfg.summary_tags);
if (ctx.cfg.summary_log > 0) {
html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
NULL, NULL, 0);
}
html("</table>");
}
+
+void cgit_print_repo_readme()
+{
+ if (ctx.repo->readme) {
+ html("<div id='summary'>");
+ html_include(ctx.repo->readme);
+ html("</div>");
+ }
+}
diff --git a/ui-summary.h b/ui-summary.h
index 37aedd2..3e13039 100644
--- a/ui-summary.h
+++ b/ui-summary.h
@@ -1,6 +1,7 @@
#ifndef UI_SUMMARY_H
#define UI_SUMMARY_H
extern void cgit_print_summary();
+extern void cgit_print_repo_readme();
#endif /* UI_SUMMARY_H */