-rw-r--r-- | cgit.c | 2 | ||||
-rw-r--r-- | cgit.h | 3 | ||||
-rw-r--r-- | shared.c | 5 | ||||
-rw-r--r-- | ui-diff.c | 14 | ||||
-rw-r--r-- | ui-log.c | 2 | ||||
-rw-r--r-- | ui-patch.c | 2 | ||||
-rw-r--r-- | ui-shared.c | 14 |
7 files changed, 34 insertions, 8 deletions
@@ -1,747 +1,749 @@ /* cgit.c: cgi for the git scm * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "cache.h" #include "cmd.h" #include "configfile.h" #include "html.h" #include "ui-shared.h" #include "ui-stats.h" #include "scan-tree.h" const char *cgit_version = CGIT_VERSION; void add_mimetype(const char *name, const char *value) { struct string_list_item *item; item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); item->util = xstrdup(value); } struct cgit_filter *new_filter(const char *cmd, int extra_args) { struct cgit_filter *f; if (!cmd || !cmd[0]) return NULL; f = xmalloc(sizeof(struct cgit_filter)); f->cmd = xstrdup(cmd); f->argv = xmalloc((2 + extra_args) * sizeof(char *)); f->argv[0] = f->cmd; f->argv[1] = NULL; return f; } static void process_cached_repolist(const char *path); void repo_config(struct cgit_repo *repo, const char *name, const char *value) { if (!strcmp(name, "name")) repo->name = xstrdup(value); else if (!strcmp(name, "clone-url")) repo->clone_url = xstrdup(value); else if (!strcmp(name, "desc")) repo->desc = xstrdup(value); else if (!strcmp(name, "owner")) repo->owner = xstrdup(value); else if (!strcmp(name, "defbranch")) repo->defbranch = xstrdup(value); else if (!strcmp(name, "snapshots")) repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-log-filecount")) repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); else if (!strcmp(name, "enable-log-linecount")) repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); else if (!strcmp(name, "enable-remote-branches")) repo->enable_remote_branches = atoi(value); else if (!strcmp(name, "enable-subject-links")) repo->enable_subject_links = atoi(value); else if (!strcmp(name, "max-stats")) repo->max_stats = cgit_find_stats_period(value, NULL); else if (!strcmp(name, "module-link")) repo->module_link= xstrdup(value); else if (!strcmp(name, "section")) repo->section = xstrdup(value); else if (!strcmp(name, "readme") && value != NULL) { if (*value == '/') repo->readme = xstrdup(value); else repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); } else if (ctx.cfg.enable_filter_overrides) { if (!strcmp(name, "about-filter")) repo->about_filter = new_filter(value, 0); else if (!strcmp(name, "commit-filter")) repo->commit_filter = new_filter(value, 0); else if (!strcmp(name, "source-filter")) repo->source_filter = new_filter(value, 1); } } void config_cb(const char *name, const char *value) { if (!strcmp(name, "section") || !strcmp(name, "repo.group")) ctx.cfg.section = xstrdup(value); else if (!strcmp(name, "repo.url")) ctx.repo = cgit_add_repo(value); else if (ctx.repo && !strcmp(name, "repo.path")) ctx.repo->path = trim_end(value, '/'); else if (ctx.repo && !prefixcmp(name, "repo.")) repo_config(ctx.repo, name + 5, value); else if (!strcmp(name, "root-title")) ctx.cfg.root_title = xstrdup(value); else if (!strcmp(name, "root-desc")) ctx.cfg.root_desc = xstrdup(value); else if (!strcmp(name, "root-readme")) ctx.cfg.root_readme = xstrdup(value); else if (!strcmp(name, "css")) ctx.cfg.css = xstrdup(value); else if (!strcmp(name, "favicon")) ctx.cfg.favicon = xstrdup(value); else if (!strcmp(name, "footer")) ctx.cfg.footer = xstrdup(value); else if (!strcmp(name, "head-include")) ctx.cfg.head_include = xstrdup(value); else if (!strcmp(name, "header")) ctx.cfg.header = xstrdup(value); else if (!strcmp(name, "logo")) ctx.cfg.logo = xstrdup(value); else if (!strcmp(name, "index-header")) ctx.cfg.index_header = xstrdup(value); else if (!strcmp(name, "index-info")) ctx.cfg.index_info = xstrdup(value); else if (!strcmp(name, "logo-link")) ctx.cfg.logo_link = xstrdup(value); else if (!strcmp(name, "module-link")) ctx.cfg.module_link = xstrdup(value); else if (!strcmp(name, "virtual-root")) { 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, "noplainemail")) ctx.cfg.noplainemail = atoi(value); else if (!strcmp(name, "noheader")) ctx.cfg.noheader = atoi(value); else if (!strcmp(name, "snapshots")) ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-filter-overrides")) ctx.cfg.enable_filter_overrides = atoi(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, "enable-remote-branches")) ctx.cfg.enable_remote_branches = atoi(value); else if (!strcmp(name, "enable-subject-links")) ctx.cfg.enable_subject_links = atoi(value); else if (!strcmp(name, "enable-tree-linenumbers")) ctx.cfg.enable_tree_linenumbers = atoi(value); else if (!strcmp(name, "max-stats")) ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 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-scanrc-ttl")) ctx.cfg.cache_scanrc_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, "about-filter")) ctx.cfg.about_filter = new_filter(value, 0); else if (!strcmp(name, "commit-filter")) ctx.cfg.commit_filter = new_filter(value, 0); else if (!strcmp(name, "embedded")) ctx.cfg.embedded = atoi(value); else if (!strcmp(name, "max-atom-items")) ctx.cfg.max_atom_items = 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-blob-size")) ctx.cfg.max_blob_size = 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, "scan-path")) if (!ctx.cfg.nocache && ctx.cfg.cache_size) process_cached_repolist(value); else scan_tree(value, repo_config); else if (!strcmp(name, "source-filter")) ctx.cfg.source_filter = new_filter(value, 1); 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, "side-by-side-diffs")) ctx.cfg.ssdiff = 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 (!prefixcmp(name, "mimetype.")) add_mimetype(name + 9, value); else if (!strcmp(name, "include")) parse_configfile(value, config_cb); } static void querystring_cb(const char *name, const char *value) { if (!value) 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")) { if (*value == '/') value++; 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); } else if (!strcmp(name, "period")) { ctx.qry.period = xstrdup(value); } else if (!strcmp(name, "ss")) { ctx.qry.ssdiff = atoi(value); } else if (!strcmp(name, "all")) { ctx.qry.show_all = atoi(value); + } else if (!strcmp(name, "context")) { + ctx.qry.context = atoi(value); } } char *xstrdupn(const char *str) { return (str ? xstrdup(str) : NULL); } 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_scanrc_ttl = 15; ctx->cfg.cache_static_ttl = -1; ctx->cfg.css = "/cgit.css"; ctx->cfg.logo = "/cgit.png"; ctx->cfg.local_time = 0; ctx->cfg.enable_tree_linenumbers = 1; 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.max_blob_size = 0; ctx->cfg.max_stats = 0; 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.section = ""; ctx->cfg.summary_branches = 10; ctx->cfg.summary_log = 10; ctx->cfg.summary_tags = 10; ctx->cfg.max_atom_items = 10; ctx->cfg.ssdiff = 0; ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); ctx->env.https = xstrdupn(getenv("HTTPS")); ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 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; ctx->page.etag = NULL; memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); if (ctx->env.script_name) ctx->cfg.script_name = ctx->env.script_name; if (ctx->env.query_string) ctx->qry.raw = ctx->env.query_string; if (!ctx->env.cgit_config) ctx->env.cgit_config = CGIT_CONFIG; } 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; ctx->page.status = 404; ctx->page.statusmsg = "not found"; 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"; 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_vpath is set, assume ctx->qry.path contains a "virtual" * in-project path limit to be made available at ctx->qry.vpath. * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL). */ ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL; 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); } char *build_snapshot_setting(int bitmap) { const struct cgit_snapshot_format *f; char *result = xstrdup(""); char *tmp; int len; for (f = cgit_snapshot_formats; f->suffix; f++) { if (f->bit & bitmap) { tmp = result; result = xstrdup(fmt("%s%s ", tmp, f->suffix)); free(tmp); } } len = strlen(result); if (len) result[len - 1] = '\0'; return result; } char *get_first_line(char *txt) { char *t = xstrdup(txt); char *p = strchr(t, '\n'); if (p) *p = '\0'; return t; } void print_repo(FILE *f, struct cgit_repo *repo) { fprintf(f, "repo.url=%s\n", repo->url); fprintf(f, "repo.name=%s\n", repo->name); fprintf(f, "repo.path=%s\n", repo->path); if (repo->owner) fprintf(f, "repo.owner=%s\n", repo->owner); if (repo->desc) { char *tmp = get_first_line(repo->desc); fprintf(f, "repo.desc=%s\n", tmp); free(tmp); } if (repo->readme) fprintf(f, "repo.readme=%s\n", repo->readme); if (repo->defbranch) fprintf(f, "repo.defbranch=%s\n", repo->defbranch); if (repo->module_link) fprintf(f, "repo.module-link=%s\n", repo->module_link); if (repo->section) fprintf(f, "repo.section=%s\n", repo->section); if (repo->clone_url) fprintf(f, "repo.clone-url=%s\n", repo->clone_url); fprintf(f, "repo.enable-log-filecount=%d\n", repo->enable_log_filecount); fprintf(f, "repo.enable-log-linecount=%d\n", repo->enable_log_linecount); if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); if (repo->snapshots != ctx.cfg.snapshots) { char *tmp = build_snapshot_setting(repo->snapshots); fprintf(f, "repo.snapshots=%s\n", tmp); free(tmp); } if (repo->max_stats != ctx.cfg.max_stats) fprintf(f, "repo.max-stats=%s\n", cgit_find_stats_periodname(repo->max_stats)); fprintf(f, "\n"); } void print_repolist(FILE *f, struct cgit_repolist *list, int start) { int i; for(i = start; i < list->count; i++) print_repo(f, &list->repos[i]); } /* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' * and return 0 on success. */ static int generate_cached_repolist(const char *path, const char *cached_rc) { char *locked_rc; int idx; FILE *f; locked_rc = xstrdup(fmt("%s.lock", cached_rc)); f = fopen(locked_rc, "wx"); if (!f) { /* Inform about the error unless the lockfile already existed, * since that only means we've got concurrent requests. */ if (errno != EEXIST) fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", locked_rc, strerror(errno), errno); return errno; } idx = cgit_repolist.count; scan_tree(path, repo_config); print_repolist(f, &cgit_repolist, idx); if (rename(locked_rc, cached_rc)) fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", locked_rc, cached_rc, strerror(errno), errno); fclose(f); return 0; } static void process_cached_repolist(const char *path) { struct stat st; char *cached_rc; time_t age; cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, hash_str(path))); if (stat(cached_rc, &st)) { /* Nothing is cached, we need to scan without forking. And * if we fail to generate a cached repolist, we need to * invoke scan_tree manually. */ if (generate_cached_repolist(path, cached_rc)) scan_tree(path, repo_config); return; } parse_configfile(cached_rc, config_cb); /* If the cached configfile hasn't expired, lets exit now */ age = time(NULL) - st.st_mtime; if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) return; /* The cached repolist has been parsed, but it was old. So lets * rescan the specified path and generate a new cached repolist * in a child-process to avoid latency for the current request. */ if (fork()) return; exit(generate_cached_repolist(path, cached_rc)); } 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 (!strcmp(argv[i], "--nohttp")) { ctx.env.no_http = "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) || !strncmp(argv[i], "--scan-path=", 12)) { /* HACK: the global snapshot bitmask defines the * set of allowed snapshot formats, but the config * file hasn't been parsed yet so the mask is * currently 0. By setting all bits high before * scanning we make sure that any in-repo cgitrc * snapshot setting is respected by scan_tree(). * BTW: we assume that there'll never be more than * 255 different snapshot formats supported by cgit... */ ctx.cfg.snapshots = 0xFF; scan++; scan_tree(argv[i] + 12, repo_config); } } if (scan) { qsort(cgit_repolist.repos, cgit_repolist.count, sizeof(struct cgit_repo), cmp_repos); print_repolist(stdout, &cgit_repolist, 0); 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; return ctx.cfg.cache_repo_ttl; } int main(int argc, const char **argv) { const char *path; char *qry; int err, ttl; prepare_context(&ctx); cgit_repolist.length = 0; cgit_repolist.count = 0; cgit_repolist.repos = NULL; cgit_parse_args(argc, argv); parse_configfile(ctx.env.cgit_config, config_cb); ctx.repo = NULL; http_parse_querystring(ctx.qry.raw, querystring_cb); /* If virtual-root isn't specified in cgitrc, lets pretend * that virtual-root equals SCRIPT_NAME. */ if (!ctx.cfg.virtual_root) ctx.cfg.virtual_root = ctx.cfg.script_name; /* If no url parameter is specified on the querystring, lets * use PATH_INFO as url. This allows cgit to work with virtual * urls without the need for rewriterules in the webserver (as * long as PATH_INFO is included in the cache lookup key). */ path = ctx.env.path_info; if (!ctx.qry.url && path) { if (path[0] == '/') path++; ctx.qry.url = xstrdup(path); if (ctx.qry.raw) { qry = ctx.qry.raw; ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); free(qry); } else ctx.qry.raw = xstrdup(ctx.qry.url); cgit_parse_url(ctx.qry.url); } ttl = calc_ttl(); ctx.page.expires += ttl*60; if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) ctx.cfg.nocache = 1; 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) cgit_print_error(fmt("Error processing page: %s (%d)", strerror(err), err)); return err; } @@ -1,304 +1,305 @@ #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 <string-list.h> #include <xdiff-interface.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_filter { char *cmd; char **argv; int old_stdout; int pipe_fh[2]; int pid; int exitstatus; }; struct cgit_repo { char *url; char *name; char *path; char *desc; char *owner; char *defbranch; char *module_link; char *readme; char *section; char *clone_url; int snapshots; int enable_log_filecount; int enable_log_linecount; int enable_remote_branches; int enable_subject_links; int max_stats; time_t mtime; struct cgit_filter *about_filter; struct cgit_filter *commit_filter; struct cgit_filter *source_filter; }; typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, const char *value); 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; char *period; int ofs; int nohead; char *sort; int showmsg; int ssdiff; int show_all; + int context; char *vpath; }; struct cgit_config { char *agefile; char *cache_root; char *clone_prefix; char *css; char *favicon; char *footer; char *head_include; char *header; char *index_header; char *index_info; char *logo; char *logo_link; char *module_link; char *robots; char *root_title; char *root_desc; char *root_readme; char *script_name; char *section; 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_scanrc_ttl; int cache_static_ttl; int embedded; int enable_filter_overrides; int enable_index_links; int enable_log_filecount; int enable_log_linecount; int enable_remote_branches; int enable_subject_links; int enable_tree_linenumbers; int local_time; int max_atom_items; int max_repo_count; int max_commit_count; int max_lock_attempts; int max_msg_len; int max_repodesc_len; int max_blob_size; int max_stats; int nocache; int noplainemail; int noheader; int renamelimit; int snapshots; int summary_branches; int summary_log; int summary_tags; int ssdiff; struct string_list mimetypes; struct cgit_filter *about_filter; struct cgit_filter *commit_filter; struct cgit_filter *source_filter; }; struct cgit_page { time_t modified; time_t expires; size_t size; char *mimetype; char *charset; char *filename; char *etag; char *title; int status; char *statusmsg; }; struct cgit_environment { char *cgit_config; char *http_host; char *https; char *no_http; char *path_info; char *query_string; char *request_method; char *script_name; char *server_name; char *server_port; }; struct cgit_context { struct cgit_environment env; 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, unsigned long *old_size, unsigned long *new_size, - int *binary, linediff_fn fn); + int *binary, int context, 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); extern int cgit_open_filter(struct cgit_filter *filter); extern int cgit_close_filter(struct cgit_filter *filter); extern int readfile(const char *path, char **buf, size_t *size); #endif /* CGIT_H */ @@ -1,433 +1,434 @@ /* 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 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]; memset(ret, 0, sizeof(struct cgit_repo)); ret->url = trim_end(url, '/'); ret->name = ret->url; ret->path = NULL; ret->desc = "[no description]"; ret->owner = NULL; ret->section = ctx.cfg.section; 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->enable_remote_branches = ctx.cfg.enable_remote_branches; ret->enable_subject_links = ctx.cfg.enable_subject_links; ret->max_stats = ctx.cfg.max_stats; ret->module_link = ctx.cfg.module_link; ret->readme = NULL; ret->mtime = -1; ret->about_filter = ctx.cfg.about_filter; ret->commit_filter = ctx.cfg.commit_filter; ret->source_filter = ctx.cfg.source_filter; 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, unsigned long *old_size, - unsigned long *new_size, int *binary, linediff_fn fn) + unsigned long *new_size, int *binary, int context, + 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; *old_size = file1.size; *new_size = file2.size; if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { *binary = 1; if (file1.size) free(file1.ptr); if (file2.size) free(file2.ptr); return 0; } memset(&diff_params, 0, sizeof(diff_params)); memset(&emit_params, 0, sizeof(emit_params)); memset(&emit_cb, 0, sizeof(emit_cb)); diff_params.flags = XDF_NEED_MINIMAL; - emit_params.ctxlen = 3; + emit_params.ctxlen = context > 0 ? context : 3; emit_params.flags = XDL_EMIT_FUNCNAMES; emit_cb.outf = filediff_cb; emit_cb.priv = fn; xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); if (file1.size) free(file1.ptr); if (file2.size) free(file2.ptr); return 0; } void cgit_diff_tree(const 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; } int cgit_open_filter(struct cgit_filter *filter) { filter->old_stdout = chk_positive(dup(STDOUT_FILENO), "Unable to duplicate STDOUT"); chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess"); filter->pid = chk_non_negative(fork(), "Unable to create subprocess"); if (filter->pid == 0) { close(filter->pipe_fh[1]); chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO), "Unable to use pipe as STDIN"); execvp(filter->cmd, filter->argv); die("Unable to exec subprocess %s: %s (%d)", filter->cmd, strerror(errno), errno); } close(filter->pipe_fh[0]); chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO), "Unable to use pipe as STDOUT"); close(filter->pipe_fh[1]); return 0; } int cgit_close_filter(struct cgit_filter *filter) { chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), "Unable to restore STDOUT"); close(filter->old_stdout); if (filter->pid < 0) return 0; waitpid(filter->pid, &filter->exitstatus, 0); if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus)) return 0; die("Subprocess %s exited abnormally", filter->cmd); } /* Read the content of the specified file into a newly allocated buffer, * zeroterminate the buffer and return 0 on success, errno otherwise. */ int readfile(const char *path, char **buf, size_t *size) { int fd, e; struct stat st; fd = open(path, O_RDONLY); if (fd == -1) return errno; if (fstat(fd, &st)) { e = errno; close(fd); return e; } if (!S_ISREG(st.st_mode)) { close(fd); return EISDIR; } *buf = xmalloc(st.st_size + 1); *size = read_in_full(fd, *buf, st.st_size); e = errno; (*buf)[*size] = '\0'; close(fd); return (*size == st.st_size ? 0 : e); } @@ -1,355 +1,363 @@ /* 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" #include "ui-ssdiff.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 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; unsigned long old_size; unsigned long new_size; int binary:1; } *items; static int use_ssdiff = 0; 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, ctx.qry.sha1, ctx.qry.sha2, info->new_path, 0); 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'>"); if (info->binary) { htmlf("bin</td><td class='graph'>%d -> %d bytes", info->old_size, info->new_size); return; } 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) { int binary = 0; unsigned long old_size = 0; unsigned long new_size = 0; files++; lines_added = 0; lines_removed = 0; cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, - &binary, count_diff_lines); + &binary, 0, 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; items[files-1].old_size = old_size; items[files-1].new_size = new_size; items[files-1].binary = binary; 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, const char *prefix) { - int i; + int i, save_context = ctx.qry.context; html("<div class='diffstat-header'>"); cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.sha2, NULL, 0); if (prefix) htmlf(" (limited to '%s')", prefix); + html(" ("); + ctx.qry.context = (save_context > 0 ? save_context : 3) << 1; + cgit_self_link("more", NULL, NULL, &ctx); + html("/"); + ctx.qry.context = (save_context > 3 ? save_context : 3) >> 1; + cgit_self_link("less", NULL, NULL, &ctx); + ctx.qry.context = save_context; + html(" context)"); html("</div>"); html("<table summary='diffstat' class='diffstat'>"); max_changes = 0; cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix); 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 print_ssdiff_link() { if (!strcmp(ctx.qry.page, "diff")) { if (use_ssdiff) cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1); else cgit_diff_link("Side-by-side diff", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1); } } static void filepair_cb(struct diff_filepair *pair) { unsigned long old_size = 0; unsigned long new_size = 0; int binary = 0; linediff_fn print_line_fn = print_line; if (use_ssdiff) { cgit_ssdiff_header_begin(); print_line_fn = cgit_ssdiff_line_cb; } header(pair->one->sha1, pair->one->path, pair->one->mode, pair->two->sha1, pair->two->path, pair->two->mode); if (use_ssdiff) cgit_ssdiff_header_end(); if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { if (S_ISGITLINK(pair->one->mode)) print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); if (S_ISGITLINK(pair->two->mode)) print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); if (use_ssdiff) cgit_ssdiff_footer(); return; } if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, - &new_size, &binary, print_line_fn)) + &new_size, &binary, ctx.qry.context, print_line_fn)) cgit_print_error("Error running diff"); if (binary) { if (use_ssdiff) html("<tr><td colspan='4'>Binary files differ</td></tr>"); else html("Binary files differ"); } if (use_ssdiff) cgit_ssdiff_footer(); } 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; } 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))); } if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) use_ssdiff = 1; print_ssdiff_link(); cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix); if (use_ssdiff) { html("<table summary='ssdiff' class='ssdiff'>"); } else { html("<table summary='diff' class='diff'>"); html("<tr><td>"); } cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); if (!use_ssdiff) html("</td></tr>"); html("</table>"); } @@ -1,237 +1,237 @@ /* 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) { unsigned long old_size = 0; unsigned long new_size = 0; int binary = 0; files++; if (ctx.repo->enable_log_linecount) cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, - &new_size, &binary, count_lines); + &new_size, &binary, 0, count_lines); } void show_commit_decorations(struct commit *commit) { struct name_decoration *deco; static char buf[1024]; buf[sizeof(buf) - 1] = 0; deco = lookup_decoration(&name_decoration, &commit->object); while (deco) { if (!prefixcmp(deco->name, "refs/heads/")) { strncpy(buf, deco->name + 11, sizeof(buf) - 1); cgit_log_link(buf, NULL, "branch-deco", buf, NULL, ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); } else if (!prefixcmp(deco->name, "tag: refs/tags/")) { strncpy(buf, deco->name + 15, sizeof(buf) - 1); cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); } else if (!prefixcmp(deco->name, "refs/tags/")) { strncpy(buf, deco->name + 10, sizeof(buf) - 1); cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); } else if (!prefixcmp(deco->name, "refs/remotes/")) { strncpy(buf, deco->name + 13, sizeof(buf) - 1); cgit_log_link(buf, NULL, "remote-deco", NULL, sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); } else { strncpy(buf, deco->name, sizeof(buf) - 1); cgit_commit_link(buf, NULL, "deco", ctx.qry.head, sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); } deco = deco->next; } } void print_commit(struct commit *commit) { struct commitinfo *info; char *tmp; int cols = 2; info = cgit_parse_commit(commit); htmlf("<tr%s><td>", ctx.qry.showmsg ? " class='logheader'" : ""); tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp); html_link_open(tmp, NULL, NULL); cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); html_link_close(); 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), ctx.qry.vpath, 0); show_commit_decorations(commit); 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); load_ref_decorations(DECORATE_FULL_REFS); rev.show_decorations = 1; 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"); if (pager) { html(" ("); cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath, 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.vpath, ofs - cnt, ctx.qry.grep, ctx.qry.search, ctx.qry.showmsg); html(" "); } if ((commit = get_revision(&rev)) != NULL) { cgit_log_link("[next]", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath, ofs + cnt, ctx.qry.grep, 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, ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); html("</td></tr>\n"); } } @@ -1,131 +1,131 @@ /* ui-patch.c: generate patch view * * Copyright (C) 2007 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 void print_line(char *line, int len) { char c = line[len-1]; line[len-1] = '\0'; htmlf("%s\n", line); 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)); htmlf("diff --git a/%s b/%s\n", path1, path2); if (is_null_sha1(sha1)) path1 = "dev/null"; if (is_null_sha1(sha2)) path2 = "dev/null"; if (mode1 == 0) htmlf("new file mode %.6o\n", mode2); if (mode2 == 0) htmlf("deleted file mode %.6o\n", mode1); if (!subproject) { abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); htmlf("index %s..%s", abbrev1, abbrev2); free(abbrev1); free(abbrev2); if (mode1 != 0 && mode2 != 0) { htmlf(" %.6o", mode1); if (mode2 != mode1) htmlf("..%.6o", mode2); } htmlf("\n--- a/%s\n", path1); htmlf("+++ b/%s\n", path2); } } static void filepair_cb(struct diff_filepair *pair) { unsigned long old_size = 0; unsigned long new_size = 0; int binary = 0; 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, &old_size, - &new_size, &binary, print_line)) + &new_size, &binary, 0, print_line)) html("Error running diff"); if (binary) html("Binary files differ\n"); } void cgit_print_patch(char *hex, const char *prefix) { struct commit *commit; struct commitinfo *info; unsigned char sha1[20], old_sha1[20]; char *patchname; if (!hex) hex = ctx.qry.head; 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); if (commit->parents && commit->parents->item) hashcpy(old_sha1, commit->parents->item->object.sha1); else hashclr(old_sha1); patchname = fmt("%s.patch", sha1_to_hex(sha1)); ctx.page.mimetype = "text/plain"; ctx.page.filename = patchname; cgit_print_http_headers(&ctx); htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1)); htmlf("From: %s", info->author); if (!ctx.cfg.noplainemail) { htmlf(" %s", info->author_email); } html("\n"); html("Date: "); cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); htmlf("Subject: %s\n\n", info->subject); if (info->msg && *info->msg) { htmlf("%s", info->msg); if (info->msg[strlen(info->msg) - 1] != '\n') html("\n"); } html("---\n"); if (prefix) htmlf("(limited to '%s')\n\n", prefix); cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix); html("--\n"); htmlf("cgit %s\n", CGIT_VERSION); cgit_free_commitinfo(info); } diff --git a/ui-shared.c b/ui-shared.c index e991799..c99bcec 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -1,884 +1,898 @@ /* 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", "Nov", "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(const char *msg) { html("<div class='error'>"); html_txt(msg); html("</div>\n"); } char *cgit_httpscheme() { if (ctx.env.https && !strcmp(ctx.env.https, "on")) return "https://"; else return "http://"; } char *cgit_hosturl() { if (ctx.env.http_host) return ctx.env.http_host; if (!ctx.env.server_name) return NULL; if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) return ctx.env.server_name; return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port)); } 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(const char *page, const 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(const char *page, const char *name, const char *title, const char *class, const 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(const char *name, const char *title, const char *class, const char *pattern, int ofs) { site_link(NULL, name, title, class, pattern, ofs); } static char *repolink(const char *title, const char *class, const char *page, const char *head, const 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 = "&"; } if (head && strcmp(head, ctx.repo->defbranch)) { html(delim); html("h="); html_url_arg(head); delim = "&"; } return fmt("%s", delim); } static void reporevlink(const char *page, const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { char *delim; delim = repolink(title, class, page, head, path); if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(rev); } html("'>"); html_txt(name); html("</a>"); } void cgit_summary_link(const char *name, const char *title, const char *class, const char *head) { reporevlink(NULL, name, title, class, head, NULL, NULL); } void cgit_tag_link(const char *name, const char *title, const char *class, const char *head, const char *rev) { reporevlink("tag", name, title, class, head, rev, NULL); } void cgit_tree_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { reporevlink("tree", name, title, class, head, rev, path); } void cgit_plain_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { reporevlink("plain", name, title, class, head, rev, path); } void cgit_log_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path, int ofs, const char *grep, const char *pattern, int showmsg) { 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, const char *title, const char *class, const char *head, const char *rev, const char *path, int toggle_ssdiff) { 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] = '.'; } char *delim; delim = repolink(title, class, "commit", head, path); if (rev && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(rev); delim = "&"; } if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { html(delim); html("ss=1"); + delim = "&"; + } + if (ctx.qry.context > 0 && ctx.qry.context != 3) { + html(delim); + html("context="); + htmlf("%d", ctx.qry.context); + delim = "&"; } html("'>"); html_txt(name); html("</a>"); } void cgit_refs_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { reporevlink("refs", name, title, class, head, rev, path); } void cgit_snapshot_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *archivename) { reporevlink("snapshot", name, title, class, head, rev, archivename); } void cgit_diff_link(const char *name, const char *title, const char *class, const char *head, const char *new_rev, const char *old_rev, const char *path, int toggle_ssdiff) { char *delim; delim = repolink(title, class, "diff", head, path); if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(new_rev); delim = "&"; } if (old_rev) { html(delim); html("id2="); html_url_arg(old_rev); delim = "&"; } if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { html(delim); html("ss=1"); + delim = "&"; + } + if (ctx.qry.context > 0 && ctx.qry.context != 3) { + html(delim); + html("context="); + htmlf("%d", ctx.qry.context); + delim = "&"; } html("'>"); html_txt(name); html("</a>"); } void cgit_patch_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { reporevlink("patch", name, title, class, head, rev, path); } void cgit_stats_link(const char *name, const char *title, const char *class, const char *head, const char *path) { reporevlink("stats", name, title, class, head, NULL, path); } void cgit_self_link(char *name, const char *title, const char *class, struct cgit_context *ctx) { if (!strcmp(ctx->qry.page, "repolist")) return cgit_index_link(name, title, class, ctx->qry.search, ctx->qry.ofs); else if (!strcmp(ctx->qry.page, "summary")) return cgit_summary_link(name, title, class, ctx->qry.head); else if (!strcmp(ctx->qry.page, "tag")) return cgit_tag_link(name, title, class, ctx->qry.head, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL); else if (!strcmp(ctx->qry.page, "tree")) return cgit_tree_link(name, title, class, ctx->qry.head, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, ctx->qry.path); else if (!strcmp(ctx->qry.page, "plain")) return cgit_plain_link(name, title, class, ctx->qry.head, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, ctx->qry.path); else if (!strcmp(ctx->qry.page, "log")) return cgit_log_link(name, title, class, ctx->qry.head, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, ctx->qry.path, ctx->qry.ofs, ctx->qry.grep, ctx->qry.search, ctx->qry.showmsg); else if (!strcmp(ctx->qry.page, "commit")) return cgit_commit_link(name, title, class, ctx->qry.head, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, ctx->qry.path, 0); else if (!strcmp(ctx->qry.page, "patch")) return cgit_patch_link(name, title, class, ctx->qry.head, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, ctx->qry.path); else if (!strcmp(ctx->qry.page, "refs")) return cgit_refs_link(name, title, class, ctx->qry.head, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, ctx->qry.path); else if (!strcmp(ctx->qry.page, "snapshot")) return cgit_snapshot_link(name, title, class, ctx->qry.head, ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, ctx->qry.path); else if (!strcmp(ctx->qry.page, "diff")) return cgit_diff_link(name, title, class, ctx->qry.head, ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path, 0); else if (!strcmp(ctx->qry.page, "stats")) return cgit_stats_link(name, title, class, ctx->qry.head, ctx->qry.path); /* Don't known how to make link for this page */ repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path); html("><!-- cgit_self_link() doesn't know how to make link for page '"); html_txt(ctx->qry.page); html("' -->"); html_txt(name); html("</a>"); } void cgit_object_link(struct object *obj) { 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...", shortrev), NULL, NULL, ctx.qry.head, fullrev, NULL, 0); return; } else if (obj->type == OBJ_TREE) page = "tree"; else if (obj->type == OBJ_TAG) page = "tag"; else page = "blob"; 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, const 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, const 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->env.no_http && !strcmp(ctx->env.no_http, "1")) return; if (ctx->page.status) htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg); 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)); if (ctx->page.etag) htmlf("ETag: \"%s\"\n", ctx->page.etag); html("\n"); if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD")) exit(0); } void cgit_print_docstart(struct cgit_context *ctx) { if (ctx->cfg.embedded) { if (ctx->cfg.header) html_include(ctx->cfg.header); return; } 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='"); html(cgit_httpscheme()); html_attr(cgit_hosturl()); html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath, fmt("h=%s", ctx->qry.head))); html("' type='application/atom+xml'/>\n"); } if (ctx->cfg.head_include) html_include(ctx->cfg.head_include); html("</head>\n"); html("<body>\n"); if (ctx->cfg.header) html_include(ctx->cfg.header); } void cgit_print_docend() { html("</div> <!-- class=content -->\n"); if (ctx.cfg.embedded) { html("</div> <!-- id=cgit -->\n"); if (ctx.cfg.footer) html_include(ctx.cfg.footer); return; } 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("</div> <!-- id=cgit -->\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&path=%s", sha1_to_hex(fileid), buf)); html_link_open(url, NULL, "menu"); html_txt(strlpart(buf, 20)); html_link_close(); return 0; } void cgit_add_hidden_formfields(int incl_head, int incl_search, const char *page) { char *url; if (!ctx.cfg.virtual_root) { url = fmt("%s/%s", ctx.qry.repo, page); if (ctx.qry.vpath) url = fmt("%s/%s", url, ctx.qry.vpath); 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); } } static const char *hc(struct cgit_context *ctx, const char *page) { return strcmp(ctx->qry.page, page) ? NULL : "active"; } static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path) { char *old_path = ctx->qry.path; char *p = path, *q, *end = path + strlen(path); ctx->qry.path = NULL; cgit_self_link("root", NULL, NULL, ctx); ctx->qry.path = p = path; while (p < end) { if (!(q = strchr(p, '/'))) q = end; *q = '\0'; html_txt("/"); cgit_self_link(p, NULL, NULL, ctx); if (q < end) *q = '/'; p = q + 1; } ctx->qry.path = old_path; } static void print_header(struct cgit_context *ctx) { html("<table id='header'>\n"); html("<tr>\n"); if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 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"); cgit_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"); } void cgit_print_pageheader(struct cgit_context *ctx) { html("<div id='cgit'>"); if (!ctx->cfg.noheader) print_header(ctx); html("<table class='tabs'><tr><td>\n"); if (ctx->repo) { cgit_summary_link("summary", NULL, hc(ctx, "summary"), ctx->qry.head); cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head, ctx->qry.sha1, NULL); cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head, NULL, ctx->qry.vpath, 0, NULL, NULL, ctx->qry.showmsg); cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath); cgit_commit_link("commit", NULL, hc(ctx, "commit"), ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0); cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head, ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0); if (ctx->repo->max_stats) cgit_stats_link("stats", NULL, hc(ctx, "stats"), ctx->qry.head, ctx->qry.vpath); if (ctx->repo->readme) reporevlink("about", "about", NULL, hc(ctx, "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.vpath, NULL)); html("'>\n"); cgit_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(ctx, "repolist"), NULL, 0); if (ctx->cfg.root_readme) site_link("about", "about", NULL, hc(ctx, "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"); if (ctx->qry.vpath) { html("<div class='path'>"); html("path: "); cgit_print_path_crumbs(ctx, ctx->qry.vpath); html("</div>"); } 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 *prefix; char *filename; unsigned char sha1[20]; if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 && (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1])) hex++; prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex)); for (f = cgit_snapshot_formats; f->suffix; f++) { if (!(snapshots & f->bit)) continue; filename = fmt("%s%s", prefix, f->suffix); cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); html("<br/>"); } } |