diff options
50 files changed, 565 insertions, 1807 deletions
diff --git a/AUTHORS b/AUTHORS index 031de338f904..256ea6b3bc7d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,5 +1,5 @@ Maintainer: - Jason A. Donenfeld <Jason@zx2c4.com> + June McEnroe <june@causal.agency> Contributors: Jason A. Donenfeld <Jason@zx2c4.com> diff --git a/Makefile b/Makefile index 96ad7cd8783b..5967a327bb2e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ all:: -CGIT_VERSION = v1.2.1 +CGIT_VERSION = 1.4.1 CGIT_SCRIPT_NAME = cgit.cgi CGIT_SCRIPT_PATH = /var/www/htdocs/cgit CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) @@ -14,7 +14,7 @@ htmldir = $(docdir) pdfdir = $(docdir) mandir = $(prefix)/share/man SHA1_HEADER = <openssl/sha.h> -GIT_VER = 2.23.0 +GIT_VER = 2.36.1 GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz INSTALL = install COPYTREE = cp -r @@ -88,7 +88,6 @@ install: all $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css $(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 $(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir) $(COPYTREE) filters/* $(DESTDIR)$(filterdir) @@ -111,7 +110,6 @@ uninstall: rm -f $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png - rm -f $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico uninstall-doc: uninstall-man uninstall-html uninstall-pdf diff --git a/README b/README index 7a6b4a40ca57..2094b87df057 100644 --- a/README +++ b/README @@ -1,8 +1,9 @@ -cgit - CGI for Git -================== +cgit-pink - CGI for Git +======================= -This is an attempt to create a fast web interface for the Git SCM, using a -built-in cache to decrease server I/O pressure. +This is a fork of cgit, an attempt to create a fast web interface +for the Git SCM, using a built-in cache to decrease server I/O +pressure. Installation ------------ @@ -32,18 +33,6 @@ This will install `cgit.cgi` and `cgit.css` into `/var/www/htdocs/cgit`. You can configure this location (and a few other things) by providing a `cgit.conf` file (see the Makefile for details). -If you'd like to compile without Lua support, you may use: - - $ make NO_LUA=1 - -And if you'd like to specify a Lua implementation, you may use: - - $ make LUA_PKGCONFIG=lua5.1 - -If this is not specified, the Lua implementation will be auto-detected, -preferring LuaJIT if many are present. Acceptable values are generally "lua", -"luajit", "lua5.1", and "lua5.2". - Dependencies ------------ @@ -51,7 +40,6 @@ Dependencies * libzip * libcrypto (OpenSSL) * libssl (OpenSSL) -* optional: luajit or lua, most reliably used when pkg-config is available Apache configuration -------------------- @@ -92,8 +80,9 @@ the HTTP headers `Modified` and `Expires`. Online presence --------------- -* The cgit homepage is hosted by cgit at <https://git.zx2c4.com/cgit/about/> +* The cgit-pink homepage is hosted by cgit at + <https://git.causal.agency/cgit-pink/about> -* Patches, bug reports, discussions and support should go to the cgit - mailing list: <cgit@lists.zx2c4.com>. To sign up, visit - <https://lists.zx2c4.com/mailman/listinfo/cgit> +* Patches, bug reports, discussions and support should go to the cgit-pink + mailing list: <list+cgit@causal.agency>. Archives are available at: + <https://causal.agency/list/cgit.html> diff --git a/cache.c b/cache.c index 2c70be784d61..59372541cbc9 100644 --- a/cache.c +++ b/cache.c @@ -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 */ @@ -265,6 +270,13 @@ static int process_slot(struct cache_slot *slot) { int err; + /* + * Make sure any buffered data is flushed before we redirect, + * do sendfile(2) or write(2) + */ + if (fflush(stdout)) + return errno; + err = open_slot(slot); if (!err && slot->match) { if (is_expired(slot)) { @@ -401,12 +413,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; } diff --git a/cgit.c b/cgit.c index ac8c6418ba46..dd28a79315ac 100644 --- a/cgit.c +++ b/cgit.c @@ -324,11 +324,11 @@ static void querystring_cb(const char *name, const char *value) 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; + ctx.qry.oid = xstrdup(value); + ctx.qry.has_oid = 1; } else if (!strcmp(name, "id2")) { - ctx.qry.sha2 = xstrdup(value); - ctx.qry.has_sha1 = 1; + ctx.qry.oid2 = xstrdup(value); + ctx.qry.has_oid = 1; } else if (!strcmp(name, "ofs")) { ctx.qry.ofs = atoi(value); } else if (!strcmp(name, "path")) { @@ -378,7 +378,7 @@ static void prepare_context(void) ctx.cfg.commit_sort = 0; ctx.cfg.css = "/cgit.css"; ctx.cfg.logo = "/cgit.png"; - ctx.cfg.favicon = "/favicon.ico"; + ctx.cfg.favicon = NULL; ctx.cfg.local_time = 0; ctx.cfg.enable_http_clone = 1; ctx.cfg.enable_index_owner = 1; @@ -428,7 +428,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 +507,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); @@ -579,7 +581,7 @@ static void prepare_repo_env(int *nongit) * load local configuration from the git repository, so we do them both while * the HOME variables are unset. */ setup_git_directory_gently(nongit); - init_display_notes(NULL); + load_display_notes(NULL); } static int prepare_repo_cmd(int nongit) @@ -674,7 +676,7 @@ static inline void authenticate_post(void) len = MAX_AUTHENTICATION_POST_BYTES; if ((len = read(STDIN_FILENO, buffer, len)) < 0) die_errno("Could not read POST from stdin"); - if (write(STDOUT_FILENO, buffer, len) < 0) + if (fwrite(buffer, 1, len, stdout) < len) die_errno("Could not write POST to stdout"); cgit_close_filter(ctx.cfg.auth_filter); exit(0); @@ -963,13 +965,7 @@ static void cgit_parse_args(int argc, const char **argv) for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--version")) { - printf("CGit %s | https://git.zx2c4.com/cgit/\n\nCompiled in features:\n", CGIT_VERSION); -#ifdef NO_LUA - printf("[-] "); -#else - printf("[+] "); -#endif - printf("Lua scripting\n"); + printf("CGit-pink %s | https://git.causal.agency/cgit-pink/\n\nCompiled in features:\n", CGIT_VERSION); #ifndef HAVE_LINUX_SENDFILE printf("[-] "); #else @@ -992,9 +988,9 @@ static void cgit_parse_args(int argc, const char **argv) } else if (skip_prefix(argv[i], "--head=", &arg)) { ctx.qry.head = xstrdup(arg); ctx.qry.has_symref = 1; - } else if (skip_prefix(argv[i], "--sha1=", &arg)) { - ctx.qry.sha1 = xstrdup(arg); - ctx.qry.has_sha1 = 1; + } else if (skip_prefix(argv[i], "--oid=", &arg)) { + ctx.qry.oid = xstrdup(arg); + ctx.qry.has_oid = 1; } else if (skip_prefix(argv[i], "--ofs=", &arg)) { ctx.qry.ofs = atoi(arg); } else if (skip_prefix(argv[i], "--scan-tree=", &arg) || @@ -1037,7 +1033,7 @@ static int calc_ttl(void) if (!strcmp(ctx.qry.page, "snapshot")) return ctx.cfg.cache_snapshot_ttl; - if (ctx.qry.has_sha1) + if (ctx.qry.has_oid) return ctx.cfg.cache_static_ttl; if (ctx.qry.has_symref) @@ -1051,7 +1047,6 @@ int cmd_main(int argc, const char **argv) const char *path; int err, ttl; - cgit_init_filters(); atexit(cgit_cleanup_filters); prepare_context(); diff --git a/cgit.css b/cgit.css index d4aadbfa1084..51ddbf833754 100644 --- a/cgit.css +++ b/cgit.css @@ -75,7 +75,7 @@ div#cgit table.tabs td { } div#cgit table.tabs td a { - padding: 2px 0.75em; + padding: 2px 0.25em; color: #777; font-size: 110%; } @@ -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; @@ -437,11 +441,6 @@ div#cgit div.commit-subject { padding: 0em; } -div#cgit div.commit-msg { - white-space: pre; - font-family: monospace; -} - div#cgit div.notes-header { font-weight: bold; padding-top: 1.5em; @@ -538,30 +537,24 @@ div#cgit table.diff { width: 100%; } -div#cgit table.diff td { - font-family: monospace; - white-space: pre; -} - -div#cgit table.diff td div.head { +div#cgit table.diff td span.head { font-weight: bold; - margin-top: 1em; color: black; } -div#cgit table.diff td div.hunk { +div#cgit table.diff td span.hunk { color: #009; } -div#cgit table.diff td div.add { +div#cgit table.diff td span.add { color: green; } -div#cgit table.diff td div.del { +div#cgit table.diff td span.del { color: red; } -div#cgit .sha1 { +div#cgit .oid { font-family: monospace; font-size: 90%; } @@ -581,7 +574,6 @@ div#cgit table.list td.reposection { div#cgit a.button { font-size: 80%; - padding: 0em 0.5em; } div#cgit a.primary { @@ -671,7 +663,6 @@ div#cgit div.footer a:hover { div#cgit a.branch-deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #88ff88; border: solid 1px #007700; @@ -679,7 +670,6 @@ div#cgit a.branch-deco { div#cgit a.tag-deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ffff88; border: solid 1px #777700; @@ -687,7 +677,6 @@ div#cgit a.tag-deco { div#cgit a.tag-annotated-deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ffcc88; border: solid 1px #777700; @@ -695,7 +684,6 @@ div#cgit a.tag-annotated-deco { div#cgit a.remote-deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ccccff; border: solid 1px #000077; @@ -703,7 +691,6 @@ div#cgit a.remote-deco { div#cgit a.deco { color: #000; - margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ff8888; border: solid 1px #770000; @@ -714,7 +701,6 @@ div#cgit div.commit-subject a.tag-deco, div#cgit div.commit-subject a.tag-annotated-deco, div#cgit div.commit-subject a.remote-deco, div#cgit div.commit-subject a.deco { - margin-left: 1em; font-size: 75%; } diff --git a/cgit.h b/cgit.h index 7ec46b484640..72fcd8498a07 100644 --- a/cgit.h +++ b/cgit.h @@ -14,7 +14,7 @@ #include <tag.h> #include <diff.h> #include <diffcore.h> -#include <argv-array.h> +#include <strvec.h> #include <refs.h> #include <revision.h> #include <log-tree.h> @@ -164,7 +164,7 @@ struct reflist { struct cgit_query { int has_symref; - int has_sha1; + int has_oid; int has_difftype; char *raw; char *repo; @@ -172,8 +172,8 @@ struct cgit_query { char *search; char *grep; char *head; - char *sha1; - char *sha2; + char *oid; + char *oid2; char *path; char *name; char *url; @@ -385,7 +385,6 @@ extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv); extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype); extern void cgit_cleanup_filters(void); -extern void cgit_init_filters(void); extern void cgit_prepare_repo_env(struct cgit_repo * repo); diff --git a/cgit.mk b/cgit.mk index 3fcc1ca31440..5b9ed5be8e46 100644 --- a/cgit.mk +++ b/cgit.mk @@ -27,32 +27,6 @@ ifdef NO_C99_FORMAT CFLAGS += -DNO_C99_FORMAT endif -ifdef NO_LUA - LUA_MESSAGE := linking without specified Lua support - CGIT_CFLAGS += -DNO_LUA -else -ifeq ($(LUA_PKGCONFIG),) - LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \ - $(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \ - done) - LUA_MODE := autodetected -else - LUA_MODE := specified -endif -ifneq ($(LUA_PKGCONFIG),) - LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG) - LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null) - LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null) - CGIT_LIBS += $(LUA_LIBS) - CGIT_CFLAGS += $(LUA_CFLAGS) -else - LUA_MESSAGE := linking without autodetected Lua support - NO_LUA := YesPlease - CGIT_CFLAGS += -DNO_LUA -endif - -endif - # Add -ldl to linker flags on systems that commonly use GNU libc. ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD)) CGIT_LIBS += -ldl @@ -130,7 +104,6 @@ $(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs) $(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $< $(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS) - @echo 1>&1 " * $(LUA_MESSAGE)" $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS) CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS)) diff --git a/cgitrc.5.txt b/cgitrc.5.txt index ba77826fd0c2..cafb5355ea25 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -219,7 +219,7 @@ enable-tree-linenumbers:: favicon:: Url used as link to a shortcut icon for cgit. It is suggested to use the value "/favicon.ico" since certain browsers will ignore other - values. Default value: "/favicon.ico". + values. Default value: none. footer:: The content of the file specified with this option will be included @@ -407,9 +407,12 @@ side-by-side-diffs:: snapshots:: Text which specifies the default set of snapshot formats that cgit generates links for. The value is a space-separated list of zero or - more of the values "tar", "tar.gz", "tar.bz2", "tar.xz" and "zip". - The special value "all" enables all snapshot formats. - Default value: none. + more of the values "tar", "tar.gz", "tar.bz2", "tar.lz", "tar.xz", + "tar.zst" and "zip". The special value "all" enables all snapshot + formats. Default value: none. + All compressors use default settings. Some settings can be influenced + with environment variables, for example set ZSTD_CLEVEL=10 in web + server environment for higher (but slower) zstd compression. source-filter:: Specifies a command which will be invoked to format plaintext blobs @@ -576,11 +579,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: @@ -629,37 +632,6 @@ specification with the relevant string; available values are: 'exec:':: The default "one process per filter" mode. -'lua:':: - Executes the script using a built-in Lua interpreter. The script is - loaded once per execution of cgit, and may be called multiple times - during cgit's lifetime, making it a good choice for repeated filters - such as the 'email filter'. It responds to three functions: - - 'filter_open(argument1, argument2, argument3, ...)':: - This is called upon activation of the filter for a particular - set of data. - 'filter_write(buffer)':: - This is called whenever cgit writes data to the webpage. - 'filter_close()':: - This is called when the current filtering operation is - completed. It must return an integer value. Usually 0 - indicates success. - - Additionally, cgit exposes to the Lua the following built-in functions: - - 'html(str)':: - Writes 'str' to the webpage. - 'html_txt(str)':: - HTML escapes and writes 'str' to the webpage. - 'html_attr(str)':: - HTML escapes for an attribute and writes "str' to the webpage. - 'html_url_path(str)':: - URL escapes for a path and writes 'str' to the webpage. - 'html_url_arg(str)':: - URL escapes for an argument and writes 'str' to the webpage. - 'html_include(file)':: - Includes 'file' in webpage. - Parameters are provided to filters as follows. @@ -693,9 +665,6 @@ auth filter:: with a 302 redirect, and write to output one or more "Set-Cookie" HTTP headers, each followed by a newline. - Please see `filters/simple-authentication.lua` for a clear example - script that may be modified. - commit filter:: This filter is given no arguments. The commit message text that is to be filtered is available on standard input and the filtered text is diff --git a/cmd.c b/cmd.c index bf6d8f516fe0..0eb75b1da877 100644 --- a/cmd.c +++ b/cmd.c @@ -74,22 +74,22 @@ static void blame_fn(void) static void blob_fn(void) { - cgit_print_blob(ctx.qry.sha1, ctx.qry.path, ctx.qry.head, 0); + cgit_print_blob(ctx.qry.oid, ctx.qry.path, ctx.qry.head, 0); } static void commit_fn(void) { - cgit_print_commit(ctx.qry.sha1, ctx.qry.path); + cgit_print_commit(ctx.qry.oid, ctx.qry.path); } static void diff_fn(void) { - cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 0); + cgit_print_diff(ctx.qry.oid, ctx.qry.oid2, ctx.qry.path, 1, 0); } static void rawdiff_fn(void) { - cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 1); + cgit_print_diff(ctx.qry.oid, ctx.qry.oid2, ctx.qry.path, 1, 1); } static void info_fn(void) @@ -99,7 +99,7 @@ static void info_fn(void) static void log_fn(void) { - cgit_print_log(ctx.qry.sha1, ctx.qry.ofs, ctx.cfg.max_commit_count, + cgit_print_log(ctx.qry.oid, ctx.qry.ofs, ctx.cfg.max_commit_count, ctx.qry.grep, ctx.qry.search, ctx.qry.path, 1, ctx.repo->enable_commit_graph, ctx.repo->commit_sort); @@ -125,7 +125,7 @@ static void repolist_fn(void) static void patch_fn(void) { - cgit_print_patch(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path); + cgit_print_patch(ctx.qry.oid, ctx.qry.oid2, ctx.qry.path); } static void plain_fn(void) @@ -140,7 +140,7 @@ static void refs_fn(void) static void snapshot_fn(void) { - cgit_print_snapshot(ctx.qry.head, ctx.qry.sha1, ctx.qry.path, + cgit_print_snapshot(ctx.qry.head, ctx.qry.oid, ctx.qry.path, ctx.qry.nohead); } @@ -156,12 +156,12 @@ static void summary_fn(void) static void tag_fn(void) { - cgit_print_tag(ctx.qry.sha1); + cgit_print_tag(ctx.qry.oid); } static void tree_fn(void) { - cgit_print_tree(ctx.qry.sha1, ctx.qry.path); + cgit_print_tree(ctx.qry.oid, ctx.qry.path); } #define def_cmd(name, want_repo, want_vpath, is_clone) \ diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index 56ff59384f2c..000000000000 --- a/favicon.ico +++ /dev/null Binary files differdiff --git a/filter.c b/filter.c index 70f5b749989c..2b6c838e6cdf 100644 --- a/filter.c +++ b/filter.c @@ -8,12 +8,6 @@ #include "cgit.h" #include "html.h" -#ifndef NO_LUA -#include <dlfcn.h> -#include <lua.h> -#include <lualib.h> -#include <lauxlib.h> -#endif static inline void reap_filter(struct cgit_filter *filter) { @@ -48,6 +42,7 @@ static int open_exec_filter(struct cgit_filter *base, va_list ap) for (i = 0; i < filter->base.argument_count; i++) filter->argv[i + 1] = va_arg(ap, char *); + chk_zero(fflush(stdout), "unable to flush STDOUT"); filter->old_stdout = chk_positive(dup(STDOUT_FILENO), "Unable to duplicate STDOUT"); chk_zero(pipe(pipe_fh), "Unable to create pipe to subprocess"); @@ -71,6 +66,7 @@ static int close_exec_filter(struct cgit_filter *base) struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base; int i, exit_status = 0; + chk_zero(fflush(stdout), "unable to flush STDOUT"); chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), "Unable to restore STDOUT"); close(filter->old_stdout); @@ -136,234 +132,6 @@ void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **ar filter->base.argument_count = 0; } -#ifdef NO_LUA -void cgit_init_filters(void) -{ -} -#endif - -#ifndef NO_LUA -static ssize_t (*libc_write)(int fd, const void *buf, size_t count); -static ssize_t (*filter_write)(struct cgit_filter *base, const void *buf, size_t count) = NULL; -static struct cgit_filter *current_write_filter = NULL; - -void cgit_init_filters(void) -{ - libc_write = dlsym(RTLD_NEXT, "write"); - if (!libc_write) - die("Could not locate libc's write function"); -} - -ssize_t write(int fd, const void *buf, size_t count) -{ - if (fd != STDOUT_FILENO || !filter_write) - return libc_write(fd, buf, count); - return filter_write(current_write_filter, buf, count); -} - -static inline void hook_write(struct cgit_filter *filter, ssize_t (*new_write)(struct cgit_filter *base, const void *buf, size_t count)) -{ - /* We want to avoid buggy nested patterns. */ - assert(filter_write == NULL); - assert(current_write_filter == NULL); - current_write_filter = filter; - filter_write = new_write; -} - -static inline void unhook_write(void) -{ - assert(filter_write != NULL); - assert(current_write_filter != NULL); - filter_write = NULL; - current_write_filter = NULL; -} - -struct lua_filter { - struct cgit_filter base; - char *script_file; - lua_State *lua_state; -}; - -static void error_lua_filter(struct lua_filter *filter) -{ - die("Lua error in %s: %s", filter->script_file, lua_tostring(filter->lua_state, -1)); - lua_pop(filter->lua_state, 1); -} - -static ssize_t write_lua_filter(struct cgit_filter *base, const void *buf, size_t count) -{ - struct lua_filter *filter = (struct lua_filter *)base; - - lua_getglobal(filter->lua_state, "filter_write"); - lua_pushlstring(filter->lua_state, buf, count); - if (lua_pcall(filter->lua_state, 1, 0, 0)) { - error_lua_filter(filter); - errno = EIO; - return -1; - } - return count; -} - -static inline int hook_lua_filter(lua_State *lua_state, void (*fn)(const char *txt)) -{ - const char *str; - ssize_t (*save_filter_write)(struct cgit_filter *base, const void *buf, size_t count); - struct cgit_filter *save_filter; - - str = lua_tostring(lua_state, 1); - if (!str) - return 0; - - save_filter_write = filter_write; - save_filter = current_write_filter; - unhook_write(); - fn(str); - hook_write(save_filter, save_filter_write); - - return 0; -} - -static int html_lua_filter(lua_State *lua_state) -{ - return hook_lua_filter(lua_state, html); -} - -static int html_txt_lua_filter(lua_State *lua_state) -{ - return hook_lua_filter(lua_state, html_txt); -} - -static int html_attr_lua_filter(lua_State *lua_state) -{ - return hook_lua_filter(lua_state, html_attr); -} - -static int html_url_path_lua_filter(lua_State *lua_state) -{ - return hook_lua_filter(lua_state, html_url_path); -} - -static int html_url_arg_lua_filter(lua_State *lua_state) -{ - return hook_lua_filter(lua_state, html_url_arg); -} - -static int html_include_lua_filter(lua_State *lua_state) -{ - return hook_lua_filter(lua_state, (void (*)(const char *))html_include); -} - -static void cleanup_lua_filter(struct cgit_filter *base) -{ - struct lua_filter *filter = (struct lua_filter *)base; - - if (!filter->lua_state) - return; - - lua_close(filter->lua_state); - filter->lua_state = NULL; - if (filter->script_file) { - free(filter->script_file); - filter->script_file = NULL; - } -} - -static int init_lua_filter(struct lua_filter *filter) -{ - if (filter->lua_state) - return 0; - - if (!(filter->lua_state = luaL_newstate())) - return 1; - - luaL_openlibs(filter->lua_state); - - lua_pushcfunction(filter->lua_state, html_lua_filter); - lua_setglobal(filter->lua_state, "html"); - lua_pushcfunction(filter->lua_state, html_txt_lua_filter); - lua_setglobal(filter->lua_state, "html_txt"); - lua_pushcfunction(filter->lua_state, html_attr_lua_filter); - lua_setglobal(filter->lua_state, "html_attr"); - lua_pushcfunction(filter->lua_state, html_url_path_lua_filter); - lua_setglobal(filter->lua_state, "html_url_path"); - lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter); - lua_setglobal(filter->lua_state, "html_url_arg"); - lua_pushcfunction(filter->lua_state, html_include_lua_filter); - lua_setglobal(filter->lua_state, "html_include"); - - if (luaL_dofile(filter->lua_state, filter->script_file)) { - error_lua_filter(filter); - lua_close(filter->lua_state); - filter->lua_state = NULL; - return 1; - } - return 0; -} - -static int open_lua_filter(struct cgit_filter *base, va_list ap) -{ - struct lua_filter *filter = (struct lua_filter *)base; - int i; - - if (init_lua_filter(filter)) - return 1; - - hook_write(base, write_lua_filter); - - lua_getglobal(filter->lua_state, "filter_open"); - for (i = 0; i < filter->base.argument_count; ++i) - lua_pushstring(filter->lua_state, va_arg(ap, char *)); - if (lua_pcall(filter->lua_state, filter->base.argument_count, 0, 0)) { - error_lua_filter(filter); - return 1; - } - return 0; -} - -static int close_lua_filter(struct cgit_filter *base) -{ - struct lua_filter *filter = (struct lua_filter *)base; - int ret = 0; - - lua_getglobal(filter->lua_state, "filter_close"); - if (lua_pcall(filter->lua_state, 0, 1, 0)) { - error_lua_filter(filter); - ret = -1; - } else { - ret = lua_tonumber(filter->lua_state, -1); - lua_pop(filter->lua_state, 1); - } - - unhook_write(); - return ret; -} - -static void fprintf_lua_filter(struct cgit_filter *base, FILE *f, const char *prefix) -{ - struct lua_filter *filter = (struct lua_filter *)base; - fprintf(f, "%slua:%s\n", prefix, filter->script_file); -} - - -static struct cgit_filter *new_lua_filter(const char *cmd, int argument_count) -{ - struct lua_filter *filter; - - filter = xmalloc(sizeof(*filter)); - memset(filter, 0, sizeof(*filter)); - filter->base.open = open_lua_filter; - filter->base.close = close_lua_filter; - filter->base.fprintf = fprintf_lua_filter; - filter->base.cleanup = cleanup_lua_filter; - filter->base.argument_count = argument_count; - filter->script_file = xstrdup(cmd); - - return &filter->base; -} - -#endif - - int cgit_open_filter(struct cgit_filter *filter, ...) { int result; @@ -395,9 +163,6 @@ static const struct { struct cgit_filter *(*ctor)(const char *cmd, int argument_count); } filter_specs[] = { { "exec", new_exec_filter }, -#ifndef NO_LUA - { "lua", new_lua_filter }, -#endif }; struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype) diff --git a/filters/commit-links.sh b/filters/commit-links.sh index 58819524ced8..796ac308d237 100755 --- a/filters/commit-links.sh +++ b/filters/commit-links.sh @@ -19,7 +19,7 @@ regex='' # This expression generates links to commits referenced by their SHA1. regex=$regex' -s|\b([0-9a-fA-F]{7,40})\b|<a href="./?id=\1">\1</a>|g' +s|\b([0-9a-fA-F]{7,64})\b|<a href="./?id=\1">\1</a>|g' # This expression generates links to a fictional bugtracker. regex=$regex' diff --git a/filters/email-gravatar.lua b/filters/email-gravatar.lua deleted file mode 100644 index c39b490d6bb6..000000000000 --- a/filters/email-gravatar.lua +++ /dev/null @@ -1,35 +0,0 @@ --- This script may be used with the email-filter or repo.email-filter settings in cgitrc. --- It adds gravatar icons to author names. It is designed to be used with the lua: --- prefix in filters. It is much faster than the corresponding python script. --- --- Requirements: --- luaossl --- <http://25thandclement.com/~william/projects/luaossl.html> --- - -local digest = require("openssl.digest") - -function md5_hex(input) - local b = digest.new("md5"):final(input) - local x = "" - for i = 1, #b do - x = x .. string.format("%.2x", string.byte(b, i)) - end - return x -end - -function filter_open(email, page) - buffer = "" - md5 = md5_hex(email:sub(2, -2):lower()) -end - -function filter_close() - html("<img src='//www.gravatar.com/avatar/" .. md5 .. "?s=13&d=retro' width='13' height='13' alt='Gravatar' /> " .. buffer) - return 0 -end - -function filter_write(str) - buffer = buffer .. str -end - - diff --git a/filters/email-gravatar.py b/filters/email-gravatar.py index d70440ea543c..012113c59132 100755 --- a/filters/email-gravatar.py +++ b/filters/email-gravatar.py @@ -1,8 +1,5 @@ #!/usr/bin/env python3 -# Please prefer the email-gravatar.lua using lua: as a prefix over this script. This -# script is very slow, in comparison. -# # This script may be used with the email-filter or repo.email-filter settings in cgitrc. # # The following environment variables can be used to retrieve the configuration diff --git a/filters/email-libravatar.lua b/filters/email-libravatar.lua deleted file mode 100644 index 7336baf830a1..000000000000 --- a/filters/email-libravatar.lua +++ /dev/null @@ -1,36 +0,0 @@ --- This script may be used with the email-filter or repo.email-filter settings in cgitrc. --- It adds libravatar icons to author names. It is designed to be used with the lua: --- prefix in filters. --- --- Requirements: --- luaossl --- <http://25thandclement.com/~william/projects/luaossl.html> --- - -local digest = require("openssl.digest") - -function md5_hex(input) - local b = digest.new("md5"):final(input) - local x = "" - for i = 1, #b do - x = x .. string.format("%.2x", string.byte(b, i)) - end - return x -end - -function filter_open(email, page) - buffer = "" - md5 = md5_hex(email:sub(2, -2):lower()) -end - -function filter_close() - baseurl = os.getenv("HTTPS") and "https://seccdn.libravatar.org/" or "http://cdn.libravatar.org/" - html("<img src='" .. baseurl .. "avatar/" .. md5 .. "?s=13&d=retro' width='13' height='13' alt='Libravatar' /> " .. buffer) - return 0 -end - -function filter_write(str) - buffer = buffer .. str -end - - diff --git a/filters/file-authentication.lua b/filters/file-authentication.lua deleted file mode 100644 index 024880463c2a..000000000000 --- a/filters/file-authentication.lua +++ /dev/null @@ -1,359 +0,0 @@ --- This script may be used with the auth-filter. --- --- Requirements: --- luaossl --- <http://25thandclement.com/~william/projects/luaossl.html> --- luaposix --- <https://github.com/luaposix/luaposix> --- -local sysstat = require("posix.sys.stat") -local unistd = require("posix.unistd") -local rand = require("openssl.rand") -local hmac = require("openssl.hmac") - --- This file should contain a series of lines in the form of: --- username1:hash1 --- username2:hash2 --- username3:hash3 --- ... --- Hashes can be generated using something like `mkpasswd -m sha-512 -R 300000`. --- This file should not be world-readable. -local users_filename = "/etc/cgit-auth/users" - --- This file should contain a series of lines in the form of: --- groupname1:username1,username2,username3,... --- ... -local groups_filename = "/etc/cgit-auth/groups" - --- This file should contain a series of lines in the form of: --- reponame1:groupname1,groupname2,groupname3,... --- ... -local repos_filename = "/etc/cgit-auth/repos" - --- Set this to a path this script can write to for storing a persistent --- cookie secret, which should not be world-readable. -local secret_filename = "/var/cache/cgit/auth-secret" - --- --- --- Authentication functions follow below. Swap these out if you want different authentication semantics. --- --- - --- Looks up a hash for a given user. -function lookup_hash(user) - local line - for line in io.lines(users_filename) do - local u, h = string.match(line, "(.-):(.+)") - if u:lower() == user:lower() then - return h - end - end - return nil -end - --- Looks up users for a given repo. -function lookup_users(repo) - local users = nil - local groups = nil - local line, group, user - for line in io.lines(repos_filename) do - local r, g = string.match(line, "(.-):(.+)") - if r == repo then - groups = { } - for group in string.gmatch(g, "([^,]+)") do - groups[group:lower()] = true - end - break - end - end - if groups == nil then - return nil - end - for line in io.lines(groups_filename) do - local g, u = string.match(line, "(.-):(.+)") - if groups[g:lower()] then - if users == nil then - users = { } - end - for user in string.gmatch(u, "([^,]+)") do - users[user:lower()] = true - end - end - end - return users -end - - --- Sets HTTP cookie headers based on post and sets up redirection. -function authenticate_post() - local hash = lookup_hash(post["username"]) - local redirect = validate_value("redirect", post["redirect"]) - - if redirect == nil then - not_found() - return 0 - end - - redirect_to(redirect) - - if hash == nil or hash ~= unistd.crypt(post["password"], hash) then - set_cookie("cgitauth", "") - else - -- One week expiration time - local username = secure_value("username", post["username"], os.time() + 604800) - set_cookie("cgitauth", username) - end - - html("\n") - return 0 -end - - --- Returns 1 if the cookie is valid and 0 if it is not. -function authenticate_cookie() - accepted_users = lookup_users(cgit["repo"]) - if accepted_users == nil then - -- We return as valid if the repo is not protected. - return 1 - end - - local username = validate_value("username", get_cookie(http["cookie"], "cgitauth")) - if username == nil or not accepted_users[username:lower()] then - return 0 - else - return 1 - end -end - --- Prints the html for the login form. -function body() - html("<h2>Authentication Required</h2>") - html("<form method='post' action='") - html_attr(cgit["login"]) - html("'>") - html("<input type='hidden' name='redirect' value='") - html_attr(secure_value("redirect", cgit["url"], 0)) - html("' />") - html("<table>") - html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>") - html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>") - html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>") - html("</table></form>") - - return 0 -end - - - --- --- --- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions. --- --- - -local actions = {} -actions["authenticate-post"] = authenticate_post -actions["authenticate-cookie"] = authenticate_cookie -actions["body"] = body - -function filter_open(...) - action = actions[select(1, ...)] - - http = {} - http["cookie"] = select(2, ...) - http["method"] = select(3, ...) - http["query"] = select(4, ...) - http["referer"] = select(5, ...) - http["path"] = select(6, ...) - http["host"] = select(7, ...) - http["https"] = select(8, ...) - - cgit = {} - cgit["repo"] = select(9, ...) - cgit["page"] = select(10, ...) - cgit["url"] = select(11, ...) - cgit["login"] = select(12, ...) - -end - -function filter_close() - return action() -end - -function filter_write(str) - post = parse_qs(str) -end - - --- --- --- Utility functions based on keplerproject/wsapi. --- --- - -function url_decode(str) - if not str then - return "" - end - str = string.gsub(str, "+", " ") - str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end) - str = string.gsub(str, "\r\n", "\n") - return str -end - -function url_encode(str) - if not str then - return "" - end - str = string.gsub(str, "\n", "\r\n") - str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end) - str = string.gsub(str, " ", "+") - return str -end - -function parse_qs(qs) - local tab = {} - for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do - tab[url_decode(key)] = url_decode(val) - end - return tab -end - -function get_cookie(cookies, name) - cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";") - return url_decode(string.match(cookies, ";" .. name .. "=(.-);")) -end - -function tohex(b) - local x = "" - for i = 1, #b do - x = x .. string.format("%.2x", string.byte(b, i)) - end - return x -end - --- --- --- Cookie construction and validation helpers. --- --- - -local secret = nil - --- Loads a secret from a file, creates a secret, or returns one from memory. -function get_secret() - if secret ~= nil then - return secret - end - local secret_file = io.open(secret_filename, "r") - if secret_file == nil then - local old_umask = sysstat.umask(63) - local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16)) - local temporary_file = io.open(temporary_filename, "w") - if temporary_file == nil then - os.exit(177) - end - temporary_file:write(tohex(rand.bytes(32))) - temporary_file:close() - unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same. - unistd.unlink(temporary_filename) - sysstat.umask(old_umask) - secret_file = io.open(secret_filename, "r") - end - if secret_file == nil then - os.exit(177) - end - secret = secret_file:read() - secret_file:close() - if secret:len() ~= 64 then - os.exit(177) - end - return secret -end - --- Returns value of cookie if cookie is valid. Otherwise returns nil. -function validate_value(expected_field, cookie) - local i = 0 - local value = "" - local field = "" - local expiration = 0 - local salt = "" - local chmac = "" - - if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then - return nil - end - - for component in string.gmatch(cookie, "[^|]+") do - if i == 0 then - field = component - elseif i == 1 then - value = component - elseif i == 2 then - expiration = tonumber(component) - if expiration == nil then - expiration = -1 - end - elseif i == 3 then - salt = component - elseif i == 4 then - chmac = component - else - break - end - i = i + 1 - end - - if chmac == nil or chmac:len() == 0 then - return nil - end - - -- Lua hashes strings, so these comparisons are time invariant. - if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then - return nil - end - - if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then - return nil - end - - if url_decode(field) ~= expected_field then - return nil - end - - return url_decode(value) -end - -function secure_value(field, value, expiration) - if value == nil or value:len() <= 0 then - return "" - end - - local authstr = "" - local salt = tohex(rand.bytes(16)) - value = url_encode(value) - field = url_encode(field) - authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt - authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr)) - return authstr -end - -function set_cookie(cookie, value) - html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly") - if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then - html("; secure") - end - html("\n") -end - -function redirect_to(url) - html("Status: 302 Redirect\n") - html("Cache-Control: no-cache, no-store\n") - html("Location: " .. url .. "\n") -end - -function not_found() - html("Status: 404 Not Found\n") - html("Cache-Control: no-cache, no-store\n\n") -end diff --git a/filters/gentoo-ldap-authentication.lua b/filters/gentoo-ldap-authentication.lua deleted file mode 100644 index 673c88d1021c..000000000000 --- a/filters/gentoo-ldap-authentication.lua +++ /dev/null @@ -1,360 +0,0 @@ --- This script may be used with the auth-filter. Be sure to configure it as you wish. --- --- Requirements: --- luaossl --- <http://25thandclement.com/~william/projects/luaossl.html> --- lualdap >= 1.2 --- <https://git.zx2c4.com/lualdap/about/> --- luaposix --- <https://github.com/luaposix/luaposix> --- -local sysstat = require("posix.sys.stat") -local unistd = require("posix.unistd") -local lualdap = require("lualdap") -local rand = require("openssl.rand") -local hmac = require("openssl.hmac") - --- --- --- Configure these variables for your settings. --- --- - --- A list of password protected repositories, with which gentooAccess --- group is allowed to access each one. -local protected_repos = { - glouglou = "infra", - portage = "dev" -} - --- Set this to a path this script can write to for storing a persistent --- cookie secret, which should be guarded. -local secret_filename = "/var/cache/cgit/auth-secret" - - --- --- --- Authentication functions follow below. Swap these out if you want different authentication semantics. --- --- - --- Sets HTTP cookie headers based on post and sets up redirection. -function authenticate_post() - local redirect = validate_value("redirect", post["redirect"]) - - if redirect == nil then - not_found() - return 0 - end - - redirect_to(redirect) - - local groups = gentoo_ldap_user_groups(post["username"], post["password"]) - if groups == nil then - set_cookie("cgitauth", "") - else - -- One week expiration time - set_cookie("cgitauth", secure_value("gentoogroups", table.concat(groups, ","), os.time() + 604800)) - end - - html("\n") - return 0 -end - - --- Returns 1 if the cookie is valid and 0 if it is not. -function authenticate_cookie() - local required_group = protected_repos[cgit["repo"]] - if required_group == nil then - -- We return as valid if the repo is not protected. - return 1 - end - - local user_groups = validate_value("gentoogroups", get_cookie(http["cookie"], "cgitauth")) - if user_groups == nil or user_groups == "" then - return 0 - end - for group in string.gmatch(user_groups, "[^,]+") do - if group == required_group then - return 1 - end - end - return 0 -end - --- Prints the html for the login form. -function body() - html("<h2>Gentoo LDAP Authentication Required</h2>") - html("<form method='post' action='") - html_attr(cgit["login"]) - html("'>") - html("<input type='hidden' name='redirect' value='") - html_attr(secure_value("redirect", cgit["url"], 0)) - html("' />") - html("<table>") - html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>") - html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>") - html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>") - html("</table></form>") - - return 0 -end - --- --- --- Gentoo LDAP support. --- --- - -function gentoo_ldap_user_groups(username, password) - -- Ensure the user is alphanumeric - if username == nil or username:match("%W") then - return nil - end - - local who = "uid=" .. username .. ",ou=devs,dc=gentoo,dc=org" - - local ldap, err = lualdap.open_simple { - uri = "ldap://ldap1.gentoo.org", - who = who, - password = password, - starttls = true, - certfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.crt", - keyfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.key", - cacertfile = "/var/www/uwsgi/cgit/gentoo-ldap/ca.pem" - } - if ldap == nil then - return nil - end - - local group_suffix = ".group" - local group_suffix_len = group_suffix:len() - local groups = {} - for dn, attribs in ldap:search { base = who, scope = "subtree" } do - local access = attribs["gentooAccess"] - if dn == who and access ~= nil then - for i, v in ipairs(access) do - local vlen = v:len() - if vlen > group_suffix_len and v:sub(-group_suffix_len) == group_suffix then - table.insert(groups, v:sub(1, vlen - group_suffix_len)) - end - end - end - end - - ldap:close() - - return groups -end - --- --- --- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions. --- --- - -local actions = {} -actions["authenticate-post"] = authenticate_post -actions["authenticate-cookie"] = authenticate_cookie -actions["body"] = body - -function filter_open(...) - action = actions[select(1, ...)] - - http = {} - http["cookie"] = select(2, ...) - http["method"] = select(3, ...) - http["query"] = select(4, ...) - http["referer"] = select(5, ...) - http["path"] = select(6, ...) - http["host"] = select(7, ...) - http["https"] = select(8, ...) - - cgit = {} - cgit["repo"] = select(9, ...) - cgit["page"] = select(10, ...) - cgit["url"] = select(11, ...) - cgit["login"] = select(12, ...) - -end - -function filter_close() - return action() -end - -function filter_write(str) - post = parse_qs(str) -end - - --- --- --- Utility functions based on keplerproject/wsapi. --- --- - -function url_decode(str) - if not str then - return "" - end - str = string.gsub(str, "+", " ") - str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end) - str = string.gsub(str, "\r\n", "\n") - return str -end - -function url_encode(str) - if not str then - return "" - end - str = string.gsub(str, "\n", "\r\n") - str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end) - str = string.gsub(str, " ", "+") - return str -end - -function parse_qs(qs) - local tab = {} - for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do - tab[url_decode(key)] = url_decode(val) - end - return tab -end - -function get_cookie(cookies, name) - cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";") - return string.match(cookies, ";" .. name .. "=(.-);") -end - -function tohex(b) - local x = "" - for i = 1, #b do - x = x .. string.format("%.2x", string.byte(b, i)) - end - return x -end - --- --- --- Cookie construction and validation helpers. --- --- - -local secret = nil - --- Loads a secret from a file, creates a secret, or returns one from memory. -function get_secret() - if secret ~= nil then - return secret - end - local secret_file = io.open(secret_filename, "r") - if secret_file == nil then - local old_umask = sysstat.umask(63) - local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16)) - local temporary_file = io.open(temporary_filename, "w") - if temporary_file == nil then - os.exit(177) - end - temporary_file:write(tohex(rand.bytes(32))) - temporary_file:close() - unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same. - unistd.unlink(temporary_filename) - sysstat.umask(old_umask) - secret_file = io.open(secret_filename, "r") - end - if secret_file == nil then - os.exit(177) - end - secret = secret_file:read() - secret_file:close() - if secret:len() ~= 64 then - os.exit(177) - end - return secret -end - --- Returns value of cookie if cookie is valid. Otherwise returns nil. -function validate_value(expected_field, cookie) - local i = 0 - local value = "" - local field = "" - local expiration = 0 - local salt = "" - local chmac = "" - - if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then - return nil - end - - for component in string.gmatch(cookie, "[^|]+") do - if i == 0 then - field = component - elseif i == 1 then - value = component - elseif i == 2 then - expiration = tonumber(component) - if expiration == nil then - expiration = -1 - end - elseif i == 3 then - salt = component - elseif i == 4 then - chmac = component - else - break - end - i = i + 1 - end - - if chmac == nil or chmac:len() == 0 then - return nil - end - - -- Lua hashes strings, so these comparisons are time invariant. - if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then - return nil - end - - if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then - return nil - end - - if url_decode(field) ~= expected_field then - return nil - end - - return url_decode(value) -end - -function secure_value(field, value, expiration) - if value == nil or value:len() <= 0 then - return "" - end - - local authstr = "" - local salt = tohex(rand.bytes(16)) - value = url_encode(value) - field = url_encode(field) - authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt - authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr)) - return authstr -end - -function set_cookie(cookie, value) - html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly") - if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then - html("; secure") - end - html("\n") -end - -function redirect_to(url) - html("Status: 302 Redirect\n") - html("Cache-Control: no-cache, no-store\n") - html("Location: " .. url .. "\n") -end - -function not_found() - html("Status: 404 Not Found\n") - html("Cache-Control: no-cache, no-store\n\n") -end diff --git a/filters/html-converters/md2html b/filters/html-converters/md2html index dc20f42a05cf..59f43a84167f 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/filters/owner-example.lua b/filters/owner-example.lua deleted file mode 100644 index 50fc25a8e53c..000000000000 --- a/filters/owner-example.lua +++ /dev/null @@ -1,17 +0,0 @@ --- This script is an example of an owner-filter. It replaces the --- usual query link with one to a fictional homepage. This script may --- be used with the owner-filter or repo.owner-filter settings in --- cgitrc with the `lua:` prefix. - -function filter_open() - buffer = "" -end - -function filter_close() - html(string.format("<a href=\"%s\">%s</a>", "http://wiki.example.com/about/" .. buffer, buffer)) - return 0 -end - -function filter_write(str) - buffer = buffer .. str -end diff --git a/filters/simple-authentication.lua b/filters/simple-authentication.lua deleted file mode 100644 index 23d345763bf6..000000000000 --- a/filters/simple-authentication.lua +++ /dev/null @@ -1,314 +0,0 @@ --- This script may be used with the auth-filter. Be sure to configure it as you wish. --- --- Requirements: --- luaossl --- <http://25thandclement.com/~william/projects/luaossl.html> --- luaposix --- <https://github.com/luaposix/luaposix> --- -local sysstat = require("posix.sys.stat") -local unistd = require("posix.unistd") -local rand = require("openssl.rand") -local hmac = require("openssl.hmac") - --- --- --- Configure these variables for your settings. --- --- - --- A list of password protected repositories along with the users who can access them. -local protected_repos = { - glouglou = { laurent = true, jason = true }, - qt = { jason = true, bob = true } -} - --- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`. -local users = { - jason = "$6$rounds=300000$YYJct3n/o.ruYK$HhpSeuCuW1fJkpvMZOZzVizeLsBKcGA/aF2UPuV5v60JyH2MVSG6P511UMTj2F3H75.IT2HIlnvXzNb60FcZH1", - laurent = "$6$rounds=300000$dP0KNHwYb3JKigT$pN/LG7rWxQ4HniFtx5wKyJXBJUKP7R01zTNZ0qSK/aivw8ywGAOdfYiIQFqFhZFtVGvr11/7an.nesvm8iJUi.", - bob = "$6$rounds=300000$jCLCCt6LUpTz$PI1vvd1yaVYcCzqH8QAJFcJ60b6W/6sjcOsU7mAkNo7IE8FRGW1vkjF8I/T5jt/auv5ODLb1L4S2s.CAyZyUC" -} - --- Set this to a path this script can write to for storing a persistent --- cookie secret, which should be guarded. -local secret_filename = "/var/cache/cgit/auth-secret" - --- --- --- Authentication functions follow below. Swap these out if you want different authentication semantics. --- --- - --- Sets HTTP cookie headers based on post and sets up redirection. -function authenticate_post() - local hash = users[post["username"]] - local redirect = validate_value("redirect", post["redirect"]) - - if redirect == nil then - not_found() - return 0 - end - - redirect_to(redirect) - - if hash == nil or hash ~= unistd.crypt(post["password"], hash) then - set_cookie("cgitauth", "") - else - -- One week expiration time - local username = secure_value("username", post["username"], os.time() + 604800) - set_cookie("cgitauth", username) - end - - html("\n") - return 0 -end - - --- Returns 1 if the cookie is valid and 0 if it is not. -function authenticate_cookie() - accepted_users = protected_repos[cgit["repo"]] - if accepted_users == nil then - -- We return as valid if the repo is not protected. - return 1 - end - - local username = validate_value("username", get_cookie(http["cookie"], "cgitauth")) - if username == nil or not accepted_users[username:lower()] then - return 0 - else - return 1 - end -end - --- Prints the html for the login form. -function body() - html("<h2>Authentication Required</h2>") - html("<form method='post' action='") - html_attr(cgit["login"]) - html("'>") - html("<input type='hidden' name='redirect' value='") - html_attr(secure_value("redirect", cgit["url"], 0)) - html("' />") - html("<table>") - html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>") - html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>") - html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>") - html("</table></form>") - - return 0 -end - - - --- --- --- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions. --- --- - -local actions = {} -actions["authenticate-post"] = authenticate_post -actions["authenticate-cookie"] = authenticate_cookie -actions["body"] = body - -function filter_open(...) - action = actions[select(1, ...)] - - http = {} - http["cookie"] = select(2, ...) - http["method"] = select(3, ...) - http["query"] = select(4, ...) - http["referer"] = select(5, ...) - http["path"] = select(6, ...) - http["host"] = select(7, ...) - http["https"] = select(8, ...) - - cgit = {} - cgit["repo"] = select(9, ...) - cgit["page"] = select(10, ...) - cgit["url"] = select(11, ...) - cgit["login"] = select(12, ...) - -end - -function filter_close() - return action() -end - -function filter_write(str) - post = parse_qs(str) -end - - --- --- --- Utility functions based on keplerproject/wsapi. --- --- - -function url_decode(str) - if not str then - return "" - end - str = string.gsub(str, "+", " ") - str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end) - str = string.gsub(str, "\r\n", "\n") - return str -end - -function url_encode(str) - if not str then - return "" - end - str = string.gsub(str, "\n", "\r\n") - str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end) - str = string.gsub(str, " ", "+") - return str -end - -function parse_qs(qs) - local tab = {} - for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do - tab[url_decode(key)] = url_decode(val) - end - return tab -end - -function get_cookie(cookies, name) - cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";") - return url_decode(string.match(cookies, ";" .. name .. "=(.-);")) -end - -function tohex(b) - local x = "" - for i = 1, #b do - x = x .. string.format("%.2x", string.byte(b, i)) - end - return x -end - --- --- --- Cookie construction and validation helpers. --- --- - -local secret = nil - --- Loads a secret from a file, creates a secret, or returns one from memory. -function get_secret() - if secret ~= nil then - return secret - end - local secret_file = io.open(secret_filename, "r") - if secret_file == nil then - local old_umask = sysstat.umask(63) - local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16)) - local temporary_file = io.open(temporary_filename, "w") - if temporary_file == nil then - os.exit(177) - end - temporary_file:write(tohex(rand.bytes(32))) - temporary_file:close() - unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same. - unistd.unlink(temporary_filename) - sysstat.umask(old_umask) - secret_file = io.open(secret_filename, "r") - end - if secret_file == nil then - os.exit(177) - end - secret = secret_file:read() - secret_file:close() - if secret:len() ~= 64 then - os.exit(177) - end - return secret -end - --- Returns value of cookie if cookie is valid. Otherwise returns nil. -function validate_value(expected_field, cookie) - local i = 0 - local value = "" - local field = "" - local expiration = 0 - local salt = "" - local chmac = "" - - if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then - return nil - end - - for component in string.gmatch(cookie, "[^|]+") do - if i == 0 then - field = component - elseif i == 1 then - value = component - elseif i == 2 then - expiration = tonumber(component) - if expiration == nil then - expiration = -1 - end - elseif i == 3 then - salt = component - elseif i == 4 then - chmac = component - else - break - end - i = i + 1 - end - - if chmac == nil or chmac:len() == 0 then - return nil - end - - -- Lua hashes strings, so these comparisons are time invariant. - if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then - return nil - end - - if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then - return nil - end - - if url_decode(field) ~= expected_field then - return nil - end - - return url_decode(value) -end - -function secure_value(field, value, expiration) - if value == nil or value:len() <= 0 then - return "" - end - - local authstr = "" - local salt = tohex(rand.bytes(16)) - value = url_encode(value) - field = url_encode(field) - authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt - authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr)) - return authstr -end - -function set_cookie(cookie, value) - html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly") - if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then - html("; secure") - end - html("\n") -end - -function redirect_to(url) - html("Status: 302 Redirect\n") - html("Cache-Control: no-cache, no-store\n") - html("Location: " .. url .. "\n") -end - -function not_found() - html("Status: 404 Not Found\n") - html("Cache-Control: no-cache, no-store\n\n") -end diff --git a/git b/git -Subproject 5fa0f5238b0cd46cfe7f6fa76c3f526ea98148d +Subproject e54793a95afeea1e10de1e5ad7eab914e741625 diff --git a/html.c b/html.c index 7f81965fddcd..ced781adc6b5 100644 --- a/html.c +++ b/html.c @@ -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); } @@ -80,7 +80,7 @@ char *fmtalloc(const char *format, ...) void html_raw(const char *data, size_t size) { - if (write(STDOUT_FILENO, data, size) != size) + if (fwrite(data, 1, size, stdout) != size) die_errno("write error on html output"); } diff --git a/parsing.c b/parsing.c index 7b3980e6b1d0..72b59b3c46a6 100644 --- a/parsing.c +++ b/parsing.c @@ -127,9 +127,8 @@ static int end_of_header(const char *p) struct commitinfo *cgit_parse_commit(struct commit *commit) { - const int sha1hex_len = 40; struct commitinfo *ret; - const char *p = get_cached_commit_buffer(the_repository, commit, NULL); + const char *p = repo_get_commit_buffer(the_repository, commit, NULL); const char *t; ret = xcalloc(1, sizeof(struct commitinfo)); @@ -140,10 +139,10 @@ struct commitinfo *cgit_parse_commit(struct commit *commit) if (!skip_prefix(p, "tree ", &p)) die("Bad commit: %s", oid_to_hex(&commit->object.oid)); - p += sha1hex_len + 1; + p += the_hash_algo->hexsz + 1; while (skip_prefix(p, "parent ", &p)) - p += sha1hex_len + 1; + p += the_hash_algo->hexsz + 1; if (p && skip_prefix(p, "author ", &p)) { parse_user(p, &ret->author, &ret->author_email, diff --git a/robots.txt b/robots.txt index 4ce948fec2c9..1b33266d534a 100644 --- a/robots.txt +++ b/robots.txt @@ -1,3 +1,4 @@ User-agent: * Disallow: /*/snapshot/* +Disallow: /*/blame/* Allow: / diff --git a/scan-tree.c b/scan-tree.c index 6a2f65a86b2c..1e3b43debf5b 100644 --- a/scan-tree.c +++ b/scan-tree.c @@ -137,8 +137,6 @@ static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn) repo->path = xstrdup(path->buf); while (!repo->owner) { if ((pwd = getpwuid(st.st_uid)) == NULL) { - fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", - path->buf, strerror(errno), errno); break; } if (pwd->pw_gecos) diff --git a/shared.c b/shared.c index 8115469a7cec..0bceb9891228 100644 --- a/shared.c +++ b/shared.c @@ -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/filters/dump.lua b/tests/filters/dump.lua deleted file mode 100644 index 1f15c931051c..000000000000 --- a/tests/filters/dump.lua +++ /dev/null @@ -1,17 +0,0 @@ -function filter_open(...) - buffer = "" - for i = 1, select("#", ...) do - buffer = buffer .. select(i, ...) .. " " - end -end - -function filter_close() - html(buffer) - return 0 -end - -function filter_write(str) - buffer = buffer .. string.upper(str) -end - - diff --git a/tests/setup.sh b/tests/setup.sh index 7590f0494411..31e7d5bb27b7 100755 --- a/tests/setup.sh +++ b/tests/setup.sh @@ -60,12 +60,6 @@ fi FILTER_DIRECTORY=$(cd ../filters && pwd) -if cgit --version | grep -F -q "[+] Lua scripting"; then - export CGIT_HAS_LUA=1 -else - export CGIT_HAS_LUA=0 -fi - mkrepo() { name=$1 count=$2 @@ -80,13 +74,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 +93,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 @@ -104,7 +102,7 @@ virtual-root=/ cache-root=$PWD/cache cache-size=1021 -snapshots=tar.gz tar.bz zip +snapshots=tar.gz tar.bz tar.lz tar.xz tar.zst zip enable-log-filecount=1 enable-log-linecount=1 summary-log=5 @@ -140,19 +138,6 @@ repo.email-filter=exec:$FILTER_DIRECTORY/dump.sh repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh repo.readme=master:a+b EOF - - if [ $CGIT_HAS_LUA -eq 1 ]; then - cat >>cgitrc <<EOF -repo.url=filter-lua -repo.path=$PWD/repos/filter/.git -repo.desc=filtered repo -repo.about-filter=lua:$FILTER_DIRECTORY/dump.lua -repo.commit-filter=lua:$FILTER_DIRECTORY/dump.lua -repo.email-filter=lua:$FILTER_DIRECTORY/dump.lua -repo.source-filter=lua:$FILTER_DIRECTORY/dump.lua -repo.readme=master:a+b -EOF - fi } cgit_query() diff --git a/tests/t0001-validate-git-versions.sh b/tests/t0001-validate-git-versions.sh index 3200f31101a0..dd84fe3fcb94 100755 --- a/tests/t0001-validate-git-versions.sh +++ b/tests/t0001-validate-git-versions.sh @@ -1,5 +1,9 @@ #!/bin/sh +if [ "${CGIT_TEST_NO_GIT_VERSION}" = "YesPlease" ]; then + exit 0 +fi + test_description='Check Git version is correct' CGIT_TEST_NO_CREATE_REPOS=YesPlease . ./setup.sh @@ -29,10 +33,10 @@ test_expect_success 'test submodule version matches Makefile' ' else ( cd ../.. && - sm_sha1=$(git ls-files --stage -- git | + sm_oid=$(git ls-files --stage -- git | sed -e "s/^[0-9]* \\([0-9a-f]*\\) [0-9] .*$/\\1/") && cd git && - git describe --match "v[0-9]*" $sm_sha1 + git describe --match "v[0-9]*" $sm_oid ) | sed -e "s/^v//" -e "s/-/./" >sm_version && test_cmp sm_version makefile_version fi diff --git a/tests/t0105-commit.sh b/tests/t0105-commit.sh index 9cdf55c02576..cfed1e7d6933 100755 --- a/tests/t0105-commit.sh +++ b/tests/t0105-commit.sh @@ -11,7 +11,7 @@ test_expect_success 'find commit subject' ' grep "<div class=.commit-subject.>commit 5<" tmp ' -test_expect_success 'find commit msg' 'grep "<div class=.commit-msg.></div>" tmp' +test_expect_success 'find commit msg' 'grep "<pre class=.commit-msg.></pre>" tmp' test_expect_success 'find diffstat' 'grep "<table summary=.diffstat. class=.diffstat.>" tmp' test_expect_success 'find diff summary' ' @@ -25,12 +25,12 @@ test_expect_success 'get root commit' ' ' test_expect_success 'root commit contains diffstat' ' - grep "<a href=./foo/diff/file-1.id=[0-9a-f]\{40\}.>file-1</a>" tmp + grep "<a href=./foo/diff/file-1.id=[0-9a-f]\{40,64\}.>file-1</a>" tmp ' test_expect_success 'root commit contains diff' ' - grep ">diff --git a/file-1 b/file-1<" tmp && - grep "<div class=.add.>+1</div>" tmp + grep ">diff --git a/file-1 b/file-1" tmp && + grep "<span class=.add.>+1</span>" tmp ' test_done diff --git a/tests/t0106-diff.sh b/tests/t0106-diff.sh index 82b645ec72ab..62a0a74a6473 100755 --- a/tests/t0106-diff.sh +++ b/tests/t0106-diff.sh @@ -9,11 +9,11 @@ test_expect_success 'find blob link' 'grep "<a href=./foo/tree/file-5?id=" tmp' test_expect_success 'find added file' 'grep "new file mode 100644" tmp' test_expect_success 'find hunk header' ' - grep "<div class=.hunk.>@@ -0,0 +1 @@</div>" tmp + grep "<span class=.hunk.>@@ -0,0 +1 @@</span>" tmp ' test_expect_success 'find added line' ' - grep "<div class=.add.>+5</div>" tmp + grep "<span class=.add.>+5</span>" tmp ' test_done diff --git a/tests/t0107-snapshot.sh b/tests/t0107-snapshot.sh index 6cf7aaa6fca5..0811ec407442 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' ' @@ -38,6 +38,129 @@ test_expect_success 'verify untarred file-5' ' test_line_count = 1 master/file-5 ' +if test -n "$(which lzip 2>/dev/null)"; then + test_set_prereq LZIP +else + say 'Skipping LZIP validation tests: lzip not found' +fi + +test_expect_success LZIP 'get foo/snapshot/master.tar.lz' ' + cgit_url "foo/snapshot/master.tar.lz" >tmp +' + +test_expect_success LZIP 'check html headers' ' + head -n 1 tmp | + grep "Content-Type: application/x-lzip" && + + head -n 2 tmp | + grep "Content-Disposition: inline; filename=.master.tar.lz." +' + +test_expect_success LZIP 'strip off the header lines' ' + strip_headers <tmp >master.tar.lz +' + +test_expect_success LZIP 'verify lzip format' ' + lzip --test master.tar.lz +' + +test_expect_success LZIP 'untar' ' + rm -rf master && + lzip -dc master.tar.lz | tar -xf - +' + +test_expect_success LZIP 'count files' ' + ls master/ >output && + test_line_count = 5 output +' + +test_expect_success LZIP 'verify untarred file-5' ' + grep "^5$" master/file-5 && + test_line_count = 1 master/file-5 +' + +if test -n "$(which xz 2>/dev/null)"; then + test_set_prereq XZ +else + say 'Skipping XZ validation tests: xz not found' +fi + +test_expect_success XZ 'get foo/snapshot/master.tar.xz' ' + cgit_url "foo/snapshot/master.tar.xz" >tmp +' + +test_expect_success XZ 'check html headers' ' + head -n 1 tmp | + grep "Content-Type: application/x-xz" && + + head -n 2 tmp | + grep "Content-Disposition: inline; filename=.master.tar.xz." +' + +test_expect_success XZ 'strip off the header lines' ' + strip_headers <tmp >master.tar.xz +' + +test_expect_success XZ 'verify xz format' ' + xz --test master.tar.xz +' + +test_expect_success XZ 'untar' ' + rm -rf master && + xz -dc master.tar.xz | tar -xf - +' + +test_expect_success XZ 'count files' ' + ls master/ >output && + test_line_count = 5 output +' + +test_expect_success XZ 'verify untarred file-5' ' + grep "^5$" master/file-5 && + test_line_count = 1 master/file-5 +' + +if test -n "$(which zstd 2>/dev/null)"; then + test_set_prereq ZSTD +else + say 'Skipping ZSTD validation tests: zstd not found' +fi + +test_expect_success ZSTD 'get foo/snapshot/master.tar.zst' ' + cgit_url "foo/snapshot/master.tar.zst" >tmp +' + +test_expect_success ZSTD 'check html headers' ' + head -n 1 tmp | + grep "Content-Type: application/x-zstd" && + + head -n 2 tmp | + grep "Content-Disposition: inline; filename=.master.tar.zst." +' + +test_expect_success ZSTD 'strip off the header lines' ' + strip_headers <tmp >master.tar.zst +' + +test_expect_success ZSTD 'verify zstd format' ' + zstd --test master.tar.zst +' + +test_expect_success ZSTD 'untar' ' + rm -rf master && + zstd -dc master.tar.zst | tar -xf - +' + +test_expect_success ZSTD 'count files' ' + ls master/ >output && + test_line_count = 5 output +' + +test_expect_success ZSTD 'verify untarred file-5' ' + grep "^5$" master/file-5 && + test_line_count = 1 master/file-5 +' + test_expect_success 'get foo/snapshot/master.zip' ' cgit_url "foo/snapshot/master.zip" >tmp ' diff --git a/tests/t0109-gitconfig.sh b/tests/t0109-gitconfig.sh index 3ba668490d97..189ef2816620 100755 --- a/tests/t0109-gitconfig.sh +++ b/tests/t0109-gitconfig.sh @@ -9,6 +9,12 @@ test -n "$(which strace 2>/dev/null)" || { exit } +strace true 2>/dev/null || { + skip_all='Skipping access validation tests: strace not functional' + test_done + exit +} + test_no_home_access () { non_existent_path="/path/to/some/place/that/does/not/possibly/exist" while test -d "$non_existent_path"; do @@ -19,7 +25,7 @@ test_no_home_access () { -E CGIT_CONFIG="$PWD/cgitrc" \ -E QUERY_STRING="url=$1" \ -e access -f -o strace.out cgit && - test_must_fail grep "$non_existent_path" strace.out + ! grep "$non_existent_path" strace.out } test_no_home_access_success() { diff --git a/tests/t0111-filter.sh b/tests/t0111-filter.sh index 2fdc3669f492..e5d357507cc3 100755 --- a/tests/t0111-filter.sh +++ b/tests/t0111-filter.sh @@ -4,9 +4,6 @@ test_description='Check filtered content' . ./setup.sh prefixes="exec" -if [ $CGIT_HAS_LUA -eq 1 ]; then - prefixes="$prefixes lua" -fi for prefix in $prefixes do diff --git a/ui-atom.c b/ui-atom.c index 1056f3639752..0cf8441d4691 100644 --- a/ui-atom.c +++ b/ui-atom.c @@ -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; + int first = 1; 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 = 0; + } 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"); diff --git a/ui-blame.c b/ui-blame.c index 644c30ad289e..aedce8dfd24a 100644 --- a/ui-blame.c +++ b/ui-blame.c @@ -10,7 +10,7 @@ #include "ui-blame.h" #include "html.h" #include "ui-shared.h" -#include "argv-array.h" +#include "strvec.h" #include "blame.h" @@ -48,12 +48,21 @@ static void emit_blame_entry_hash(struct blame_entry *ent) unsigned long line = 0; char *detail = emit_suspect_detail(suspect); - html("<span class='sha1'>"); + html("<span class='oid'>"); cgit_commit_link(find_unique_abbrev(oid, DEFAULT_ABBREV), detail, NULL, ctx.qry.head, oid_to_hex(oid), suspect->path); 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"); } @@ -104,7 +113,7 @@ static void print_object(const struct object_id *oid, const char *path, enum object_type type; char *buf; unsigned long size; - struct argv_array rev_argv = ARGV_ARRAY_INIT; + struct strvec rev_argv = STRVEC_INIT; struct rev_info revs; struct blame_scoreboard sb; struct blame_origin *o; @@ -124,15 +133,16 @@ static void print_object(const struct object_id *oid, const char *path, return; } - argv_array_push(&rev_argv, "blame"); - argv_array_push(&rev_argv, rev); + strvec_push(&rev_argv, "blame"); + strvec_push(&rev_argv, rev); init_revisions(&revs, NULL); revs.diffopt.flags.allow_textconv = 1; - setup_revisions(rev_argv.argc, rev_argv.argv, &revs, NULL); + setup_revisions(rev_argv.nr, rev_argv.v, &revs, NULL); 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; @@ -256,7 +269,7 @@ static int basedir_len(const char *path) void cgit_print_blame(void) { - const char *rev = ctx.qry.sha1; + const char *rev = ctx.qry.oid; struct object_id oid; struct commit *commit; struct pathspec_item path_items = { @@ -290,8 +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, commit->maybe_tree, "", 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) diff --git a/ui-blob.c b/ui-blob.c index 30e2d4bf5f54..c10ae42ebd05 100644 --- a/ui-blob.c +++ b/ui-blob.c @@ -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,8 +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, lookup_commit_reference(the_repository, &oid)->maybe_tree, - "", 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); @@ -91,8 +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, commit->maybe_tree, - "", 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); @@ -148,8 +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, commit->maybe_tree, - "", 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 9a47b54c5605..b49259e694e1 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"); @@ -70,15 +71,15 @@ void cgit_print_commit(char *hex, const char *prefix) html_txt(show_date(info->committer_date, info->committer_tz, cgit_date_mode(DATE_ISO8601))); html("</td></tr>\n"); - html("<tr><th>commit</th><td colspan='2' class='sha1'>"); + html("<tr><th>commit</th><td colspan='2' class='oid'>"); tmp = oid_to_hex(&commit->object.oid); cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix); html(" ("); cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix); html(")</td></tr>\n"); - html("<tr><th>tree</th><td colspan='2' class='sha1'>"); + html("<tr><th>tree</th><td colspan='2' class='oid'>"); tmp = xstrdup(hex); - cgit_tree_link(oid_to_hex(&commit->maybe_tree->object.oid), NULL, NULL, + cgit_tree_link(oid_to_hex(get_commit_tree_oid(commit)), NULL, NULL, ctx.qry.head, tmp, NULL); if (prefix) { html(" /"); @@ -95,7 +96,7 @@ void cgit_print_commit(char *hex, const char *prefix) continue; } html("<tr><th>parent</th>" - "<td colspan='2' class='sha1'>"); + "<td colspan='2' class='oid'>"); tmp = tmp2 = oid_to_hex(&p->item->object.oid); if (ctx.repo->enable_subject_links) { parent_info = cgit_parse_commit(parent); @@ -109,7 +110,7 @@ void cgit_print_commit(char *hex, const char *prefix) parents++; } if (ctx.repo->snapshots) { - html("<tr><th>download</th><td colspan='2' class='sha1'>"); + html("<tr><th>download</th><td colspan='2' class='oid'>"); cgit_print_snapshot_links(ctx.repo, hex, "<br/>"); html("</td></tr>"); } @@ -120,11 +121,11 @@ void cgit_print_commit(char *hex, const char *prefix) cgit_close_filter(ctx.repo->commit_filter); show_commit_decorations(commit); html("</div>"); - html("<div class='commit-msg'>"); + html("<pre class='commit-msg'>"); cgit_open_filter(ctx.repo->commit_filter); html_txt(info->msg); cgit_close_filter(ctx.repo->commit_filter); - html("</div>"); + html("</pre>"); if (notes.len != 0) { html("<div class='notes-header'>Notes</div>"); html("<div class='notes'>"); @@ -139,7 +140,7 @@ void cgit_print_commit(char *hex, const char *prefix) tmp = oid_to_hex(&commit->parents->item->object.oid); else tmp = NULL; - cgit_print_diff(ctx.qry.sha1, tmp, prefix, 0, 0); + cgit_print_diff(ctx.qry.oid, tmp, prefix, 0, 0); } strbuf_release(¬es); cgit_free_commitinfo(info); diff --git a/ui-diff.c b/ui-diff.c index c60aefd1d629..2a64ae8f14af 100644 --- a/ui-diff.c +++ b/ui-diff.c @@ -97,8 +97,8 @@ static void print_fileinfo(struct fileinfo *info) 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); + cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.oid, + ctx.qry.oid2, info->new_path); if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) { htmlf(" (%s from ", info->status == DIFF_STATUS_COPIED ? "copied" : "renamed"); @@ -194,8 +194,8 @@ static void cgit_print_diffstat(const struct object_id *old_oid, int i; html("<div class='diffstat-header'>"); - cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, - ctx.qry.sha2, NULL); + cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.oid, + ctx.qry.oid2, NULL); if (prefix) { html(" (limited to '"); html_txt(prefix); @@ -231,11 +231,11 @@ static void print_line(char *line, int len) else if (line[0] == '@') class = "hunk"; - htmlf("<div class='%s'>", class); + htmlf("<span class='%s'>", class); line[len-1] = '\0'; html_txt(line); - html("</div>"); line[len-1] = c; + html("</span>\n"); } static void header(const struct object_id *oid1, char *path1, int mode1, @@ -245,22 +245,23 @@ static void header(const struct object_id *oid1, char *path1, int mode1, int subproject; subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); - html("<div class='head'>"); + html("<span class='head'>"); html("diff --git a/"); html_txt(path1); html(" b/"); html_txt(path2); + html("\n"); if (mode1 == 0) - htmlf("<br/>new file mode %.6o", mode2); + htmlf("new file mode %.6o\n", mode2); if (mode2 == 0) - htmlf("<br/>deleted file mode %.6o", mode1); + htmlf("deleted file mode %.6o\n", mode1); if (!subproject) { abbrev1 = xstrdup(find_unique_abbrev(oid1, DEFAULT_ABBREV)); abbrev2 = xstrdup(find_unique_abbrev(oid2, DEFAULT_ABBREV)); - htmlf("<br/>index %s..%s", abbrev1, abbrev2); + htmlf("index %s..%s", abbrev1, abbrev2); free(abbrev1); free(abbrev2); if (mode1 != 0 && mode2 != 0) { @@ -268,28 +269,31 @@ static void header(const struct object_id *oid1, char *path1, int mode1, if (mode2 != mode1) htmlf("..%.6o", mode2); } + html("\n"); if (is_null_oid(oid1)) { path1 = "dev/null"; - html("<br/>--- /"); + html("--- /"); } else - html("<br/>--- a/"); + html("--- a/"); if (mode1 != 0) cgit_tree_link(path1, NULL, NULL, ctx.qry.head, oid_to_hex(old_rev_oid), path1); else html_txt(path1); + html("\n"); if (is_null_oid(oid2)) { path2 = "dev/null"; - html("<br/>+++ /"); + html("+++ /"); } else - html("<br/>+++ b/"); + html("+++ b/"); if (mode2 != 0) cgit_tree_link(path2, NULL, NULL, ctx.qry.head, oid_to_hex(new_rev_oid), path2); else html_txt(path2); + html("\n"); } - html("</div>"); + html("</span>"); } static void filepair_cb(struct diff_filepair *pair) @@ -413,7 +417,7 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, "Bad commit: %s", oid_to_hex(new_rev_oid)); return; } - new_tree_oid = &commit->maybe_tree->object.oid; + new_tree_oid = get_commit_tree_oid(commit); if (old_rev) { if (get_oid(old_rev, old_rev_oid)) { @@ -434,7 +438,7 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, "Bad commit: %s", oid_to_hex(old_rev_oid)); return; } - old_tree_oid = &commit2->maybe_tree->object.oid; + old_tree_oid = get_commit_tree_oid(commit2); } else { old_tree_oid = NULL; } @@ -488,12 +492,12 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, html("<table summary='ssdiff' class='ssdiff'>"); } else { html("<table summary='diff' class='diff'>"); - html("<tr><td>"); + html("<tr><td><pre>"); } cgit_diff_tree(old_rev_oid, new_rev_oid, filepair_cb, prefix, ctx.qry.ignorews); if (!use_ssdiff) - html("</td></tr>"); + html("</pre></td></tr>"); html("</table>"); if (show_ctrls) diff --git a/ui-log.c b/ui-log.c index dc5cb1eb6a0b..cfa9192b9365 100644 --- a/ui-log.c +++ b/ui-log.c @@ -10,7 +10,7 @@ #include "ui-log.h" #include "html.h" #include "ui-shared.h" -#include "argv-array.h" +#include "strvec.h" static int files, add_lines, rem_lines, lines_counted; @@ -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: @@ -74,24 +75,28 @@ void show_commit_decorations(struct commit *commit) * don't display anything. */ break; case DECORATION_REF_LOCAL: + html(" "); cgit_log_link(buf, NULL, "branch-deco", buf, NULL, ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, 0); break; case DECORATION_REF_TAG: - if (!peel_ref(deco->name, &peeled)) - is_annotated = !oidcmp(&commit->object.oid, &peeled); + html(" "); + 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: if (!ctx.repo->enable_remote_branches) break; + html(" "); cgit_log_link(buf, NULL, "remote-deco", NULL, oid_to_hex(&commit->object.oid), ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, 0); break; default: + html(" "); cgit_commit_link(buf, NULL, "deco", ctx.qry.head, oid_to_hex(&commit->object.oid), ctx.qry.vpath); @@ -153,12 +158,12 @@ static int show_commit(struct commit *commit, struct rev_info *revs) rem_lines = 0; revs->diffopt.flags.recursive = 1; - diff_tree_oid(&parent->maybe_tree->object.oid, - &commit->maybe_tree->object.oid, + diff_tree_oid(get_commit_tree_oid(parent), + get_commit_tree_oid(commit), "", &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; @@ -366,23 +371,23 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern { struct rev_info rev; struct commit *commit; - struct argv_array rev_argv = ARGV_ARRAY_INIT; + struct strvec rev_argv = STRVEC_INIT; int i, columns = commit_graph ? 4 : 3; int must_free_tip = 0; /* rev_argv.argv[0] will be ignored by setup_revisions */ - argv_array_push(&rev_argv, "log_rev_setup"); + strvec_push(&rev_argv, "log_rev_setup"); if (!tip) tip = ctx.qry.head; tip = disambiguate_ref(tip, &must_free_tip); - argv_array_push(&rev_argv, tip); + strvec_push(&rev_argv, tip); if (grep && pattern && *pattern) { pattern = xstrdup(pattern); if (!strcmp(grep, "grep") || !strcmp(grep, "author") || !strcmp(grep, "committer")) { - argv_array_pushf(&rev_argv, "--%s=%s", grep, pattern); + strvec_pushf(&rev_argv, "--%s=%s", grep, pattern); } else if (!strcmp(grep, "range")) { char *arg; /* Split the pattern at whitespace and add each token @@ -390,14 +395,14 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern * rev-list options. Also, replace the previously * pushed tip (it's no longer relevant). */ - argv_array_pop(&rev_argv); + strvec_pop(&rev_argv); while ((arg = next_token(&pattern))) { if (*arg == '-') { fprintf(stderr, "Bad range expr: %s\n", arg); break; } - argv_array_push(&rev_argv, arg); + strvec_push(&rev_argv, arg); } } } @@ -412,22 +417,22 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern } if (commit_graph && !ctx.qry.follow) { - argv_array_push(&rev_argv, "--graph"); - argv_array_push(&rev_argv, "--color"); + strvec_push(&rev_argv, "--graph"); + strvec_push(&rev_argv, "--color"); graph_set_column_colors(column_colors_html, COLUMN_COLORS_HTML_MAX); } if (commit_sort == 1) - argv_array_push(&rev_argv, "--date-order"); + strvec_push(&rev_argv, "--date-order"); else if (commit_sort == 2) - argv_array_push(&rev_argv, "--topo-order"); + strvec_push(&rev_argv, "--topo-order"); if (path && ctx.qry.follow) - argv_array_push(&rev_argv, "--follow"); - argv_array_push(&rev_argv, "--"); + strvec_push(&rev_argv, "--follow"); + strvec_push(&rev_argv, "--"); if (path) - argv_array_push(&rev_argv, path); + strvec_push(&rev_argv, path); init_revisions(&rev, NULL); rev.abbrev = DEFAULT_ABBREV; @@ -436,7 +441,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern rev.show_root_diff = 0; rev.ignore_missing = 1; rev.simplify_history = 1; - setup_revisions(rev_argv.argc, rev_argv.argv, &rev, NULL); + setup_revisions(rev_argv.nr, rev_argv.v, &rev, NULL); load_ref_decorations(NULL, DECORATE_FULL_REFS); rev.show_decorations = 1; rev.grep_filter.ignore_case = 1; @@ -463,7 +468,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern if (pager) { html(" ("); cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, - NULL, ctx.qry.head, ctx.qry.sha1, + NULL, ctx.qry.head, ctx.qry.oid, ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep, ctx.qry.search, ctx.qry.showmsg ? 0 : 1, ctx.qry.follow); @@ -488,8 +493,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 +514,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) { @@ -519,7 +522,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern if (ofs > 0) { html("<li>"); cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, - ctx.qry.sha1, ctx.qry.vpath, + ctx.qry.oid, ctx.qry.vpath, ofs - cnt, ctx.qry.grep, ctx.qry.search, ctx.qry.showmsg, ctx.qry.follow); @@ -528,7 +531,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern if ((commit = get_revision(&rev)) != NULL) { html("<li>"); cgit_log_link("[next]", NULL, NULL, ctx.qry.head, - ctx.qry.sha1, ctx.qry.vpath, + ctx.qry.oid, ctx.qry.vpath, ofs + cnt, ctx.qry.grep, ctx.qry.search, ctx.qry.showmsg, ctx.qry.follow); diff --git a/ui-patch.c b/ui-patch.c index 5a964108e51e..4ac03cbef1d8 100644 --- a/ui-patch.c +++ b/ui-patch.c @@ -61,7 +61,7 @@ void cgit_print_patch(const char *new_rev, const char *old_rev, } if (is_null_oid(&old_rev_oid)) { - memcpy(rev_range, oid_to_hex(&new_rev_oid), GIT_SHA1_HEXSZ + 1); + memcpy(rev_range, oid_to_hex(&new_rev_oid), the_hash_algo->hexsz + 1); } else { xsnprintf(rev_range, REV_RANGE_LEN, "%s..%s", oid_to_hex(&old_rev_oid), oid_to_hex(&new_rev_oid)); diff --git a/ui-plain.c b/ui-plain.c index b73c1cfed15f..65a205fad701 100644 --- a/ui-plain.c +++ b/ui-plain.c @@ -99,7 +99,7 @@ static void print_dir(const struct object_id *oid, const char *base, fullpath = NULL; } html("<li>"); - cgit_plain_link("../", NULL, NULL, ctx.qry.head, ctx.qry.sha1, + cgit_plain_link("../", NULL, NULL, ctx.qry.head, ctx.qry.oid, fullpath); html("</li>\n"); } @@ -118,7 +118,7 @@ static void print_dir_entry(const struct object_id *oid, const char *base, if (S_ISGITLINK(mode)) { cgit_submodule_link(NULL, fullpath, oid_to_hex(oid)); } else - cgit_plain_link(path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, + cgit_plain_link(path, NULL, NULL, ctx.qry.head, ctx.qry.oid, fullpath); html("</li>\n"); free(fullpath); @@ -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; @@ -163,7 +163,7 @@ static int basedir_len(const char *path) void cgit_print_plain(void) { - const char *rev = ctx.qry.sha1; + const char *rev = ctx.qry.oid; struct object_id oid; struct commit *commit; struct pathspec_item path_items = { @@ -193,13 +193,13 @@ void cgit_print_plain(void) if (!path_items.match) { path_items.match = ""; walk_tree_ctx.match_baselen = -1; - print_dir(&commit->maybe_tree->object.oid, "", 0, ""); + print_dir(get_commit_tree_oid(commit), "", 0, ""); walk_tree_ctx.match = 2; } else walk_tree_ctx.match_baselen = basedir_len(path_items.match); - read_tree_recursive(the_repository, commit->maybe_tree, - "", 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 7cf763891fcc..97b11c5f1678 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -20,7 +20,7 @@ static time_t read_agefile(const char *path) if (readfile(path, &buf, &size)) { free(buf); - return -1; + return 0; } if (parse_date(buf, &date_buf) == 0) @@ -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_summary_link(ctx.repo->name, NULL, NULL, NULL); html("</td><td>"); repourl = cgit_repourl(ctx.repo->url); html_link_open(repourl, NULL, NULL); @@ -353,8 +353,10 @@ void cgit_print_repolist(void) if (ctx.cfg.enable_index_links) { html("<td>"); cgit_summary_link("summary", NULL, "button", NULL); + html(" "); cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 0, NULL, NULL, ctx.qry.showmsg, 0); + html(" "); cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); html("</td>"); } diff --git a/ui-shared.c b/ui-shared.c index d2358f292823..72a150532d40 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, ...) @@ -521,45 +522,45 @@ static void cgit_self_link(char *name, const char *title, const char *class) else if (!strcmp(ctx.qry.page, "summary")) cgit_summary_link(name, title, class, ctx.qry.head); else if (!strcmp(ctx.qry.page, "tag")) - cgit_tag_link(name, title, class, ctx.qry.has_sha1 ? - ctx.qry.sha1 : ctx.qry.head); + cgit_tag_link(name, title, class, ctx.qry.has_oid ? + ctx.qry.oid : ctx.qry.head); else if (!strcmp(ctx.qry.page, "tree")) cgit_tree_link(name, title, class, ctx.qry.head, - ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "plain")) cgit_plain_link(name, title, class, ctx.qry.head, - ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "blame")) cgit_blame_link(name, title, class, ctx.qry.head, - ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "log")) cgit_log_link(name, title, class, ctx.qry.head, - ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, ctx.qry.search, ctx.qry.showmsg, ctx.qry.follow); else if (!strcmp(ctx.qry.page, "commit")) cgit_commit_link(name, title, class, ctx.qry.head, - ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "patch")) cgit_patch_link(name, title, class, ctx.qry.head, - ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "refs")) cgit_refs_link(name, title, class, ctx.qry.head, - ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "snapshot")) cgit_snapshot_link(name, title, class, ctx.qry.head, - ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, + ctx.qry.has_oid ? ctx.qry.oid : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "diff")) cgit_diff_link(name, title, class, ctx.qry.head, - ctx.qry.sha1, ctx.qry.sha2, + ctx.qry.oid, ctx.qry.oid2, ctx.qry.path); else if (!strcmp(ctx.qry.page, "stats")) cgit_stats_link(name, title, class, ctx.qry.head, @@ -834,7 +835,7 @@ void cgit_print_docend(void) if (ctx.cfg.footer) html_include(ctx.cfg.footer); else { - htmlf("<div class='footer'>generated by <a href='https://git.zx2c4.com/cgit/about/'>cgit %s</a> " + htmlf("<div class='footer'>generated by <a href='https://git.causal.agency/cgit-pink/about/'>cgit-pink %s</a> " "(<a href='https://git-scm.com/'>git %s</a>) at ", cgit_version, git_version_string); html_txt(show_date(time(NULL), 0, cgit_date_mode(DATE_ISO8601))); html("</div>\n"); @@ -893,6 +894,15 @@ void cgit_add_clone_urls(void (*fn)(const char *)) add_clone_urls(fn, ctx.cfg.clone_prefix, ctx.repo->url); } +static int print_this_commit_option(void) +{ + struct object_id oid; + if (!ctx.qry.head || get_oid(ctx.qry.head, &oid)) + return 1; + html_option(oid_to_hex(&oid), "this commit", ctx.qry.head); + return 0; +} + static int print_branch_option(const char *refname, const struct object_id *oid, int flags, void *cb_data) { @@ -918,10 +928,10 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, 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.oid) + html_hidden("id", ctx.qry.oid); + if (ctx.qry.oid2) + html_hidden("id2", ctx.qry.oid2); if (ctx.qry.showmsg) html_hidden("showmsg", "1"); @@ -994,15 +1004,18 @@ 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_summary_link(ctx.repo->name, NULL, NULL, NULL); if (ctx.env.authenticated) { html("</td><td class='form'>"); html("<form method='get'>\n"); cgit_add_hidden_formfields(0, 1, ctx.qry.page); html("<select name='h' onchange='this.form.submit();'>\n"); + print_this_commit_option(); + html("<optgroup label='branches'>"); for_each_branch_ref(print_branch_option, ctx.qry.head); if (ctx.repo->enable_remote_branches) for_each_remote_ref(print_branch_option, ctx.qry.head); + html("</optgroup>"); html("</select> "); html("<input type='submit' value='switch'/>"); html("</form>"); @@ -1015,7 +1028,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); @@ -1031,32 +1050,41 @@ void cgit_print_pageheader(void) html("<table class='tabs'><tr><td>\n"); if (ctx.env.authenticated && ctx.repo) { - if (ctx.repo->readme.nr) + if (ctx.repo->readme.nr) { reporevlink("about", "about", NULL, hc("about"), ctx.qry.head, NULL, NULL); + html(" "); + } cgit_summary_link("summary", NULL, hc("summary"), ctx.qry.head); + html(" "); cgit_refs_link("refs", NULL, hc("refs"), ctx.qry.head, - ctx.qry.sha1, NULL); + ctx.qry.oid, NULL); + html(" "); cgit_log_link("log", NULL, hc("log"), ctx.qry.head, NULL, ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, ctx.qry.follow); + html(" "); if (ctx.qry.page && !strcmp(ctx.qry.page, "blame")) cgit_blame_link("blame", NULL, hc("blame"), ctx.qry.head, - ctx.qry.sha1, ctx.qry.vpath); + ctx.qry.oid, ctx.qry.vpath); else cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head, - ctx.qry.sha1, ctx.qry.vpath); + ctx.qry.oid, ctx.qry.vpath); + html(" "); cgit_commit_link("commit", NULL, hc("commit"), - ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath); + ctx.qry.head, ctx.qry.oid, ctx.qry.vpath); + html(" "); cgit_diff_link("diff", NULL, hc("diff"), ctx.qry.head, - ctx.qry.sha1, ctx.qry.sha2, ctx.qry.vpath); - if (ctx.repo->max_stats) + ctx.qry.oid, ctx.qry.oid2, ctx.qry.vpath); + if (ctx.repo->max_stats) { + html(" "); cgit_stats_link("stats", NULL, hc("stats"), ctx.qry.head, ctx.qry.vpath); + } if (ctx.repo->homepage) { - html("<a href='"); + html(" <a href='"); html_attr(ctx.repo->homepage); html("'>homepage</a>"); } @@ -1200,9 +1228,12 @@ void cgit_set_title_from_path(const char *path) if (!path) return; - for (last_slash = path + strlen(path); (slash = memrchr(path, '/', last_slash - path)) != NULL; last_slash = slash) { + last_slash = path + strlen(path); + for (slash = last_slash; slash > path; --slash) { + if (*slash != '/') continue; strbuf_add(&sb, slash + 1, last_slash - slash - 1); strbuf_addstr(&sb, " \xc2\xab "); + last_slash = slash; } strbuf_add(&sb, path, last_slash - path); strbuf_addf(&sb, " - %s", ctx.page.title); diff --git a/ui-snapshot.c b/ui-snapshot.c index 9461d51a5979..280139355ab0 100644 --- a/ui-snapshot.c +++ b/ui-snapshot.c @@ -13,32 +13,35 @@ static int write_archive_type(const char *format, const char *hex, const char *prefix) { - struct argv_array argv = ARGV_ARRAY_INIT; + struct strvec argv = STRVEC_INIT; const char **nargv; int result; - argv_array_push(&argv, "snapshot"); - argv_array_push(&argv, format); + strvec_push(&argv, "snapshot"); + strvec_push(&argv, format); if (prefix) { struct strbuf buf = STRBUF_INIT; strbuf_addstr(&buf, prefix); strbuf_addch(&buf, '/'); - argv_array_push(&argv, "--prefix"); - argv_array_push(&argv, buf.buf); + strvec_push(&argv, "--prefix"); + strvec_push(&argv, buf.buf); strbuf_release(&buf); } - argv_array_push(&argv, hex); + strvec_push(&argv, hex); /* * Now we need to copy the pointers to arguments into a new * structure because write_archive will rearrange its arguments * which may result in duplicated/missing entries causing leaks - * or double-frees in argv_array_clear. + * or double-frees in strvec_clear. */ - nargv = xmalloc(sizeof(char *) * (argv.argc + 1)); - /* argv_array guarantees a trailing NULL entry. */ - memcpy(nargv, argv.argv, sizeof(char *) * (argv.argc + 1)); + nargv = xmalloc(sizeof(char *) * (argv.nr + 1)); + /* strvec guarantees a trailing NULL entry. */ + memcpy(nargv, argv.v, sizeof(char *) * (argv.nr + 1)); - result = write_archive(argv.argc, nargv, NULL, the_repository, NULL, 0); - argv_array_clear(&argv); + if (fflush(stdout)) + return errno; + + result = write_archive(argv.nr, nargv, NULL, the_repository, NULL, 0); + strvec_clear(&argv); free(nargv); return result; } @@ -79,18 +82,32 @@ static int write_tar_bzip2_archive(const char *hex, const char *prefix) return write_compressed_tar_archive(hex, prefix, argv); } +static int write_tar_lzip_archive(const char *hex, const char *prefix) +{ + char *argv[] = { "lzip", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); +} + static int write_tar_xz_archive(const char *hex, const char *prefix) { char *argv[] = { "xz", NULL }; return write_compressed_tar_archive(hex, prefix, argv); } +static int write_tar_zstd_archive(const char *hex, const char *prefix) +{ + char *argv[] = { "zstd", "-T0", NULL }; + return write_compressed_tar_archive(hex, prefix, argv); +} + const struct cgit_snapshot_format cgit_snapshot_formats[] = { /* .tar must remain the 0 index */ { ".tar", "application/x-tar", write_tar_archive }, { ".tar.gz", "application/x-gzip", write_tar_gzip_archive }, { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive }, + { ".tar.lz", "application/x-lzip", write_tar_lzip_archive }, { ".tar.xz", "application/x-xz", write_tar_xz_archive }, + { ".tar.zst", "application/x-zstd", write_tar_zstd_archive }, { ".zip", "application/x-zip", write_zip_archive }, { NULL } }; diff --git a/ui-stats.c b/ui-stats.c index 7272a61a2fff..40ed6c21df62 100644 --- a/ui-stats.c +++ b/ui-stats.c @@ -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>"); diff --git a/ui-summary.c b/ui-summary.c index 947812a814ca..df7f739af595 100644 --- a/ui-summary.c +++ b/ui-summary.c @@ -114,6 +114,9 @@ void cgit_print_repo_readme(const char *path) } free(mimetype); + if (path) + ctx.page.title = fmtalloc("%s - %s", path, ctx.page.title); + cgit_print_layout_start(); if (ctx.repo->readme.nr == 0) goto done; diff --git a/ui-tag.c b/ui-tag.c index 846d5b141f43..059524291557 100644 --- a/ui-tag.c +++ b/ui-tag.c @@ -25,15 +25,15 @@ static void print_tag_content(char *buf) html_txt(buf); html("</div>"); if (p) { - html("<div class='commit-msg'>"); + html("<pre class='commit-msg'>"); html_txt(++p); - html("</div>"); + html("</pre>"); } } static void print_download_links(char *revname) { - html("<tr><th>download</th><td class='sha1'>"); + html("<tr><th>download</th><td class='oid'>"); cgit_print_snapshot_links(ctx.repo, revname, "<br/>"); html("</td></tr>"); } @@ -91,7 +91,7 @@ void cgit_print_tag(char *revname) cgit_close_filter(ctx.repo->email_filter); html("</td></tr>\n"); } - html("<tr><td>tagged object</td><td class='sha1'>"); + html("<tr><td>tagged object</td><td class='oid'>"); cgit_object_link(tag->tagged); html("</td></tr>\n"); if (ctx.repo->snapshots) @@ -106,7 +106,7 @@ void cgit_print_tag(char *revname) html("<tr><td>tag name</td><td>"); html_txt(revname); html("</td></tr>\n"); - html("<tr><td>tagged object</td><td class='sha1'>"); + html("<tr><td>tagged object</td><td class='oid'>"); cgit_object_link(obj); html("</td></tr>\n"); if (ctx.repo->snapshots) diff --git a/ui-tree.c b/ui-tree.c index 84eb17d6470d..21e0b88448a4 100644 --- a/ui-tree.c +++ b/ui-tree.c @@ -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; + int 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,22 +240,45 @@ 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>"); cgit_log_link("log", NULL, "button", ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL, ctx.qry.showmsg, 0); - if (ctx.repo->max_stats) + if (ctx.repo->max_stats) { + html(" "); cgit_stats_link("stats", NULL, "button", ctx.qry.head, fullpath.buf); - if (!S_ISGITLINK(mode)) + } + if (!S_ISGITLINK(mode)) { + html(" "); cgit_plain_link("plain", NULL, "button", ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf); - if (!S_ISDIR(mode) && ctx.repo->enable_blame) + } + if (!S_ISDIR(mode) && ctx.repo->enable_blame) { + html(" "); 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 +318,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 +349,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; } @@ -370,12 +393,12 @@ void cgit_print_tree(const char *rev, char *path) walk_tree_ctx.curr_rev = xstrdup(rev); if (path == NULL) { - ls_tree(&commit->maybe_tree->object.oid, NULL, &walk_tree_ctx); + ls_tree(get_commit_tree_oid(commit), NULL, &walk_tree_ctx); goto cleanup; } - read_tree_recursive(the_repository, commit->maybe_tree, "", 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) |