aboutsummaryrefslogtreecommitdiffstatshomepage
Commit message (Collapse)AuthorAge
...
* CGIT 0.8.3v0.8.3Lars Hjemli2009-09-13
| | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* Merge branch 'stable'Lars Hjemli2009-09-13
|\
| * CGIT 0.8.2.2v0.8.2.2Lars Hjemli2009-09-13
| | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | Merge branch 'lh/repo-scan'Lars Hjemli2009-09-13
|\ \
| * | cgit.c: respect repo-local 'snapshots' option for --scan-pathLars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The repo-specific 'snapshots' option is bitwise AND'ed with the global 'snapshots' option during parsing, and since the global cgitrc hasn't been parsed when --scan-path is processed the global 'snapshots' will always be 0 (i.e. no repo-specific 'snapshots' setting will have any effect). This patch fixes the issue by setting the global 'snapshots' mask to 0xFF (hence relying on later parsing of the generated cgitrc repolist to do the right thing). Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cgit.c: only print first line of repo.desc in print_repo()Lars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | | | | Since repo.desc might have been populated by reading the 'description' file in GIT_DIR, it may contain newlines. And by printing the literal value, we may then generate an invalid cgitrc include-file. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | Add and use cgit_find_stats_periodname() in print_repo()Lars Hjemli2009-08-24
| | | | | | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cgit.c: generate repo.snapshots in print_repo()Lars Hjemli2009-08-24
| | | | | | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cgit.c: add missing options to print_repo()Lars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | | | | Note: print_repo() still ignores repo.max-stats and repo.snapshots, which both requires additional work since these settings are represented internally as an enum and a bitmap. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | shared.c: initialize cgit_repo structs properlyLars Hjemli2009-08-24
| | | | | | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | Add config option 'enable-filter-overrides'Lars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | This option must be enabled if repo-specific cgitrc files should be allowed to override any of the 'filter' options. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cgitrc.5.txt: fix markup errorsLars Hjemli2009-08-24
| | | | | | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | ui-repolist: handle empty sections similar to NULL sectionsLars Hjemli2009-08-24
| | | | | | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | Add support for repo-local cgitrc fileLars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | When recursively scanning a directory tree looking for git repositories, cgit will now parse cgitrc files found within such repositories. The repo-specific config files can include any repo-specific options except 'repo.url' and 'repo.path'. Also, in such config files the 'repo.' prefix can not be used, i.e. the valid options then becomes: * name * clone-url * desc * ower * defbranch * snapshots * enable-log-filecount * enable-log-linecount * max-stats * module-link * section * about-filter * commit-filter * source-filter * readme Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cgit.c: refactor repo_config() from config_cb()Lars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The new function repo_config() is used to handle all 'simple' repo options, for the following reasons: * code readability * parser performance * upcoming support for repo-local cgitrc files during scanning Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | ui-repolist.c: sort by section name, repo name as defaultLars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | | | | When no sorting is requested by the client, cgit will now sort by section name followed by repo name. This allows repos to be registered/ discovered independently of their display order. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | Add config option 'repo.section'Lars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | This option can be used to specify the section name for the current repository. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | Introduce 'section' as canonical spelling for 'repo.group'Lars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | | | | The 'repo.' prefix should be reserved for repo-specific options, but the option 'repo.group' must still be honored to stay backwards compatible. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | Add support for --scan-path command line optionLars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | This is an alias for --scan-tree (which might be deprecated in the future). Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | Rename 'repo.scan' to 'scan-path'Lars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | The 'repo.' prefix will soon be reserved for repo-specific config options. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cgitrc.5.txt: document repo.scan and cache-scanrc-ttlLars Hjemli2009-08-24
| | | | | | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cgit.c: add support for caching autodetected repositoriesLars Hjemli2009-08-24
| | | | | | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cgit.c: make print_repolist() and print_repo() reusable for cachingLars Hjemli2009-08-24
| | | | | | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cache.h: export hash_str()Lars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | This function will be reused to cache the result of tree scanning. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | cgit.c: add support for cgitrc option 'repo.scan'Lars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | When specified, the specified path will be scanned for repositories. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | scan-tree: detect non-bare repository and stop scanning earlyLars Hjemli2009-08-24
| | | | | | | | | | | | | | | | | | | | | We don't want to descend into every subdirectory of a git repository with a workdir. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | | Merge branch 'fp/linenumbers'Lars Hjemli2009-09-13
|\ \ \
| * | | cgit.css: make the blob display in tree view a bit prettierLars Hjemli2009-08-21
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Adding some padding to the linenumbers while right-aligning them and removing the background color makes the page more readable. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | | Rename "linenumbers" to "enable-tree-linenumbers", change default to "1"Lars Hjemli2009-08-21
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This makes the name of the cgitrc option more descriptive and at the same time changes the default from "0" to "1" in an attempt to stay backwards compatible - prior to the introduction of "source-filter" and "linenumbers", cgit always generated linenumber links in the tree view, but now this feature can be turned off (one might want to do this if the source-filter performs line-wrapping etc). While at it, the documentation is updated to match the surrounding descriptions. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | | Add 'linenumbers' config optionFlorian Pritz2009-08-21
| | | | | | | | | | | | | | | | | | | | Signed-off-by: Florian Pritz <bluewind@xssn.at> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * | | ui-tree.c: show line numbers when highlightingFlorian Pritz2009-08-21
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | When source-filter is enabled, cgit currently will not display linenumbers in the tree view. This patch restores the linenumber function. Signed-off-by: Florian Pritz <bluewind@xssn.at> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | | | Use GIT-1.6.4.3Lars Hjemli2009-09-13
| | | | | | | | | | | | | | | | Signed-off-by: Lars Hjemli <larsh@slackbox.hjemli.net>
* | | | Merge branch 'stable'Lars Hjemli2009-09-06
|\ \ \ \ | |_|/ / |/| | / | | |/ | |/|
| * | ui-plain.c: only return the blob with the specified pathLars Hjemli2009-09-06
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | When a path to a directory was specified for the 'plain' view, each blob in the directory used to be returned to the client. This patch fixes the issue by matching the path of each blob against the requested path. Noticed-by: Lars Stoltenow <penma@penma.de> Signed-off-by: Lars Hjemli <larsh@slackbox.hjemli.net>
* | | cgitrc.5.txt: fix description and markup for 'snapshots' optionLars Hjemli2009-08-24
| |/ |/| | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | scan-tree: split the pw_gecos field at the ',' to get the real nameStefan Naewe2009-08-20
| | | | | | | | | | Signed-off-by: Stefan Naewe <stefan.naewe@atlas-elektronik.com> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | cgit.c: fix caching keyed on PATH_INFO with no QUERY_STRINGLars Hjemli2009-08-20
| | | | | | | | | | | | | | | | When generating a hash for caching, ctx.qry.raw is used as key. And since cgit_parse_url() zero-terminates it's argument (after the repo path), ctx.qry.raw must xstrdup(ctx.qry.url). Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | Add and use a common readfile() functionLars Hjemli2009-08-18
| | | | | | | | | | | | | | | | | | | | | | | | This function is used to read the full content of a textfile into a newly allocated buffer (with zerotermination). It replaces the earlier readfile() in scan-tree.c (which was rather error-prone[1]), and is reused by read_agefile() in ui-repolist.c. 1: No checks for EINTR and EAGAIN, fixed-size buffer Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | Merge branch 'stable'Lars Hjemli2009-08-18
|\|
| * truncate buffer before reading empty filesSimon Arlott2009-08-18
| | | | | | | | | | | | | | | | | | | | If readfile() reads an empty file, fgets() won't truncate the buffer and it'll still contain the contents of the previously read file. [lh: fixed similar issue in ui-repolist.c] Signed-off-by: Simon Arlott <simon@fire.lp0.eu> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | Merge branch 'stable'Lars Hjemli2009-08-18
|\|
| * ui-shared: don't print header <img/> if there isn't a logo definedMatthew Metnetsky2009-08-18
| | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | Merge branch 'stable'Lars Hjemli2009-08-17
|\|
| * cgit.c: do not segfault on unexpected query-string formatLars Hjemli2009-08-17
| | | | | | | | | | | | | | | | The querystring_cb() function will be invoked with a NULL value when the querystring contains a name not followed by a '='. Such a value used to cause a segfault, which this patch fixes. Signed-off-by: Lars Hjemli <hjemli@gmail.com>
| * ui-tag.c: do not segfault when id is missing from query-stringLars Hjemli2009-08-17
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The purpose of the tag page is to print info about a specific tag, but if no tag was specified on the query-string cgit used to segfault. With this patch, cgit will fallback to the value of the 'h' parameter instead (which is never NULL due to prepare_repo_cmd() in cgit.c). It will now also verify that the specified tagname is in fact a valid ref in the 'refs/tags/' namespace, i.e. specifying 'id=master' will trigger a 'Bad tag reference' error. Noticed-by: Eric Wong <normalperson@yhbt.net> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | ui-log.c: handle lightweight tags when printing commit decorationsLars Hjemli2009-08-16
| | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | ui-refs.c: improve handling of lightweight tagsStefan Bühler2009-08-16
| | | | | | | | | | | | | | | | | | | | When a lightweight tag is referencing a commit object, cgit now uses the commit date when comparing tag age. Also, the commitdate and author info is printed in the refs view, making lightweight tags appear similar to annotated tags. Signed-off-by: Stefan Bühler <lighttpd@stbuehler.de> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | Use GIT-1.6.3.4Lars Hjemli2009-08-16
| | | | | | | | Signed-off-by: Lars Hjemli <hjemli@gmail.com>
* | Merge branch 'lh/embedded'Lars Hjemli2009-08-11
|\ \
| * | ui-shared: add support for header/footer options when embedded=1Lars Hjemli2009-08-11
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | When embedded=1, cgit used to ignore the header and footer options. But honoring these options when embedded=1 makes it possible to "frame" the html fragment generated by cgit with any kind of static content, i.e. it should become easier to integrate cgit with site-specfic layouts. Original-patch-by: Mark Constable <markc@renta.net> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
class="k">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-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-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); } } 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.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_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; }