diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | cache.c | 51 | ||||
-rw-r--r-- | cgit.c | 31 | ||||
-rw-r--r-- | cgit.css | 4 | ||||
-rw-r--r-- | cgit.h | 7 | ||||
-rw-r--r-- | cgit.js | 68 | ||||
-rw-r--r-- | cgitrc.5.txt | 35 | ||||
-rw-r--r-- | cmd.c | 18 | ||||
-rwxr-xr-x | filters/html-converters/md2html | 7 | ||||
m--------- | git | 0 | ||||
-rw-r--r-- | html.c | 2 | ||||
-rw-r--r-- | robots.txt | 1 | ||||
-rw-r--r-- | shared.c | 12 | ||||
-rwxr-xr-x | tests/setup.sh | 12 | ||||
-rwxr-xr-x | tests/t0107-snapshot.sh | 17 | ||||
-rw-r--r-- | ui-atom.c | 34 | ||||
-rw-r--r-- | ui-blame.c | 25 | ||||
-rw-r--r-- | ui-blob.c | 20 | ||||
-rw-r--r-- | ui-commit.c | 3 | ||||
-rw-r--r-- | ui-log.c | 15 | ||||
-rw-r--r-- | ui-plain.c | 7 | ||||
-rw-r--r-- | ui-repolist.c | 2 | ||||
-rw-r--r-- | ui-shared.c | 77 | ||||
-rw-r--r-- | ui-shared.h | 2 | ||||
-rw-r--r-- | ui-stats.c | 51 | ||||
-rw-r--r-- | ui-tree.c | 49 |
26 files changed, 376 insertions, 177 deletions
@@ -14,7 +14,7 @@ htmldir = $(docdir) pdfdir = $(docdir) mandir = $(prefix)/share/man SHA1_HEADER = <openssl/sha.h> -GIT_VER = 2.29.0 +GIT_VER = 2.39.0 GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz INSTALL = install COPYTREE = cp -r @@ -87,6 +87,7 @@ install: all $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css + $(INSTALL) -m 0644 cgit.js $(DESTDIR)$(CGIT_DATA_PATH)/cgit.js $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png $(INSTALL) -m 0644 favicon.ico $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico $(INSTALL) -m 0644 robots.txt $(DESTDIR)$(CGIT_DATA_PATH)/robots.txt @@ -85,40 +85,45 @@ static int close_slot(struct cache_slot *slot) /* Print the content of the active cache slot (but skip the key). */ static int print_slot(struct cache_slot *slot) { + off_t off; #ifdef HAVE_LINUX_SENDFILE - off_t start_off; - int ret; + off_t size; +#endif + + off = slot->keylen + 1; - start_off = slot->keylen + 1; +#ifdef HAVE_LINUX_SENDFILE + size = slot->cache_st.st_size; do { - ret = sendfile(STDOUT_FILENO, slot->cache_fd, &start_off, - slot->cache_st.st_size - start_off); + ssize_t ret; + ret = sendfile(STDOUT_FILENO, slot->cache_fd, &off, size - off); if (ret < 0) { if (errno == EAGAIN || errno == EINTR) continue; + /* Fall back to read/write on EINVAL or ENOSYS */ + if (errno == EINVAL || errno == ENOSYS) + break; return errno; } - return 0; + if (off == size) + return 0; } while (1); -#else - ssize_t i, j; +#endif - i = lseek(slot->cache_fd, slot->keylen + 1, SEEK_SET); - if (i != slot->keylen + 1) + if (lseek(slot->cache_fd, off, SEEK_SET) != off) return errno; do { - i = j = xread(slot->cache_fd, slot->buf, sizeof(slot->buf)); - if (i > 0) - j = xwrite(STDOUT_FILENO, slot->buf, i); - } while (i > 0 && j == i); - - if (i < 0 || j != i) - return errno; - else - return 0; -#endif + ssize_t ret; + ret = xread(slot->cache_fd, slot->buf, sizeof(slot->buf)); + if (ret < 0) + return errno; + if (ret == 0) + return 0; + if (write_in_full(STDOUT_FILENO, slot->buf, ret) < 0) + return errno; + } while (1); } /* Check if the slot has expired */ @@ -401,12 +406,12 @@ int cache_process(int size, const char *path, const char *key, int ttl, static char *sprintftime(const char *format, time_t time) { static char buf[64]; - struct tm *tm; + struct tm tm; if (!time) return NULL; - tm = gmtime(&time); - strftime(buf, sizeof(buf)-1, format, tm); + gmtime_r(&time, &tm); + strftime(buf, sizeof(buf)-1, format, &tm); return buf; } @@ -56,6 +56,8 @@ static void repo_config(struct cgit_repo *repo, const char *name, const char *va repo->homepage = xstrdup(value); else if (!strcmp(name, "defbranch")) repo->defbranch = xstrdup(value); + else if (!strcmp(name, "default-page")) + repo->default_page = xstrdup(value); else if (!strcmp(name, "extra-head-content")) repo->extra_head_content = xstrdup(value); else if (!strcmp(name, "snapshots")) @@ -141,8 +143,12 @@ static void config_cb(const char *name, const char *value) ctx.cfg.root_desc = xstrdup(value); else if (!strcmp(name, "root-readme")) ctx.cfg.root_readme = xstrdup(value); + else if (!strcmp(name, "root-default-page")) + ctx.cfg.root_default_page = xstrdup(value); else if (!strcmp(name, "css")) - ctx.cfg.css = xstrdup(value); + string_list_append(&ctx.cfg.css, xstrdup(value)); + else if (!strcmp(name, "js")) + string_list_append(&ctx.cfg.js, xstrdup(value)); else if (!strcmp(name, "favicon")) ctx.cfg.favicon = xstrdup(value); else if (!strcmp(name, "footer")) @@ -155,6 +161,8 @@ static void config_cb(const char *name, const char *value) ctx.cfg.logo = xstrdup(value); else if (!strcmp(name, "logo-link")) ctx.cfg.logo_link = xstrdup(value); + else if (!strcmp(name, "default-page")) + ctx.cfg.default_page = xstrdup(value); else if (!strcmp(name, "module-link")) ctx.cfg.module_link = xstrdup(value); else if (!strcmp(name, "strict-export")) @@ -237,9 +245,11 @@ static void config_cb(const char *name, const char *value) 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")) + else if (!strcmp(name, "max-repo-count")) { ctx.cfg.max_repo_count = atoi(value); - else if (!strcmp(name, "max-commit-count")) + if (ctx.cfg.max_repo_count <= 0) + ctx.cfg.max_repo_count = INT_MAX; + } else if (!strcmp(name, "max-commit-count")) ctx.cfg.max_commit_count = atoi(value); else if (!strcmp(name, "project-list")) ctx.cfg.project_list = xstrdup(expand_macros(value)); @@ -376,7 +386,7 @@ static void prepare_context(void) ctx.cfg.case_sensitive_sort = 1; ctx.cfg.branch_sort = 0; ctx.cfg.commit_sort = 0; - ctx.cfg.css = "/cgit.css"; + ctx.cfg.default_page= "summary"; ctx.cfg.logo = "/cgit.png"; ctx.cfg.favicon = "/favicon.ico"; ctx.cfg.local_time = 0; @@ -397,6 +407,7 @@ static void prepare_context(void) 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.root_default_page = "repolist"; ctx.cfg.scan_hidden_path = 0; ctx.cfg.script_name = CGIT_SCRIPT_NAME; ctx.cfg.section = ""; @@ -428,7 +439,7 @@ static void prepare_context(void) ctx.page.modified = time(NULL); ctx.page.expires = ctx.page.modified; ctx.page.etag = NULL; - string_list_init(&ctx.cfg.mimetypes, 1); + string_list_init_dup(&ctx.cfg.mimetypes); if (ctx.env.script_name) ctx.cfg.script_name = xstrdup(ctx.env.script_name); if (ctx.env.query_string) @@ -507,9 +518,11 @@ static inline void parse_readme(const char *readme, char **filename, char **ref, /* Check if the readme is tracked in the git repo. */ colon = strchr(readme, ':'); if (colon && strlen(colon) > 1) { - /* If it starts with a colon, we want to use - * the default branch */ - if (colon == readme && repo->defbranch) + /* If it starts with a colon, we want to use head given + * from query or the default branch */ + if (colon == readme && ctx.qry.head) + *ref = xstrdup(ctx.qry.head); + else if (colon == readme && repo->defbranch) *ref = xstrdup(repo->defbranch); else *ref = xstrndup(readme, colon - readme); @@ -806,6 +819,8 @@ static void print_repo(FILE *f, struct cgit_repo *repo) } if (repo->defbranch) fprintf(f, "repo.defbranch=%s\n", repo->defbranch); + if (repo->default_page) + fprintf(f, "repo.default-page=%s\n", repo->default_page); if (repo->extra_head_content) fprintf(f, "repo.extra-head-content=%s\n", repo->extra_head_content); if (repo->module_link) @@ -363,6 +363,10 @@ div#cgit table.blame td.lines > div > pre { top: 0; } +div#cgit table.blame .oid { + font-size: 100%; +} + div#cgit table.bin-blob { margin-top: 0.5em; border: solid 1px black; @@ -25,6 +25,7 @@ #include <utf8.h> #include <notes.h> #include <graph.h> +#include <inttypes.h> /* Add isgraph(x) to Git's sane ctype support (see git-compat-util.h) */ #undef isgraph @@ -86,6 +87,7 @@ struct cgit_repo { char *owner; char *homepage; char *defbranch; + char *default_page; char *module_link; struct string_list readme; char *section; @@ -195,7 +197,7 @@ struct cgit_config { char *cache_root; char *clone_prefix; char *clone_url; - char *css; + char *default_page; char *favicon; char *footer; char *head_include; @@ -206,10 +208,12 @@ struct cgit_config { char *module_link; char *project_list; struct string_list readme; + struct string_list css; char *robots; char *root_title; char *root_desc; char *root_readme; + char *root_default_page; char *script_name; char *section; char *repository_sort; @@ -264,6 +268,7 @@ struct cgit_config { int branch_sort; int commit_sort; struct string_list mimetypes; + struct string_list js; struct cgit_filter *about_filter; struct cgit_filter *commit_filter; struct cgit_filter *source_filter; @@ -0,0 +1,68 @@ +/* cgit.js: javacript functions for cgit + * + * Copyright (C) 2006-2018 cgit Development Team <cgit@lists.zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + +(function () { + +/* This follows the logic and suffixes used in ui-shared.c */ + +var age_classes = [ "age-mins", "age-hours", "age-days", "age-weeks", "age-months", "age-years" ]; +var age_suffix = [ "min.", "hours", "days", "weeks", "months", "years", "years" ]; +var age_next = [ 60, 3600, 24 * 3600, 7 * 24 * 3600, 30 * 24 * 3600, 365 * 24 * 3600, 365 * 24 * 3600 ]; +var age_limit = [ 7200, 24 * 7200, 7 * 24 * 7200, 30 * 24 * 7200, 365 * 25 * 7200, 365 * 25 * 7200 ]; +var update_next = [ 10, 5 * 60, 1800, 24 * 3600, 24 * 3600, 24 * 3600, 24 * 3600 ]; + +function render_age(e, age) { + var t, n; + + for (n = 0; n < age_classes.length; n++) + if (age < age_limit[n]) + break; + + t = Math.round(age / age_next[n]) + " " + age_suffix[n]; + + if (e.textContent != t) { + e.textContent = t; + if (n == age_classes.length) + n--; + if (e.className != age_classes[n]) + e.className = age_classes[n]; + } +} + +function aging() { + var n, next = 24 * 3600, + now_ut = Math.round((new Date().getTime() / 1000)); + + for (n = 0; n < age_classes.length; n++) { + var m, elems = document.getElementsByClassName(age_classes[n]); + + if (elems.length && update_next[n] < next) + next = update_next[n]; + + for (m = 0; m < elems.length; m++) { + var age = now_ut - elems[m].getAttribute("data-ut"); + + render_age(elems[m], age); + } + } + + /* + * We only need to come back when the age might have changed. + * Eg, if everything is counted in hours already, once per + * 5 minutes is accurate enough. + */ + + window.setTimeout(aging, next * 1000); +} + +document.addEventListener("DOMContentLoaded", function() { + /* we can do the aging on DOM content load since no layout dependency */ + aging(); +}, false); + +})(); diff --git a/cgitrc.5.txt b/cgitrc.5.txt index 33a6a8c..ac86864 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -126,7 +126,14 @@ commit-sort:: css:: Url which specifies the css document to include in all cgit pages. - Default value: "/cgit.css". + Default value: "/cgit.css". May be given multiple times, each + css URL path is added in the head section of the document in turn. + +default-page:: + Specifies the default page for repositories. This setting is only used + if `repo.default-page` is unspecified. Possible values: "about", + "summary", "refs", "log", "tree", "commit", "diff", "stats". Default + value: "summary". email-filter:: Specifies a command which will be invoked to format names and email @@ -238,6 +245,11 @@ include:: Name of a configfile to include before the rest of the current config- file is parsed. Default value: none. See also: "MACRO EXPANSION". +js:: + Url which specifies the javascript script document to include in all cgit + pages. Default value: "/cgit.js". Setting this to an empty string will + disable generation of the link to this file in the head section. + local-time:: Flag which, if set to "1", makes cgit print commit and tag times in the servers timezone. Default value: "0". @@ -269,7 +281,8 @@ max-message-length:: max-repo-count:: Specifies the number of entries to list per page on the repository - index page. Default value: "50". + index page. The value "0" shows all repositories without limitation. + Default value: "50". max-repodesc-length:: Specifies the maximum number of repo description characters to display @@ -352,6 +365,10 @@ robots:: Text used as content for the "robots" meta-tag. Default value: "index, nofollow". +root-default-page:: + Specifies the default root page. Possible values are "repolist" and + "about". Default value: "repolist". + root-desc:: Text printed below the heading on the repository index page. Default value: "a fast webinterface for the git dscm". @@ -475,6 +492,10 @@ repo.commit-sort:: ordering. If unset, the default ordering of "git log" is used. Default value: unset. +repo.default-page:: + Specifies the default page for the repository. Default value: global + default-page. + repo.defbranch:: The name of the default branch for this repository. If no such branch exists in the repository, the first branch name (when sorted) is used @@ -579,11 +600,11 @@ repo.readme:: verbatim as the "About" page for this repo. You may also specify a git refspec by head or by hash by prepending the refspec followed by a colon. For example, "master:docs/readme.mkd". If the value begins - with a colon, i.e. ":docs/readme.rst", the default branch of the - repository will be used. Sharing any file will expose that entire - directory tree to the "/about/PATH" endpoints, so be sure that there - are no non-public files located in the same directory as the readme - file. Default value: <readme>. + with a colon, i.e. ":docs/readme.rst", the head giving in query or + the default branch of the repository will be used. Sharing any file + will expose that entire directory tree to the "/about/PATH" endpoints, + so be sure that there are no non-public files located in the same + directory as the readme file. Default value: <readme>. repo.section:: Override the current section name for this repository. Default value: @@ -51,13 +51,10 @@ static void about_fn(void) free(redirect); } else if (ctx.repo->readme.nr) cgit_print_repo_readme(ctx.qry.path); - else if (ctx.repo->homepage) - cgit_redirect(ctx.repo->homepage, false); else { - char *currenturl = cgit_currenturl(); - char *redirect = fmtalloc("%s../", currenturl); + char *redirect = fmtalloc("%s%s/summary/", + ctx.cfg.virtual_root, ctx.repo->url); cgit_redirect(redirect, false); - free(currenturl); free(redirect); } } else @@ -195,10 +192,13 @@ struct cgit_cmd *cgit_get_cmd(void) int i; if (ctx.qry.page == NULL) { - if (ctx.repo) - ctx.qry.page = "summary"; - else - ctx.qry.page = "repolist"; + if (ctx.repo) { + if (ctx.repo->default_page && *ctx.repo->default_page) + ctx.qry.page = ctx.repo->default_page; + else + ctx.qry.page = ctx.cfg.default_page; + } else + ctx.qry.page = ctx.cfg.root_default_page; } for (i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) diff --git a/filters/html-converters/md2html b/filters/html-converters/md2html index dc20f42..59f43a8 100755 --- a/filters/html-converters/md2html +++ b/filters/html-converters/md2html @@ -86,11 +86,7 @@ div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div# margin: 15px 0; } .markdown-body hr { - background: transparent url("/dirty-shade.png") repeat-x 0 0; - border: 0 none; - color: #ccc; - height: 4px; - padding: 0; + border: 2px solid #ccc; } .markdown-body>h2:first-child, .markdown-body>h1:first-child, .markdown-body>h1:first-child+h2, .markdown-body>h3:first-child, .markdown-body>h4:first-child, .markdown-body>h5:first-child, .markdown-body>h6:first-child { margin-top: 0; @@ -301,6 +297,7 @@ markdown.markdownFromFile( "markdown.extensions.fenced_code", "markdown.extensions.codehilite", "markdown.extensions.tables", + "markdown.extensions.sane_lists", TocExtension(anchorlink=True)], extension_configs={ "markdown.extensions.codehilite":{"css_class":"highlight"}}) diff --git a/git b/git -Subproject 69986e19ffcfb9af674ae5180689ab7bbf92ed2 +Subproject c48035d29b4e524aed3a32f0403676f0d912886 @@ -59,7 +59,7 @@ char *fmt(const char *format, ...) va_start(args, format); len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args); va_end(args); - if (len > sizeof(buf[bufidx])) { + if (len >= sizeof(buf[bufidx])) { fprintf(stderr, "[html.c] string truncated: %s\n", format); exit(1); } @@ -1,3 +1,4 @@ User-agent: * Disallow: /*/snapshot/* +Disallow: /*/blame/* Allow: / @@ -341,9 +341,8 @@ void cgit_diff_tree(const struct object_id *old_oid, filepair_fn fn, const char *prefix, int ignorews) { struct diff_options opt; - struct pathspec_item item; + struct pathspec_item *item; - memset(&item, 0, sizeof(item)); diff_setup(&opt); opt.output_format = DIFF_FORMAT_CALLBACK; opt.detect_rename = 1; @@ -354,10 +353,11 @@ void cgit_diff_tree(const struct object_id *old_oid, opt.format_callback = cgit_diff_tree_cb; opt.format_callback_data = fn; if (prefix) { - item.match = xstrdup(prefix); - item.len = strlen(prefix); + item = xcalloc(1, sizeof(*item)); + item->match = xstrdup(prefix); + item->len = strlen(prefix); opt.pathspec.nr = 1; - opt.pathspec.items = &item; + opt.pathspec.items = item; } diff_setup_done(&opt); @@ -367,8 +367,6 @@ void cgit_diff_tree(const struct object_id *old_oid, diff_root_tree_oid(new_oid, "", &opt); diffcore_std(&opt); diff_flush(&opt); - - free(item.match); } void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix) diff --git a/tests/setup.sh b/tests/setup.sh index 5879348..8db810f 100755 --- a/tests/setup.sh +++ b/tests/setup.sh @@ -80,13 +80,17 @@ mkrepo() { git commit -m "commit $n" n=$(expr $n + 1) done - if test "$3" = "testplus" - then + case "$3" in + testplus) echo "hello" >a+b git add a+b git commit -m "add a+b" git branch "1+2" - fi + ;; + commit-graph) + git commit-graph write + ;; + esac ) } @@ -95,7 +99,7 @@ setup_repos() rm -rf cache mkdir -p cache mkrepo repos/foo 5 >/dev/null - mkrepo repos/bar 50 >/dev/null + mkrepo repos/bar 50 commit-graph >/dev/null mkrepo repos/foo+bar 10 testplus >/dev/null mkrepo "repos/with space" 2 >/dev/null mkrepo repos/filter 5 testplus >/dev/null diff --git a/tests/t0107-snapshot.sh b/tests/t0107-snapshot.sh index c164d3e..0811ec4 100755 --- a/tests/t0107-snapshot.sh +++ b/tests/t0107-snapshot.sh @@ -25,7 +25,7 @@ test_expect_success 'verify gzip format' ' test_expect_success 'untar' ' rm -rf master && - tar -xzf master.tar.gz + gzip -dc master.tar.gz | tar -xf - ' test_expect_success 'count files' ' @@ -61,13 +61,12 @@ test_expect_success LZIP 'strip off the header lines' ' ' test_expect_success LZIP 'verify lzip format' ' - lzip --test master.tar.lz && - cp master.tar.lz /tmp/. + lzip --test master.tar.lz ' test_expect_success LZIP 'untar' ' rm -rf master && - tar --lzip -xf master.tar.lz + lzip -dc master.tar.lz | tar -xf - ' test_expect_success LZIP 'count files' ' @@ -103,13 +102,12 @@ test_expect_success XZ 'strip off the header lines' ' ' test_expect_success XZ 'verify xz format' ' - xz --test master.tar.xz && - cp master.tar.xz /tmp/. + xz --test master.tar.xz ' test_expect_success XZ 'untar' ' rm -rf master && - tar --xz -xf master.tar.xz + xz -dc master.tar.xz | tar -xf - ' test_expect_success XZ 'count files' ' @@ -145,13 +143,12 @@ test_expect_success ZSTD 'strip off the header lines' ' ' test_expect_success ZSTD 'verify zstd format' ' - zstd --test master.tar.zst && - cp master.tar.zst /tmp/. + zstd --test master.tar.zst ' test_expect_success ZSTD 'untar' ' rm -rf master && - tar --zstd -xf master.tar.zst + zstd -dc master.tar.zst | tar -xf - ' test_expect_success ZSTD 'count files' ' @@ -67,17 +67,12 @@ static void add_entry(struct commit *commit, const char *host) html("'/>\n"); free(pageurl); } - htmlf("<id>%s</id>\n", hex); + html("<id>"); + html_txtf("urn:%s:%s", the_hash_algo->name, hex); + html("</id>\n"); html("<content type='text'>\n"); html_txt(info->msg); html("</content>\n"); - html("<content type='xhtml'>\n"); - html("<div xmlns='http://www.w3.org/1999/xhtml'>\n"); - html("<pre>\n"); - html_txt(info->msg); - html("</pre>\n"); - html("</div>\n"); - html("</content>\n"); html("</entry>\n"); cgit_free_commitinfo(info); } @@ -90,6 +85,7 @@ void cgit_print_atom(char *tip, const char *path, int max_count) struct commit *commit; struct rev_info rev; int argc = 2; + bool first = true; if (ctx.qry.show_all) argv[1] = "--all"; @@ -130,18 +126,30 @@ void cgit_print_atom(char *tip, const char *path, int max_count) html_txt(ctx.repo->desc); html("</subtitle>\n"); if (host) { + char *fullurl = cgit_currentfullurl(); char *repourl = cgit_repourl(ctx.repo->url); + html("<id>"); + html_txtf("%s%s%s", cgit_httpscheme(), host, fullurl); + html("</id>\n"); + html("<link rel='self' href='"); + html_attrf("%s%s%s", cgit_httpscheme(), host, fullurl); + html("'/>\n"); html("<link rel='alternate' type='text/html' href='"); - html(cgit_httpscheme()); - html_attr(host); - html_attr(repourl); + html_attrf("%s%s%s", cgit_httpscheme(), host, repourl); html("'/>\n"); + free(fullurl); free(repourl); } while ((commit = get_revision(&rev)) != NULL) { + if (first) { + html("<updated>"); + html_txt(show_date(commit->date, 0, + date_mode_from_type(DATE_ISO8601_STRICT))); + html("</updated>\n"); + first = false; + } add_entry(commit, host); - free_commit_buffer(the_repository->parsed_objects, commit); - free_commit_list(commit->parents); + release_commit_memory(the_repository->parsed_objects, commit); commit->parents = NULL; } html("</feed>\n"); @@ -54,6 +54,15 @@ static void emit_blame_entry_hash(struct blame_entry *ent) html("</span>"); free(detail); + if (!parse_commit(suspect->commit) && suspect->commit->parents) { + struct commit *parent = suspect->commit->parents->item; + + html(" "); + cgit_blame_link("^", "Blame the previous revision", NULL, + ctx.qry.head, oid_to_hex(&parent->object.oid), + suspect->path); + } + while (line++ < ent->num_lines) html("\n"); } @@ -132,7 +141,8 @@ static void print_object(const struct object_id *oid, const char *path, init_scoreboard(&sb); sb.revs = &revs; sb.repo = the_repository; - setup_scoreboard(&sb, path, &o); + sb.path = path; + setup_scoreboard(&sb, &o); o->suspects = blame_entry_prepend(NULL, 0, sb.num_lines, o); prio_queue_put(&sb.commits, o->commit); blame_origin_decref(o); @@ -151,6 +161,10 @@ static void print_object(const struct object_id *oid, const char *path, cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path); html(")\n"); + if (buffer_is_binary(buf, size)) { + html("<div class='error'>blob is binary.</div>"); + goto cleanup; + } if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { htmlf("<div class='error'>blob size (%ldKB)" " exceeds display size limit (%dKB).</div>", @@ -220,8 +234,7 @@ cleanup: } static int walk_tree(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, - void *cbdata) + const char *pathname, unsigned mode, void *cbdata) { struct walk_tree_context *walk_tree_ctx = cbdata; @@ -290,10 +303,8 @@ void cgit_print_blame(void) walk_tree_ctx.match_baselen = (path_items.match) ? basedir_len(path_items.match) : -1; - read_tree_recursive(the_repository, - repo_get_commit_tree(the_repository, commit), - "", 0, 0, - &paths, walk_tree, &walk_tree_ctx); + read_tree(the_repository, repo_get_commit_tree(the_repository, commit), + &paths, walk_tree, &walk_tree_ctx); if (!walk_tree_ctx.state) cgit_print_error_page(404, "Not found", "Not found"); else if (walk_tree_ctx.state == 2) @@ -19,7 +19,7 @@ struct walk_tree_context { }; static int walk_tree(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *cbdata) + const char *pathname, unsigned mode, void *cbdata) { struct walk_tree_context *walk_tree_ctx = cbdata; @@ -56,9 +56,9 @@ int cgit_ref_path_exists(const char *path, const char *ref, int file_only) goto done; if (oid_object_info(the_repository, &oid, &size) != OBJ_COMMIT) goto done; - read_tree_recursive(the_repository, - repo_get_commit_tree(the_repository, lookup_commit_reference(the_repository, &oid)), - "", 0, 0, &paths, walk_tree, &walk_tree_ctx); + read_tree(the_repository, + repo_get_commit_tree(the_repository, lookup_commit_reference(the_repository, &oid)), + &paths, walk_tree, &walk_tree_ctx); done: free(path_items.match); @@ -92,10 +92,8 @@ int cgit_print_file(char *path, const char *head, int file_only) type = oid_object_info(the_repository, &oid, &size); if (type == OBJ_COMMIT) { commit = lookup_commit_reference(the_repository, &oid); - read_tree_recursive(the_repository, - repo_get_commit_tree(the_repository, commit), - "", 0, 0, &paths, walk_tree, - &walk_tree_ctx); + read_tree(the_repository, repo_get_commit_tree(the_repository, commit), + &paths, walk_tree, &walk_tree_ctx); if (!walk_tree_ctx.found_path) return -1; type = oid_object_info(the_repository, &oid, &size); @@ -151,10 +149,8 @@ void cgit_print_blob(const char *hex, char *path, const char *head, int file_onl if ((!hex) && type == OBJ_COMMIT && path) { commit = lookup_commit_reference(the_repository, &oid); - read_tree_recursive(the_repository, - repo_get_commit_tree(the_repository, commit), - "", 0, 0, &paths, walk_tree, - &walk_tree_ctx); + read_tree(the_repository, repo_get_commit_tree(the_repository, commit), + &paths, walk_tree, &walk_tree_ctx); type = oid_object_info(the_repository, &oid, &size); } diff --git a/ui-commit.c b/ui-commit.c index 948118c..0787237 100644 --- a/ui-commit.c +++ b/ui-commit.c @@ -39,10 +39,11 @@ void cgit_print_commit(char *hex, const char *prefix) } info = cgit_parse_commit(commit); - format_display_notes(&oid, ¬es, PAGE_ENCODING, 0); + format_display_notes(&oid, ¬es, PAGE_ENCODING, 1); load_ref_decorations(NULL, DECORATE_FULL_REFS); + ctx.page.title = fmtalloc("%s - %s", info->subject, ctx.page.title); cgit_print_layout_start(); cgit_print_diff_ctrls(); html("<table summary='commit info' class='commit-info'>\n"); @@ -65,8 +65,9 @@ void show_commit_decorations(struct commit *commit) return; html("<span class='decoration'>"); while (deco) { - struct object_id peeled; + struct object_id oid_tag, peeled; int is_annotated = 0; + strlcpy(buf, prettify_refname(deco->name), sizeof(buf)); switch(deco->type) { case DECORATION_NONE: @@ -79,8 +80,8 @@ void show_commit_decorations(struct commit *commit) ctx.qry.showmsg, 0); break; case DECORATION_REF_TAG: - if (!peel_ref(deco->name, &peeled)) - is_annotated = !oidcmp(&commit->object.oid, &peeled); + if (!read_ref(deco->name, &oid_tag) && !peel_iterated_oid(&oid_tag, &peeled)) + is_annotated = !oideq(&oid_tag, &peeled); cgit_tag_link(buf, NULL, is_annotated ? "tag-annotated-deco" : "tag-deco", buf); break; case DECORATION_REF_REMOTE: @@ -158,7 +159,7 @@ static int show_commit(struct commit *commit, struct rev_info *revs) "", &revs->diffopt); diffcore_std(&revs->diffopt); - found = !diff_queue_is_empty(); + found = !diff_queue_is_empty(&revs->diffopt); saved_fmt = revs->diffopt.output_format; revs->diffopt.output_format = DIFF_FORMAT_CALLBACK; revs->diffopt.format_callback = cgit_diff_tree_cb; @@ -488,8 +489,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; /* nop */) { if (show_commit(commit, &rev)) i++; - free_commit_buffer(the_repository->parsed_objects, commit); - free_commit_list(commit->parents); + release_commit_memory(the_repository->parsed_objects, commit); commit->parents = NULL; } @@ -510,8 +510,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern i++; print_commit(commit, &rev); } - free_commit_buffer(the_repository->parsed_objects, commit); - free_commit_list(commit->parents); + release_commit_memory(the_repository->parsed_objects, commit); commit->parents = NULL; } if (pager) { @@ -130,7 +130,7 @@ static void print_dir_tail(void) } static int walk_tree(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *cbdata) + const char *pathname, unsigned mode, void *cbdata) { struct walk_tree_context *walk_tree_ctx = cbdata; @@ -198,9 +198,8 @@ void cgit_print_plain(void) } else walk_tree_ctx.match_baselen = basedir_len(path_items.match); - read_tree_recursive(the_repository, - repo_get_commit_tree(the_repository, commit), - "", 0, 0, &paths, walk_tree, &walk_tree_ctx); + read_tree(the_repository, repo_get_commit_tree(the_repository, commit), + &paths, walk_tree, &walk_tree_ctx); if (!walk_tree_ctx.match) cgit_print_error_page(404, "Not found", "Not found"); else if (walk_tree_ctx.match == 2) diff --git a/ui-repolist.c b/ui-repolist.c index 529a203..2390eae 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -321,7 +321,7 @@ void cgit_print_repolist(void) } htmlf("<tr><td class='%s'>", !sorted && section ? "sublevel-repo" : "toplevel-repo"); - cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); + cgit_repo_link(ctx.repo->name, NULL, NULL, NULL); html("</td><td>"); repourl = cgit_repourl(ctx.repo->url); html_link_open(repourl, NULL, NULL); diff --git a/ui-shared.c b/ui-shared.c index 151ac17..81e1f46 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -22,10 +22,11 @@ static char *http_date(time_t t) 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); + struct tm tm; + gmtime_r(&t, &tm); + 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 *fmt, ...) @@ -327,10 +328,16 @@ static void reporevlink(const char *page, const char *name, const char *title, html("</a>"); } +void cgit_repo_link(const char *name, const char *title, const char *class, + const char *head) +{ + reporevlink(NULL, name, title, class, head, NULL, NULL); +} + void cgit_summary_link(const char *name, const char *title, const char *class, const char *head) { - reporevlink(NULL, name, title, class, head, NULL, NULL); + reporevlink("summary", name, title, class, head, NULL, NULL); } void cgit_tag_link(const char *name, const char *title, const char *class, @@ -672,7 +679,7 @@ const struct date_mode *cgit_date_mode(enum date_mode_type type) static void print_rel_date(time_t t, int tz, double value, const char *class, const char *suffix) { - htmlf("<span class='%s' title='", class); + htmlf("<span class='%s' data-ut='%" PRIu64 "' title='", class, (uint64_t)t); html_attr(show_date(t, tz, cgit_date_mode(DATE_ISO8601))); htmlf("'>%.0f %s</span>", value, suffix); } @@ -767,6 +774,38 @@ static void print_rel_vcs_link(const char *url) html(" Git repository'/>\n"); } +static int emit_css_link(struct string_list_item *s, void *arg) +{ + /* Do not emit anything if css= is specified. */ + if (s && *s->string == '\0') + return 0; + + html("<link rel='stylesheet' type='text/css' href='"); + if (s) + html_attr(s->string); + else + html_attr((const char *)arg); + html("'/>\n"); + + return 0; +} + +static int emit_js_link(struct string_list_item *s, void *arg) +{ + /* Do not emit anything if js= is specified. */ + if (s && *s->string == '\0') + return 0; + + html("<script type='text/javascript' src='"); + if (s) + html_attr(s->string); + else + html_attr((const char *)arg); + html("'></script>\n"); + + return 0; +} + void cgit_print_docstart(void) { char *host = cgit_hosturl(); @@ -786,9 +825,17 @@ void cgit_print_docstart(void) 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.css.items) + for_each_string_list(&ctx.cfg.css, emit_css_link, NULL); + else + emit_css_link(NULL, "/cgit.css"); + + if (ctx.cfg.js.items) + for_each_string_list(&ctx.cfg.js, emit_js_link, NULL); + else + emit_js_link(NULL, "/cgit.js"); + if (ctx.cfg.favicon) { html("<link rel='shortcut icon' href='"); html_attr(ctx.cfg.favicon); @@ -994,7 +1041,7 @@ static void print_header(void) if (ctx.repo) { cgit_index_link("index", NULL, NULL, NULL, NULL, 0, 1); html(" : "); - cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); + cgit_repo_link(ctx.repo->name, NULL, NULL, NULL); if (ctx.env.authenticated) { html("</td><td class='form'>"); html("<form method='get'>\n"); @@ -1015,7 +1062,13 @@ static void print_header(void) if (ctx.repo) { html_txt(ctx.repo->desc); html("</td><td class='sub right'>"); - html_txt(ctx.repo->owner); + if (ctx.repo->owner_filter) { + cgit_open_filter(ctx.repo->owner_filter); + html_txt(ctx.repo->owner); + cgit_close_filter(ctx.repo->owner_filter); + } else { + html_txt(ctx.repo->owner); + } } else { if (ctx.cfg.root_desc) html_txt(ctx.cfg.root_desc); @@ -1083,7 +1136,7 @@ void cgit_print_pageheader(void) html("</form>\n"); } else if (ctx.env.authenticated) { char *currenturl = cgit_currenturl(); - site_link(NULL, "index", NULL, hc("repolist"), NULL, NULL, 0, 1); + site_link("repolist", "index", NULL, hc("repolist"), NULL, NULL, 0, 1); if (ctx.cfg.root_readme) site_link("about", "about", NULL, hc("about"), NULL, NULL, 0, 1); diff --git a/ui-shared.h b/ui-shared.h index 6964873..4d14858 100644 --- a/ui-shared.h +++ b/ui-shared.h @@ -17,6 +17,8 @@ extern void cgit_add_clone_urls(void (*fn)(const char *)); extern void cgit_index_link(const char *name, const char *title, const char *class, const char *pattern, const char *sort, int ofs, int always_root); +extern void cgit_repo_link(const char *name, const char *title, + const char *class, const char *head); extern void cgit_summary_link(const char *name, const char *title, const char *class, const char *head); extern void cgit_tag_link(const char *name, const char *title, @@ -166,7 +166,7 @@ static void add_commit(struct string_list *authors, struct commit *commit, struct authorstat *authorstat; struct string_list *items; char *tmp; - struct tm *date; + struct tm date; time_t t; uintptr_t *counter; @@ -180,9 +180,9 @@ static void add_commit(struct string_list *authors, struct commit *commit, authorstat = author->util; items = &authorstat->list; t = info->committer_date; - date = gmtime(&t); - period->trunc(date); - tmp = xstrdup(period->pretty(date)); + gmtime_r(&t, &date); + period->trunc(&date); + tmp = xstrdup(period->pretty(&date)); item = string_list_insert(items, tmp); counter = (uintptr_t *)&item->util; if (*counter) @@ -215,15 +215,15 @@ static struct string_list collect_stats(const struct cgit_period *period) int argc = 3; time_t now; long i; - struct tm *tm; + struct tm tm; char tmp[11]; time(&now); - tm = gmtime(&now); - period->trunc(tm); + gmtime_r(&now, &tm); + period->trunc(&tm); for (i = 1; i < period->count; i++) - period->dec(tm); - strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm); + period->dec(&tm); + strftime(tmp, sizeof(tmp), "%Y-%m-%d", &tm); argv[2] = xstrdup(fmt("--since=%s", tmp)); if (ctx.qry.path) { argv[3] = "--"; @@ -241,8 +241,7 @@ static struct string_list collect_stats(const struct cgit_period *period) memset(&authors, 0, sizeof(authors)); while ((commit = get_revision(&rev)) != NULL) { add_commit(&authors, commit, period); - free_commit_buffer(the_repository->parsed_objects, commit); - free_commit_list(commit->parents); + release_commit_memory(the_repository->parsed_objects, commit); commit->parents = NULL; } return authors; @@ -261,21 +260,21 @@ static void print_combined_authorrow(struct string_list *authors, int from, struct string_list_item *date; time_t now; long i, j, total, subtotal; - struct tm *tm; + struct tm tm; char *tmp; time(&now); - tm = gmtime(&now); - period->trunc(tm); + gmtime_r(&now, &tm); + period->trunc(&tm); for (i = 1; i < period->count; i++) - period->dec(tm); + period->dec(&tm); total = 0; htmlf("<tr><td class='%s'>%s</td>", leftclass, fmt(name, to - from + 1)); for (j = 0; j < period->count; j++) { - tmp = period->pretty(tm); - period->inc(tm); + tmp = period->pretty(&tm); + period->inc(&tm); subtotal = 0; for (i = from; i <= to; i++) { author = &authors->items[i]; @@ -300,20 +299,20 @@ static void print_authors(struct string_list *authors, int top, struct string_list_item *date; time_t now; long i, j, total; - struct tm *tm; + struct tm tm; char *tmp; time(&now); - tm = gmtime(&now); - period->trunc(tm); + gmtime_r(&now, &tm); + period->trunc(&tm); for (i = 1; i < period->count; i++) - period->dec(tm); + period->dec(&tm); html("<table class='stats'><tr><th>Author</th>"); for (j = 0; j < period->count; j++) { - tmp = period->pretty(tm); + tmp = period->pretty(&tm); htmlf("<th>%s</th>", tmp); - period->inc(tm); + period->inc(&tm); } html("<th>Total</th></tr>\n"); @@ -329,10 +328,10 @@ static void print_authors(struct string_list *authors, int top, items = &authorstat->list; total = 0; for (j = 0; j < period->count; j++) - period->dec(tm); + period->dec(&tm); for (j = 0; j < period->count; j++) { - tmp = period->pretty(tm); - period->inc(tm); + tmp = period->pretty(&tm); + period->inc(&tm); date = string_list_lookup(items, tmp); if (!date) html("<td>0</td>"); @@ -89,6 +89,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch enum object_type type; char *buf; unsigned long size; + bool is_binary; type = oid_object_info(the_repository, oid, &size); if (type == OBJ_BAD) { @@ -103,6 +104,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch "Error reading object %s", oid_to_hex(oid)); return; } + is_binary = buffer_is_binary(buf, size); cgit_set_title_from_path(path); @@ -110,7 +112,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch htmlf("blob: %s (", oid_to_hex(oid)); cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path); - if (ctx.repo->enable_blame) { + if (ctx.repo->enable_blame && !is_binary) { html(") ("); cgit_blame_link("blame", NULL, NULL, ctx.qry.head, rev, path); @@ -123,7 +125,7 @@ static void print_object(const struct object_id *oid, const char *path, const ch return; } - if (buffer_is_binary(buf, size)) + if (is_binary) print_binary_buffer(buf, size); else print_text_buffer(basename, buf, size); @@ -139,8 +141,7 @@ struct single_tree_ctx { }; static int single_tree_cb(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, - void *cbdata) + const char *pathname, unsigned mode, void *cbdata) { struct single_tree_ctx *ctx = cbdata; @@ -185,8 +186,7 @@ static void write_tree_link(const struct object_id *oid, char *name, tree_ctx.name = NULL; tree_ctx.count = 0; - read_tree_recursive(the_repository, tree, "", 0, 1, - &paths, single_tree_cb, &tree_ctx); + read_tree(the_repository, tree, &paths, single_tree_cb, &tree_ctx); if (tree_ctx.count != 1) break; @@ -199,14 +199,16 @@ static void write_tree_link(const struct object_id *oid, char *name, } static int ls_item(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *cbdata) + const char *pathname, unsigned mode, void *cbdata) { struct walk_tree_context *walk_tree_ctx = cbdata; char *name; struct strbuf fullpath = STRBUF_INIT; + struct strbuf linkpath = STRBUF_INIT; struct strbuf class = STRBUF_INIT; enum object_type type; unsigned long size = 0; + char *buf; name = xstrdup(pathname); strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "", @@ -218,8 +220,7 @@ static int ls_item(const struct object_id *oid, struct strbuf *base, htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", name, oid_to_hex(oid)); - free(name); - return 0; + goto cleanup; } } @@ -239,6 +240,21 @@ static int ls_item(const struct object_id *oid, struct strbuf *base, cgit_tree_link(name, NULL, class.buf, ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf); } + if (S_ISLNK(mode)) { + html(" -> "); + buf = read_object_file(oid, &type, &size); + if (!buf) { + htmlf("Error reading object: %s", oid_to_hex(oid)); + goto cleanup; + } + strbuf_addbuf(&linkpath, &fullpath); + strbuf_addf(&linkpath, "/../%s", buf); + strbuf_normalize_path(&linkpath); + cgit_tree_link(buf, NULL, class.buf, ctx.qry.head, + walk_tree_ctx->curr_rev, linkpath.buf); + free(buf); + strbuf_release(&linkpath); + } htmlf("</td><td class='ls-size'>%li</td>", size); html("<td>"); @@ -255,6 +271,8 @@ static int ls_item(const struct object_id *oid, struct strbuf *base, cgit_blame_link("blame", NULL, "button", ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf); html("</td></tr>\n"); + +cleanup: free(name); strbuf_release(&fullpath); strbuf_release(&class); @@ -294,14 +312,13 @@ static void ls_tree(const struct object_id *oid, const char *path, struct walk_t } ls_head(); - read_tree_recursive(the_repository, tree, "", 0, 1, - &paths, ls_item, walk_tree_ctx); + read_tree(the_repository, tree, &paths, ls_item, walk_tree_ctx); ls_tail(); } static int walk_tree(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *cbdata) + const char *pathname, unsigned mode, void *cbdata) { struct walk_tree_context *walk_tree_ctx = cbdata; @@ -326,7 +343,7 @@ static int walk_tree(const struct object_id *oid, struct strbuf *base, return 0; } } - ls_item(oid, base, pathname, mode, stage, walk_tree_ctx); + ls_item(oid, base, pathname, mode, walk_tree_ctx); return 0; } @@ -374,10 +391,8 @@ void cgit_print_tree(const char *rev, char *path) goto cleanup; } - read_tree_recursive(the_repository, - repo_get_commit_tree(the_repository, commit), - "", 0, 0, - &paths, walk_tree, &walk_tree_ctx); + read_tree(the_repository, repo_get_commit_tree(the_repository, commit), + &paths, walk_tree, &walk_tree_ctx); if (walk_tree_ctx.state == 1) ls_tail(); else if (walk_tree_ctx.state == 2) |