aboutsummaryrefslogtreecommitdiff
diff refs
from:
to:
flip
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--AUTHORS12
-rw-r--r--Makefile26
-rw-r--r--README99
-rw-r--r--README.md95
-rw-r--r--cache.c53
-rw-r--r--cgit.c244
-rw-r--r--cgit.css421
-rw-r--r--cgit.h72
-rw-r--r--cgit.js68
-rw-r--r--cgit.mk1
-rw-r--r--cgit.svg5
-rw-r--r--cgitrc.5.txt154
-rw-r--r--cmd.c31
-rw-r--r--favicon.svg5
-rw-r--r--filter.c22
-rwxr-xr-xfilters/ci-jenkins.sh58
-rwxr-xr-xfilters/commit-links.sh2
-rw-r--r--filters/email-gravatar.lua17
-rw-r--r--filters/email-libravatar.lua17
-rw-r--r--filters/file-authentication.lua31
-rw-r--r--filters/gentoo-ldap-authentication.lua31
-rwxr-xr-xfilters/html-converters/md2html89
-rw-r--r--filters/simple-authentication.lua31
-rwxr-xr-xfilters/syntax-highlighting.py45
-rwxr-xr-xfilters/syntax-highlighting.sh14
-rwxr-xr-xgen-version.sh18
m---------git0
-rw-r--r--html.c2
-rw-r--r--parsing.c19
-rw-r--r--robots.txt1
-rw-r--r--scan-tree.c43
-rw-r--r--scan-tree.h4
-rw-r--r--shared.c73
-rw-r--r--solarized.css140
-rwxr-xr-xtests/setup.sh14
-rwxr-xr-xtests/t0001-validate-git-versions.sh10
-rwxr-xr-xtests/t0105-commit.sh2
-rwxr-xr-xtests/t0106-diff.sh61
-rwxr-xr-xtests/t0107-snapshot.sh125
-rwxr-xr-xtests/t0109-gitconfig.sh8
-rw-r--r--tests/t0112-ci.sh127
-rw-r--r--tests/t0113-ci-lua.sh78
-rw-r--r--tests/t0114-broken-repo.sh71
-rw-r--r--ui-atom.c40
-rw-r--r--ui-atom.h2
-rw-r--r--ui-blame.c56
-rw-r--r--ui-blob.c38
-rw-r--r--ui-ci.c152
-rw-r--r--ui-ci.h7
-rw-r--r--ui-clone.c54
-rw-r--r--ui-commit.c23
-rw-r--r--ui-diff.c116
-rw-r--r--ui-log.c69
-rw-r--r--ui-log.h2
-rw-r--r--ui-patch.c16
-rw-r--r--ui-plain.c27
-rw-r--r--ui-refs.c13
-rw-r--r--ui-repolist.c8
-rw-r--r--ui-shared.c327
-rw-r--r--ui-shared.h6
-rw-r--r--ui-snapshot.c57
-rw-r--r--ui-ssdiff.c23
-rw-r--r--ui-stats.c63
-rw-r--r--ui-summary.c2
-rw-r--r--ui-summary.h2
-rw-r--r--ui-tag.c20
-rw-r--r--ui-tree.c69
68 files changed, 2726 insertions, 906 deletions
diff --git a/.gitignore b/.gitignore
index 661df34..084a985 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,3 +10,4 @@ cgitrc.5.pdf
cgitrc.5.xml
*.o
*.d
+__pycache__/
diff --git a/AUTHORS b/AUTHORS
index 031de33..e68846e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,13 +1,5 @@
Maintainer:
- Jason A. Donenfeld <Jason@zx2c4.com>
+ Saya Andy <saya.andy@posteo.com>
Contributors:
- Jason A. Donenfeld <Jason@zx2c4.com>
- Lukas Fleischer <cgit@cryptocrack.de>
- Johan Herland <johan@herland.net>
- Lars Hjemli <hjemli@gmail.com>
- Ferry Huberts <ferry.huberts@pelagic.nl>
- John Keeping <john@keeping.me.uk>
-
-Previous Maintainer:
- Lars Hjemli <hjemli@gmail.com>
+ Saya Andy <saya.andy@posteo.com>
diff --git a/Makefile b/Makefile
index 05ea71f..b87116a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
all::
-CGIT_VERSION = v1.2.1
+CGIT_VERSION = v1.3.1
CGIT_SCRIPT_NAME = cgit.cgi
CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
@@ -14,8 +14,8 @@ htmldir = $(docdir)
pdfdir = $(docdir)
mandir = $(prefix)/share/man
SHA1_HEADER = <openssl/sha.h>
-GIT_VER = 2.18.0
-GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.gz
+GIT_VER = 2.54.0
+GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz
INSTALL = install
COPYTREE = cp -r
MAN5_TXT = $(wildcard *.5.txt)
@@ -87,8 +87,12 @@ install: all
$(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
$(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
+ $(INSTALL) -m 0644 solarized.css $(DESTDIR)$(CGIT_DATA_PATH)/solarized.css
+ $(INSTALL) -m 0644 cgit.js $(DESTDIR)$(CGIT_DATA_PATH)/cgit.js
$(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
+ $(INSTALL) -m 0644 cgit.svg $(DESTDIR)$(CGIT_DATA_PATH)/cgit.svg
$(INSTALL) -m 0644 favicon.ico $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico
+ $(INSTALL) -m 0644 favicon.svg $(DESTDIR)$(CGIT_DATA_PATH)/favicon.svg
$(INSTALL) -m 0644 robots.txt $(DESTDIR)$(CGIT_DATA_PATH)/robots.txt
$(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir)
$(COPYTREE) filters/* $(DESTDIR)$(filterdir)
@@ -107,11 +111,23 @@ install-pdf: doc-pdf
$(INSTALL) -m 0755 -d $(DESTDIR)$(pdfdir)
$(INSTALL) -m 0644 $(DOC_PDF) $(DESTDIR)$(pdfdir)
+define rm_f
+rm -f $(1)
+
+endef
+
uninstall:
rm -f $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
+ rm -f $(DESTDIR)$(CGIT_DATA_PATH)/solarized.css
+ rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.js
rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
+ rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.svg
rm -f $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico
+ rm -f $(DESTDIR)$(CGIT_DATA_PATH)/favicon.svg
+ rm -f $(DESTDIR)$(CGIT_DATA_PATH)/robots.txt
+ $(foreach file,$(patsubst filters/%,%,$(shell find filters/ ! -type d)), \
+ $(call rm_f,$(DESTDIR)$(filterdir)/$(file)))
uninstall-doc: uninstall-man uninstall-html uninstall-pdf
@@ -148,7 +164,7 @@ $(DOC_PDF): %.pdf : %.txt
clean: clean-doc
$(RM) cgit VERSION CGIT-CFLAGS *.o tags
- $(RM) -r .deps
+ $(RM) -r .depend
cleanall: clean
$(MAKE) -C git clean
@@ -157,7 +173,7 @@ clean-doc:
$(RM) cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
get-git:
- curl -L $(GIT_URL) | tar -xzf - && rm -rf git && mv git-$(GIT_VER) git
+ curl -L $(GIT_URL) | tar -xJf - && rm -rf git && mv git-$(GIT_VER) git
tags:
$(QUIET_TAGS)find . -name '*.[ch]' | xargs ctags
diff --git a/README b/README
deleted file mode 100644
index 7a6b4a4..0000000
--- a/README
+++ /dev/null
@@ -1,99 +0,0 @@
-cgit - 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.
-
-Installation
-------------
-
-Building cgit involves building a proper version of Git. How to do this
-depends on how you obtained the cgit sources:
-
-a) If you're working in a cloned cgit repository, you first need to
-initialize and update the Git submodule:
-
- $ git submodule init # register the Git submodule in .git/config
- $ $EDITOR .git/config # if you want to specify a different url for git
- $ git submodule update # clone/fetch and checkout correct git version
-
-b) If you're building from a cgit tarball, you can download a proper git
-version like this:
-
- $ make get-git
-
-When either a) or b) has been performed, you can build and install cgit like
-this:
-
- $ make
- $ sudo make install
-
-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
-------------
-
-* libzip
-* libcrypto (OpenSSL)
-* libssl (OpenSSL)
-* optional: luajit or lua, most reliably used when pkg-config is available
-
-Apache configuration
---------------------
-
-A new `Directory` section must probably be added for cgit, possibly something
-like this:
-
- <Directory "/var/www/htdocs/cgit/">
- AllowOverride None
- Options +ExecCGI
- Order allow,deny
- Allow from all
- </Directory>
-
-
-Runtime configuration
----------------------
-
-The file `/etc/cgitrc` is read by cgit before handling a request. In addition
-to runtime parameters, this file may also contain a list of repositories
-displayed by cgit (see `cgitrc.5.txt` for further details).
-
-The cache
----------
-
-When cgit is invoked it looks for a cache file matching the request and
-returns it to the client. If no such cache file exists (or if it has expired),
-the content for the request is written into the proper cache file before the
-file is returned.
-
-If the cache file has expired but cgit is unable to obtain a lock for it, the
-stale cache file is returned to the client. This is done to favour page
-throughput over page freshness.
-
-The generated content contains the complete response to the client, including
-the HTTP headers `Modified` and `Expires`.
-
-Online presence
----------------
-
-* The cgit homepage is hosted by cgit at <https://git.zx2c4.com/cgit/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>
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1e13a37
--- /dev/null
+++ b/README.md
@@ -0,0 +1,95 @@
+## cgitext – CGI for Git extension by Saya Andy
+
+This is a custom expansion to cgit. Its most prominent changes by now are:
+
+* CI/CD tab, linking to an external service on ref pages by using template. The Jenkins plugin is added to support a scenario when a ref lacks a pipeline.
+
+* Theming now relies on CSS variables set in cgit.css, to allow easier custom additions and implicit filter theming.
+
+* Allow to use multiple favicons and of MIME type different from x-icon (f.ex. svg/png/webp).
+
+* Use vector-based format for favicon and cgit logo and make it default.
+
+* Include optional solarized.css addon theme.
+
+* Allow user to pick variable refs for diff in web ui.
+
+The original cgit project is hosted at [git.zx2c4.com](https://git.zx2c4.com/cgit/about/)
+
+## cgit – 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.
+
+### Installation
+
+Building cgit involves building a proper version of Git. How to do this depends on how you obtained the cgit sources:
+
+1. If you're working in a cloned cgit repository, you first need to initialize and update the Git submodule:
+
+ $ git submodule init # register the Git submodule in .git/config
+ $ $EDITOR .git/config # if you want to specify a different url for git
+ $ git submodule update # clone/fetch and checkout correct git version
+
+2. If you're building from a cgit tarball, you can download a proper git version like this:
+
+ $ make get-git
+
+When either `1.` or `2.` has been performed, you can build and install cgit like
+this:
+```
+make
+sudo make install
+```
+
+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
+
+* libzip
+* libcrypto (OpenSSL)
+* libssl (OpenSSL)
+* optional: luajit or lua, most reliably used when pkg-config is available
+
+### Apache configuration
+
+A new `Directory` section must probably be added for cgit, possibly something like this:
+
+```
+<Directory "/var/www/htdocs/cgit/">
+ AllowOverride None
+ Options +ExecCGI
+ Order allow,deny
+ Allow from all
+</Directory>
+```
+
+### Runtime configuration
+
+The file `/etc/cgitrc` is read by cgit before handling a request. In addition to runtime parameters, this file may also contain a list of repositories displayed by cgit (see `cgitrc.5.txt` for further details).
+
+### The cache
+
+When cgit is invoked it looks for a cache file matching the request and returns it to the client. If no such cache file exists (or if it has expired), the content for the request is written into the proper cache file before the file is returned.
+
+If the cache file has expired but cgit is unable to obtain a lock for it, the stale cache file is returned to the client. This is done to favour page throughput over page freshness.
+
+The generated content contains the complete response to the client, including the HTTP headers `Modified` and `Expires`.
+
+### Online presence
+
+* The cgit homepage is hosted by cgit at [git.zx2c4.com](https://git.zx2c4.com/cgit/about/)
+
+* Patches, bug reports, discussions and support should go to the cgit mailing list: [cgit@lists.zx2c4.com](mailto:cgit@lists.zx2c4.com). To sign up, visit [lists.zx2c4.com](https://lists.zx2c4.com/mailman/listinfo/cgit)
diff --git a/cache.c b/cache.c
index 2c70be7..e70af13 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 */
@@ -180,6 +185,8 @@ static int lock_slot(struct cache_slot *slot)
slot->lock_fd = -1;
return saved_errno;
}
+ if (ftruncate(slot->lock_fd, 0) < 0)
+ return errno;
if (xwrite(slot->lock_fd, slot->key, slot->keylen + 1) < 0)
return errno;
return 0;
@@ -401,12 +408,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 6301b87..bb32dc7 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,11 +1,18 @@
/* cgit.c: cgi for the git scm
*
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ * Copyright (C) 2026 Saya Andy <saya.andy@posteo.com>
+ *
+ * Modified 2026 by Saya Andy:
+ * Always check there is git repo on repo.path
+ * Allow to include multiple favicons
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "cache.h"
#include "cmd.h"
@@ -19,6 +26,16 @@
const char *cgit_version = CGIT_VERSION;
+__attribute__((constructor))
+static void constructor_environment()
+{
+ /* Do not look in /etc/ for gitconfig and gitattributes. */
+ setenv("GIT_CONFIG_NOSYSTEM", "1", 1);
+ setenv("GIT_ATTR_NOSYSTEM", "1", 1);
+ unsetenv("HOME");
+ unsetenv("XDG_CONFIG_HOME");
+}
+
static void add_mimetype(const char *name, const char *value)
{
struct string_list_item *item;
@@ -29,29 +46,47 @@ static void add_mimetype(const char *name, const char *value)
static void process_cached_repolist(const char *path);
-static void repo_config(struct cgit_repo *repo, const char *name, const char *value)
+void cgit_repo_config(struct cgit_repo *repo, const char *name, const char *value)
{
const char *path;
struct string_list_item *item;
if (!strcmp(name, "name"))
- repo->name = xstrdup(value);
+ repo->name = strdup_first_line(value);
else if (!strcmp(name, "clone-url"))
- repo->clone_url = xstrdup(value);
+ repo->clone_url = strdup_first_line(value);
else if (!strcmp(name, "desc"))
- repo->desc = xstrdup(value);
+ repo->desc = strdup_first_line(value);
else if (!strcmp(name, "owner"))
- repo->owner = xstrdup(value);
+ repo->owner = strdup_first_line(value);
else if (!strcmp(name, "homepage"))
- repo->homepage = xstrdup(value);
+ repo->homepage = strdup_first_line(value);
+ else if (!strcmp(name, "ci-url")) {
+ repo->ci_url = strdup_first_line(value);
+ /* An explicit repo.ci-url would otherwise never be used
+ * when the more specific urls are inherited from the
+ * global settings, so discard those.
+ */
+ if (repo->ci_branch_url == ctx.cfg.ci_branch_url)
+ repo->ci_branch_url = NULL;
+ if (repo->ci_tag_url == ctx.cfg.ci_tag_url)
+ repo->ci_tag_url = NULL;
+ } else if (!strcmp(name, "ci-branch-url"))
+ repo->ci_branch_url = strdup_first_line(value);
+ else if (!strcmp(name, "ci-tag-url"))
+ repo->ci_tag_url = strdup_first_line(value);
else if (!strcmp(name, "defbranch"))
- repo->defbranch = xstrdup(value);
+ repo->defbranch = strdup_first_line(value);
else if (!strcmp(name, "extra-head-content"))
- repo->extra_head_content = xstrdup(value);
+ repo->extra_head_content = strdup_first_line(value);
else if (!strcmp(name, "snapshots"))
repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
+ else if (!strcmp(name, "enable-blame"))
+ repo->enable_blame = atoi(value);
else if (!strcmp(name, "enable-commit-graph"))
repo->enable_commit_graph = atoi(value);
+ else if (!strcmp(name, "enable-follow-links"))
+ repo->enable_follow_links = atoi(value);
else if (!strcmp(name, "enable-log-filecount"))
repo->enable_log_filecount = atoi(value);
else if (!strcmp(name, "enable-log-linecount"))
@@ -75,22 +110,22 @@ static void repo_config(struct cgit_repo *repo, const char *name, const char *va
} else if (!strcmp(name, "max-stats"))
repo->max_stats = cgit_find_stats_period(value, NULL);
else if (!strcmp(name, "module-link"))
- repo->module_link= xstrdup(value);
+ repo->module_link= strdup_first_line(value);
else if (skip_prefix(name, "module-link.", &path)) {
- item = string_list_append(&repo->submodules, xstrdup(path));
- item->util = xstrdup(value);
+ item = string_list_append(&repo->submodules, strdup_first_line(path));
+ item->util = strdup_first_line(value);
} else if (!strcmp(name, "section"))
- repo->section = xstrdup(value);
+ repo->section = strdup_first_line(value);
else if (!strcmp(name, "snapshot-prefix"))
- repo->snapshot_prefix = xstrdup(value);
+ repo->snapshot_prefix = strdup_first_line(value);
else if (!strcmp(name, "readme") && value != NULL) {
if (repo->readme.items == ctx.cfg.readme.items)
memset(&repo->readme, 0, sizeof(repo->readme));
- string_list_append(&repo->readme, xstrdup(value));
+ string_list_append(&repo->readme, strdup_first_line(value));
} else if (!strcmp(name, "logo") && value != NULL)
- repo->logo = xstrdup(value);
+ repo->logo = strdup_first_line(value);
else if (!strcmp(name, "logo-link") && value != NULL)
- repo->logo_link = xstrdup(value);
+ repo->logo_link = strdup_first_line(value);
else if (!strcmp(name, "hide"))
repo->hide = atoi(value);
else if (!strcmp(name, "ignore"))
@@ -106,6 +141,8 @@ static void repo_config(struct cgit_repo *repo, const char *name, const char *va
repo->email_filter = cgit_new_filter(value, EMAIL);
else if (!strcmp(name, "owner-filter"))
repo->owner_filter = cgit_new_filter(value, OWNER);
+ else if (!strcmp(name, "ci-filter"))
+ repo->ci_filter = cgit_new_filter(value, CI);
}
}
@@ -114,39 +151,47 @@ static void config_cb(const char *name, const char *value)
const char *arg;
if (!strcmp(name, "section"))
- ctx.cfg.section = xstrdup(value);
+ ctx.cfg.section = strdup_first_line(value);
else if (!strcmp(name, "repo.url"))
ctx.repo = cgit_add_repo(value);
else if (ctx.repo && !strcmp(name, "repo.path"))
ctx.repo->path = trim_end(value, '/');
else if (ctx.repo && skip_prefix(name, "repo.", &arg))
- repo_config(ctx.repo, arg, value);
+ cgit_repo_config(ctx.repo, arg, value);
else if (!strcmp(name, "readme"))
- string_list_append(&ctx.cfg.readme, xstrdup(value));
+ string_list_append(&ctx.cfg.readme, strdup_first_line(value));
else if (!strcmp(name, "root-title"))
- ctx.cfg.root_title = xstrdup(value);
+ ctx.cfg.root_title = strdup_first_line(value);
else if (!strcmp(name, "root-desc"))
- ctx.cfg.root_desc = xstrdup(value);
+ ctx.cfg.root_desc = strdup_first_line(value);
else if (!strcmp(name, "root-readme"))
- ctx.cfg.root_readme = xstrdup(value);
+ ctx.cfg.root_readme = strdup_first_line(value);
else if (!strcmp(name, "css"))
- ctx.cfg.css = xstrdup(value);
+ string_list_append(&ctx.cfg.css, strdup_first_line(value));
+ else if (!strcmp(name, "js"))
+ string_list_append(&ctx.cfg.js, strdup_first_line(value));
else if (!strcmp(name, "favicon"))
- ctx.cfg.favicon = xstrdup(value);
+ string_list_append(&ctx.cfg.favicon, strdup_first_line(value));
else if (!strcmp(name, "footer"))
- ctx.cfg.footer = xstrdup(value);
+ ctx.cfg.footer = strdup_first_line(value);
else if (!strcmp(name, "head-include"))
- ctx.cfg.head_include = xstrdup(value);
+ ctx.cfg.head_include = strdup_first_line(value);
else if (!strcmp(name, "header"))
- ctx.cfg.header = xstrdup(value);
+ ctx.cfg.header = strdup_first_line(value);
else if (!strcmp(name, "logo"))
- ctx.cfg.logo = xstrdup(value);
+ ctx.cfg.logo = strdup_first_line(value);
else if (!strcmp(name, "logo-link"))
- ctx.cfg.logo_link = xstrdup(value);
+ ctx.cfg.logo_link = strdup_first_line(value);
else if (!strcmp(name, "module-link"))
- ctx.cfg.module_link = xstrdup(value);
+ ctx.cfg.module_link = strdup_first_line(value);
+ else if (!strcmp(name, "ci-url"))
+ ctx.cfg.ci_url = strdup_first_line(value);
+ else if (!strcmp(name, "ci-branch-url"))
+ ctx.cfg.ci_branch_url = strdup_first_line(value);
+ else if (!strcmp(name, "ci-tag-url"))
+ ctx.cfg.ci_tag_url = strdup_first_line(value);
else if (!strcmp(name, "strict-export"))
- ctx.cfg.strict_export = xstrdup(value);
+ ctx.cfg.strict_export = strdup_first_line(value);
else if (!strcmp(name, "virtual-root"))
ctx.cfg.virtual_root = ensure_end(value, '/');
else if (!strcmp(name, "noplainemail"))
@@ -188,7 +233,7 @@ static void config_cb(const char *name, const char *value)
else if (!strcmp(name, "cache-size"))
ctx.cfg.cache_size = atoi(value);
else if (!strcmp(name, "cache-root"))
- ctx.cfg.cache_root = xstrdup(expand_macros(value));
+ ctx.cfg.cache_root = strdup_first_line(expand_macros(value));
else if (!strcmp(name, "cache-root-ttl"))
ctx.cfg.cache_root_ttl = atoi(value);
else if (!strcmp(name, "cache-repo-ttl"))
@@ -215,6 +260,8 @@ static void config_cb(const char *name, const char *value)
ctx.cfg.owner_filter = cgit_new_filter(value, OWNER);
else if (!strcmp(name, "auth-filter"))
ctx.cfg.auth_filter = cgit_new_filter(value, AUTH);
+ else if (!strcmp(name, "ci-filter"))
+ ctx.cfg.ci_filter = cgit_new_filter(value, CI);
else if (!strcmp(name, "embedded"))
ctx.cfg.embedded = atoi(value);
else if (!strcmp(name, "max-atom-items"))
@@ -225,26 +272,28 @@ static void config_cb(const char *name, const char *value)
ctx.cfg.max_repodesc_len = atoi(value);
else if (!strcmp(name, "max-blob-size"))
ctx.cfg.max_blob_size = atoi(value);
- else if (!strcmp(name, "max-repo-count"))
+ else if (!strcmp(name, "max-repo-count")) {
ctx.cfg.max_repo_count = atoi(value);
- else if (!strcmp(name, "max-commit-count"))
+ if (ctx.cfg.max_repo_count <= 0)
+ ctx.cfg.max_repo_count = INT_MAX;
+ } else if (!strcmp(name, "max-commit-count"))
ctx.cfg.max_commit_count = atoi(value);
else if (!strcmp(name, "project-list"))
- ctx.cfg.project_list = xstrdup(expand_macros(value));
+ ctx.cfg.project_list = strdup_first_line(expand_macros(value));
else if (!strcmp(name, "scan-path"))
if (ctx.cfg.cache_size)
process_cached_repolist(expand_macros(value));
else if (ctx.cfg.project_list)
scan_projects(expand_macros(value),
- ctx.cfg.project_list, repo_config);
+ ctx.cfg.project_list);
else
- scan_tree(expand_macros(value), repo_config);
+ scan_tree(expand_macros(value));
else if (!strcmp(name, "scan-hidden-path"))
ctx.cfg.scan_hidden_path = atoi(value);
else if (!strcmp(name, "section-from-path"))
ctx.cfg.section_from_path = atoi(value);
else if (!strcmp(name, "repository-sort"))
- ctx.cfg.repository_sort = xstrdup(value);
+ ctx.cfg.repository_sort = strdup_first_line(value);
else if (!strcmp(name, "section-sort"))
ctx.cfg.section_sort = atoi(value);
else if (!strcmp(name, "source-filter"))
@@ -258,19 +307,19 @@ static void config_cb(const char *name, const char *value)
else if (!strcmp(name, "side-by-side-diffs"))
ctx.cfg.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED;
else if (!strcmp(name, "agefile"))
- ctx.cfg.agefile = xstrdup(value);
+ ctx.cfg.agefile = strdup_first_line(value);
else if (!strcmp(name, "mimetype-file"))
- ctx.cfg.mimetype_file = xstrdup(value);
+ ctx.cfg.mimetype_file = strdup_first_line(value);
else if (!strcmp(name, "renamelimit"))
ctx.cfg.renamelimit = atoi(value);
else if (!strcmp(name, "remove-suffix"))
ctx.cfg.remove_suffix = atoi(value);
else if (!strcmp(name, "robots"))
- ctx.cfg.robots = xstrdup(value);
+ ctx.cfg.robots = strdup_first_line(value);
else if (!strcmp(name, "clone-prefix"))
- ctx.cfg.clone_prefix = xstrdup(value);
+ ctx.cfg.clone_prefix = strdup_first_line(value);
else if (!strcmp(name, "clone-url"))
- ctx.cfg.clone_url = xstrdup(value);
+ ctx.cfg.clone_url = strdup_first_line(value);
else if (!strcmp(name, "local-time"))
ctx.cfg.local_time = atoi(value);
else if (!strcmp(name, "commit-sort")) {
@@ -312,11 +361,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")) {
@@ -364,9 +413,7 @@ static void prepare_context(void)
ctx.cfg.case_sensitive_sort = 1;
ctx.cfg.branch_sort = 0;
ctx.cfg.commit_sort = 0;
- ctx.cfg.css = "/cgit.css";
- ctx.cfg.logo = "/cgit.png";
- ctx.cfg.favicon = "/favicon.ico";
+ ctx.cfg.logo = "/cgit.svg";
ctx.cfg.local_time = 0;
ctx.cfg.enable_http_clone = 1;
ctx.cfg.enable_index_owner = 1;
@@ -416,7 +463,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)
@@ -431,16 +478,15 @@ struct refmatch {
int match;
};
-static int find_current_ref(const char *refname, const struct object_id *oid,
- int flags, void *cb_data)
+static int find_current_ref(const struct reference *ref, void *cb_data)
{
struct refmatch *info;
info = (struct refmatch *)cb_data;
- if (!strcmp(refname, info->req_ref))
+ if (!strcmp(ref->name, info->req_ref))
info->match = 1;
if (!info->first_ref)
- info->first_ref = xstrdup(refname);
+ info->first_ref = xstrdup(ref->name);
return info->match;
}
@@ -458,7 +504,8 @@ static char *find_default_branch(struct cgit_repo *repo)
info.req_ref = repo->defbranch;
info.first_ref = NULL;
info.match = 0;
- for_each_branch_ref(find_current_ref, &info);
+ refs_for_each_branch_ref(get_main_ref_store(the_repository),
+ find_current_ref, &info);
if (info.match)
ref = info.req_ref;
else
@@ -475,7 +522,8 @@ static char *guess_defbranch(void)
const char *ref, *refname;
struct object_id oid;
- ref = resolve_ref_unsafe("HEAD", 0, &oid, NULL);
+ ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", 0, &oid, NULL);
if (!ref || !skip_prefix(ref, "refs/heads/", &refname))
return "master";
return xstrdup(refname);
@@ -495,9 +543,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);
@@ -563,18 +613,16 @@ static void prepare_repo_env(int *nongit)
/* The path to the git repository. */
setenv("GIT_DIR", ctx.repo->path, 1);
- /* Do not look in /etc/ for gitconfig and gitattributes. */
- setenv("GIT_CONFIG_NOSYSTEM", "1", 1);
- setenv("GIT_ATTR_NOSYSTEM", "1", 1);
- unsetenv("HOME");
- unsetenv("XDG_CONFIG_HOME");
-
/* Setup the git directory and initialize the notes system. Both of these
* load local configuration from the git repository, so we do them both while
- * the HOME variables are unset. */
+ * the HOME variables are unset. Initializing the notes system requires an
+ * actual repository, as it would otherwise dereference the not yet known
+ * hash algorithm. */
setup_git_directory_gently(nongit);
- init_display_notes(NULL);
+ if (!*nongit)
+ load_display_notes(NULL);
}
+
static int prepare_repo_cmd(int nongit)
{
struct object_id oid;
@@ -619,7 +667,7 @@ static int prepare_repo_cmd(int nongit)
return 1;
}
- if (get_oid(ctx.qry.head, &oid)) {
+ if (repo_get_oid(the_repository, ctx.qry.head, &oid)) {
char *old_head = ctx.qry.head;
ctx.qry.head = xstrdup(ctx.repo->defbranch);
cgit_print_error_page(404, "Not found",
@@ -645,7 +693,7 @@ static inline void open_auth_filter(const char *function)
ctx.env.https ? ctx.env.https : "",
ctx.qry.repo ? ctx.qry.repo : "",
ctx.qry.page ? ctx.qry.page : "",
- ctx.qry.url ? ctx.qry.url : "",
+ cgit_currentfullurl(),
cgit_loginurl());
}
@@ -769,15 +817,6 @@ static char *build_snapshot_setting(int bitmap)
return strbuf_detach(&result, NULL);
}
-static char *get_first_line(char *txt)
-{
- char *t = xstrdup(txt);
- char *p = strchr(t, '\n');
- if (p)
- *p = '\0';
- return t;
-}
-
static void print_repo(FILE *f, struct cgit_repo *repo)
{
struct string_list_item *item;
@@ -786,11 +825,8 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
fprintf(f, "repo.path=%s\n", repo->path);
if (repo->owner)
fprintf(f, "repo.owner=%s\n", repo->owner);
- if (repo->desc) {
- char *tmp = get_first_line(repo->desc);
- fprintf(f, "repo.desc=%s\n", tmp);
- free(tmp);
- }
+ if (repo->desc)
+ fprintf(f, "repo.desc=%s\n", repo->desc);
for_each_string_list_item(item, &repo->readme) {
if (item->util)
fprintf(f, "repo.readme=%s:%s\n", (char *)item->util, item->string);
@@ -807,10 +843,20 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
fprintf(f, "repo.section=%s\n", repo->section);
if (repo->homepage)
fprintf(f, "repo.homepage=%s\n", repo->homepage);
+ if (repo->ci_url)
+ fprintf(f, "repo.ci-url=%s\n", repo->ci_url);
+ if (repo->ci_branch_url)
+ fprintf(f, "repo.ci-branch-url=%s\n", repo->ci_branch_url);
+ if (repo->ci_tag_url)
+ fprintf(f, "repo.ci-tag-url=%s\n", repo->ci_tag_url);
if (repo->clone_url)
fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
+ fprintf(f, "repo.enable-blame=%d\n",
+ repo->enable_blame);
fprintf(f, "repo.enable-commit-graph=%d\n",
repo->enable_commit_graph);
+ fprintf(f, "repo.enable-follow-links=%d\n",
+ repo->enable_follow_links);
fprintf(f, "repo.enable-log-filecount=%d\n",
repo->enable_log_filecount);
fprintf(f, "repo.enable-log-linecount=%d\n",
@@ -825,6 +871,8 @@ static void print_repo(FILE *f, struct cgit_repo *repo)
cgit_fprintf_filter(repo->email_filter, f, "repo.email-filter=");
if (repo->owner_filter && repo->owner_filter != ctx.cfg.owner_filter)
cgit_fprintf_filter(repo->owner_filter, f, "repo.owner-filter=");
+ if (repo->ci_filter && repo->ci_filter != ctx.cfg.ci_filter)
+ cgit_fprintf_filter(repo->ci_filter, f, "repo.ci-filter=");
if (repo->snapshots != ctx.cfg.snapshots) {
char *tmp = build_snapshot_setting(repo->snapshots);
fprintf(f, "repo.snapshots=%s\n", tmp ? tmp : "");
@@ -887,9 +935,9 @@ static int generate_cached_repolist(const char *path, const char *cached_rc)
}
idx = cgit_repolist.count;
if (ctx.cfg.project_list)
- scan_projects(path, ctx.cfg.project_list, repo_config);
+ scan_projects(path, ctx.cfg.project_list);
else
- scan_tree(path, repo_config);
+ scan_tree(path);
print_repolist(f, &cgit_repolist, idx);
if (rename(locked_rc.buf, cached_rc))
fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
@@ -919,10 +967,9 @@ static void process_cached_repolist(const char *path)
*/
if (generate_cached_repolist(path, cached_rc.buf)) {
if (ctx.cfg.project_list)
- scan_projects(path, ctx.cfg.project_list,
- repo_config);
+ scan_projects(path, ctx.cfg.project_list);
else
- scan_tree(path, repo_config);
+ scan_tree(path);
}
goto out;
}
@@ -954,7 +1001,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);
+ printf("CGit %s | https://sayag.it/cgitext.git | https://git.zx2c4.com/cgit/\n\nCompiled in features:\n", CGIT_VERSION);
#ifdef NO_LUA
printf("[-] ");
#else
@@ -983,9 +1030,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) ||
@@ -1003,7 +1050,7 @@ static void cgit_parse_args(int argc, const char **argv)
*/
ctx.cfg.snapshots = 0xFF;
scan++;
- scan_tree(arg, repo_config);
+ scan_tree(arg);
}
}
if (scan) {
@@ -1028,7 +1075,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)
@@ -1037,6 +1084,12 @@ static int calc_ttl(void)
return ctx.cfg.cache_repo_ttl;
}
+static NORETURN void cgit_die_routine(const char *msg, va_list params)
+{
+ cgit_vprint_error_page(400, "Bad request", msg, params);
+ exit(0);
+}
+
int cmd_main(int argc, const char **argv)
{
const char *path;
@@ -1044,6 +1097,7 @@ int cmd_main(int argc, const char **argv)
cgit_init_filters();
atexit(cgit_cleanup_filters);
+ set_die_routine(cgit_die_routine);
prepare_context();
cgit_repolist.length = 0;
diff --git a/cgit.css b/cgit.css
index d4aadbf..7892eca 100644
--- a/cgit.css
+++ b/cgit.css
@@ -1,15 +1,88 @@
div#cgit {
+ --bg: white;
+ --bg-zebra: #f7f7f7;
+ --bg-hl: #eee;
+ --bg-hl2: #ccc;
+ --fg: #333;
+ --fg-strong: #000;
+ --fg-mid: #444;
+ --fg-mute: #777;
+ --fg-dim: #888;
+ --fg-faint: #bbb;
+ --fg-faintest: #ccc;
+ --fg-on-tint: black;
+ --fg-linenum: gray;
+ --border: #ccc;
+ --border-mid: #aaa;
+ --border-dark: #777;
+ --border-strong: black;
+
+ --red: red;
+ --red-mid: #c00;
+ --red-dark: #800;
+ --green: green;
+ --green-mid: #080;
+ --green-dark: #040;
+ --blue: blue;
+ --blue-dark: #009;
+
+ --graph-col1: #a00;
+ --graph-col2: #0a0;
+ --graph-col3: #aa0;
+ --graph-col4: #00a;
+ --graph-col5: #a0a;
+ --graph-col6: #0aa;
+
+ --graph-add-bg: #5c5;
+ --graph-rem-bg: #c55;
+
+ --notes-bg: #ffd;
+ --notes-border: #ee9;
+ --add-bg: #cfc;
+ --add-bg-dark: #aca;
+ --del-bg: #fcc;
+ --del-bg-dark: #caa;
+ --changed-bg: #ffc;
+ --changed-bg-dark: #cca;
+ --hunk-bg: #ccf;
+
+ --deco-branch-bg: #8f8;
+ --deco-branch-border: #070;
+ --deco-tag-bg: #ff8;
+ --deco-tag-border: #770;
+ --deco-tag-annotated-bg: #fc8;
+ --deco-tag-annotated-border: #770;
+ --deco-remote-bg: #ccf;
+ --deco-remote-border: #007;
+ --deco-other-bg: #f88;
+ --deco-other-border: #700;
+
+ --syn-keyword: #000;
+ --syn-keyword-alt: #0057ae;
+ --syn-type: #010181;
+ --syn-string: #bf0303;
+ --syn-string-alt: #818100;
+ --syn-escape: #f0f;
+ --syn-comment: #838183;
+ --syn-number: #b07e00;
+ --syn-preproc: #008200;
+ --syn-operator: #000;
+ --syn-interpolation: #0057ae;
+ --syn-linenum: #555;
+ --syn-error: #bf0303;
+ --syn-mark-bg: #ffb;
+
padding: 0em;
margin: 0em;
font-family: sans-serif;
font-size: 10pt;
- color: #333;
- background: white;
+ color: var(--fg);
+ background: var(--bg);
padding: 4px;
}
div#cgit a {
- color: blue;
+ color: var(--blue);
text-decoration: none;
}
@@ -38,7 +111,7 @@ div#cgit table#header td.main {
}
div#cgit table#header td.main a {
- color: #000;
+ color: var(--fg-strong);
}
div#cgit table#header td.form {
@@ -56,13 +129,13 @@ div#cgit table#header td.form select {
}
div#cgit table#header td.sub {
- color: #777;
- border-top: solid 1px #ccc;
+ color: var(--fg-mute);
+ border-top: solid 1px var(--border);
padding-left: 10px;
}
div#cgit table.tabs {
- border-bottom: solid 3px #ccc;
+ border-bottom: solid 3px var(--border);
border-collapse: collapse;
margin-top: 2em;
margin-bottom: 0px;
@@ -76,13 +149,13 @@ div#cgit table.tabs td {
div#cgit table.tabs td a {
padding: 2px 0.75em;
- color: #777;
+ color: var(--fg-mute);
font-size: 110%;
}
div#cgit table.tabs td a.active {
- color: #000;
- background-color: #ccc;
+ color: var(--fg-strong);
+ background-color: var(--bg-hl2);
}
div#cgit table.tabs a[href^="http://"]:after, div#cgit table.tabs a[href^="https://"]:after {
@@ -109,14 +182,14 @@ div#cgit table.tabs td.form select {
div#cgit div.path {
margin: 0px;
padding: 5px 2em 2px 2em;
- color: #000;
- background-color: #eee;
+ color: var(--fg-strong);
+ background-color: var(--bg-hl);
}
div#cgit div.content {
margin: 0px;
padding: 2em;
- border-bottom: solid 3px #ccc;
+ border-bottom: solid 3px var(--border);
}
@@ -127,46 +200,46 @@ div#cgit table.list {
}
div#cgit table.list tr {
- background: white;
+ background: var(--bg);
}
div#cgit table.list tr.logheader {
- background: #eee;
+ background: var(--bg-hl);
}
div#cgit table.list tr:nth-child(even) {
- background: #f7f7f7;
+ background: var(--bg-zebra);
}
div#cgit table.list tr:nth-child(odd) {
- background: white;
+ background: var(--bg);
}
div#cgit table.list tr:hover {
- background: #eee;
+ background: var(--bg-hl);
}
div#cgit table.list tr.nohover {
- background: white;
+ background: var(--bg);
}
div#cgit table.list tr.nohover:hover {
- background: white;
+ background: var(--bg);
}
div#cgit table.list tr.nohover-highlight:hover:nth-child(even) {
- background: #f7f7f7;
+ background: var(--bg-zebra);
}
div#cgit table.list tr.nohover-highlight:hover:nth-child(odd) {
- background: white;
+ background: var(--bg);
}
div#cgit table.list th {
font-weight: bold;
- /* color: #888;
- border-top: dashed 1px #888;
- border-bottom: dashed 1px #888;
+ /* color: var(--fg-dim);
+ border-top: dashed 1px var(--fg-dim);
+ border-bottom: dashed 1px var(--fg-dim);
*/
padding: 0.1em 0.5em 0.05em 0.5em;
vertical-align: baseline;
@@ -183,27 +256,27 @@ div#cgit table.list td.commitgraph {
}
div#cgit table.list td.commitgraph .column1 {
- color: #a00;
+ color: var(--graph-col1);
}
div#cgit table.list td.commitgraph .column2 {
- color: #0a0;
+ color: var(--graph-col2);
}
div#cgit table.list td.commitgraph .column3 {
- color: #aa0;
+ color: var(--graph-col3);
}
div#cgit table.list td.commitgraph .column4 {
- color: #00a;
+ color: var(--graph-col4);
}
div#cgit table.list td.commitgraph .column5 {
- color: #a0a;
+ color: var(--graph-col5);
}
div#cgit table.list td.commitgraph .column6 {
- color: #0aa;
+ color: var(--graph-col6);
}
div#cgit table.list td.logsubject {
@@ -218,16 +291,16 @@ div#cgit table.list td.logmsg {
}
div#cgit table.list td a {
- color: black;
+ color: var(--fg-strong);
}
div#cgit table.list td a.ls-dir {
font-weight: bold;
- color: #00f;
+ color: var(--blue);
}
div#cgit table.list td a:hover {
- color: #00f;
+ color: var(--blue);
}
div#cgit img {
@@ -261,21 +334,21 @@ div#cgit div#summary {
div#cgit table#downloads {
float: right;
border-collapse: collapse;
- border: solid 1px #777;
+ border: solid 1px var(--border-dark);
margin-left: 0.5em;
margin-bottom: 0.5em;
}
div#cgit table#downloads th {
- background-color: #ccc;
+ background-color: var(--bg-hl2);
}
div#cgit div#blob {
- border: solid 1px black;
+ border: solid 1px var(--border-strong);
}
div#cgit div.error {
- color: red;
+ color: var(--red);
font-weight: bold;
margin: 1em 2em;
}
@@ -297,21 +370,21 @@ div#cgit td.ls-mode {
div#cgit table.blob {
margin-top: 0.5em;
- border-top: solid 1px black;
+ border-top: solid 1px var(--border-strong);
}
div#cgit table.blob td.hashes,
div#cgit table.blob td.lines {
margin: 0; padding: 0 0 0 0.5em;
vertical-align: top;
- color: black;
+ color: var(--fg-strong);
}
div#cgit table.blob td.linenumbers {
margin: 0; padding: 0 0.5em 0 0.5em;
vertical-align: top;
text-align: right;
- border-right: 1px solid gray;
+ border-right: 1px solid var(--fg-linenum);
}
div#cgit table.blob pre {
@@ -320,14 +393,19 @@ div#cgit table.blob pre {
div#cgit table.blob td.linenumbers a,
div#cgit table.ssdiff td.lineno a {
- color: gray;
+ color: var(--fg-linenum);
text-align: right;
text-decoration: none;
}
div#cgit table.blob td.linenumbers a:hover,
div#cgit table.ssdiff td.lineno a:hover {
- color: black;
+ color: var(--fg-strong);
+}
+
+div#cgit table.blob td.linenumbers a:target:before {
+ color: var(--red);
+ content: "\2BA9";
}
div#cgit table.blame td.hashes,
@@ -346,11 +424,11 @@ div#cgit table.blame td.linenumbers div.alt {
}
div#cgit table.blame div.alt:nth-child(even) {
- background: #eee;
+ background: var(--bg-hl);
}
div#cgit table.blame div.alt:nth-child(odd) {
- background: white;
+ background: var(--bg);
}
div#cgit table.blame td.lines > div {
@@ -363,22 +441,26 @@ 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;
+ border: solid 1px var(--border-strong);
}
div#cgit table.bin-blob th {
font-family: monospace;
white-space: pre;
- border: solid 1px #777;
+ border: solid 1px var(--border-dark);
padding: 0.5em 1em;
}
div#cgit table.bin-blob td {
font-family: monospace;
white-space: pre;
- border-left: solid 1px #777;
+ border-left: solid 1px var(--border-dark);
padding: 0em 1em;
}
@@ -396,10 +478,18 @@ div#cgit div.cgit-panel {
margin-top: 1.5em;
}
+div#cgit form.diff-ctrls div.cgit-panel {
+ float: none;
+}
+
+div#cgit form.diff-ctrls {
+ float: right;
+}
+
div#cgit div.cgit-panel table {
border-collapse: collapse;
- border: solid 1px #aaa;
- background-color: #eee;
+ border: solid 1px var(--border-mid);
+ background-color: var(--bg-hl);
}
div#cgit div.cgit-panel th {
@@ -450,8 +540,8 @@ div#cgit div.notes-header {
div#cgit div.notes {
white-space: pre;
font-family: monospace;
- border: solid 1px #ee9;
- background-color: #ffd;
+ border: solid 1px var(--notes-border);
+ background-color: var(--notes-bg);
padding: 0.3em 2em 0.3em 1em;
float: left;
}
@@ -467,8 +557,8 @@ div#cgit div.diffstat-header {
div#cgit table.diffstat {
border-collapse: collapse;
- border: solid 1px #aaa;
- background-color: #eee;
+ border: solid 1px var(--border-mid);
+ background-color: var(--bg-hl);
}
div#cgit table.diffstat th {
@@ -491,19 +581,19 @@ div#cgit table.diffstat td.mode {
div#cgit table.diffstat td span.modechange {
padding-left: 1em;
- color: red;
+ color: var(--red);
}
div#cgit table.diffstat td.add a {
- color: green;
+ color: var(--green);
}
div#cgit table.diffstat td.del a {
- color: red;
+ color: var(--red);
}
div#cgit table.diffstat td.upd a {
- color: blue;
+ color: var(--blue);
}
div#cgit table.diffstat td.graph {
@@ -522,15 +612,15 @@ div#cgit table.diffstat td.graph td {
}
div#cgit table.diffstat td.graph td.add {
- background-color: #5c5;
+ background-color: var(--graph-add-bg);
}
div#cgit table.diffstat td.graph td.rem {
- background-color: #c55;
+ background-color: var(--graph-rem-bg);
}
div#cgit div.diffstat-summary {
- color: #888;
+ color: var(--fg-dim);
padding-top: 0.5em;
}
@@ -546,22 +636,22 @@ div#cgit table.diff td {
div#cgit table.diff td div.head {
font-weight: bold;
margin-top: 1em;
- color: black;
+ color: var(--fg-strong);
}
div#cgit table.diff td div.hunk {
- color: #009;
+ color: var(--blue-dark);
}
div#cgit table.diff td div.add {
- color: green;
+ color: var(--green);
}
div#cgit table.diff td div.del {
- color: red;
+ color: var(--red);
}
-div#cgit .sha1 {
+div#cgit .oid {
font-family: monospace;
font-size: 90%;
}
@@ -576,7 +666,7 @@ div#cgit .right {
div#cgit table.list td.reposection {
font-style: italic;
- color: #888;
+ color: var(--fg-dim);
}
div#cgit a.button {
@@ -613,7 +703,7 @@ div#cgit ul.pager li {
}
div#cgit ul.pager a {
- color: #777;
+ color: var(--fg-mute);
}
div#cgit ul.pager .current {
@@ -622,46 +712,46 @@ div#cgit ul.pager .current {
div#cgit span.age-mins {
font-weight: bold;
- color: #080;
+ color: var(--green-mid);
}
div#cgit span.age-hours {
- color: #080;
+ color: var(--green-mid);
}
div#cgit span.age-days {
- color: #040;
+ color: var(--green-dark);
}
div#cgit span.age-weeks {
- color: #444;
+ color: var(--fg-mid);
}
div#cgit span.age-months {
- color: #888;
+ color: var(--fg-dim);
}
div#cgit span.age-years {
- color: #bbb;
+ color: var(--fg-faint);
}
div#cgit span.insertions {
- color: #080;
+ color: var(--green-mid);
}
div#cgit span.deletions {
- color: #800;
+ color: var(--red-dark);
}
div#cgit div.footer {
margin-top: 0.5em;
text-align: center;
font-size: 80%;
- color: #ccc;
+ color: var(--fg-faintest);
}
div#cgit div.footer a {
- color: #ccc;
+ color: var(--fg-faintest);
text-decoration: none;
}
@@ -670,43 +760,48 @@ div#cgit div.footer a:hover {
}
div#cgit a.branch-deco {
- color: #000;
+ color: var(--fg-strong);
margin: 0px 0.5em;
padding: 0px 0.25em;
- background-color: #88ff88;
- border: solid 1px #007700;
+ background-color: var(--deco-branch-bg);
+ border: solid 1px var(--deco-branch-border);
+ border-radius: 2px;
}
div#cgit a.tag-deco {
- color: #000;
+ color: var(--fg-strong);
margin: 0px 0.5em;
padding: 0px 0.25em;
- background-color: #ffff88;
- border: solid 1px #777700;
+ background-color: var(--deco-tag-bg);
+ border: solid 1px var(--deco-tag-border);
+ border-radius: 2px;
}
div#cgit a.tag-annotated-deco {
- color: #000;
+ color: var(--fg-strong);
margin: 0px 0.5em;
padding: 0px 0.25em;
- background-color: #ffcc88;
- border: solid 1px #777700;
+ background-color: var(--deco-tag-annotated-bg);
+ border: solid 1px var(--deco-tag-annotated-border);
+ border-radius: 2px;
}
div#cgit a.remote-deco {
- color: #000;
+ color: var(--fg-strong);
margin: 0px 0.5em;
padding: 0px 0.25em;
- background-color: #ccccff;
- border: solid 1px #000077;
+ background-color: var(--deco-remote-bg);
+ border: solid 1px var(--deco-remote-border);
+ border-radius: 2px;
}
div#cgit a.deco {
- color: #000;
+ color: var(--fg-strong);
margin: 0px 0.5em;
padding: 0px 0.25em;
- background-color: #ff8888;
- border: solid 1px #770000;
+ background-color: var(--deco-other-bg);
+ border: solid 1px var(--deco-other-border);
+ border-radius: 2px;
}
div#cgit div.commit-subject a.branch-deco,
@@ -719,21 +814,21 @@ div#cgit div.commit-subject a.deco {
}
div#cgit table.stats {
- border: solid 1px black;
+ border: solid 1px var(--border-strong);
border-collapse: collapse;
}
div#cgit table.stats th {
text-align: left;
padding: 1px 0.5em;
- background-color: #eee;
- border: solid 1px black;
+ background-color: var(--bg-hl);
+ border: solid 1px var(--border-strong);
}
div#cgit table.stats td {
text-align: right;
padding: 1px 0.5em;
- border: solid 1px black;
+ border: solid 1px var(--border-strong);
}
div#cgit table.stats td.total {
@@ -742,9 +837,9 @@ div#cgit table.stats td.total {
}
div#cgit table.stats td.sum {
- color: #c00;
+ color: var(--red-mid);
font-weight: bold;
-/* background-color: #eee; */
+/* background-color: var(--bg-hl); */
}
div#cgit table.stats td.left {
@@ -753,14 +848,14 @@ div#cgit table.stats td.left {
div#cgit table.vgraph {
border-collapse: separate;
- border: solid 1px black;
+ border: solid 1px var(--border-strong);
height: 200px;
}
div#cgit table.vgraph th {
- background-color: #eee;
+ background-color: var(--bg-hl);
font-weight: bold;
- border: solid 1px white;
+ border: solid 1px var(--bg);
padding: 1px 0.5em;
}
@@ -770,18 +865,18 @@ div#cgit table.vgraph td {
}
div#cgit table.vgraph div.bar {
- background-color: #eee;
+ background-color: var(--bg-hl);
}
div#cgit table.hgraph {
- border: solid 1px black;
+ border: solid 1px var(--border-strong);
width: 800px;
}
div#cgit table.hgraph th {
- background-color: #eee;
+ background-color: var(--bg-hl);
font-weight: bold;
- border: solid 1px black;
+ border: solid 1px var(--border-strong);
padding: 1px 0.5em;
}
@@ -791,7 +886,7 @@ div#cgit table.hgraph td {
}
div#cgit table.hgraph div.bar {
- background-color: #eee;
+ background-color: var(--bg-hl);
height: 1em;
}
@@ -804,83 +899,83 @@ div#cgit table.ssdiff td {
font-family: monospace;
white-space: pre;
padding: 1px 4px 1px 4px;
- border-left: solid 1px #aaa;
- border-right: solid 1px #aaa;
+ border-left: solid 1px var(--border-mid);
+ border-right: solid 1px var(--border-mid);
}
div#cgit table.ssdiff td.add {
- color: black;
- background: #cfc;
+ color: var(--fg-on-tint);
+ background: var(--add-bg);
min-width: 50%;
}
div#cgit table.ssdiff td.add_dark {
- color: black;
- background: #aca;
+ color: var(--fg-on-tint);
+ background: var(--add-bg-dark);
min-width: 50%;
}
div#cgit table.ssdiff span.add {
- background: #cfc;
+ background: var(--add-bg);
font-weight: bold;
}
div#cgit table.ssdiff td.del {
- color: black;
- background: #fcc;
+ color: var(--fg-on-tint);
+ background: var(--del-bg);
min-width: 50%;
}
div#cgit table.ssdiff td.del_dark {
- color: black;
- background: #caa;
+ color: var(--fg-on-tint);
+ background: var(--del-bg-dark);
min-width: 50%;
}
div#cgit table.ssdiff span.del {
- background: #fcc;
+ background: var(--del-bg);
font-weight: bold;
}
div#cgit table.ssdiff td.changed {
- color: black;
- background: #ffc;
+ color: var(--fg-on-tint);
+ background: var(--changed-bg);
min-width: 50%;
}
div#cgit table.ssdiff td.changed_dark {
- color: black;
- background: #cca;
+ color: var(--fg-on-tint);
+ background: var(--changed-bg-dark);
min-width: 50%;
}
div#cgit table.ssdiff td.lineno {
- color: black;
- background: #eee;
+ color: var(--fg-on-tint);
+ background: var(--bg-hl);
text-align: right;
width: 3em;
min-width: 3em;
}
div#cgit table.ssdiff td.hunk {
- color: black;
- background: #ccf;
- border-top: solid 1px #aaa;
- border-bottom: solid 1px #aaa;
+ color: var(--fg-on-tint);
+ background: var(--hunk-bg);
+ border-top: solid 1px var(--border-mid);
+ border-bottom: solid 1px var(--border-mid);
}
div#cgit table.ssdiff td.head {
- border-top: solid 1px #aaa;
- border-bottom: solid 1px #aaa;
+ border-top: solid 1px var(--border-mid);
+ border-bottom: solid 1px var(--border-mid);
}
div#cgit table.ssdiff td.head div.head {
font-weight: bold;
- color: black;
+ color: var(--fg-on-tint);
}
div#cgit table.ssdiff td.foot {
- border-top: solid 1px #aaa;
+ border-top: solid 1px var(--border-mid);
border-left: none;
border-right: none;
border-bottom: none;
@@ -893,3 +988,63 @@ div#cgit table.ssdiff td.space {
div#cgit table.ssdiff td.space div {
min-height: 3em;
}
+
+div#cgit .hl.kwa,
+div#cgit .hl.kwc {
+ color: var(--syn-keyword);
+ font-weight: bold;
+}
+
+div#cgit .hl.kwb {
+ color: var(--syn-keyword-alt);
+}
+
+div#cgit .hl.kwd {
+ color: var(--syn-type);
+}
+
+div#cgit .hl.str {
+ color: var(--syn-string);
+}
+
+div#cgit .hl.dstr,
+div#cgit .hl.pps {
+ color: var(--syn-string-alt);
+}
+
+div#cgit .hl.esc {
+ color: var(--syn-escape);
+}
+
+div#cgit .hl.com,
+div#cgit .hl.slc {
+ color: var(--syn-comment);
+ font-style: italic;
+}
+
+div#cgit .hl.num {
+ color: var(--syn-number);
+}
+
+div#cgit .hl.dir,
+div#cgit .hl.ppc {
+ color: var(--syn-preproc);
+}
+
+div#cgit .hl.opt,
+div#cgit .hl.sym {
+ color: var(--syn-operator);
+}
+
+div#cgit .hl.ipl {
+ color: var(--syn-interpolation);
+}
+
+div#cgit .hl.lin,
+div#cgit .hl.line {
+ color: var(--syn-linenum);
+}
+
+div#cgit .hl.mark {
+ background-color: var(--syn-mark-bg);
+}
diff --git a/cgit.h b/cgit.h
index 32dfd7a..e2b8cbc 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,29 +1,35 @@
#ifndef CGIT_H
#define CGIT_H
+#include <stdbool.h>
#include <git-compat-util.h>
-#include <stdbool.h>
-#include <cache.h>
-#include <grep.h>
-#include <object.h>
-#include <tree.h>
+#include <archive.h>
#include <commit.h>
-#include <tag.h>
-#include <diff.h>
#include <diffcore.h>
-#include <argv-array.h>
+#include <diff.h>
+#include <environment.h>
+#include <graph.h>
+#include <grep.h>
+#include <hex.h>
+#include <log-tree.h>
+#include <notes.h>
+#include <object.h>
+#include <object-name.h>
+#include <odb.h>
+#include <path.h>
#include <refs.h>
#include <revision.h>
-#include <log-tree.h>
-#include <archive.h>
+#include <setup.h>
#include <string-list.h>
+#include <strvec.h>
+#include <tag.h>
+#include <tree.h>
+#include <utf8.h>
+#include <wrapper.h>
#include <xdiff-interface.h>
#include <xdiff/xdiff.h>
-#include <utf8.h>
-#include <notes.h>
-#include <graph.h>
/* Add isgraph(x) to Git's sane ctype support (see git-compat-util.h) */
#undef isgraph
@@ -57,13 +63,13 @@ typedef enum {
} diff_type;
typedef enum {
- ABOUT, COMMIT, SOURCE, EMAIL, AUTH, OWNER
+ ABOUT, COMMIT, SOURCE, EMAIL, AUTH, OWNER, CI
} filter_type;
struct cgit_filter {
int (*open)(struct cgit_filter *, va_list ap);
int (*close)(struct cgit_filter *);
- void (*fprintf)(struct cgit_filter *, FILE *, const char *prefix);
+ void (*fprintfp)(struct cgit_filter *, FILE *, const char *prefix);
void (*cleanup)(struct cgit_filter *);
int argument_count;
};
@@ -84,6 +90,9 @@ struct cgit_repo {
char *extra_head_content;
char *owner;
char *homepage;
+ char *ci_url;
+ char *ci_branch_url;
+ char *ci_tag_url;
char *defbranch;
char *module_link;
struct string_list readme;
@@ -93,7 +102,9 @@ struct cgit_repo {
char *logo_link;
char *snapshot_prefix;
int snapshots;
+ int enable_blame;
int enable_commit_graph;
+ int enable_follow_links;
int enable_log_filecount;
int enable_log_linecount;
int enable_remote_branches;
@@ -108,14 +119,12 @@ struct cgit_repo {
struct cgit_filter *source_filter;
struct cgit_filter *email_filter;
struct cgit_filter *owner_filter;
+ struct cgit_filter *ci_filter;
struct string_list submodules;
int hide;
int ignore;
};
-typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
- const char *value);
-
struct cgit_repolist {
int length;
int count;
@@ -162,7 +171,7 @@ struct reflist {
struct cgit_query {
int has_symref;
- int has_sha1;
+ int has_oid;
int has_difftype;
char *raw;
char *repo;
@@ -170,8 +179,8 @@ struct cgit_query {
char *search;
char *grep;
char *head;
- char *sha1;
- char *sha2;
+ char *oid;
+ char *oid2;
char *path;
char *name;
char *url;
@@ -191,10 +200,12 @@ struct cgit_query {
struct cgit_config {
char *agefile;
char *cache_root;
+ char *ci_url;
+ char *ci_branch_url;
+ char *ci_tag_url;
char *clone_prefix;
char *clone_url;
- char *css;
- char *favicon;
+ struct string_list favicon;
char *footer;
char *head_include;
char *header;
@@ -204,6 +215,7 @@ struct cgit_config {
char *module_link;
char *project_list;
struct string_list readme;
+ struct string_list css;
char *robots;
char *root_title;
char *root_desc;
@@ -262,12 +274,14 @@ struct cgit_config {
int branch_sort;
int commit_sort;
struct string_list mimetypes;
+ struct string_list js;
struct cgit_filter *about_filter;
struct cgit_filter *commit_filter;
struct cgit_filter *source_filter;
struct cgit_filter *email_filter;
struct cgit_filter *owner_filter;
struct cgit_filter *auth_filter;
+ struct cgit_filter *ci_filter;
};
struct cgit_page {
@@ -325,7 +339,10 @@ extern const struct cgit_snapshot_format cgit_snapshot_formats[];
extern char *cgit_default_repo_desc;
extern struct cgit_repo *cgit_add_repo(const char *url);
extern struct cgit_repo *cgit_get_repoinfo(const char *url);
-extern void cgit_repo_config_cb(const char *name, const char *value);
+extern void cgit_repo_config(struct cgit_repo *repo, const char *name,
+ const char *value);
+
+extern int cgit_have_repository(void);
extern int chk_zero(int result, char *msg);
extern int chk_positive(int result, char *msg);
@@ -338,8 +355,7 @@ extern void strbuf_ensure_end(struct strbuf *sb, char c);
extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
extern void cgit_free_reflist_inner(struct reflist *list);
-extern int cgit_refs_cb(const char *refname, const struct object_id *oid,
- int flags, void *cb_data);
+extern int cgit_refs_cb(const struct reference *ref, void *cb_data);
extern void cgit_free_commitinfo(struct commitinfo *info);
extern void cgit_free_taginfo(struct taginfo *info);
@@ -387,7 +403,9 @@ extern void cgit_init_filters(void);
extern void cgit_prepare_repo_env(struct cgit_repo * repo);
-extern int readfile(const char *path, char **buf, size_t *size);
+extern int read_first_line(const char *path, char **buf, size_t *size);
+
+extern char *strdup_first_line(const char *txt);
extern char *expand_macros(const char *txt);
diff --git a/cgit.js b/cgit.js
new file mode 100644
index 0000000..df3ad4e
--- /dev/null
+++ b/cgit.js
@@ -0,0 +1,68 @@
+/* cgit.js: javacript functions for cgit
+ *
+ * Copyright (C) 2006-2018 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ * (see COPYING for full license text)
+ */
+
+(function () {
+
+/* This follows the logic and suffixes used in ui-shared.c */
+
+var age_classes = [ "age-mins", "age-hours", "age-days", "age-weeks", "age-months", "age-years" ];
+var age_suffix = [ "min.", "hours", "days", "weeks", "months", "years", "years" ];
+var age_next = [ 60, 3600, 24 * 3600, 7 * 24 * 3600, 30 * 24 * 3600, 365 * 24 * 3600, 365 * 24 * 3600 ];
+var age_limit = [ 7200, 24 * 7200, 7 * 24 * 7200, 30 * 24 * 7200, 365 * 25 * 7200, 365 * 25 * 7200 ];
+var update_next = [ 10, 5 * 60, 1800, 24 * 3600, 24 * 3600, 24 * 3600, 24 * 3600 ];
+
+function render_age(e, age) {
+ var t, n;
+
+ for (n = 0; n < age_classes.length; n++)
+ if (age < age_limit[n])
+ break;
+
+ t = Math.round(age / age_next[n]) + " " + age_suffix[n];
+
+ if (e.textContent != t) {
+ e.textContent = t;
+ if (n == age_classes.length)
+ n--;
+ if (e.className != age_classes[n])
+ e.className = age_classes[n];
+ }
+}
+
+function aging() {
+ var n, next = 24 * 3600,
+ now_ut = Math.round((new Date().getTime() / 1000));
+
+ for (n = 0; n < age_classes.length; n++) {
+ var m, elems = document.getElementsByClassName(age_classes[n]);
+
+ if (elems.length && update_next[n] < next)
+ next = update_next[n];
+
+ for (m = 0; m < elems.length; m++) {
+ var age = now_ut - elems[m].getAttribute("data-ut");
+
+ render_age(elems[m], age);
+ }
+ }
+
+ /*
+ * We only need to come back when the age might have changed.
+ * Eg, if everything is counted in hours already, once per
+ * 5 minutes is accurate enough.
+ */
+
+ window.setTimeout(aging, next * 1000);
+}
+
+document.addEventListener("DOMContentLoaded", function() {
+ /* we can do the aging on DOM content load since no layout dependency */
+ aging();
+}, false);
+
+})();
diff --git a/cgit.mk b/cgit.mk
index 3fcc1ca..c4b735d 100644
--- a/cgit.mk
+++ b/cgit.mk
@@ -79,6 +79,7 @@ CGIT_OBJ_NAMES += shared.o
CGIT_OBJ_NAMES += ui-atom.o
CGIT_OBJ_NAMES += ui-blame.o
CGIT_OBJ_NAMES += ui-blob.o
+CGIT_OBJ_NAMES += ui-ci.o
CGIT_OBJ_NAMES += ui-clone.o
CGIT_OBJ_NAMES += ui-commit.o
CGIT_OBJ_NAMES += ui-diff.o
diff --git a/cgit.svg b/cgit.svg
new file mode 100644
index 0000000..43fee2c
--- /dev/null
+++ b/cgit.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="96" height="64" viewBox="0 0 96 64" fill="none" stroke="#a00" stroke-width="5">
+ <title>cgit</title>
+ <path d="M31.5 27.5A9 9 0 0 0 22.5 18.5h-5A9 9 0 0 0 8.5 27.5V38a9 9 0 0 0 9 9h5a9 9 0 0 0 9-9M31.5 38v12.5a8 8 0 0 1-8 8H17a8 8 0 0 1-8-8M46.5 3v5m0 8v33M62.5 9v29.75a7.75 7.75 0 0 0 15.5 0V37"/>
+ <path stroke-width="4" d="M56 18h25"/>
+</svg>
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 34b351b..527233d 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -100,6 +100,28 @@ cache-static-ttl::
version of repository pages accessed with a fixed SHA1. See also:
"CACHE". Default value: -1".
+ci-branch-url::
+ Url template used by the "ci" tab when a branch is being viewed. If
+ unspecified, `ci-url` is used instead. Default value: none. See also:
+ "CI URL EXPANSION".
+
+ci-filter::
+ Specifies a command which decides whether the "ci" tab is shown for
+ the ref currently being viewed, which is useful when only some refs
+ have a pipeline. Default value: none. See also: "FILTER API".
+
+ci-tag-url::
+ Url template used by the "ci" tab when a tag is being viewed. If
+ unspecified, `ci-url' is used instead. Default value: none. See also:
+ "CI URL EXPANSION".
+
+ci-url::
+ Url template used by the "ci" tab for refs which are not covered by
+ `ci-branch-url` or `ci-tag-url`. When at least one of the three ci url
+ settings is specified, cgit adds a "ci" tab which redirects to the
+ expanded url for the ref currently being viewed. Default value: none.
+ See also: "CI URL EXPANSION".
+
clone-prefix::
Space-separated list of common prefixes which, when combined with a
repository url, generates valid clone urls for the repository. This
@@ -126,7 +148,8 @@ commit-sort::
css::
Url which specifies the css document to include in all cgit pages.
- Default value: "/cgit.css".
+ Default value: "/cgit.css". May be given multiple times, each
+ css URL path is added in the head section of the document in turn.
email-filter::
Specifies a command which will be invoked to format names and email
@@ -217,9 +240,14 @@ enable-tree-linenumbers::
for plaintext blobs printed in the tree view. Default value: "1".
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".
+ Url used as link to a shortcut icon for cgit. It is suggested to
+ always include the value "/favicon.ico" since certain browsers will
+ ignore other values. May be given multiple times, each favicon URL
+ path is added in the head section of the document in turn, letting
+ the browser pick the icon it prefers. The mimetype of each icon is
+ deduced from the extension of its url ("ico", "png", "svg", "gif",
+ "jpg", "jpeg", "webp" and "avif" are recognized) and emitted as a
+ "type" attribute. Default value: "/favicon.svg", "/favicon.ico".
footer::
The content of the file specified with this option will be included
@@ -238,13 +266,18 @@ include::
Name of a configfile to include before the rest of the current config-
file is parsed. Default value: none. See also: "MACRO EXPANSION".
+js::
+ Url which specifies the javascript script document to include in all cgit
+ pages. Default value: "/cgit.js". Setting this to an empty string will
+ disable generation of the link to this file in the head section.
+
local-time::
Flag which, if set to "1", makes cgit print commit and tag times in the
servers timezone. Default value: "0".
logo::
Url which specifies the source of an image which will be used as a logo
- on all cgit pages. Default value: "/cgit.png".
+ on all cgit pages. Default value: "/cgit.svg".
logo-link::
Url loaded when clicking on the cgit logo image. If unspecified the
@@ -269,7 +302,8 @@ max-message-length::
max-repo-count::
Specifies the number of entries to list per page on the repository
- index page. Default value: "50".
+ index page. The value "0" shows all repositories without limitation.
+ Default value: "50".
max-repodesc-length::
Specifies the maximum number of repo description characters to display
@@ -407,9 +441,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
@@ -458,6 +495,22 @@ repo.branch-sort::
list, and when set to "name" enables ordering by branch name. Default
value: "name".
+repo.ci-branch-url::
+ Override the global setting `ci-branch-url' for this repository.
+ Default value: <ci-branch-url>. See also: "CI URL EXPANSION".
+
+repo.ci-filter::
+ Override the default ci-filter. Default value: none. See also:
+ "enable-filter-overrides". See also: "FILTER API".
+
+repo.ci-tag-url::
+ Override the global setting `ci-tag-url' for this repository. Default
+ value: <ci-tag-url>. See also: "CI URL EXPANSION".
+
+repo.ci-url::
+ Override the global setting `ci-url' for this repository. Default
+ value: <ci-url>. See also: "CI URL EXPANSION".
+
repo.clone-url::
A list of space-separated urls which can be used to clone this repo.
Default value: none. See also: "MACRO EXPANSION".
@@ -485,10 +538,18 @@ repo.email-filter::
Override the default email-filter. Default value: none. See also:
"enable-filter-overrides". See also: "FILTER API".
+repo.enable-blame::
+ A flag which can be used to disable the global setting
+ `enable-blame'. Default value: none.
+
repo.enable-commit-graph::
A flag which can be used to disable the global setting
`enable-commit-graph'. Default value: none.
+repo.enable-follow-links::
+ A flag which can be used to disable the global setting
+ `enable-follow-links'. Default value: none.
+
repo.enable-html-serving::
A flag which can be used to override the global setting
`enable-html-serving`. Default value: none.
@@ -572,11 +633,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:
@@ -692,6 +753,20 @@ auth filter::
Please see `filters/simple-authentication.lua` for a clear example
script that may be modified.
+ci filter::
+ This filter is given three parameters: the name of the branch or tag
+ being viewed, the string "branch" or "tag" to say which of the two it
+ is, and the ci url that the "ci" tab would redirect to. It decides
+ whether that tab is shown at all, by returning zero from the exit code
+ / close function to show it and non-zero to hide it. Nothing is written
+ to its standard input, and it must not write to standard output, since
+ that would land in the middle of the page being rendered.
+
+ The filter is consulted while rendering every page of the repository,
+ not only ref pages, so a filter which contacts the ci system should
+ cache its verdict and use a short timeout. Please see
+ `filters/ci-jenkins.sh` for an example.
+
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
@@ -758,6 +833,46 @@ the environment variables defined in "FILTER API":
- repo.clone-url
+CI URL EXPANSION
+----------------
+The options `ci-url', `ci-branch-url' and `ci-tag-url' (and their
+`repo.'-prefixed counterparts) are url templates in which the following
+tokens are substituted when the "ci" tab is followed:
+
+$ref::
+ The name of the branch or tag currently being viewed.
+
+$repo::
+ The url of the repository, i.e. the value of `repo.url'.
+
+$slug::
+ $repo with any ".git" suffix removed and all slashes replaced by
+ dashes, e.g. "sayauz/web.git" becomes "sayauz-web".
+
+Two consecutive dollar signs produce a single literal one, and any other
+"$" is left untouched. Tokens are substituted verbatim, so the resulting
+url must already be properly encoded.
+
+When a tag is being viewed, `ci-tag-url' is used; for anything else
+`ci-branch-url' is used. Whichever of the two applies falls back to
+`ci-url' when it is unset, and the "ci" tab yields "404 Not found" when
+neither is set for the current ref. This makes it possible to point at a
+Jenkins multibranch pipeline, where branches and tags live at different
+locations:
+
+ ci-branch-url=https://ci.example.org/job/$slug/job/$ref
+ ci-tag-url=https://ci.example.org/job/$slug/view/tags/job/$ref
+
+Setting `repo.ci-url' discards the values which the repository would
+otherwise inherit from the global `ci-branch-url' and `ci-tag-url', so
+that a single repository can be pointed at a different ci system without
+having to override both of them.
+
+Note that cgit itself never contacts the ci system, so a tab shown for a
+ref which has no pipeline only reveals that after being followed. Use
+`ci-filter' to suppress the tab in that case.
+
+
CACHE
-----
@@ -806,8 +921,15 @@ cache-size=1000
# Specify some default clone urls using macro expansion
clone-url=git://foo.org/$CGIT_REPO_URL git@foo.org:$CGIT_REPO_URL
+
# Specify the css url
css=/css/cgit.css
+css=/css/solarized.css
+
+
+# Add a "ci" tab pointing at a Jenkins multibranch pipeline
+ci-branch-url=https://ci.foo.org/job/$slug/job/$ref
+ci-tag-url=https://ci.foo.org/job/$slug/view/tags/job/$ref
# Show owner on index page
@@ -842,7 +964,8 @@ enable-log-linecount=1
branch-sort=age
-# Add a cgit favicon
+# Add cgit favicons
+favicon=/favicon.svg
favicon=/favicon.ico
@@ -1002,3 +1125,4 @@ AUTHOR
------
Lars Hjemli <hjemli@gmail.com>
Jason A. Donenfeld <Jason@zx2c4.com>
+Saya Andy <saya.andy@posteo.com>
diff --git a/cmd.c b/cmd.c
index 63f0ae5..0ccd677 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,6 +1,10 @@
/* cmd.c: the cgit command dispatcher
*
* Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
+ * Copyright (C) 2026 Saya Andy <saya.andy@posteo.com>
+ *
+ * Modified 2026 by Saya Andy:
+ * Include ci ui element
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
@@ -13,6 +17,7 @@
#include "ui-atom.h"
#include "ui-blame.h"
#include "ui-blob.h"
+#include "ui-ci.h"
#include "ui-clone.h"
#include "ui-commit.h"
#include "ui-diff.h"
@@ -66,7 +71,7 @@ static void about_fn(void)
static void blame_fn(void)
{
- if (ctx.cfg.enable_blame)
+ if (ctx.repo->enable_blame)
cgit_print_blame();
else
cgit_print_error_page(403, "Forbidden", "Blame is disabled");
@@ -74,22 +79,27 @@ 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 ci_fn(void)
+{
+ cgit_print_ci();
}
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 +109,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 +135,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 +150,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 +166,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) \
@@ -175,6 +185,7 @@ struct cgit_cmd *cgit_get_cmd(void)
def_cmd(about, 0, 0, 0),
def_cmd(blame, 1, 1, 0),
def_cmd(blob, 1, 0, 0),
+ def_cmd(ci, 1, 0, 0),
def_cmd(commit, 1, 1, 0),
def_cmd(diff, 1, 1, 0),
def_cmd(info, 1, 0, 1),
diff --git a/favicon.svg b/favicon.svg
new file mode 100644
index 0000000..6be3df4
--- /dev/null
+++ b/favicon.svg
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="52" height="52" viewBox="0 0 52 52" fill="none" stroke-width="6">
+ <title>cgit</title>
+ <g stroke="#f33"><path id="g" d="M37.5 15A9 9 0 0 0 28.5 6h-5A9 9 0 0 0 14.5 15v10.5a9 9 0 0 0 9 9h5a9 9 0 0 0 9-9M37.5 25.5V38a8 8 0 0 1-8 8H23a8 8 0 0 1-8-8"/></g>
+ <use href="#g" stroke="#a00" stroke-width="4"/>
+</svg>
diff --git a/filter.c b/filter.c
index 70f5b74..7d722a3 100644
--- a/filter.c
+++ b/filter.c
@@ -1,6 +1,10 @@
/* filter.c: filter framework functions
*
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ * Copyright (C) 2026 Saya Andy <saya.andy@posteo.com>
+ *
+ * Modified 2026 by Saya Andy:
+ * Integrate ci filter
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
@@ -30,12 +34,14 @@ void cgit_cleanup_filters(void)
reap_filter(ctx.cfg.email_filter);
reap_filter(ctx.cfg.owner_filter);
reap_filter(ctx.cfg.auth_filter);
+ reap_filter(ctx.cfg.ci_filter);
for (i = 0; i < cgit_repolist.count; ++i) {
reap_filter(cgit_repolist.repos[i].about_filter);
reap_filter(cgit_repolist.repos[i].commit_filter);
reap_filter(cgit_repolist.repos[i].source_filter);
reap_filter(cgit_repolist.repos[i].email_filter);
reap_filter(cgit_repolist.repos[i].owner_filter);
+ reap_filter(cgit_repolist.repos[i].ci_filter);
}
}
@@ -114,7 +120,7 @@ static struct cgit_filter *new_exec_filter(const char *cmd, int argument_count)
f = xmalloc(sizeof(*f));
/* We leave argv for now and assign it below. */
- cgit_exec_filter_init(f, xstrdup(cmd), NULL);
+ cgit_exec_filter_init(f, strdup_first_line(cmd), NULL);
f->base.argument_count = argument_count;
args_size = (2 + argument_count) * sizeof(char *);
f->argv = xmalloc(args_size);
@@ -128,7 +134,7 @@ void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **ar
memset(filter, 0, sizeof(*filter));
filter->base.open = open_exec_filter;
filter->base.close = close_exec_filter;
- filter->base.fprintf = fprintf_exec_filter;
+ filter->base.fprintfp = fprintf_exec_filter;
filter->base.cleanup = cleanup_exec_filter;
filter->cmd = cmd;
filter->argv = argv;
@@ -353,10 +359,10 @@ static struct cgit_filter *new_lua_filter(const char *cmd, int argument_count)
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.fprintfp = fprintf_lua_filter;
filter->base.cleanup = cleanup_lua_filter;
filter->base.argument_count = argument_count;
- filter->script_file = xstrdup(cmd);
+ filter->script_file = strdup_first_line(cmd);
return &filter->base;
}
@@ -385,7 +391,7 @@ int cgit_close_filter(struct cgit_filter *filter)
void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
{
- filter->fprintf(filter, f, prefix);
+ filter->fprintfp(filter, f, prefix);
}
@@ -402,7 +408,7 @@ static const struct {
struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
{
- char *colon;
+ const char *colon;
int i;
size_t len;
int argument_count;
@@ -424,6 +430,10 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
argument_count = 12;
break;
+ case CI:
+ argument_count = 3;
+ break;
+
case EMAIL:
argument_count = 2;
break;
diff --git a/filters/ci-jenkins.sh b/filters/ci-jenkins.sh
new file mode 100755
index 0000000..842af21
--- /dev/null
+++ b/filters/ci-jenkins.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+# This script may be used with the ci-filter or repo.ci-filter setting in
+# cgitrc to hide the "ci" tab for refs which have no pipeline on a Jenkins
+# instance.
+#
+# Arguments:
+# $1 the name of the branch or tag being viewed
+# $2 "branch" or "tag"
+# $3 the ci url which the "ci" tab would redirect to
+#
+# Exit with a zero status to show the tab, non-zero to hide it. This script
+# must not write anything to standard output, as that would end up in the
+# middle of the page cgit is rendering.
+#
+# The filter is consulted while rendering every repository page, so the
+# verdict is cached on disk to keep Jenkins from being hammered, and the
+# probe is given a short timeout so that an unreachable Jenkins degrades
+# into a missing tab rather than a hanging web server.
+#
+# Set CI_NETRC to a netrc(5) file if the Jenkins instance requires
+# authentication; without it a private job answers 403 and the tab is
+# hidden even though the pipeline exists.
+
+CI_CACHE_DIR="${CI_CACHE_DIR:-/var/cache/cgit/ci-filter}"
+CI_CACHE_TTL_MINUTES="${CI_CACHE_TTL_MINUTES:-5}"
+CI_TIMEOUT="${CI_TIMEOUT:-2}"
+
+url="$3"
+test -n "$url" || exit 1
+
+# Jenkins job pages are often not readable anonymously, so query the REST
+# API rather than the page the tab points at.
+probe="$url/api/json?tree=name"
+
+key="$(printf '%s' "$url" | cksum | tr -cd '0-9')"
+cache="$CI_CACHE_DIR/$key"
+
+mkdir -p "$CI_CACHE_DIR" 2>/dev/null
+
+if test -f "$cache" &&
+ test -z "$(find "$cache" -mmin "+$CI_CACHE_TTL_MINUTES" 2>/dev/null)"
+then
+ exit "$(cat "$cache")"
+fi
+
+status=0
+curl --silent --fail --head --output /dev/null \
+ --max-time "$CI_TIMEOUT" \
+ ${CI_NETRC:+--netrc-file "$CI_NETRC"} \
+ "$probe" >/dev/null 2>&1 || status=1
+
+if test -d "$CI_CACHE_DIR"
+then
+ printf '%s\n' "$status" >"$cache.$$" 2>/dev/null &&
+ mv "$cache.$$" "$cache" 2>/dev/null
+fi
+
+exit "$status"
diff --git a/filters/commit-links.sh b/filters/commit-links.sh
index 5881952..796ac30 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
index 52cf426..c39b490 100644
--- a/filters/email-gravatar.lua
+++ b/filters/email-gravatar.lua
@@ -3,15 +3,24 @@
-- prefix in filters. It is much faster than the corresponding python script.
--
-- Requirements:
--- luacrypto >= 0.3
--- <http://mkottman.github.io/luacrypto/>
+-- luaossl
+-- <http://25thandclement.com/~william/projects/luaossl.html>
--
-local crypto = require("crypto")
+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 = crypto.digest("md5", email:sub(2, -2):lower())
+ md5 = md5_hex(email:sub(2, -2):lower())
end
function filter_close()
diff --git a/filters/email-libravatar.lua b/filters/email-libravatar.lua
index b0e2447..7336baf 100644
--- a/filters/email-libravatar.lua
+++ b/filters/email-libravatar.lua
@@ -3,15 +3,24 @@
-- prefix in filters.
--
-- Requirements:
--- luacrypto >= 0.3
--- <http://mkottman.github.io/luacrypto/>
+-- luaossl
+-- <http://25thandclement.com/~william/projects/luaossl.html>
--
-local crypto = require("crypto")
+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 = crypto.digest("md5", email:sub(2, -2):lower())
+ md5 = md5_hex(email:sub(2, -2):lower())
end
function filter_close()
diff --git a/filters/file-authentication.lua b/filters/file-authentication.lua
index 6ee1e19..0248804 100644
--- a/filters/file-authentication.lua
+++ b/filters/file-authentication.lua
@@ -1,15 +1,15 @@
-- This script may be used with the auth-filter.
--
-- Requirements:
--- luacrypto >= 0.3
--- <http://mkottman.github.io/luacrypto/>
+-- 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 crypto = require("crypto")
-
+local rand = require("openssl.rand")
+local hmac = require("openssl.hmac")
-- This file should contain a series of lines in the form of:
-- username1:hash1
@@ -225,6 +225,13 @@ function get_cookie(cookies, name)
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
--
--
@@ -242,12 +249,12 @@ function get_secret()
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." .. crypto.hex(crypto.rand.bytes(16))
+ 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(crypto.hex(crypto.rand.bytes(32)))
+ 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)
@@ -272,7 +279,7 @@ function validate_value(expected_field, cookie)
local field = ""
local expiration = 0
local salt = ""
- local hmac = ""
+ local chmac = ""
if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
return nil
@@ -291,19 +298,19 @@ function validate_value(expected_field, cookie)
elseif i == 3 then
salt = component
elseif i == 4 then
- hmac = component
+ chmac = component
else
break
end
i = i + 1
end
- if hmac == nil or hmac:len() == 0 then
+ if chmac == nil or chmac:len() == 0 then
return nil
end
-- Lua hashes strings, so these comparisons are time invariant.
- if hmac ~= crypto.hmac.digest("sha256", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, get_secret()) then
+ if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
return nil
end
@@ -324,11 +331,11 @@ function secure_value(field, value, expiration)
end
local authstr = ""
- local salt = crypto.hex(crypto.rand.bytes(16))
+ local salt = tohex(rand.bytes(16))
value = url_encode(value)
field = url_encode(field)
authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
- authstr = authstr .. "|" .. crypto.hmac.digest("sha256", authstr, get_secret())
+ authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
return authstr
end
diff --git a/filters/gentoo-ldap-authentication.lua b/filters/gentoo-ldap-authentication.lua
index b4d98c2..673c88d 100644
--- a/filters/gentoo-ldap-authentication.lua
+++ b/filters/gentoo-ldap-authentication.lua
@@ -1,8 +1,8 @@
-- This script may be used with the auth-filter. Be sure to configure it as you wish.
--
-- Requirements:
--- luacrypto >= 0.3
--- <http://mkottman.github.io/luacrypto/>
+-- luaossl
+-- <http://25thandclement.com/~william/projects/luaossl.html>
-- lualdap >= 1.2
-- <https://git.zx2c4.com/lualdap/about/>
-- luaposix
@@ -10,9 +10,9 @@
--
local sysstat = require("posix.sys.stat")
local unistd = require("posix.unistd")
-local crypto = require("crypto")
local lualdap = require("lualdap")
-
+local rand = require("openssl.rand")
+local hmac = require("openssl.hmac")
--
--
@@ -226,6 +226,13 @@ function get_cookie(cookies, name)
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
--
--
@@ -243,12 +250,12 @@ function get_secret()
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." .. crypto.hex(crypto.rand.bytes(16))
+ 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(crypto.hex(crypto.rand.bytes(32)))
+ 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)
@@ -273,7 +280,7 @@ function validate_value(expected_field, cookie)
local field = ""
local expiration = 0
local salt = ""
- local hmac = ""
+ local chmac = ""
if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
return nil
@@ -292,19 +299,19 @@ function validate_value(expected_field, cookie)
elseif i == 3 then
salt = component
elseif i == 4 then
- hmac = component
+ chmac = component
else
break
end
i = i + 1
end
- if hmac == nil or hmac:len() == 0 then
+ if chmac == nil or chmac:len() == 0 then
return nil
end
-- Lua hashes strings, so these comparisons are time invariant.
- if hmac ~= crypto.hmac.digest("sha256", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, get_secret()) then
+ if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
return nil
end
@@ -325,11 +332,11 @@ function secure_value(field, value, expiration)
end
local authstr = ""
- local salt = crypto.hex(crypto.rand.bytes(16))
+ local salt = tohex(rand.bytes(16))
value = url_encode(value)
field = url_encode(field)
authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
- authstr = authstr .. "|" .. crypto.hmac.digest("sha256", authstr, get_secret())
+ authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
return authstr
end
diff --git a/filters/html-converters/md2html b/filters/html-converters/md2html
index dc20f42..4f6ad37 100755
--- a/filters/html-converters/md2html
+++ b/filters/html-converters/md2html
@@ -2,8 +2,46 @@
import markdown
import sys
import io
-from pygments.formatters import HtmlFormatter
from markdown.extensions.toc import TocExtension
+
+# Pygments' token classes, mapped onto the --syn-* custom properties that
+# cgit.css defines. The fallbacks are the palette those properties default to,
+# so that the output stays readable under a stylesheet without them.
+SYNTAX_COLORS = (
+ (('k', 'kc', 'kd', 'kn', 'kr'), '--syn-keyword', '#000',
+ 'font-weight: bold'),
+ (('kp',), '--syn-keyword', '#000', None),
+ (('kt',), '--syn-type', '#010181', None),
+ (('nc', 'nf', 'nn', 'ne'), '--syn-keyword-alt', '#0057ae', None),
+ (('s', 'sa', 'sb', 'sc', 'dl', 'sd', 's2', 'sh', 'sx', 'sr', 's1', 'ss'),
+ '--syn-string', '#bf0303', None),
+ (('se',), '--syn-escape', '#f0f', None),
+ (('si',), '--syn-interpolation', '#0057ae', None),
+ (('c', 'ch', 'cm', 'c1', 'cs'), '--syn-comment', '#838183',
+ 'font-style: italic'),
+ (('cp', 'cpf'), '--syn-preproc', '#008200', None),
+ (('m', 'mb', 'mf', 'mh', 'mi', 'mo', 'il'), '--syn-number', '#b07e00',
+ None),
+ (('o', 'ow', 'p'), '--syn-operator', '#000', None),
+ (('err',), '--syn-error', '#bf0303', None),
+ (('gd',), '--red', 'red', None),
+ (('gi',), '--green', 'green', None),
+ (('lineno', 'linenos'), '--syn-linenum', '#555', None),
+)
+
+
+def style_defs(prefix='.highlight'):
+ rules = []
+ for classes, prop, fallback, extra in SYNTAX_COLORS:
+ selector = ', '.join('%s .%s' % (prefix, c) for c in classes)
+ decls = 'color: var(%s, %s)' % (prop, fallback)
+ if extra:
+ decls += '; ' + extra
+ rules.append('%s { %s }' % (selector, decls))
+ rules.append('%s .hll { background-color: var(--syn-mark-bg, #ffb) }'
+ % prefix)
+ return '\n'.join(rules) + '\n'
+
sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
sys.stdout.write('''
@@ -20,7 +58,7 @@ sys.stdout.write('''
margin-bottom: 0 !important;
}
.markdown-body a.absent {
- color: #c00;
+ color: var(--red-mid, #c00);
}
.markdown-body a.anchor {
display: block;
@@ -42,7 +80,7 @@ sys.stdout.write('''
}
.markdown-body h1 .mini-icon-link, .markdown-body h2 .mini-icon-link, .markdown-body h3 .mini-icon-link, .markdown-body h4 .mini-icon-link, .markdown-body h5 .mini-icon-link, .markdown-body h6 .mini-icon-link {
display: none;
- color: #000;
+ color: var(--fg-strong, #000);
}
.markdown-body h1:hover a.anchor, .markdown-body h2:hover a.anchor, .markdown-body h3:hover a.anchor, .markdown-body h4:hover a.anchor, .markdown-body h5:hover a.anchor, .markdown-body h6:hover a.anchor {
text-decoration: none;
@@ -55,19 +93,19 @@ sys.stdout.write('''
display: inline-block;
}
div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#cgit .markdown-body h3 a.toclink, div#cgit .markdown-body h4 a.toclink, div#cgit .markdown-body h5 a.toclink, div#cgit .markdown-body h6 a.toclink {
- color: black;
+ color: var(--fg-strong, #000);
}
.markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code {
font-size: inherit;
}
.markdown-body h1 {
font-size: 28px;
- color: #000;
+ color: var(--fg-strong, #000);
}
.markdown-body h2 {
font-size: 24px;
- border-bottom: 1px solid #ccc;
- color: #000;
+ border-bottom: 1px solid var(--border, #ccc);
+ color: var(--fg-strong, #000);
}
.markdown-body h3 {
font-size: 18px;
@@ -79,18 +117,14 @@ div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#
font-size: 14px;
}
.markdown-body h6 {
- color: #777;
+ color: var(--fg-mute, #777);
font-size: 14px;
}
.markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre {
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 var(--border, #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;
@@ -152,9 +186,9 @@ div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#
margin-bottom: 0px;
}
.markdown-body blockquote {
- border-left: 4px solid #DDD;
+ border-left: 4px solid var(--border, #ddd);
padding: 0 15px;
- color: #777;
+ color: var(--fg-mute, #777);
}
.markdown-body blockquote>:first-child {
margin-top: 0px;
@@ -166,15 +200,15 @@ div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#
font-weight: bold;
}
.markdown-body table th, .markdown-body table td {
- border: 1px solid #ccc;
+ border: 1px solid var(--border, #ccc);
padding: 6px 13px;
}
.markdown-body table tr {
- border-top: 1px solid #ccc;
- background-color: #fff;
+ border-top: 1px solid var(--border, #ccc);
+ background-color: var(--bg, #fff);
}
.markdown-body table tr:nth-child(2n) {
- background-color: #f8f8f8;
+ background-color: var(--bg-zebra, #f8f8f8);
}
.markdown-body img {
max-width: 100%;
@@ -186,7 +220,7 @@ div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#
overflow: hidden;
}
.markdown-body span.frame>span {
- border: 1px solid #ddd;
+ border: 1px solid var(--border, #ddd);
display: block;
float: left;
overflow: hidden;
@@ -200,7 +234,7 @@ div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#
}
.markdown-body span.frame span span {
clear: both;
- color: #333;
+ color: var(--fg, #333);
display: block;
padding: 5px 0 0;
}
@@ -258,8 +292,8 @@ div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#
.markdown-body code, .markdown-body tt {
margin: 0 2px;
padding: 0px 5px;
- border: 1px solid #eaeaea;
- background-color: #f8f8f8;
+ border: 1px solid var(--border, #eaeaea);
+ background-color: var(--bg-zebra, #f8f8f8);
border-radius: 3px;
}
.markdown-body code {
@@ -273,8 +307,8 @@ div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#
background: transparent;
}
.markdown-body .highlight pre, .markdown-body pre {
- background-color: #f8f8f8;
- border: 1px solid #ccc;
+ background-color: var(--bg-zebra, #f8f8f8);
+ border: 1px solid var(--border, #ccc);
font-size: 13px;
line-height: 19px;
overflow: auto;
@@ -288,9 +322,9 @@ div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#
border: none;
}
''')
-sys.stdout.write(HtmlFormatter(style='pastie').get_style_defs('.highlight'))
+sys.stdout.write(style_defs('.highlight'))
sys.stdout.write('''
-</style>
+</style>
''')
sys.stdout.write("<div class='markdown-body'>")
sys.stdout.flush()
@@ -301,6 +335,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/simple-authentication.lua b/filters/simple-authentication.lua
index 77d1fd0..23d3457 100644
--- a/filters/simple-authentication.lua
+++ b/filters/simple-authentication.lua
@@ -1,15 +1,15 @@
-- This script may be used with the auth-filter. Be sure to configure it as you wish.
--
-- Requirements:
--- luacrypto >= 0.3
--- <http://mkottman.github.io/luacrypto/>
+-- 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 crypto = require("crypto")
-
+local rand = require("openssl.rand")
+local hmac = require("openssl.hmac")
--
--
@@ -180,6 +180,13 @@ function get_cookie(cookies, name)
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
--
--
@@ -197,12 +204,12 @@ function get_secret()
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." .. crypto.hex(crypto.rand.bytes(16))
+ 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(crypto.hex(crypto.rand.bytes(32)))
+ 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)
@@ -227,7 +234,7 @@ function validate_value(expected_field, cookie)
local field = ""
local expiration = 0
local salt = ""
- local hmac = ""
+ local chmac = ""
if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
return nil
@@ -246,19 +253,19 @@ function validate_value(expected_field, cookie)
elseif i == 3 then
salt = component
elseif i == 4 then
- hmac = component
+ chmac = component
else
break
end
i = i + 1
end
- if hmac == nil or hmac:len() == 0 then
+ if chmac == nil or chmac:len() == 0 then
return nil
end
-- Lua hashes strings, so these comparisons are time invariant.
- if hmac ~= crypto.hmac.digest("sha256", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, get_secret()) then
+ if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
return nil
end
@@ -279,11 +286,11 @@ function secure_value(field, value, expiration)
end
local authstr = ""
- local salt = crypto.hex(crypto.rand.bytes(16))
+ local salt = tohex(rand.bytes(16))
value = url_encode(value)
field = url_encode(field)
authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
- authstr = authstr .. "|" .. crypto.hmac.digest("sha256", authstr, get_secret())
+ authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
return authstr
end
diff --git a/filters/syntax-highlighting.py b/filters/syntax-highlighting.py
index e912594..d757e2d 100755
--- a/filters/syntax-highlighting.py
+++ b/filters/syntax-highlighting.py
@@ -30,11 +30,50 @@ from pygments.lexers import guess_lexer_for_filename
from pygments.formatters import HtmlFormatter
+# Pygments' token classes, mapped onto the --syn-* custom properties that
+# cgit.css defines. The fallbacks are the palette those properties default to,
+# so that the output stays readable under a stylesheet without them.
+SYNTAX_COLORS = (
+ (('k', 'kc', 'kd', 'kn', 'kr'), '--syn-keyword', '#000',
+ 'font-weight: bold'),
+ (('kp',), '--syn-keyword', '#000', None),
+ (('kt',), '--syn-type', '#010181', None),
+ (('nc', 'nf', 'nn', 'ne'), '--syn-keyword-alt', '#0057ae', None),
+ (('s', 'sa', 'sb', 'sc', 'dl', 'sd', 's2', 'sh', 'sx', 'sr', 's1', 'ss'),
+ '--syn-string', '#bf0303', None),
+ (('se',), '--syn-escape', '#f0f', None),
+ (('si',), '--syn-interpolation', '#0057ae', None),
+ (('c', 'ch', 'cm', 'c1', 'cs'), '--syn-comment', '#838183',
+ 'font-style: italic'),
+ (('cp', 'cpf'), '--syn-preproc', '#008200', None),
+ (('m', 'mb', 'mf', 'mh', 'mi', 'mo', 'il'), '--syn-number', '#b07e00',
+ None),
+ (('o', 'ow', 'p'), '--syn-operator', '#000', None),
+ (('err',), '--syn-error', '#bf0303', None),
+ (('gd',), '--red', 'red', None),
+ (('gi',), '--green', 'green', None),
+ (('lineno', 'linenos'), '--syn-linenum', '#555', None),
+)
+
+
+def style_defs(prefix='.highlight'):
+ rules = []
+ for classes, prop, fallback, extra in SYNTAX_COLORS:
+ selector = ', '.join('%s .%s' % (prefix, c) for c in classes)
+ decls = 'color: var(%s, %s)' % (prop, fallback)
+ if extra:
+ decls += '; ' + extra
+ rules.append('%s { %s }' % (selector, decls))
+ rules.append('%s .hll { background-color: var(--syn-mark-bg, #ffb) }'
+ % prefix)
+ return '\n'.join(rules) + '\n'
+
+
sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8', errors='replace')
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
data = sys.stdin.read()
filename = sys.argv[1]
-formatter = HtmlFormatter(style='pastie', nobackground=True)
+formatter = HtmlFormatter(nobackground=True)
try:
lexer = guess_lexer_for_filename(filename, data)
@@ -48,8 +87,8 @@ except TypeError:
lexer = TextLexer()
# highlight! :-)
-# printout pygments' css definitions as well
+# printout the css definitions for pygments' token classes as well
sys.stdout.write('<style>')
-sys.stdout.write(formatter.get_style_defs('.highlight'))
+sys.stdout.write(style_defs('.highlight'))
sys.stdout.write('</style>')
sys.stdout.write(highlight(data, lexer, formatter, outfile=None))
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
index 840bc34..bb8d60c 100755
--- a/filters/syntax-highlighting.sh
+++ b/filters/syntax-highlighting.sh
@@ -8,8 +8,10 @@
# might have to use an external call to sed instead.
#
# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
-# highlighting, so you'll probably want something like the following included
-# in your css file:
+# highlighting. cgit.css defines these classes in terms of its --syn-* custom
+# properties, so nothing has to be added when using the default stylesheet.
+# The palette below, from highlight 3.13, is what those properties default to;
+# a custom stylesheet wanting the same colors can use it directly:
#
# Style definition file generated by highlight 2.4.8, http://www.andre-simon.de/
#
@@ -114,8 +116,12 @@ EXTENSION="${BASENAME##*.}"
# Version 2 can be found (for example) on EPEL 5, while version 3 can be
# found (for example) on EPEL 6.
#
+# Note that -I (--include-style) is not used: the stylesheet it embeds in the
+# output would override the .hl rules in cgit.css and with them any theming
+# done through the --syn-* custom properties.
+#
# This is for version 2
-exec highlight --force -f -I -X -S "$EXTENSION" 2>/dev/null
+exec highlight --force -f -X -S "$EXTENSION" 2>/dev/null
# This is for version 3
-#exec highlight --force -f -I -O xhtml -S "$EXTENSION" 2>/dev/null
+#exec highlight --force -f -O xhtml -S "$EXTENSION" 2>/dev/null
diff --git a/gen-version.sh b/gen-version.sh
index 80cf49a..b493cf5 100755
--- a/gen-version.sh
+++ b/gen-version.sh
@@ -3,10 +3,24 @@
# Get version-info specified in Makefile
V=$1
-# Use `git describe` to get current version if we're inside a git repo
+# When building from a git repository, derive the version from the most recent
+# release tag instead. The result follows Semantic Versioning 2.0.0, using the
+# number of commits made since that tag as build metadata, so seven commits
+# after the v1.3.1 tag gives "v1.3.1+7", while a build of the tag itself gives
+# plain "v1.3.1". Release tags are expected to be named vMAJOR.MINOR.PATCH.
if test "$(git rev-parse --git-dir 2>/dev/null)" = '.git'
then
- V=$(git describe --abbrev=4 HEAD 2>/dev/null)
+ tag=$(git describe --abbrev=0 --match 'v[0-9]*' HEAD 2>/dev/null)
+ if test -n "$tag"
+ then
+ ahead=$(git rev-list --count "$tag..HEAD" 2>/dev/null)
+ if test -n "$ahead" && test "$ahead" -gt 0
+ then
+ V="$tag+$ahead"
+ else
+ V="$tag"
+ fi
+ fi
fi
new="CGIT_VERSION = $V"
diff --git a/git b/git
-Subproject 53f9a3e157dbbc901a02ac2c73346d375e24978
+Subproject 94f057755b7941b321fd11fec1b2e3ca5313a4e
diff --git a/html.c b/html.c
index 7f81965..0bac34b 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);
}
diff --git a/parsing.c b/parsing.c
index 9e73e70..bdaed25 100644
--- a/parsing.c
+++ b/parsing.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
/*
@@ -17,7 +19,7 @@
*/
void cgit_parse_url(const char *url)
{
- char *c, *cmd, *p;
+ char *c, *cmd, *p, *buf;
struct cgit_repo *repo;
if (!url || url[0] == '\0')
@@ -30,11 +32,12 @@ void cgit_parse_url(const char *url)
return;
}
+ buf = xstrdup(url);
cmd = NULL;
- c = strchr(url, '/');
+ c = strchr(buf, '/');
while (c) {
c[0] = '\0';
- repo = cgit_get_repoinfo(url);
+ repo = cgit_get_repoinfo(buf);
if (repo) {
ctx.repo = repo;
cmd = c;
@@ -54,6 +57,7 @@ void cgit_parse_url(const char *url)
if (cmd[1])
ctx.qry.page = xstrdup(cmd + 1);
}
+ free(buf);
}
static char *substr(const char *head, const char *tail)
@@ -127,9 +131,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(commit, NULL);
+ const char *p = repo_get_commit_buffer(the_repository, commit, NULL);
const char *t;
ret = xcalloc(1, sizeof(struct commitinfo));
@@ -140,10 +143,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,
@@ -199,7 +202,7 @@ struct taginfo *cgit_parse_tag(struct tag *tag)
const char *p;
struct taginfo *ret = NULL;
- data = read_object_file(&tag->object.oid, &type, &size);
+ data = odb_read_object(the_repository->objects, &tag->object.oid, &type, &size);
if (!data || type != OBJ_TAG)
goto cleanup;
diff --git a/robots.txt b/robots.txt
index 4ce948f..1b33266 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 6a2f65a..c120efe 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -47,27 +47,27 @@ out:
}
static struct cgit_repo *repo;
-static repo_config_fn config_fn;
static void scan_tree_repo_config(const char *name, const char *value)
{
- config_fn(repo, name, value);
+ cgit_repo_config(repo, name, value);
}
-static int gitconfig_config(const char *key, const char *value, void *cb)
+static int gitconfig_config(const char *key, const char *value,
+ const __attribute__((unused)) struct config_context *ctx, void *cb)
{
const char *name;
if (!strcmp(key, "gitweb.owner"))
- config_fn(repo, "owner", value);
+ cgit_repo_config(repo, "owner", value);
else if (!strcmp(key, "gitweb.description"))
- config_fn(repo, "desc", value);
+ cgit_repo_config(repo, "desc", value);
else if (!strcmp(key, "gitweb.category"))
- config_fn(repo, "section", value);
+ cgit_repo_config(repo, "section", value);
else if (!strcmp(key, "gitweb.homepage"))
- config_fn(repo, "homepage", value);
+ cgit_repo_config(repo, "homepage", value);
else if (skip_prefix(key, "cgit.", &name))
- config_fn(repo, name, value);
+ cgit_repo_config(repo, name, value);
return 0;
}
@@ -79,7 +79,7 @@ static char *xstrrchr(char *s, char *from, int c)
return from < s ? NULL : from;
}
-static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
+static void add_repo(const char *base, struct strbuf *path)
{
struct stat st;
struct passwd *pwd;
@@ -121,7 +121,6 @@ static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
strbuf_setlen(&rel, rel.len - 1);
repo = cgit_add_repo(rel.buf);
- config_fn = fn;
if (ctx.cfg.enable_git_config) {
strbuf_addstr(path, "config");
git_config_from_file(gitconfig_config, path->buf, NULL);
@@ -134,7 +133,7 @@ static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
strip_suffix_mem(repo->url, &urllen, "/");
repo->url[urllen] = '\0';
}
- repo->path = xstrdup(path->buf);
+ repo->path = strdup_first_line(path->buf);
while (!repo->owner) {
if ((pwd = getpwuid(st.st_uid)) == NULL) {
fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
@@ -144,13 +143,13 @@ static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
if (pwd->pw_gecos)
if ((p = strchr(pwd->pw_gecos, ',')))
*p = '\0';
- repo->owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
+ repo->owner = strdup_first_line(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
}
if (repo->desc == cgit_default_repo_desc || !repo->desc) {
strbuf_addstr(path, "description");
if (!stat(path->buf, &st))
- readfile(path->buf, &repo->desc, &size);
+ read_first_line(path->buf, &repo->desc, &size);
strbuf_setlen(path, pathlen);
}
@@ -167,7 +166,7 @@ static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
}
if (slash && !n) {
*slash = '\0';
- repo->section = xstrdup(rel.buf);
+ repo->section = strdup_first_line(rel.buf);
*slash = '/';
if (starts_with(repo->name, repo->section)) {
repo->name += strlen(repo->section);
@@ -184,7 +183,7 @@ static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
strbuf_release(&rel);
}
-static void scan_path(const char *base, const char *path, repo_config_fn fn)
+static void scan_path(const char *base, const char *path)
{
DIR *dir = opendir(path);
struct dirent *ent;
@@ -200,12 +199,12 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
strbuf_add(&pathbuf, path, strlen(path));
if (is_git_dir(pathbuf.buf)) {
- add_repo(base, &pathbuf, fn);
+ add_repo(base, &pathbuf);
goto end;
}
strbuf_addstr(&pathbuf, "/.git");
if (is_git_dir(pathbuf.buf)) {
- add_repo(base, &pathbuf, fn);
+ add_repo(base, &pathbuf);
goto end;
}
/*
@@ -230,14 +229,14 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn)
continue;
}
if (S_ISDIR(st.st_mode))
- scan_path(base, pathbuf.buf, fn);
+ scan_path(base, pathbuf.buf);
}
end:
strbuf_release(&pathbuf);
closedir(dir);
}
-void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
+void scan_projects(const char *path, const char *projectsfile)
{
struct strbuf line = STRBUF_INIT;
FILE *projects;
@@ -254,7 +253,7 @@ void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn
continue;
strbuf_insert(&line, 0, "/", 1);
strbuf_insert(&line, 0, path, strlen(path));
- scan_path(path, line.buf, fn);
+ scan_path(path, line.buf);
}
if ((err = ferror(projects))) {
fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
@@ -264,7 +263,7 @@ void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn
strbuf_release(&line);
}
-void scan_tree(const char *path, repo_config_fn fn)
+void scan_tree(const char *path)
{
- scan_path(path, path, fn);
+ scan_path(path, path);
}
diff --git a/scan-tree.h b/scan-tree.h
index 1afbd4b..def0a7a 100644
--- a/scan-tree.h
+++ b/scan-tree.h
@@ -1,2 +1,2 @@
-extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn);
-extern void scan_tree(const char *path, repo_config_fn fn);
+extern void scan_projects(const char *path, const char *projectsfile);
+extern void scan_tree(const char *path);
diff --git a/shared.c b/shared.c
index 609bd2a..8beb7c7 100644
--- a/shared.c
+++ b/shared.c
@@ -1,16 +1,28 @@
/* shared.c: global vars + some callback functions
*
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ * Copyright (C) 2026 Saya Andy <saya.andy@posteo.com>
+ *
+ * Modified 2026 by Saya Andy:
+ * Add cgit_have_repository()
+ * Add ci filter configuration
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
struct cgit_repolist cgit_repolist;
struct cgit_context ctx;
+int cgit_have_repository(void)
+{
+ return startup_info->have_repository;
+}
+
int chk_zero(int result, char *msg)
{
if (result != 0)
@@ -50,15 +62,21 @@ struct cgit_repo *cgit_add_repo(const char *url)
ret = &cgit_repolist.repos[cgit_repolist.count-1];
memset(ret, 0, sizeof(struct cgit_repo));
ret->url = trim_end(url, '/');
+ *strchrnul(ret->url, '\n') = '\0';
ret->name = ret->url;
ret->path = NULL;
ret->desc = cgit_default_repo_desc;
ret->extra_head_content = NULL;
ret->owner = NULL;
ret->homepage = NULL;
+ ret->ci_url = ctx.cfg.ci_url;
+ ret->ci_branch_url = ctx.cfg.ci_branch_url;
+ ret->ci_tag_url = ctx.cfg.ci_tag_url;
ret->section = ctx.cfg.section;
ret->snapshots = ctx.cfg.snapshots;
+ ret->enable_blame = ctx.cfg.enable_blame;
ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
+ ret->enable_follow_links = ctx.cfg.enable_follow_links;
ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
@@ -75,6 +93,7 @@ struct cgit_repo *cgit_add_repo(const char *url)
ret->source_filter = ctx.cfg.source_filter;
ret->email_filter = ctx.cfg.email_filter;
ret->owner_filter = ctx.cfg.owner_filter;
+ ret->ci_filter = ctx.cfg.ci_filter;
ret->clone_url = ctx.cfg.clone_url;
ret->submodules.strdup_strings = 1;
ret->hide = ret->ignore = 0;
@@ -161,7 +180,7 @@ static struct refinfo *cgit_mk_refinfo(const char *refname, const struct object_
ref = xmalloc(sizeof (struct refinfo));
ref->refname = xstrdup(refname);
- ref->object = parse_object(oid);
+ ref->object = parse_object(the_repository, oid);
switch (ref->object->type) {
case OBJ_TAG:
ref->tag = cgit_parse_tag((struct tag *)ref->object);
@@ -209,11 +228,10 @@ void cgit_free_reflist_inner(struct reflist *list)
free(list->refs);
}
-int cgit_refs_cb(const char *refname, const struct object_id *oid, int flags,
- void *cb_data)
+int cgit_refs_cb(const struct reference *ref, void *cb_data)
{
struct reflist *list = (struct reflist *)cb_data;
- struct refinfo *info = cgit_mk_refinfo(refname, oid);
+ struct refinfo *info = cgit_mk_refinfo(ref->name, ref->oid);
if (info)
cgit_add_ref(list, info);
@@ -240,7 +258,7 @@ static int load_mmfile(mmfile_t *file, const struct object_id *oid)
file->ptr = (char *)"";
file->size = 0;
} else {
- file->ptr = read_object_file(oid, &type,
+ file->ptr = odb_read_object(the_repository->objects, oid, &type,
(unsigned long *)&file->size);
}
return 1;
@@ -325,7 +343,7 @@ int cgit_diff_files(const struct object_id *old_oid,
diff_params.flags |= XDF_IGNORE_WHITESPACE;
emit_params.ctxlen = context > 0 ? context : 3;
emit_params.flags = XDL_EMIT_FUNCNAMES;
- emit_cb.outf = filediff_cb;
+ emit_cb.out_line = filediff_cb;
emit_cb.priv = fn;
xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
if (file1.size)
@@ -340,10 +358,9 @@ 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);
+ repo_diff_setup(the_repository, &opt);
opt.output_format = DIFF_FORMAT_CALLBACK;
opt.detect_rename = 1;
opt.rename_limit = ctx.cfg.renamelimit;
@@ -353,10 +370,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);
@@ -366,8 +384,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)
@@ -394,7 +410,7 @@ int cgit_parse_snapshots_mask(const char *str)
if (strcmp(str, "all") == 0)
return INT_MAX;
- string_list_split(&tokens, str, ' ', -1);
+ string_list_split(&tokens, str, " ", -1);
string_list_remove_empty_items(&tokens, 0);
for_each_string_list_item(item, &tokens) {
@@ -439,9 +455,10 @@ void cgit_prepare_repo_env(struct cgit_repo * repo)
}
/* Read the content of the specified file into a newly allocated buffer,
- * zeroterminate the buffer and return 0 on success, errno otherwise.
+ * zeroterminate the buffer, truncate at a new line, and return 0 on success,
+ * errno otherwise.
*/
-int readfile(const char *path, char **buf, size_t *size)
+int read_first_line(const char *path, char **buf, size_t *size)
{
int fd, e;
struct stat st;
@@ -462,10 +479,18 @@ int readfile(const char *path, char **buf, size_t *size)
*size = read_in_full(fd, *buf, st.st_size);
e = errno;
(*buf)[*size] = '\0';
+ *strchrnul(*buf, '\n') = '\0';
close(fd);
return (*size == st.st_size ? 0 : e);
}
+char *strdup_first_line(const char *txt)
+{
+ char *t = xstrdup(txt);
+ *strchrnul(t, '\n') = '\0';
+ return t;
+}
+
static int is_token_char(char c)
{
return isalnum(c) || c == '_';
@@ -540,7 +565,10 @@ char *expand_macros(const char *txt)
char *get_mimetype_for_filename(const char *filename)
{
- char *ext, *mimetype, *token, line[1024], *saveptr;
+ const char *ext;
+ char *mimetype, line[1024];
+ struct string_list list = STRING_LIST_INIT_NODUP;
+ int i;
FILE *file;
struct string_list_item *mime;
@@ -565,13 +593,16 @@ char *get_mimetype_for_filename(const char *filename)
while (fgets(line, sizeof(line), file)) {
if (!line[0] || line[0] == '#')
continue;
- mimetype = strtok_r(line, " \t\r\n", &saveptr);
- while ((token = strtok_r(NULL, " \t\r\n", &saveptr))) {
- if (!strcasecmp(ext, token)) {
+ string_list_split_in_place(&list, line, " \t\r\n", -1);
+ string_list_remove_empty_items(&list, 0);
+ mimetype = list.items[0].string;
+ for (i = 1; i < list.nr; i++) {
+ if (!strcasecmp(ext, list.items[i].string)) {
fclose(file);
return xstrdup(mimetype);
}
}
+ string_list_clear(&list, 0);
}
fclose(file);
return NULL;
diff --git a/solarized.css b/solarized.css
new file mode 100644
index 0000000..2e6e3d4
--- /dev/null
+++ b/solarized.css
@@ -0,0 +1,140 @@
+/*
+ * Solarized theme (Ethan Schoonover) with automatic light/dark selection
+ * based on the prefers-color-scheme media query.
+ *
+ * Must be listed after cgit.css, e.g.
+ *
+ * css=/cgit.css
+ * css=/solarized.css
+ */
+
+:root {
+ --sol-base03: #002b36;
+ --sol-base02: #073642;
+ --sol-base01: #586e75;
+ --sol-base00: #657b83;
+ --sol-base0: #839496;
+ --sol-base1: #93a1a1;
+ --sol-base2: #eee8d5;
+ --sol-base3: #fdf6e3;
+
+ --sol-yellow: #b58900;
+ --sol-orange: #cb4b16;
+ --sol-red: #dc322f;
+ --sol-magenta: #d33682;
+ --sol-violet: #6c71c4;
+ --sol-blue: #268bd2;
+ --sol-cyan: #2aa198;
+ --sol-green: #859900;
+
+ --sol-canvas: var(--sol-base3);
+ --sol-panel: var(--sol-base2);
+ --sol-body: var(--sol-base00);
+ --sol-emph: var(--sol-base01);
+ --sol-muted: var(--sol-base0);
+ --sol-secondary: var(--sol-base1);
+ --sol-shade: var(--sol-base03);
+ --sol-page: var(--sol-panel);
+
+ --sol-tint-soft: 18%;
+ --sol-tint: 25%;
+ --sol-tint-strong: 45%;
+
+ color-scheme: light dark;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --sol-canvas: var(--sol-base03);
+ --sol-panel: var(--sol-base02);
+ --sol-body: var(--sol-base0);
+ --sol-emph: var(--sol-base1);
+ --sol-muted: var(--sol-base00);
+ --sol-secondary: var(--sol-base01);
+ --sol-shade: var(--sol-base3);
+
+ --sol-tint-soft: 30%;
+ --sol-tint: 35%;
+ --sol-tint-strong: 55%;
+ }
+}
+
+body:has(> div#cgit) {
+ margin: 0;
+ background-color: var(--sol-page);
+}
+
+div#cgit {
+ --bg: var(--sol-canvas);
+ --bg-zebra: color-mix(in srgb, var(--sol-panel) 40%, var(--sol-canvas));
+ --bg-hl: var(--sol-panel);
+ --bg-hl2: color-mix(in srgb, var(--sol-panel) 85%, var(--sol-shade));
+ --fg: var(--sol-body);
+ --fg-strong: var(--sol-emph);
+ --fg-mid: var(--sol-body);
+ --fg-mute: var(--sol-muted);
+ --fg-dim: var(--sol-secondary);
+ --fg-faint: color-mix(in srgb, var(--sol-secondary) 65%, var(--sol-canvas));
+ --fg-faintest: color-mix(in srgb, var(--sol-secondary) 45%, var(--sol-canvas));
+ --fg-on-tint: var(--sol-emph);
+ --fg-linenum: var(--sol-secondary);
+ --border: var(--sol-panel);
+ --border-mid: var(--sol-secondary);
+ --border-dark: var(--sol-muted);
+ --border-strong: var(--sol-emph);
+
+ --red: var(--sol-red);
+ --red-mid: color-mix(in srgb, var(--sol-red) 75%, var(--sol-shade));
+ --red-dark: color-mix(in srgb, var(--sol-red) 55%, var(--sol-shade));
+ --green: var(--sol-green);
+ --green-mid: color-mix(in srgb, var(--sol-green) 70%, var(--sol-shade));
+ --green-dark: color-mix(in srgb, var(--sol-green) 50%, var(--sol-shade));
+ --blue: var(--sol-blue);
+ --blue-dark: color-mix(in srgb, var(--sol-blue) 70%, var(--sol-shade));
+
+ --graph-col1: var(--sol-red);
+ --graph-col2: var(--sol-green);
+ --graph-col3: var(--sol-yellow);
+ --graph-col4: var(--sol-blue);
+ --graph-col5: var(--sol-magenta);
+ --graph-col6: var(--sol-cyan);
+
+ --graph-add-bg: var(--sol-green);
+ --graph-rem-bg: var(--sol-red);
+
+ --notes-bg: color-mix(in srgb, var(--sol-yellow) var(--sol-tint-soft), var(--sol-canvas));
+ --notes-border: var(--sol-yellow);
+ --add-bg: color-mix(in srgb, var(--sol-green) var(--sol-tint), var(--sol-canvas));
+ --add-bg-dark: color-mix(in srgb, var(--sol-green) var(--sol-tint-strong), var(--sol-canvas));
+ --del-bg: color-mix(in srgb, var(--sol-red) var(--sol-tint), var(--sol-canvas));
+ --del-bg-dark: color-mix(in srgb, var(--sol-red) var(--sol-tint-strong), var(--sol-canvas));
+ --changed-bg: color-mix(in srgb, var(--sol-yellow) var(--sol-tint), var(--sol-canvas));
+ --changed-bg-dark: color-mix(in srgb, var(--sol-yellow) var(--sol-tint-strong), var(--sol-canvas));
+ --hunk-bg: color-mix(in srgb, var(--sol-blue) var(--sol-tint), var(--sol-canvas));
+
+ --deco-branch-bg: color-mix(in srgb, var(--sol-green) var(--sol-tint-soft), var(--sol-canvas));
+ --deco-branch-border: var(--sol-green);
+ --deco-tag-bg: color-mix(in srgb, var(--sol-yellow) var(--sol-tint-soft), var(--sol-canvas));
+ --deco-tag-border: var(--sol-yellow);
+ --deco-tag-annotated-bg: color-mix(in srgb, var(--sol-orange) var(--sol-tint-soft), var(--sol-canvas));
+ --deco-tag-annotated-border: var(--sol-yellow);
+ --deco-remote-bg: color-mix(in srgb, var(--sol-violet) var(--sol-tint-soft), var(--sol-canvas));
+ --deco-remote-border: var(--sol-violet);
+ --deco-other-bg: color-mix(in srgb, var(--sol-red) var(--sol-tint-soft), var(--sol-canvas));
+ --deco-other-border: var(--sol-red);
+
+ --syn-keyword: var(--sol-emph);
+ --syn-keyword-alt: var(--sol-blue);
+ --syn-type: var(--blue-dark);
+ --syn-string: var(--sol-red);
+ --syn-string-alt: var(--sol-green);
+ --syn-escape: var(--sol-magenta);
+ --syn-comment: var(--sol-secondary);
+ --syn-number: var(--sol-yellow);
+ --syn-preproc: var(--green-mid);
+ --syn-operator: var(--sol-emph);
+ --syn-interpolation: var(--sol-blue);
+ --syn-linenum: var(--sol-secondary);
+ --syn-error: var(--sol-red);
+ --syn-mark-bg: color-mix(in srgb, var(--sol-yellow) var(--sol-tint), var(--sol-canvas));
+}
diff --git a/tests/setup.sh b/tests/setup.sh
index 7590f04..8db810f 100755
--- a/tests/setup.sh
+++ b/tests/setup.sh
@@ -80,13 +80,17 @@ mkrepo() {
git commit -m "commit $n"
n=$(expr $n + 1)
done
- if test "$3" = "testplus"
- then
+ case "$3" in
+ testplus)
echo "hello" >a+b
git add a+b
git commit -m "add a+b"
git branch "1+2"
- fi
+ ;;
+ commit-graph)
+ git commit-graph write
+ ;;
+ esac
)
}
@@ -95,7 +99,7 @@ setup_repos()
rm -rf cache
mkdir -p cache
mkrepo repos/foo 5 >/dev/null
- mkrepo repos/bar 50 >/dev/null
+ mkrepo repos/bar 50 commit-graph >/dev/null
mkrepo repos/foo+bar 10 testplus >/dev/null
mkrepo "repos/with space" 2 >/dev/null
mkrepo repos/filter 5 testplus >/dev/null
@@ -104,7 +108,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
diff --git a/tests/t0001-validate-git-versions.sh b/tests/t0001-validate-git-versions.sh
index a65b35e..dd84fe3 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,11 +33,11 @@ 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
- ) | sed -e "s/^v//" >sm_version &&
+ 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 9cdf55c..1a12ee3 100755
--- a/tests/t0105-commit.sh
+++ b/tests/t0105-commit.sh
@@ -25,7 +25,7 @@ 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' '
diff --git a/tests/t0106-diff.sh b/tests/t0106-diff.sh
index 82b645e..e4751e9 100755
--- a/tests/t0106-diff.sh
+++ b/tests/t0106-diff.sh
@@ -16,4 +16,65 @@ test_expect_success 'find added line' '
grep "<div class=.add.>+5</div>" tmp
'
+test_expect_success 'ref picker: from: select present' '
+ grep "<select name=.id2." tmp
+'
+
+test_expect_success 'ref picker: to: select present' '
+ grep "<select name=.id." tmp
+'
+
+test_expect_success 'ref picker: from: label present' '
+ grep "from:" tmp
+'
+
+test_expect_success 'ref picker: to: label present' '
+ grep "to:" tmp
+'
+
+test_expect_success 'ref pickers live in a separate refs panel' '
+ grep "<b>diff refs</b>" tmp &&
+ grep "<b>diff options</b>" tmp
+'
+
+test_expect_success 'ref picker: master branch listed' '
+ grep "master" tmp
+'
+
+test_expect_success 'ref picker: auto-submit on change' '
+ grep "onchange=.this.form.submit()." tmp
+'
+
+test_expect_success 'compare two refs: foo/diff?id=master&id2=HEAD' '
+ cgit_query "url=foo/diff&id=master&id2=HEAD" >tmp
+'
+
+test_expect_success 'diff with explicit refs shows diffstat' '
+ grep "diffstat" tmp
+'
+
+test_expect_success 'diff with oid2 parameter uses it for old ref' '
+ cgit_query "url=foo/diff&id=master&id2=HEAD" >tmp &&
+ grep "from:" tmp
+'
+
+test_expect_success 'ref picker: to: shows master when id=master' '
+ cgit_query "url=foo/diff&id=master" >tmp &&
+ grep "master" tmp
+'
+
+test_expect_success 'ref picker: from: empty when no id2' '
+ cgit_query "url=foo/diff" >tmp &&
+ grep "<select name=.id2." tmp
+'
+
+test_expect_success 'compare initial commit with HEAD' '
+ root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD) &&
+ cgit_query "url=foo/diff&id=HEAD&id2=$root" >tmp
+'
+
+test_expect_success 'diff with HEAD and root shows content' '
+ grep "diffstat" tmp
+'
+
test_done
diff --git a/tests/t0107-snapshot.sh b/tests/t0107-snapshot.sh
index 6cf7aaa..0811ec4 100755
--- a/tests/t0107-snapshot.sh
+++ b/tests/t0107-snapshot.sh
@@ -25,7 +25,7 @@ test_expect_success 'verify gzip format' '
test_expect_success 'untar' '
rm -rf master &&
- tar -xzf master.tar.gz
+ gzip -dc master.tar.gz | tar -xf -
'
test_expect_success 'count files' '
@@ -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 3ba6684..189ef28 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/t0112-ci.sh b/tests/t0112-ci.sh
new file mode 100644
index 0000000..33609d6
--- /dev/null
+++ b/tests/t0112-ci.sh
@@ -0,0 +1,127 @@
+#!/bin/sh
+
+test_description='Check content on ci page'
+. ./setup.sh
+
+# The ci settings are only inherited by repositories which are declared
+# after them, which is used below to get a repository with no ci url and
+# one with a tag-only ci url.
+cat >cgitrc.ci <<EOF
+virtual-root=/
+cache-root=$PWD/cache
+cache-size=0
+enable-filter-overrides=1
+
+repo.url=tagonly
+repo.path=$PWD/repos/foo/.git
+repo.ci-tag-url=https://ci.example.org/tagonly/\$ref
+
+ci-branch-url=https://ci.example.org/job/\$slug/job/\$ref
+ci-tag-url=https://ci.example.org/job/\$slug/view/tags/job/\$ref
+
+repo.url=foo
+repo.path=$PWD/repos/foo/.git
+
+repo.url=sayauz/web.git
+repo.path=$PWD/repos/foo/.git
+
+repo.url=override
+repo.path=$PWD/repos/foo/.git
+repo.ci-url=https://ci.example.org/override/\$repo/\$\$/\$ref
+
+repo.url=filtered
+repo.path=$PWD/repos/foo/.git
+repo.ci-filter=exec:$PWD/ci-filter.sh
+EOF
+
+# Records its arguments and only accepts tags.
+cat >ci-filter.sh <<EOF
+#!/bin/sh
+printf '%s|%s|%s\n' "\$1" "\$2" "\$3" >>"$PWD/ci-filter.log"
+test "\$2" = tag
+EOF
+chmod +x ci-filter.sh
+
+git --git-dir="$PWD/repos/foo/.git" tag v0.17.3.2 master
+
+cgit_ci()
+{
+ CGIT_CONFIG="$PWD/cgitrc.ci" QUERY_STRING="url=$1" cgit
+}
+
+test_expect_success 'redirect to the branch ci url' '
+ cgit_ci "foo/ci&h=master" >tmp &&
+ grep "^Status: 302 Found$" tmp &&
+ grep "^Location: https://ci.example.org/job/foo/job/master$" tmp
+'
+
+test_expect_success 'redirect to the tag ci url' '
+ cgit_ci "foo/ci&h=v0.17.3.2" >tmp &&
+ grep "^Status: 302 Found$" tmp &&
+ grep "^Location: https://ci.example.org/job/foo/view/tags/job/v0.17.3.2$" tmp
+'
+
+test_expect_success 'redirect to the default branch ci url' '
+ cgit_ci "foo/ci" >tmp &&
+ grep "^Location: https://ci.example.org/job/foo/job/master$" tmp
+'
+
+test_expect_success '$slug strips ".git" and replaces slashes' '
+ cgit_ci "sayauz/web.git/ci&h=v0.17.3.2" >tmp &&
+ grep "^Location: https://ci.example.org/job/sayauz-web/view/tags/job/v0.17.3.2$" tmp
+'
+
+test_expect_success 'repo.ci-url overrides and expands $repo and $$' '
+ cgit_ci "override/ci&h=master" >tmp &&
+ grep -F "Location: https://ci.example.org/override/override/\$/master" tmp
+'
+
+test_expect_success 'repo.ci-tag-url does not apply to branches' '
+ cgit_ci "tagonly/ci&h=v0.17.3.2" >tmp &&
+ grep "^Location: https://ci.example.org/tagonly/v0.17.3.2$" tmp &&
+ cgit_ci "tagonly/ci&h=master" >tmp &&
+ grep "^Status: 404 Not found$" tmp
+'
+
+test_expect_success 'find ci tab' '
+ cgit_ci "foo/refs&h=master" >tmp &&
+ grep "href=./foo/ci/.>ci</a>" tmp
+'
+
+test_expect_success 'no ci tab without a ci url' '
+ cgit_url "foo/refs" >tmp &&
+ ! grep ">ci</a>" tmp
+'
+
+test_expect_success 'ci filter accepts a tag' '
+ rm -f ci-filter.log &&
+ cgit_ci "filtered/ci&h=v0.17.3.2" >tmp &&
+ grep "^Location: https://ci.example.org/job/filtered/view/tags/job/v0.17.3.2$" tmp &&
+ echo "v0.17.3.2|tag|https://ci.example.org/job/filtered/view/tags/job/v0.17.3.2" >expected &&
+ test_cmp expected ci-filter.log
+'
+
+test_expect_success 'ci filter rejects a branch' '
+ rm -f ci-filter.log &&
+ cgit_ci "filtered/ci&h=master" >tmp &&
+ grep "^Status: 404 Not found$" tmp &&
+ echo "master|branch|https://ci.example.org/job/filtered/job/master" >expected &&
+ test_cmp expected ci-filter.log
+'
+
+test_expect_success "ci filter hides the tab and is consulted once" '
+ rm -f ci-filter.log &&
+ cgit_ci "filtered/refs&h=master" >tmp &&
+ ! grep ">ci</a>" tmp &&
+ test_line_count = 1 ci-filter.log &&
+ cgit_ci "filtered/refs&h=v0.17.3.2" >tmp &&
+ grep "href=./filtered/ci/?h=v0.17.3.2.>ci</a>" tmp
+'
+
+test_expect_success 'ci filter is not consulted without a ci url' '
+ rm -f ci-filter.log &&
+ cgit_url "foo/refs" >tmp &&
+ test_path_is_missing ci-filter.log
+'
+
+test_done
diff --git a/tests/t0113-ci-lua.sh b/tests/t0113-ci-lua.sh
new file mode 100644
index 0000000..1e095b1
--- /dev/null
+++ b/tests/t0113-ci-lua.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+test_description='Check ci filter written in Lua'
+. ./setup.sh
+
+if test $CGIT_HAS_LUA -ne 1
+then
+ skip_all='Skipping Lua ci filter tests: Lua support not compiled in'
+ test_done
+ exit
+fi
+
+cat >cgitrc.cilua <<EOF
+virtual-root=/
+cache-root=$PWD/cache
+cache-size=0
+
+ci-url=https://ci.example.org/job/\$slug/job/\$ref
+ci-filter=lua:$PWD/ci-filter.lua
+
+repo.url=foo
+repo.path=$PWD/repos/foo/.git
+EOF
+
+# Accepts tags only, and records its arguments.
+cat >ci-filter.lua <<EOF
+function filter_open(ref, refkind, url)
+ local log = io.open("$PWD/ci-filter.log", "a")
+ log:write(ref .. "|" .. refkind .. "|" .. url .. "\n")
+ log:close()
+ accepted = (refkind == "tag")
+end
+
+function filter_write(buffer)
+end
+
+function filter_close()
+ if accepted then
+ return 0
+ end
+ return 1
+end
+EOF
+
+git --git-dir="$PWD/repos/foo/.git" tag v1.0 master
+
+cgit_ci()
+{
+ CGIT_CONFIG="$PWD/cgitrc.cilua" QUERY_STRING="url=$1" cgit
+}
+
+test_expect_success 'Lua ci filter accepts a tag' '
+ rm -f ci-filter.log &&
+ cgit_ci "foo/ci&h=v1.0" >tmp &&
+ grep "^Location: https://ci.example.org/job/foo/job/v1.0$" tmp &&
+ echo "v1.0|tag|https://ci.example.org/job/foo/job/v1.0" >expected &&
+ test_cmp expected ci-filter.log
+'
+
+test_expect_success 'Lua ci filter rejects a branch' '
+ cgit_ci "foo/ci&h=master" >tmp &&
+ grep "^Status: 404 Not found$" tmp
+'
+
+test_expect_success 'Lua ci filter controls the tab' '
+ cgit_ci "foo/refs&h=v1.0" >tmp &&
+ grep "href=./foo/ci/?h=v1.0.>ci</a>" tmp &&
+ cgit_ci "foo/refs&h=master" >tmp &&
+ ! grep ">ci</a>" tmp
+'
+
+test_expect_success 'page is still rendered correctly around the filter' '
+ cgit_ci "foo/refs&h=v1.0" >tmp &&
+ grep "</html>" tmp &&
+ ! grep "|tag|" tmp
+'
+
+test_done
diff --git a/tests/t0114-broken-repo.sh b/tests/t0114-broken-repo.sh
new file mode 100644
index 0000000..61367e6
--- /dev/null
+++ b/tests/t0114-broken-repo.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+test_description='Check that a misconfigured repo.path is reported'
+. ./setup.sh
+
+mkdir -p plaindir
+
+cat >cgitrc.broken <<EOF
+virtual-root=/
+cache-root=$PWD/cache
+cache-size=0
+
+ci-branch-url=https://ci.example.org/job/\$slug/job/\$ref
+
+repo.url=missing
+repo.path=$PWD/no/such/repo.git
+
+repo.url=notarepo
+repo.path=$PWD/plaindir
+EOF
+
+# cgit used to spin forever on these requests, so give it a deadline and
+# report a failure rather than hanging the test suite.
+cgit_broken()
+{
+ CGIT_CONFIG="$PWD/cgitrc.broken" QUERY_STRING="url=$1" perl -e '
+ my $pid = fork();
+ if ($pid == 0) {
+ exec("cgit") or die "exec: $!";
+ }
+ eval {
+ local $SIG{ALRM} = sub { die "timeout\n" };
+ alarm(20);
+ waitpid($pid, 0);
+ alarm(0);
+ };
+ if ($@) {
+ kill "KILL", $pid;
+ print STDERR "cgit did not terminate\n";
+ exit 1;
+ }
+ ' </dev/null
+}
+
+test_expect_success 'report a repo.path that does not exist' '
+ cgit_broken "missing/&h=master" >tmp &&
+ grep "Failed to open missing" tmp
+'
+
+test_expect_success 'report a repo.path that is not a repository' '
+ cgit_broken "notarepo/&h=master" >tmp &&
+ grep "Failed to open notarepo" tmp
+'
+
+test_expect_success 'report an unknown page for a broken repo' '
+ cgit_broken "missing/nosuchpage/&h=master" >tmp &&
+ grep "^Status: 404 Not found$" tmp &&
+ grep "Invalid request" tmp
+'
+
+test_expect_success 'no ci tab for a broken repo' '
+ cgit_broken "missing/nosuchpage/&h=master" >tmp &&
+ ! grep ">ci</a>" tmp
+'
+
+test_expect_success 'report a broken repo on the ci page' '
+ cgit_broken "missing/ci/&h=master" >tmp &&
+ grep "Failed to open missing" tmp
+'
+
+test_done
diff --git a/ui-atom.c b/ui-atom.c
index 3866823..0659e96 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-atom.h"
#include "html.h"
@@ -67,29 +69,25 @@ 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);
}
-void cgit_print_atom(char *tip, char *path, int max_count)
+void cgit_print_atom(char *tip, const char *path, int max_count)
{
char *host;
const char *argv[] = {NULL, tip, NULL, NULL, NULL};
struct commit *commit;
struct rev_info rev;
int argc = 2;
+ bool first = true;
if (ctx.qry.show_all)
argv[1] = "--all";
@@ -101,7 +99,7 @@ void cgit_print_atom(char *tip, char *path, int max_count)
argv[argc++] = path;
}
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
rev.abbrev = DEFAULT_ABBREV;
rev.commit_format = CMIT_FMT_DEFAULT;
rev.verbose_header = 1;
@@ -130,18 +128,30 @@ void cgit_print_atom(char *tip, char *path, int max_count)
html_txt(ctx.repo->desc);
html("</subtitle>\n");
if (host) {
+ char *fullurl = cgit_currentfullurl();
char *repourl = cgit_repourl(ctx.repo->url);
+ html("<id>");
+ html_txtf("%s%s%s", cgit_httpscheme(), host, fullurl);
+ html("</id>\n");
+ html("<link rel='self' href='");
+ html_attrf("%s%s%s", cgit_httpscheme(), host, fullurl);
+ html("'/>\n");
html("<link rel='alternate' type='text/html' href='");
- html(cgit_httpscheme());
- html_attr(host);
- html_attr(repourl);
+ html_attrf("%s%s%s", cgit_httpscheme(), host, repourl);
html("'/>\n");
+ free(fullurl);
free(repourl);
}
while ((commit = get_revision(&rev)) != NULL) {
+ if (first) {
+ html("<updated>");
+ html_txt(show_date(commit->date, 0,
+ date_mode_from_type(DATE_ISO8601_STRICT)));
+ html("</updated>\n");
+ first = false;
+ }
add_entry(commit, host);
- free_commit_buffer(commit);
- free_commit_list(commit->parents);
+ release_commit_memory(the_repository->parsed_objects, commit);
commit->parents = NULL;
}
html("</feed>\n");
diff --git a/ui-atom.h b/ui-atom.h
index 749ffd3..dda953b 100644
--- a/ui-atom.h
+++ b/ui-atom.h
@@ -1,6 +1,6 @@
#ifndef UI_ATOM_H
#define UI_ATOM_H
-extern void cgit_print_atom(char *tip, char *path, int max_count);
+extern void cgit_print_atom(char *tip, const char *path, int max_count);
#endif
diff --git a/ui-blame.c b/ui-blame.c
index 50d0580..5c6f36e 100644
--- a/ui-blame.c
+++ b/ui-blame.c
@@ -6,11 +6,13 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-blame.h"
#include "html.h"
#include "ui-shared.h"
-#include "argv-array.h"
+#include "strvec.h"
#include "blame.h"
@@ -48,12 +50,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'>");
- cgit_commit_link(find_unique_abbrev(oid, DEFAULT_ABBREV), detail,
+ html("<span class='oid'>");
+ cgit_commit_link(repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV), detail,
NULL, ctx.qry.head, oid_to_hex(oid), suspect->path);
html("</span>");
free(detail);
+ if (!repo_parse_commit(the_repository, 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,34 +115,36 @@ 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;
struct blame_entry *ent = NULL;
- type = oid_object_info(the_repository, oid, &size);
+ type = odb_read_object_info(the_repository->objects, oid, &size);
if (type == OBJ_BAD) {
cgit_print_error_page(404, "Not found", "Bad object name: %s",
oid_to_hex(oid));
return;
}
- buf = read_object_file(oid, &type, &size);
+ buf = odb_read_object(the_repository->objects, oid, &type, &size);
if (!buf) {
cgit_print_error_page(500, "Internal server error",
"Error reading object %s", oid_to_hex(oid));
return;
}
- argv_array_push(&rev_argv, "blame");
- argv_array_push(&rev_argv, rev);
- init_revisions(&revs, NULL);
+ strvec_push(&rev_argv, "blame");
+ strvec_push(&rev_argv, rev);
+ repo_init_revisions(the_repository, &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;
- setup_scoreboard(&sb, path, &o);
+ sb.repo = the_repository;
+ 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);
@@ -150,6 +163,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>",
@@ -219,8 +236,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;
@@ -247,7 +263,7 @@ static int walk_tree(const struct object_id *oid, struct strbuf *base,
static int basedir_len(const char *path)
{
- char *p = strrchr(path, '/');
+ const char *p = strrchr(path, '/');
if (p)
return p - path + 1;
return 0;
@@ -255,7 +271,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 = {
@@ -273,13 +289,13 @@ void cgit_print_blame(void)
if (!rev)
rev = ctx.qry.head;
- if (get_oid(rev, &oid)) {
+ if (repo_get_oid(the_repository, rev, &oid)) {
cgit_print_error_page(404, "Not found",
"Invalid revision name: %s", rev);
return;
}
- commit = lookup_commit_reference(&oid);
- if (!commit || parse_commit(commit)) {
+ commit = lookup_commit_reference(the_repository, &oid);
+ if (!commit || repo_parse_commit(the_repository, commit)) {
cgit_print_error_page(404, "Not found",
"Invalid commit reference: %s", rev);
return;
@@ -289,8 +305,8 @@ void cgit_print_blame(void)
walk_tree_ctx.match_baselen = (path_items.match) ?
basedir_len(path_items.match) : -1;
- read_tree_recursive(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 7b6da2a..bc91656 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-blob.h"
#include "html.h"
@@ -19,7 +21,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;
@@ -52,11 +54,13 @@ int cgit_ref_path_exists(const char *path, const char *ref, int file_only)
.file_only = file_only
};
- if (get_oid(ref, &oid))
+ if (repo_get_oid(the_repository, ref, &oid))
goto done;
- if (oid_object_info(the_repository, &oid, &size) != OBJ_COMMIT)
+ if (odb_read_object_info(the_repository->objects, &oid, &size) != OBJ_COMMIT)
goto done;
- read_tree_recursive(lookup_commit_reference(&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);
@@ -85,19 +89,20 @@ int cgit_print_file(char *path, const char *head, int file_only)
.file_only = file_only
};
- if (get_oid(head, &oid))
+ if (repo_get_oid(the_repository, head, &oid))
return -1;
- type = oid_object_info(the_repository, &oid, &size);
+ type = odb_read_object_info(the_repository->objects, &oid, &size);
if (type == OBJ_COMMIT) {
- commit = lookup_commit_reference(&oid);
- read_tree_recursive(commit->maybe_tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx);
+ commit = lookup_commit_reference(the_repository, &oid);
+ 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);
+ type = odb_read_object_info(the_repository->objects, &oid, &size);
}
if (type == OBJ_BAD)
return -1;
- buf = read_object_file(&oid, &type, &size);
+ buf = odb_read_object(the_repository->objects, &oid, &type, &size);
if (!buf)
return -1;
buf[size] = '\0';
@@ -135,19 +140,20 @@ void cgit_print_blob(const char *hex, char *path, const char *head, int file_onl
return;
}
} else {
- if (get_oid(head, &oid)) {
+ if (repo_get_oid(the_repository, head, &oid)) {
cgit_print_error_page(404, "Not found",
"Bad ref: %s", head);
return;
}
}
- type = oid_object_info(the_repository, &oid, &size);
+ type = odb_read_object_info(the_repository->objects, &oid, &size);
if ((!hex) && type == OBJ_COMMIT && path) {
- commit = lookup_commit_reference(&oid);
- read_tree_recursive(commit->maybe_tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx);
- type = oid_object_info(the_repository, &oid, &size);
+ commit = lookup_commit_reference(the_repository, &oid);
+ read_tree(the_repository, repo_get_commit_tree(the_repository, commit),
+ &paths, walk_tree, &walk_tree_ctx);
+ type = odb_read_object_info(the_repository->objects, &oid, &size);
}
if (type == OBJ_BAD) {
@@ -156,7 +162,7 @@ void cgit_print_blob(const char *hex, char *path, const char *head, int file_onl
return;
}
- buf = read_object_file(&oid, &type, &size);
+ buf = odb_read_object(the_repository->objects, &oid, &type, &size);
if (!buf) {
cgit_print_error_page(500, "Internal server error",
"Error reading object %s", hex);
diff --git a/ui-ci.c b/ui-ci.c
new file mode 100644
index 0000000..f2561a2
--- /dev/null
+++ b/ui-ci.c
@@ -0,0 +1,152 @@
+/* ui-ci.c: redirect to an external CI system
+ *
+ * Copyright (C) 2026 Saya Andy <saya.andy@posteo.com>
+ *
+ * Licensed under GNU General Public License v2
+ * (see COPYING for full license text)
+ */
+
+#define USE_THE_REPOSITORY_VARIABLE
+
+#include "cgit.h"
+#include "ui-ci.h"
+#include "html.h"
+#include "ui-shared.h"
+
+/* Memoized outcome of resolving the ci url for this request: -1 while
+ * unresolved, 0 when there is no ci url for the current ref, and 1 when
+ * ci_url holds the url to redirect to.
+ */
+static int ci_resolved = -1;
+static char *ci_url;
+
+static int ref_is_tag(const char *ref)
+{
+ struct strbuf fullref = STRBUF_INIT;
+ struct object_id oid;
+ int is_tag;
+
+ strbuf_addf(&fullref, "refs/tags/%s", ref);
+ is_tag = !repo_get_oid(the_repository, fullref.buf, &oid);
+ strbuf_release(&fullref);
+ return is_tag;
+}
+
+/* Append the repository url with any ".git" suffix removed and all
+ * slashes replaced by dashes, e.g. "sayauz/web.git" becomes
+ * "sayauz-web".
+ */
+static void add_repo_slug(struct strbuf *buf, const char *url)
+{
+ size_t len, i;
+
+ if (!strip_suffix(url, ".git", &len))
+ len = strlen(url);
+ for (i = 0; i < len; i++)
+ strbuf_addch(buf, url[i] == '/' ? '-' : url[i]);
+}
+
+static char *expand_ci_url(const char *template)
+{
+ const char *p, *rest;
+ struct strbuf buf = STRBUF_INIT;
+
+ for (p = template; *p; p++) {
+ if (*p != '$') {
+ strbuf_addch(&buf, *p);
+ continue;
+ }
+ if (skip_prefix(p, "$ref", &rest))
+ strbuf_addstr(&buf, ctx.qry.head);
+ else if (skip_prefix(p, "$repo", &rest))
+ strbuf_addstr(&buf, ctx.repo->url);
+ else if (skip_prefix(p, "$slug", &rest))
+ add_repo_slug(&buf, ctx.repo->url);
+ else if (skip_prefix(p, "$$", &rest))
+ strbuf_addch(&buf, '$');
+ else {
+ strbuf_addch(&buf, '$');
+ continue;
+ }
+ p = rest - 1;
+ }
+ return strbuf_detach(&buf, NULL);
+}
+
+/* Ask the ci filter whether the pipeline denoted by url actually exists.
+ * The filter must not write to stdout, and signals "yes" by exiting with
+ * a zero status.
+ */
+static int ci_filter_accepts(const char *refkind, const char *url)
+{
+ struct cgit_filter *filter = ctx.repo->ci_filter;
+
+ if (!filter)
+ return 1;
+ if (cgit_open_filter(filter, ctx.qry.head, refkind, url))
+ return 0;
+ return !cgit_close_filter(filter);
+}
+
+static void resolve_ci_url(void)
+{
+ const char *template, *refkind;
+
+ if (ci_resolved != -1)
+ return;
+ ci_resolved = 0;
+
+ if (!ctx.repo || !ctx.qry.head || !cgit_have_repository())
+ return;
+
+ if (ref_is_tag(ctx.qry.head)) {
+ refkind = "tag";
+ template = ctx.repo->ci_tag_url;
+ } else {
+ refkind = "branch";
+ template = ctx.repo->ci_branch_url;
+ }
+ if (!template)
+ template = ctx.repo->ci_url;
+ if (!template)
+ return;
+
+ ci_url = expand_ci_url(template);
+ if (!ci_filter_accepts(refkind, ci_url)) {
+ free(ci_url);
+ ci_url = NULL;
+ return;
+ }
+ ci_resolved = 1;
+}
+
+int cgit_ci_available(void)
+{
+ resolve_ci_url();
+ return ci_resolved == 1;
+}
+
+void cgit_print_ci(void)
+{
+ resolve_ci_url();
+
+ if (ci_resolved != 1) {
+ cgit_print_error_page(404, "Not found",
+ "No ci url available for %s",
+ ctx.qry.head);
+ return;
+ }
+
+ /* The expanded template is emitted verbatim rather than through
+ * cgit_redirect(), which percent-encodes characters such as '?',
+ * '=' and '%' that are meaningful in a ci url. Refuse anything
+ * which could be used to smuggle in extra response headers.
+ */
+ if (strpbrk(ci_url, "\r\n")) {
+ cgit_print_error_page(500, "Internal server error",
+ "Malformed ci url");
+ return;
+ }
+
+ htmlf("Status: 302 Found\nLocation: %s\n\n", ci_url);
+}
diff --git a/ui-ci.h b/ui-ci.h
new file mode 100644
index 0000000..3d7ce02
--- /dev/null
+++ b/ui-ci.h
@@ -0,0 +1,7 @@
+#ifndef UI_CI_H
+#define UI_CI_H
+
+extern int cgit_ci_available(void);
+extern void cgit_print_ci(void);
+
+#endif /* UI_CI_H */
diff --git a/ui-clone.c b/ui-clone.c
index 6ba8f36..e11f496 100644
--- a/ui-clone.c
+++ b/ui-clone.c
@@ -7,47 +7,52 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-clone.h"
#include "html.h"
#include "ui-shared.h"
#include "packfile.h"
-#include "object-store.h"
-static int print_ref_info(const char *refname, const struct object_id *oid,
- int flags, void *cb_data)
+static int print_ref_info(const struct reference *ref, void *cb_data)
{
struct object *obj;
- if (!(obj = parse_object(oid)))
+ if (!(obj = parse_object(the_repository, ref->oid)))
return 0;
- htmlf("%s\t%s\n", oid_to_hex(oid), refname);
+ htmlf("%s\t%s\n", oid_to_hex(ref->oid), ref->name);
if (obj->type == OBJ_TAG) {
- if (!(obj = deref_tag(obj, refname, 0)))
+ if (!(obj = deref_tag(the_repository, obj, ref->name, 0)))
return 0;
- htmlf("%s\t%s^{}\n", oid_to_hex(&obj->oid), refname);
+ htmlf("%s\t%s^{}\n", oid_to_hex(&obj->oid), ref->name);
}
return 0;
}
static void print_pack_info(void)
{
- struct packed_git *pack;
+ struct odb_source *source;
char *offset;
ctx.page.mimetype = "text/plain";
ctx.page.filename = "objects/info/packs";
cgit_print_http_headers();
- reprepare_packed_git(the_repository);
- for (pack = get_packed_git(the_repository); pack; pack = pack->next) {
- if (pack->pack_local) {
- offset = strrchr(pack->pack_name, '/');
- if (offset && offset[1] != '\0')
- ++offset;
- else
- offset = pack->pack_name;
- htmlf("P %s\n", offset);
+ odb_reprepare(the_repository->objects);
+ for (source = the_repository->objects->sources; source; source = source->next) {
+ struct odb_source_files *files = odb_source_files_downcast(source);
+ struct packfile_list_entry *e;
+ for (e = files->packed->packs.head; e; e = e->next) {
+ struct packed_git *p = e->pack;
+ if (p->pack_local) {
+ offset = strrchr(p->pack_name, '/');
+ if (offset && offset[1] != '\0')
+ ++offset;
+ else
+ offset = p->pack_name;
+ htmlf("P %s\n", offset);
+ }
}
}
}
@@ -87,12 +92,13 @@ void cgit_clone_info(void)
ctx.page.mimetype = "text/plain";
ctx.page.filename = "info/refs";
cgit_print_http_headers();
- for_each_ref(print_ref_info, NULL);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ print_ref_info, NULL);
}
void cgit_clone_objects(void)
{
- char *p;
+ char *p, *path;
if (!ctx.qry.path)
goto err;
@@ -113,7 +119,9 @@ void cgit_clone_objects(void)
goto err;
}
- send_file(git_path("objects/%s", ctx.qry.path));
+ path = repo_git_path(the_repository, "objects/%s", ctx.qry.path);
+ send_file(path);
+ free(path);
return;
err:
@@ -122,5 +130,9 @@ err:
void cgit_clone_head(void)
{
- send_file(git_path("%s", "HEAD"));
+ char *path;
+
+ path = repo_git_path(the_repository, "HEAD");
+ send_file(path);
+ free(path);
}
diff --git a/ui-commit.c b/ui-commit.c
index 995cb93..972e9bc 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-commit.h"
#include "html.h"
@@ -26,12 +28,12 @@ void cgit_print_commit(char *hex, const char *prefix)
if (!hex)
hex = ctx.qry.head;
- if (get_oid(hex, &oid)) {
+ if (repo_get_oid(the_repository, hex, &oid)) {
cgit_print_error_page(400, "Bad request",
"Bad object id: %s", hex);
return;
}
- commit = lookup_commit_reference(&oid);
+ commit = lookup_commit_reference(the_repository, &oid);
if (!commit) {
cgit_print_error_page(404, "Not found",
"Bad commit reference: %s", hex);
@@ -39,10 +41,11 @@ void cgit_print_commit(char *hex, const char *prefix)
}
info = cgit_parse_commit(commit);
- format_display_notes(&oid, &notes, PAGE_ENCODING, 0);
+ format_display_notes(&oid, &notes, 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 +73,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(" /");
@@ -87,7 +90,7 @@ void cgit_print_commit(char *hex, const char *prefix)
free(tmp);
html("</td></tr>\n");
for (p = commit->parents; p; p = p->next) {
- parent = lookup_commit_reference(&p->item->object.oid);
+ parent = lookup_commit_reference(the_repository, &p->item->object.oid);
if (!parent) {
html("<tr><td colspan='3'>");
cgit_print_error("Error reading parent commit");
@@ -95,7 +98,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 +112,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>");
}
@@ -139,7 +142,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(&notes);
cgit_free_commitinfo(info);
diff --git a/ui-diff.c b/ui-diff.c
index e33e9fb..5ee3830 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,11 +1,17 @@
/* ui-diff.c: show diff between two blobs
*
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ * Copyright (C) 2026 Saya Andy <saya.andy@posteo.com>
+ *
+ * Modified 2026 by Saya Andy:
+ * Select variable refs from web ui for diff
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-diff.h"
#include "html.h"
@@ -82,7 +88,7 @@ static void print_fileinfo(struct fileinfo *info)
}
html("<tr>");
- htmlf("<td class='mode'>");
+ html("<td class='mode'>");
if (is_null_oid(info->new_oid)) {
cgit_print_filemode(info->old_mode);
} else {
@@ -97,8 +103,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 +200,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);
@@ -258,8 +264,8 @@ static void header(const struct object_id *oid1, char *path1, int mode1,
htmlf("<br/>deleted file mode %.6o", mode1);
if (!subproject) {
- abbrev1 = xstrdup(find_unique_abbrev(oid1, DEFAULT_ABBREV));
- abbrev2 = xstrdup(find_unique_abbrev(oid2, DEFAULT_ABBREV));
+ abbrev1 = xstrdup(repo_find_unique_abbrev(the_repository, oid1, DEFAULT_ABBREV));
+ abbrev2 = xstrdup(repo_find_unique_abbrev(the_repository, oid2, DEFAULT_ABBREV));
htmlf("<br/>index %s..%s", abbrev1, abbrev2);
free(abbrev1);
free(abbrev2);
@@ -334,16 +340,84 @@ static void filepair_cb(struct diff_filepair *pair)
cgit_ssdiff_footer();
}
+static int print_ref_option(const struct reference *ref, void *cb_data)
+{
+ const char **sel = (const char **)cb_data;
+
+ html_option(ref->name, ref->name, sel[0]);
+ return 0;
+}
+
+static void cgit_print_ref_select(const char *name, const char *selected)
+{
+ /*
+ * Offer every branch and tag so that any two refs can be compared.
+ */
+ html("<select name='");
+ html_attr(name);
+ html("' onchange='this.form.submit();'>");
+ refs_for_each_branch_ref(get_main_ref_store(the_repository),
+ print_ref_option, (void *)&selected);
+ if (ctx.repo->enable_remote_branches)
+ refs_for_each_remote_ref(get_main_ref_store(the_repository),
+ print_ref_option, (void *)&selected);
+ refs_for_each_tag_ref(get_main_ref_store(the_repository),
+ print_ref_option, (void *)&selected);
+ html("</select>");
+}
+
void cgit_print_diff_ctrls(void)
{
int i, curr;
+ html("<form class='diff-ctrls' method='get'>");
+ /*
+ * Emit the same hidden fields as cgit_add_hidden_formfields(), except
+ * id/id2, which are carried by the ref-picker selects below. One
+ * form wraps both panels so any submission carries all controls.
+ */
+ if (!ctx.cfg.virtual_root) {
+ struct strbuf url = STRBUF_INIT;
+ strbuf_addf(&url, "%s/%s", ctx.qry.repo, ctx.qry.page);
+ if (ctx.qry.vpath)
+ strbuf_addf(&url, "/%s", ctx.qry.vpath);
+ html_hidden("url", url.buf);
+ strbuf_release(&url);
+ }
+ if (ctx.qry.head && ctx.repo->defbranch &&
+ strcmp(ctx.qry.head, ctx.repo->defbranch))
+ html_hidden("h", ctx.qry.head);
+ if (ctx.qry.showmsg)
+ html_hidden("showmsg", "1");
+
+ html("<div class='cgit-panel'>");
+ html("<b>diff refs</b>");
+ html("<table>");
+ html("<tr><td colspan='2'/></tr>");
+
+ /* ref pickers: compare any two refs (branches/tags) */
+ {
+ const char *new_ref = ctx.qry.oid ? ctx.qry.oid : ctx.qry.head;
+ const char *old_ref = ctx.qry.oid2;
+
+ html("<tr>");
+ html("<td class='label'>from:</td>");
+ html("<td class='ctrl'>");
+ cgit_print_ref_select("id2", old_ref);
+ html("</td></tr><tr>");
+ html("<td class='label'>to:</td>");
+ html("<td class='ctrl'>");
+ cgit_print_ref_select("id", new_ref);
+ html("</td></tr>");
+ }
+ html("</table>");
+ html("</div>");
+
html("<div class='cgit-panel'>");
html("<b>diff options</b>");
- html("<form method='get'>");
- cgit_add_hidden_formfields(1, 0, ctx.qry.page);
html("<table>");
html("<tr><td colspan='2'/></tr>");
+
html("<tr>");
html("<td class='label'>context:</td>");
html("<td class='ctrl'>");
@@ -377,8 +451,8 @@ void cgit_print_diff_ctrls(void)
html("<tr><td/><td class='ctrl'>");
html("<noscript><input type='submit' value='reload'/></noscript>");
html("</td></tr></table>");
- html("</form>");
html("</div>");
+ html("</form>");
}
void cgit_print_diff(const char *new_rev, const char *old_rev,
@@ -393,7 +467,7 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,
* entire commit to detect renames so we must limit the paths in our
* own callbacks and not pass the prefix to the diff machinery.
*/
- if (ctx.qry.follow && ctx.cfg.enable_follow_links) {
+ if (ctx.qry.follow && ctx.repo->enable_follow_links) {
current_prefix = prefix;
prefix = "";
} else {
@@ -402,21 +476,21 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,
if (!new_rev)
new_rev = ctx.qry.head;
- if (get_oid(new_rev, new_rev_oid)) {
+ if (repo_get_oid(the_repository, new_rev, new_rev_oid)) {
cgit_print_error_page(404, "Not found",
"Bad object name: %s", new_rev);
return;
}
- commit = lookup_commit_reference(new_rev_oid);
- if (!commit || parse_commit(commit)) {
+ commit = lookup_commit_reference(the_repository, new_rev_oid);
+ if (!commit || repo_parse_commit(the_repository, commit)) {
cgit_print_error_page(404, "Not found",
"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)) {
+ if (repo_get_oid(the_repository, old_rev, old_rev_oid)) {
cgit_print_error_page(404, "Not found",
"Bad object name: %s", old_rev);
return;
@@ -424,17 +498,17 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,
} else if (commit->parents && commit->parents->item) {
oidcpy(old_rev_oid, &commit->parents->item->object.oid);
} else {
- oidclr(old_rev_oid);
+ oidclr(old_rev_oid, the_repository->hash_algo);
}
if (!is_null_oid(old_rev_oid)) {
- commit2 = lookup_commit_reference(old_rev_oid);
- if (!commit2 || parse_commit(commit2)) {
+ commit2 = lookup_commit_reference(the_repository, old_rev_oid);
+ if (!commit2 || repo_parse_commit(the_repository, commit2)) {
cgit_print_error_page(404, "Not found",
"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;
}
@@ -442,7 +516,7 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,
if (raw) {
struct diff_options diffopt;
- diff_setup(&diffopt);
+ repo_diff_setup(the_repository, &diffopt);
diffopt.output_format = DIFF_FORMAT_PATCH;
diffopt.flags.recursive = 1;
diff_setup_done(&diffopt);
diff --git a/ui-log.c b/ui-log.c
index 3bcb657..9906bbc 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -6,11 +6,13 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#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 +67,9 @@ void show_commit_decorations(struct commit *commit)
return;
html("<span class='decoration'>");
while (deco) {
- struct object_id peeled;
+ struct object_id oid_tag, peeled;
int is_annotated = 0;
+
strlcpy(buf, prettify_refname(deco->name), sizeof(buf));
switch(deco->type) {
case DECORATION_NONE:
@@ -79,8 +82,9 @@ void show_commit_decorations(struct commit *commit)
ctx.qry.showmsg, 0);
break;
case DECORATION_REF_TAG:
- if (!peel_ref(deco->name, &peeled))
- is_annotated = !oidcmp(&commit->object.oid, &peeled);
+ if (!refs_read_ref(get_main_ref_store(the_repository), deco->name, &oid_tag) &&
+ !peel_object(the_repository, &oid_tag, &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE))
+ is_annotated = !oideq(&oid_tag, &peeled);
cgit_tag_link(buf, NULL, is_annotated ? "tag-annotated-deco" : "tag-deco", buf);
break;
case DECORATION_REF_REMOTE:
@@ -145,7 +149,7 @@ static int show_commit(struct commit *commit, struct rev_info *revs)
/* When we get here we have precisely one parent. */
parent = parents->item;
/* If we can't parse the commit, let print_commit() report an error. */
- if (parse_commit(parent))
+ if (repo_parse_commit(the_repository, parent))
return 1;
files = 0;
@@ -153,16 +157,17 @@ 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;
revs->diffopt.format_callback_data = handle_rename;
+ revs->diffopt.no_free = 1;
diff_flush(&revs->diffopt);
revs->diffopt.output_format = saved_fmt;
revs->diffopt.flags = saved_flags;
@@ -329,7 +334,7 @@ static const char *disambiguate_ref(const char *ref, int *must_free_result)
struct strbuf longref = STRBUF_INIT;
strbuf_addf(&longref, "refs/heads/%s", ref);
- if (get_oid(longref.buf, &oid) == 0) {
+ if (repo_get_oid(the_repository, longref.buf, &oid) == 0) {
*must_free_result = 1;
return strbuf_detach(&longref, NULL);
}
@@ -362,27 +367,27 @@ static char *next_token(char **src)
}
void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
- char *path, int pager, int commit_graph, int commit_sort)
+ const char *path, int pager, int commit_graph, int commit_sort)
{
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,19 +395,19 @@ 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);
}
}
}
- if (!path || !ctx.cfg.enable_follow_links) {
+ if (!path || !ctx.repo->enable_follow_links) {
/*
* If we don't have a path, "follow" is a no-op so make sure
* the variable is set to false to avoid needing to check
@@ -412,31 +417,31 @@ 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);
+ repo_init_revisions(the_repository, &rev, NULL);
rev.abbrev = DEFAULT_ABBREV;
rev.commit_format = CMIT_FMT_DEFAULT;
rev.verbose_header = 1;
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(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(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-log.h b/ui-log.h
index d324c92..325607c 100644
--- a/ui-log.h
+++ b/ui-log.h
@@ -2,7 +2,7 @@
#define UI_LOG_H
extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep,
- char *pattern, char *path, int pager,
+ char *pattern, const char *path, int pager,
int commit_graph, int commit_sort);
extern void show_commit_decorations(struct commit *commit);
diff --git a/ui-patch.c b/ui-patch.c
index 82f125b..f9d2eeb 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-patch.h"
#include "html.h"
@@ -31,12 +33,12 @@ void cgit_print_patch(const char *new_rev, const char *old_rev,
if (!new_rev)
new_rev = ctx.qry.head;
- if (get_oid(new_rev, &new_rev_oid)) {
+ if (repo_get_oid(the_repository, new_rev, &new_rev_oid)) {
cgit_print_error_page(404, "Not found",
"Bad object id: %s", new_rev);
return;
}
- commit = lookup_commit_reference(&new_rev_oid);
+ commit = lookup_commit_reference(the_repository, &new_rev_oid);
if (!commit) {
cgit_print_error_page(404, "Not found",
"Bad commit reference: %s", new_rev);
@@ -44,12 +46,12 @@ void cgit_print_patch(const char *new_rev, const char *old_rev,
}
if (old_rev) {
- if (get_oid(old_rev, &old_rev_oid)) {
+ if (repo_get_oid(the_repository, old_rev, &old_rev_oid)) {
cgit_print_error_page(404, "Not found",
"Bad object id: %s", old_rev);
return;
}
- if (!lookup_commit_reference(&old_rev_oid)) {
+ if (!lookup_commit_reference(the_repository, &old_rev_oid)) {
cgit_print_error_page(404, "Not found",
"Bad commit reference: %s", old_rev);
return;
@@ -57,11 +59,11 @@ void cgit_print_patch(const char *new_rev, const char *old_rev,
} else if (commit->parents && commit->parents->item) {
oidcpy(&old_rev_oid, &commit->parents->item->object.oid);
} else {
- oidclr(&old_rev_oid);
+ oidclr(&old_rev_oid, the_repository->hash_algo);
}
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));
@@ -78,7 +80,7 @@ void cgit_print_patch(const char *new_rev, const char *old_rev,
"%s%n%n%w(0)%b";
}
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
rev.abbrev = DEFAULT_ABBREV;
rev.verbose_header = 1;
rev.diff = 1;
diff --git a/ui-plain.c b/ui-plain.c
index ddb3e48..a2a4087 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-plain.h"
#include "html.h"
@@ -22,13 +24,13 @@ static int print_object(const struct object_id *oid, const char *path)
char *buf, *mimetype;
unsigned long size;
- type = oid_object_info(the_repository, oid, &size);
+ type = odb_read_object_info(the_repository->objects, oid, &size);
if (type == OBJ_BAD) {
cgit_print_error_page(404, "Not found", "Not found");
return 0;
}
- buf = read_object_file(oid, &type, &size);
+ buf = odb_read_object(the_repository->objects, oid, &type, &size);
if (!buf) {
cgit_print_error_page(404, "Not found", "Not found");
return 0;
@@ -99,7 +101,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 +120,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 +132,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;
@@ -155,7 +157,7 @@ static int walk_tree(const struct object_id *oid, struct strbuf *base,
static int basedir_len(const char *path)
{
- char *p = strrchr(path, '/');
+ const char *p = strrchr(path, '/');
if (p)
return p - path + 1;
return 0;
@@ -163,7 +165,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 = {
@@ -181,24 +183,25 @@ void cgit_print_plain(void)
if (!rev)
rev = ctx.qry.head;
- if (get_oid(rev, &oid)) {
+ if (repo_get_oid(the_repository, rev, &oid)) {
cgit_print_error_page(404, "Not found", "Not found");
return;
}
- commit = lookup_commit_reference(&oid);
- if (!commit || parse_commit(commit)) {
+ commit = lookup_commit_reference(the_repository, &oid);
+ if (!commit || repo_parse_commit(the_repository, commit)) {
cgit_print_error_page(404, "Not found", "Not found");
return;
}
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(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-refs.c b/ui-refs.c
index 2ec3858..11fb9fc 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-refs.h"
#include "html.h"
@@ -136,7 +138,7 @@ static int print_tag(struct refinfo *ref)
return 0;
}
-static void print_refs_link(char *path)
+static void print_refs_link(const char *path)
{
html("<tr class='nohover'><td colspan='5'>");
cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
@@ -155,9 +157,11 @@ void cgit_print_branches(int maxcount)
list.refs = NULL;
list.alloc = list.count = 0;
- for_each_branch_ref(cgit_refs_cb, &list);
+ refs_for_each_branch_ref(get_main_ref_store(the_repository),
+ cgit_refs_cb, &list);
if (ctx.repo->enable_remote_branches)
- for_each_remote_ref(cgit_refs_cb, &list);
+ refs_for_each_remote_ref(get_main_ref_store(the_repository),
+ cgit_refs_cb, &list);
if (maxcount == 0 || maxcount > list.count)
maxcount = list.count;
@@ -182,7 +186,8 @@ void cgit_print_tags(int maxcount)
list.refs = NULL;
list.alloc = list.count = 0;
- for_each_tag_ref(cgit_refs_cb, &list);
+ refs_for_each_tag_ref(get_main_ref_store(the_repository),
+ cgit_refs_cb, &list);
if (list.count == 0)
return;
qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
diff --git a/ui-repolist.c b/ui-repolist.c
index 41424c0..1b224cf 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -11,16 +11,16 @@
#include "html.h"
#include "ui-shared.h"
-static time_t read_agefile(char *path)
+static time_t read_agefile(const char *path)
{
time_t result;
size_t size;
char *buf = NULL;
struct strbuf date_buf = STRBUF_INIT;
- if (readfile(path, &buf, &size)) {
+ if (read_first_line(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);
diff --git a/ui-shared.c b/ui-shared.c
index b53c56d..686fde0 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,15 +1,26 @@
/* ui-shared.c: common web output functions
*
* Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
+ * Copyright (C) 2026 Saya Andy <saya.andy@posteo.com>
+ *
+ * Modified 2026 by Saya Andy:
+ * Implement ci link tab
+ * Check repo existence
+ * Replace memrchr() for macOS compatibility
+ * Allow multiple favicons of different mime types to be used
+ * Include favicon.svg as default
*
* Licensed under GNU General Public License v2
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-shared.h"
#include "cmd.h"
#include "html.h"
+#include "ui-ci.h"
#include "version.h"
static const char cgit_doctype[] =
@@ -22,10 +33,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, ...)
@@ -68,15 +80,48 @@ char *cgit_hosturl(void)
char *cgit_currenturl(void)
{
const char *root = cgit_rooturl();
- size_t len = strlen(root);
if (!ctx.qry.url)
return xstrdup(root);
- if (len && root[len - 1] == '/')
+ if (root[0] && root[strlen(root) - 1] == '/')
return fmtalloc("%s%s", root, ctx.qry.url);
return fmtalloc("%s/%s", root, ctx.qry.url);
}
+char *cgit_currentfullurl(void)
+{
+ const char *root = cgit_rooturl();
+ const char *orig_query = ctx.env.query_string ? ctx.env.query_string : "";
+ size_t len = strlen(orig_query);
+ char *query = xmalloc(len + 2), *start_url, *ret;
+
+ /* Remove all url=... parts from query string */
+ memcpy(query + 1, orig_query, len + 1);
+ query[0] = '?';
+ start_url = query;
+ while ((start_url = strstr(start_url, "url=")) != NULL) {
+ if (start_url[-1] == '?' || start_url[-1] == '&') {
+ const char *end_url = strchr(start_url, '&');
+ if (end_url)
+ memmove(start_url, end_url + 1, strlen(end_url));
+ else
+ start_url[0] = '\0';
+ } else
+ ++start_url;
+ }
+ if (!query[1])
+ query[0] = '\0';
+
+ if (!ctx.qry.url)
+ ret = fmtalloc("%s%s", root, query);
+ else if (root[0] && root[strlen(root) - 1] == '/')
+ ret = fmtalloc("%s%s%s", root, ctx.qry.url, query);
+ else
+ ret = fmtalloc("%s/%s%s", root, ctx.qry.url, query);
+ free(query);
+ return ret;
+}
+
const char *cgit_rooturl(void)
{
if (ctx.cfg.virtual_root)
@@ -306,6 +351,12 @@ void cgit_tag_link(const char *name, const char *title, const char *class,
reporevlink("tag", name, title, class, tag, NULL, NULL);
}
+void cgit_ci_link(const char *name, const char *title, const char *class,
+ const char *head)
+{
+ reporevlink("ci", name, title, class, head, NULL, NULL);
+}
+
void cgit_tree_link(const char *name, const char *title, const char *class,
const char *head, const char *rev, const char *path)
{
@@ -488,45 +539,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,
@@ -628,18 +679,18 @@ void cgit_submodule_link(const char *class, char *path, const char *rev)
path[len - 1] = tail;
}
-const struct date_mode *cgit_date_mode(enum date_mode_type type)
+const struct date_mode cgit_date_mode(enum date_mode_type type)
{
static struct date_mode mode;
mode.type = type;
mode.local = ctx.cfg.local_time;
- return &mode;
+ return mode;
}
static void print_rel_date(time_t t, int tz, double value,
const char *class, const char *suffix)
{
- htmlf("<span class='%s' title='", class);
+ htmlf("<span class='%s' data-ut='%" PRIu64 "' title='", class, (uint64_t)t);
html_attr(show_date(t, tz, cgit_date_mode(DATE_ISO8601)));
htmlf("'>%.0f %s</span>", value, suffix);
}
@@ -725,6 +776,51 @@ void cgit_redirect(const char *url, bool permanent)
html("\n\n");
}
+static const struct {
+ const char *ext;
+ const char *mimetype;
+} favicon_mimetypes[] = {
+ { "ico", "image/x-icon" },
+ { "png", "image/png" },
+ { "svg", "image/svg+xml" },
+ { "gif", "image/gif" },
+ { "jpg", "image/jpeg" },
+ { "jpeg", "image/jpeg" },
+ { "webp", "image/webp" },
+ { "avif", "image/avif" },
+};
+
+/* Deduce the mimetype of a favicon from the extension of its url, ignoring
+ * any query string or fragment. Returns NULL for unknown extensions.
+ */
+static const char *favicon_mimetype(const char *url)
+{
+ const char *p, *end, *ext = NULL;
+ size_t len;
+ int i;
+
+ end = strpbrk(url, "?#");
+ if (!end)
+ end = url + strlen(url);
+
+ for (p = url; p < end; p++) {
+ if (*p == '/')
+ ext = NULL;
+ else if (*p == '.')
+ ext = p + 1;
+ }
+ if (!ext)
+ return NULL;
+ len = end - ext;
+
+ for (i = 0; i < ARRAY_SIZE(favicon_mimetypes); i++) {
+ if (strlen(favicon_mimetypes[i].ext) == len &&
+ !strncasecmp(favicon_mimetypes[i].ext, ext, len))
+ return favicon_mimetypes[i].mimetype;
+ }
+ return NULL;
+}
+
static void print_rel_vcs_link(const char *url)
{
html("<link rel='vcs-git' href='");
@@ -734,6 +830,59 @@ static void print_rel_vcs_link(const char *url)
html(" Git repository'/>\n");
}
+static int emit_css_link(struct string_list_item *s, void *arg)
+{
+ /* Do not emit anything if css= is specified. */
+ if (s && *s->string == '\0')
+ return 0;
+
+ html("<link rel='stylesheet' type='text/css' href='");
+ if (s)
+ html_attr(s->string);
+ else
+ html_attr((const char *)arg);
+ html("'/>\n");
+
+ return 0;
+}
+
+static int emit_js_link(struct string_list_item *s, void *arg)
+{
+ /* Do not emit anything if js= is specified. */
+ if (s && *s->string == '\0')
+ return 0;
+
+ html("<script type='text/javascript' src='");
+ if (s)
+ html_attr(s->string);
+ else
+ html_attr((const char *)arg);
+ html("'></script>\n");
+
+ return 0;
+}
+
+static int emit_favicon_link(struct string_list_item *s, void *arg)
+{
+ const char *url = s ? s->string : (const char *)arg;
+ const char *mimetype;
+
+ /* Do not emit anything if favicon= is specified. */
+ if (!*url)
+ return 0;
+
+ html("<link rel='shortcut icon' href='");
+ html_attr(url);
+ mimetype = favicon_mimetype(url);
+ if (mimetype) {
+ html("' type='");
+ html_attr(mimetype);
+ }
+ html("'/>\n");
+
+ return 0;
+}
+
void cgit_print_docstart(void)
{
char *host = cgit_hosturl();
@@ -753,14 +902,23 @@ void cgit_print_docstart(void)
htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
if (ctx.cfg.robots && *ctx.cfg.robots)
htmlf("<meta name='robots' content='%s'/>\n", ctx.cfg.robots);
- html("<link rel='stylesheet' type='text/css' href='");
- html_attr(ctx.cfg.css);
- html("'/>\n");
- if (ctx.cfg.favicon) {
- html("<link rel='shortcut icon' href='");
- html_attr(ctx.cfg.favicon);
- html("'/>\n");
- }
+
+ if (ctx.cfg.css.items)
+ for_each_string_list(&ctx.cfg.css, emit_css_link, NULL);
+ else
+ emit_css_link(NULL, "/cgit.css");
+
+ if (ctx.cfg.js.items)
+ for_each_string_list(&ctx.cfg.js, emit_js_link, NULL);
+ else
+ emit_js_link(NULL, "/cgit.js");
+
+ if (ctx.cfg.favicon.items)
+ for_each_string_list(&ctx.cfg.favicon, emit_favicon_link, NULL);
+ else
+ emit_favicon_link(NULL, "/favicon.svg");
+ emit_favicon_link(NULL, "/favicon.ico");
+
if (host && ctx.repo && ctx.qry.head) {
char *fileurl;
struct strbuf sb = STRBUF_INIT;
@@ -801,7 +959,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://sayag.it/cgitext.git/about/'>cgitext %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");
@@ -813,13 +971,18 @@ void cgit_print_docend(void)
void cgit_print_error_page(int code, const char *msg, const char *fmt, ...)
{
va_list ap;
+ va_start(ap, fmt);
+ cgit_vprint_error_page(code, msg, fmt, ap);
+ va_end(ap);
+}
+
+void cgit_vprint_error_page(int code, const char *msg, const char *fmt, va_list ap)
+{
ctx.page.expires = ctx.cfg.cache_dynamic_ttl;
ctx.page.status = code;
ctx.page.statusmsg = msg;
cgit_print_layout_start();
- va_start(ap, fmt);
cgit_vprint_error(fmt, ap);
- va_end(ap);
cgit_print_layout_end();
}
@@ -860,10 +1023,9 @@ void cgit_add_clone_urls(void (*fn)(const char *))
add_clone_urls(fn, ctx.cfg.clone_prefix, ctx.repo->url);
}
-static int print_branch_option(const char *refname, const struct object_id *oid,
- int flags, void *cb_data)
+static int print_branch_option(const struct reference *ref, void *cb_data)
{
- char *name = (char *)refname;
+ char *name = (char *)ref->name;
html_option(name, name, ctx.qry.head);
return 0;
}
@@ -885,10 +1047,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");
@@ -912,12 +1074,13 @@ static void cgit_print_path_crumbs(char *path)
{
char *old_path = ctx.qry.path;
char *p = path, *q, *end = path + strlen(path);
+ int levels = 0;
ctx.qry.path = NULL;
cgit_self_link("root", NULL, NULL);
ctx.qry.path = p = path;
while (p < end) {
- if (!(q = strchr(p, '/')))
+ if (!(q = strchr(p, '/')) || levels > 15)
q = end;
*q = '\0';
html_txt("/");
@@ -925,6 +1088,7 @@ static void cgit_print_path_crumbs(char *path)
if (q < end)
*q = '/';
p = q + 1;
+ ++levels;
}
ctx.qry.path = old_path;
}
@@ -959,15 +1123,17 @@ 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);
- if (ctx.env.authenticated) {
+ cgit_summary_link(ctx.repo->name, NULL, NULL, NULL);
+ if (ctx.env.authenticated && cgit_have_repository()) {
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");
- for_each_branch_ref(print_branch_option, ctx.qry.head);
+ refs_for_each_branch_ref(get_main_ref_store(the_repository),
+ print_branch_option, ctx.qry.head);
if (ctx.repo->enable_remote_branches)
- for_each_remote_ref(print_branch_option, ctx.qry.head);
+ refs_for_each_remote_ref(get_main_ref_store(the_repository),
+ print_branch_option, ctx.qry.head);
html("</select> ");
html("<input type='submit' value='switch'/>");
html("</form>");
@@ -980,7 +1146,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);
@@ -1003,23 +1175,25 @@ void cgit_print_pageheader(void)
cgit_summary_link("summary", NULL, hc("summary"),
ctx.qry.head);
cgit_refs_link("refs", NULL, hc("refs"), ctx.qry.head,
- ctx.qry.sha1, NULL);
+ ctx.qry.oid, NULL);
cgit_log_link("log", NULL, hc("log"), ctx.qry.head,
NULL, ctx.qry.vpath, 0, NULL, NULL,
ctx.qry.showmsg, ctx.qry.follow);
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);
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);
cgit_diff_link("diff", NULL, hc("diff"), ctx.qry.head,
- ctx.qry.sha1, ctx.qry.sha2, ctx.qry.vpath);
+ ctx.qry.oid, ctx.qry.oid2, ctx.qry.vpath);
if (ctx.repo->max_stats)
cgit_stats_link("stats", NULL, hc("stats"),
ctx.qry.head, ctx.qry.vpath);
+ if (cgit_ci_available())
+ cgit_ci_link("ci", NULL, hc("ci"), ctx.qry.head);
if (ctx.repo->homepage) {
html("<a href='");
html_attr(ctx.repo->homepage);
@@ -1068,7 +1242,7 @@ void cgit_print_pageheader(void)
html("<div class='path'>");
html("path: ");
cgit_print_path_crumbs(ctx.qry.vpath);
- if (ctx.cfg.enable_follow_links && !strcmp(ctx.qry.page, "log")) {
+ if (ctx.repo->enable_follow_links && !strcmp(ctx.qry.page, "log")) {
html(" (");
ctx.qry.follow = !ctx.qry.follow;
cgit_self_link(ctx.qry.follow ? "follow" : "unfollow",
@@ -1106,11 +1280,11 @@ void cgit_compose_snapshot_prefix(struct strbuf *filename, const char *base,
* name starts with {v,V}[0-9] and the prettify mapping is injective,
* i.e. each stripped tag can be inverted without ambiguities.
*/
- if (get_oid(fmt("refs/tags/%s", ref), &oid) == 0 &&
+ if (repo_get_oid(the_repository, fmt("refs/tags/%s", ref), &oid) == 0 &&
(ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]) &&
- ((get_oid(fmt("refs/tags/%s", ref + 1), &oid) == 0) +
- (get_oid(fmt("refs/tags/v%s", ref + 1), &oid) == 0) +
- (get_oid(fmt("refs/tags/V%s", ref + 1), &oid) == 0) == 1))
+ ((repo_get_oid(the_repository, fmt("refs/tags/%s", ref + 1), &oid) == 0) +
+ (repo_get_oid(the_repository, fmt("refs/tags/v%s", ref + 1), &oid) == 0) +
+ (repo_get_oid(the_repository, fmt("refs/tags/V%s", ref + 1), &oid) == 0) == 1))
ref++;
strbuf_addf(filename, "%s-%s", base, ref);
@@ -1157,37 +1331,30 @@ void cgit_print_snapshot_links(const struct cgit_repo *repo, const char *ref,
strbuf_release(&filename);
}
+/* Locate the last '/' within the first len bytes of path, or NULL if
+ * there is none. Avoids memrchr(), which is a GNU extension.
+ */
+static const char *find_last_slash(const char *path, size_t len)
+{
+ while (len > 0)
+ if (path[--len] == '/')
+ return path + len;
+ return NULL;
+}
+
void cgit_set_title_from_path(const char *path)
{
- size_t path_len, path_index, path_last_end, line_len;
- char *new_title;
+ struct strbuf sb = STRBUF_INIT;
+ const char *slash, *last_slash;
if (!path)
return;
- path_len = strlen(path);
- new_title = xmalloc(path_len + 3 + strlen(ctx.page.title) + 1);
- new_title[0] = '\0';
-
- for (path_index = path_len, path_last_end = path_len; path_index-- > 0;) {
- if (path[path_index] == '/') {
- if (path_index == path_len - 1) {
- path_last_end = path_index - 1;
- continue;
- }
- strncat(new_title, &path[path_index + 1], path_last_end - path_index - 1);
- line_len = strlen(new_title);
- new_title[line_len++] = '\\';
- new_title[line_len] = '\0';
- path_last_end = path_index;
- }
+ for (last_slash = path + strlen(path); (slash = find_last_slash(path, last_slash - path)) != NULL; last_slash = slash) {
+ strbuf_add(&sb, slash + 1, last_slash - slash - 1);
+ strbuf_addstr(&sb, " \xc2\xab ");
}
- if (path_last_end)
- strncat(new_title, path, path_last_end);
-
- line_len = strlen(new_title);
- memcpy(&new_title[line_len], " - ", 3);
- new_title[line_len + 3] = '\0';
- strncat(new_title, ctx.page.title, sizeof(new_title) - strlen(new_title) - 1);
- ctx.page.title = new_title;
+ strbuf_add(&sb, path, last_slash - path);
+ strbuf_addf(&sb, " - %s", ctx.page.title);
+ ctx.page.title = strbuf_detach(&sb, NULL);
}
diff --git a/ui-shared.h b/ui-shared.h
index 4d5978b..a6627d4 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -5,6 +5,7 @@ extern const char *cgit_httpscheme(void);
extern char *cgit_hosturl(void);
extern const char *cgit_rooturl(void);
extern char *cgit_currenturl(void);
+extern char *cgit_currentfullurl(void);
extern const char *cgit_loginurl(void);
extern char *cgit_repourl(const char *reponame);
extern char *cgit_fileurl(const char *reponame, const char *pagename,
@@ -20,6 +21,8 @@ extern void cgit_summary_link(const char *name, const char *title,
const char *class, const char *head);
extern void cgit_tag_link(const char *name, const char *title,
const char *class, const char *tag);
+extern void cgit_ci_link(const char *name, const char *title,
+ const char *class, const char *head);
extern void cgit_tree_link(const char *name, const char *title,
const char *class, const char *head,
const char *rev, const char *path);
@@ -64,7 +67,7 @@ __attribute__((format (printf,1,2)))
extern void cgit_print_error(const char *fmt, ...);
__attribute__((format (printf,1,0)))
extern void cgit_vprint_error(const char *fmt, va_list ap);
-extern const struct date_mode *cgit_date_mode(enum date_mode_type type);
+extern const struct date_mode cgit_date_mode(enum date_mode_type type);
extern void cgit_print_age(time_t t, int tz, time_t max_relative);
extern void cgit_print_http_headers(void);
extern void cgit_redirect(const char *url, bool permanent);
@@ -72,6 +75,7 @@ extern void cgit_print_docstart(void);
extern void cgit_print_docend(void);
__attribute__((format (printf,3,4)))
extern void cgit_print_error_page(int code, const char *msg, const char *fmt, ...);
+extern void cgit_vprint_error_page(int code, const char *msg, const char *fmt, va_list ap);
extern void cgit_print_pageheader(void);
extern void cgit_print_filemode(unsigned short mode);
extern void cgit_compose_snapshot_prefix(struct strbuf *filename,
diff --git a/ui-snapshot.c b/ui-snapshot.c
index fa3ceaf..d157222 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-snapshot.h"
#include "html.h"
@@ -13,32 +15,32 @@
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, NULL, 0);
- argv_array_clear(&argv);
+ result = write_archive(argv.nr, nargv, NULL, the_repository, NULL, 0);
+ strvec_clear(&argv);
free(nargv);
return result;
}
@@ -79,18 +81,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 }
};
@@ -103,7 +119,7 @@ const struct object_id *cgit_snapshot_get_sig(const char *ref,
struct notes_tree *tree;
struct object_id oid;
- if (get_oid(ref, &oid))
+ if (repo_get_oid(the_repository, ref, &oid))
return NULL;
tree = &snapshot_sig_notes[f - &cgit_snapshot_formats[0]];
@@ -142,12 +158,12 @@ static int make_snapshot(const struct cgit_snapshot_format *format,
{
struct object_id oid;
- if (get_oid(hex, &oid)) {
+ if (repo_get_oid(the_repository, hex, &oid)) {
cgit_print_error_page(404, "Not found",
"Bad object id: %s", hex);
return 1;
}
- if (!lookup_commit_reference(&oid)) {
+ if (!lookup_commit_reference(the_repository, &oid)) {
cgit_print_error_page(400, "Bad request",
"Not a commit reference: %s", hex);
return 1;
@@ -156,6 +172,7 @@ static int make_snapshot(const struct cgit_snapshot_format *format,
ctx.page.mimetype = xstrdup(format->mimetype);
ctx.page.filename = xstrdup(filename);
cgit_print_http_headers();
+ init_archivers();
format->write_func(hex, prefix);
return 0;
}
@@ -175,7 +192,7 @@ static int write_sig(const struct cgit_snapshot_format *format,
return 0;
}
- buf = read_object_file(note, &type, &size);
+ buf = odb_read_object(the_repository->objects, note, &type, &size);
if (!buf) {
cgit_print_error_page(404, "Not found", "Not found");
return 0;
@@ -215,7 +232,7 @@ static const char *get_ref_from_filename(const struct cgit_repo *repo,
strbuf_addstr(&snapshot, filename);
strbuf_setlen(&snapshot, snapshot.len - strlen(format->suffix));
- if (get_oid(snapshot.buf, &oid) == 0)
+ if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0)
goto out;
reponame = cgit_snapshot_prefix(repo);
@@ -227,15 +244,15 @@ static const char *get_ref_from_filename(const struct cgit_repo *repo,
strbuf_splice(&snapshot, 0, new_start - snapshot.buf, "", 0);
}
- if (get_oid(snapshot.buf, &oid) == 0)
+ if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0)
goto out;
strbuf_insert(&snapshot, 0, "v", 1);
- if (get_oid(snapshot.buf, &oid) == 0)
+ if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0)
goto out;
strbuf_splice(&snapshot, 0, 1, "V", 1);
- if (get_oid(snapshot.buf, &oid) == 0)
+ if (repo_get_oid(the_repository, snapshot.buf, &oid) == 0)
goto out;
result = 0;
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
index 68c2044..af8bc9e 100644
--- a/ui-ssdiff.c
+++ b/ui-ssdiff.c
@@ -103,8 +103,7 @@ static int line_from_hunk(char *line, char type)
return 0;
len = buf2 - buf1;
buf2 = xmalloc(len + 1);
- strncpy(buf2, buf1, len);
- buf2[len] = '\0';
+ strlcpy(buf2, buf1, len + 1);
res = atoi(buf2);
free(buf2);
return res;
@@ -118,6 +117,7 @@ static char *replace_tabs(char *line)
int n_tabs = 0;
int i;
char *result;
+ size_t result_len;
if (linelen == 0) {
result = xmalloc(1);
@@ -129,16 +129,19 @@ static char *replace_tabs(char *line)
if (line[i] == '\t')
n_tabs += 1;
}
- result = xmalloc(linelen + n_tabs * 8 + 1);
+ result_len = linelen + n_tabs * 8;
+ result = xmalloc(result_len + 1);
result[0] = '\0';
for (;;) {
cur_buf = strchr(prev_buf, '\t');
if (!cur_buf) {
- strcat(result, prev_buf);
+ linelen = strlen(result);
+ strlcpy(&result[linelen], prev_buf, result_len - linelen + 1);
break;
} else {
- strncat(result, prev_buf, cur_buf - prev_buf);
+ linelen = strlen(result);
+ strlcpy(&result[linelen], prev_buf, cur_buf - prev_buf + 1);
linelen = strlen(result);
memset(&result[linelen], ' ', 8 - (linelen % 8));
result[linelen + 8 - (linelen % 8)] = '\0';
@@ -206,11 +209,13 @@ static void print_part_with_lcs(char *class, char *line, char *lcs)
}
} else if (line[i] == lcs[j]) {
same = 1;
- htmlf("</span>");
+ html("</span>");
j += 1;
}
html_txt(c);
}
+ if (!same)
+ html("</span>");
}
static void print_ssdiff_line(char *class,
@@ -235,7 +240,7 @@ static void print_ssdiff_line(char *class,
char *fileurl = cgit_fileurl(ctx.repo->url, "tree", old_file->path, id_str);
html("<td class='lineno'><a href='");
html(fileurl);
- htmlf("' id='%s'>%s</a>", lineno_str, lineno_str + 1);
+ htmlf("'>%s</a>", lineno_str + 1);
html("</td>");
htmlf("<td class='%s'>", class);
free(fileurl);
@@ -258,7 +263,7 @@ static void print_ssdiff_line(char *class,
char *fileurl = cgit_fileurl(ctx.repo->url, "tree", new_file->path, id_str);
html("<td class='lineno'><a href='");
html(fileurl);
- htmlf("' id='%s'>%s</a>", lineno_str, lineno_str + 1);
+ htmlf("'>%s</a>", lineno_str + 1);
html("</td>");
htmlf("<td class='%s'>", class);
free(fileurl);
@@ -404,7 +409,7 @@ void cgit_ssdiff_header_begin(void)
void cgit_ssdiff_header_end(void)
{
- html("</td><tr>");
+ html("</td></tr>");
}
void cgit_ssdiff_footer(void)
diff --git a/ui-stats.c b/ui-stats.c
index 7acd358..02c60ef 100644
--- a/ui-stats.c
+++ b/ui-stats.c
@@ -1,3 +1,13 @@
+/* ui-stats.c: generate stats view
+ *
+ * Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
+ *
+ * Licensed under GNU General Public License v2
+ * (see COPYING for full license text)
+ */
+
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-stats.h"
#include "html.h"
@@ -166,7 +176,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 +190,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,22 +225,22 @@ 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] = "--";
argv[4] = ctx.qry.path;
argc += 2;
}
- init_revisions(&rev, NULL);
+ repo_init_revisions(the_repository, &rev, NULL);
rev.abbrev = DEFAULT_ABBREV;
rev.commit_format = CMIT_FMT_DEFAULT;
rev.max_parents = 1;
@@ -241,8 +251,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(commit);
- free_commit_list(commit->parents);
+ release_commit_memory(the_repository->parsed_objects, commit);
commit->parents = NULL;
}
return authors;
@@ -261,21 +270,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 +309,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 +338,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 8e81ac4..947812a 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -99,7 +99,7 @@ static char* append_readme_path(const char *filename, const char *ref, const cha
return full_path;
}
-void cgit_print_repo_readme(char *path)
+void cgit_print_repo_readme(const char *path)
{
char *filename, *ref, *mimetype;
int free_filename = 0;
diff --git a/ui-summary.h b/ui-summary.h
index 0896650..cba696a 100644
--- a/ui-summary.h
+++ b/ui-summary.h
@@ -2,6 +2,6 @@
#define UI_SUMMARY_H
extern void cgit_print_summary(void);
-extern void cgit_print_repo_readme(char *path);
+extern void cgit_print_repo_readme(const char *path);
#endif /* UI_SUMMARY_H */
diff --git a/ui-tag.c b/ui-tag.c
index 2c96c37..8f58f14 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+#define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-tag.h"
#include "html.h"
@@ -33,7 +35,7 @@ static void print_tag_content(char *buf)
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>");
}
@@ -48,12 +50,12 @@ void cgit_print_tag(char *revname)
revname = ctx.qry.head;
strbuf_addf(&fullref, "refs/tags/%s", revname);
- if (get_oid(fullref.buf, &oid)) {
+ if (repo_get_oid(the_repository, fullref.buf, &oid)) {
cgit_print_error_page(404, "Not found",
"Bad tag reference: %s", revname);
goto cleanup;
}
- obj = parse_object(&oid);
+ obj = parse_object(the_repository, &oid);
if (!obj) {
cgit_print_error_page(500, "Internal server error",
"Bad object id: %s", oid_to_hex(&oid));
@@ -63,15 +65,15 @@ void cgit_print_tag(char *revname)
struct tag *tag;
struct taginfo *info;
- tag = lookup_tag(&oid);
- if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
+ tag = lookup_tag(the_repository, &oid);
+ if (!tag || parse_tag(the_repository, tag) || !(info = cgit_parse_tag(tag))) {
cgit_print_error_page(500, "Internal server error",
"Bad tag object: %s", revname);
goto cleanup;
}
cgit_print_layout_start();
html("<table class='commit-info'>\n");
- htmlf("<tr><td>tag name</td><td>");
+ html("<tr><td>tag name</td><td>");
html_txt(revname);
htmlf(" (%s)</td></tr>\n", oid_to_hex(&oid));
if (info->tagger_date > 0) {
@@ -91,7 +93,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)
@@ -103,10 +105,10 @@ void cgit_print_tag(char *revname)
} else {
cgit_print_layout_start();
html("<table class='commit-info'>\n");
- htmlf("<tr><td>tag name</td><td>");
+ 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 e6b3074..5ac8c9a 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -6,6 +6,8 @@
* (see COPYING for full license text)
*/
+ #define USE_THE_REPOSITORY_VARIABLE
+
#include "cgit.h"
#include "ui-tree.h"
#include "html.h"
@@ -84,25 +86,27 @@ static void print_binary_buffer(char *buf, unsigned long size)
html("</table>\n");
}
-static void print_object(const struct object_id *oid, char *path, const char *basename, const char *rev)
+static void print_object(const struct object_id *oid, const char *path, const char *basename, const char *rev)
{
enum object_type type;
char *buf;
unsigned long size;
+ bool is_binary;
- type = oid_object_info(the_repository, oid, &size);
+ type = odb_read_object_info(the_repository->objects, oid, &size);
if (type == OBJ_BAD) {
cgit_print_error_page(404, "Not found",
"Bad object name: %s", oid_to_hex(oid));
return;
}
- buf = read_object_file(oid, &type, &size);
+ buf = odb_read_object(the_repository->objects, oid, &type, &size);
if (!buf) {
cgit_print_error_page(500, "Internal server error",
"Error reading object %s", oid_to_hex(oid));
return;
}
+ is_binary = buffer_is_binary(buf, size);
cgit_set_title_from_path(path);
@@ -110,7 +114,7 @@ static void print_object(const struct object_id *oid, char *path, const char *ba
htmlf("blob: %s (", oid_to_hex(oid));
cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
rev, path);
- if (ctx.cfg.enable_blame) {
+ if (ctx.repo->enable_blame && !is_binary) {
html(") (");
cgit_blame_link("blame", NULL, NULL, ctx.qry.head,
rev, path);
@@ -123,7 +127,7 @@ static void print_object(const struct object_id *oid, char *path, const char *ba
return;
}
- if (buffer_is_binary(buf, size))
+ if (is_binary)
print_binary_buffer(buf, size);
else
print_text_buffer(basename, buf, size);
@@ -139,8 +143,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;
@@ -177,7 +180,7 @@ static void write_tree_link(const struct object_id *oid, char *name,
cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev,
fullpath->buf);
- tree = lookup_tree(&tree_ctx.oid);
+ tree = lookup_tree(the_repository, &tree_ctx.oid);
if (!tree)
return;
@@ -185,8 +188,7 @@ static void write_tree_link(const struct object_id *oid, char *name,
tree_ctx.name = NULL;
tree_ctx.count = 0;
- read_tree_recursive(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,27 +201,28 @@ 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 : "",
ctx.qry.path ? "/" : "", name);
if (!S_ISGITLINK(mode)) {
- type = oid_object_info(the_repository, oid, &size);
+ type = odb_read_object_info(the_repository->objects, oid, &size);
if (type == OBJ_BAD) {
htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
name,
oid_to_hex(oid));
- free(name);
- return 0;
+ goto cleanup;
}
}
@@ -239,6 +242,21 @@ static int ls_item(const struct object_id *oid, struct strbuf *base,
cgit_tree_link(name, NULL, class.buf, ctx.qry.head,
walk_tree_ctx->curr_rev, fullpath.buf);
}
+ if (S_ISLNK(mode)) {
+ html(" -> ");
+ buf = odb_read_object(the_repository->objects, 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>");
@@ -251,10 +269,12 @@ static int ls_item(const struct object_id *oid, struct strbuf *base,
if (!S_ISGITLINK(mode))
cgit_plain_link("plain", NULL, "button", ctx.qry.head,
walk_tree_ctx->curr_rev, fullpath.buf);
- if (!S_ISDIR(mode) && ctx.cfg.enable_blame)
+ if (!S_ISDIR(mode) && ctx.repo->enable_blame)
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);
@@ -279,7 +299,7 @@ static void ls_tail(void)
cgit_print_layout_end();
}
-static void ls_tree(const struct object_id *oid, char *path, struct walk_tree_context *walk_tree_ctx)
+static void ls_tree(const struct object_id *oid, const char *path, struct walk_tree_context *walk_tree_ctx)
{
struct tree *tree;
struct pathspec paths = {
@@ -294,13 +314,13 @@ static void ls_tree(const struct object_id *oid, char *path, struct walk_tree_co
}
ls_head();
- read_tree_recursive(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;
@@ -325,7 +345,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;
}
@@ -354,13 +374,13 @@ void cgit_print_tree(const char *rev, char *path)
if (!rev)
rev = ctx.qry.head;
- if (get_oid(rev, &oid)) {
+ if (repo_get_oid(the_repository, rev, &oid)) {
cgit_print_error_page(404, "Not found",
"Invalid revision name: %s", rev);
return;
}
- commit = lookup_commit_reference(&oid);
- if (!commit || parse_commit(commit)) {
+ commit = lookup_commit_reference(the_repository, &oid);
+ if (!commit || repo_parse_commit(the_repository, commit)) {
cgit_print_error_page(404, "Not found",
"Invalid commit reference: %s", rev);
return;
@@ -369,11 +389,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(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)