cgit - Changelog ================ Notable changes to this fork, newest release first. History before v2.0.0 belongs to upstream cgit and is not covered here. v2.7.1 (2026-09-08) ------------------- A bugfix release. A tag's snapshot names no longer depend on how git happens to store its ref. Fixed ..... * Snapshot names shed a tag's leading v reliably again. On a case-insensitive filesystem a freshly created tag kept the v until git packed the refs, so the same tag could change its snapshot names over time. v2.7.0 (2026-09-08) ------------------- A configuration release. The badly named keys are renamed, the statistics switch folds back into max-stats, the parser now reports what it ignores, and the theme toggle is gone. Added ..... * Unknown or misplaced configuration keys are reported to stderr instead of being dropped silently. Changed ....... * noheader became enable-header, with its polarity flipped. * noplainemail became enable-plain-email, with its polarity flipped. * cache-root-ttl became cache-index-ttl. * cache-repo-ttl became cache-summary-ttl. * cache-scanrc-ttl became cache-scan-ttl. * agefile became age-file. * renamelimit became rename-limit. * repo.extra-head-content became repo.head-content. * Old key spellings are not read and draw the unknown-key warning. * max-stats enables the statistics page and bounds its coarsest period again, as it did before v2.2.0. * enable-stats and repo.enable-stats are gone. * repo.max-stats=0 turns the statistics page off for one repository. Removed ....... * The header theme toggle. The light and dark theme follows the system preference alone, and a saved choice in localStorage is no longer read. * Selects no longer submit their form the moment they change. The diff and stats panels always show their reload button instead of keeping it for readers without scripting. Fixed ..... * A lua filter that fails now answers with an error page naming the script and the error, where before the page was fed back into the broken filter and the visitor got an empty response. v2.6.1 (2026-09-01) ------------------- A small release. Every clone row is now a link a browser can follow, and the diffstat graph drops the output's last inline style, so a strict Content-Security-Policy needs no exceptions. Changed ....... * Clone rows are links. The ssh, scp and git forms cannot be followed by a browser, so those rows point at the first http url in the clone list, or at the repository page when there is none, and the scp form stays out of the document head since it is not a URI. * The diffstat bars take their width from colspan on a fixed-layout table rather than from inline styles. A site pointing css= at a stylesheet of its own needs the new inner graph table rule from cgit.css, or the bars render as equal width segments spaced apart. v2.6.0 (2026-08-28) ------------------- A cleanup release. Output is ASCII only and deterministic, the statistics page drops its language breakdown, and two bugs a cross-compiler review found are fixed. Added ..... * README.txt documents how cgit urls are built, both url forms, every query parameter, the endpoints that are not pages, and worked examples. * tests/README.txt describes the suite layout, numbering and traps. * Pages carry a meta description, from the repository or root description. * Submodule links resolve through .gitmodules, and submodule rows are set apart in the tree listing. Changed ....... * Output is ASCII only. The truncation marker is an ellipsis, the title path separator is plain, and unrepresentable bytes print as a question mark. * Non-breaking space padding is done with CSS. * Pages are byte for byte deterministic. The footer drops its clock and an empty atom feed is dated at the epoch. * The charset is spelled UTF-8 everywhere. * Snapshots use registered media types, so application/gzip, application/zip and application/zstd replace their x- forms. * Status lines use the standard HTTP reason phrases. * Table header and image attributes follow the usual order. * Test scripts are renumbered into themed ranges and prefer POSIX forms. Removed ....... * The statistics page no longer breaks the tree down by language. The built-in extension table was a standing maintenance cost, classifying by extension misleads, and the tree walk it needed was the only part of the page reading object sizes. The commits-per-author table is unchanged. Fixed ..... * The statistics page no longer reads past its argument list when given a path. The array handed to git's revision setup lacked a trailing null, so a url such as /repo/stats/dir read whatever followed it on the stack. * A detached head stays selected in the branch switcher. Browsing at a raw commit or tag left it resting on the first branch, so switch moved away. v2.5.0 (2026-08-23) ------------------- A correctness and conformance release. The generated markup moves fully to HTML5, cgit stops emitting HTTP headers a front-end server should own, the page cache and repository scan get real locking, and pages pinned to a commit now say so. Added ..... * Pages pinned to a commit via the id parameter show it. The branch switcher gains a "(detached)" entry, the page title carries the short hash, and the path strip gains a rev crumb linking back to the branch tip. * The shipped server configs gain Permissions-Policy, cross-origin isolation headers and form-action in the CSP, and enable HSTS. The nginx config adds catch-all blocks that drop requests for hostnames the site does not serve, since cgit keys its cache on Host. * Operators get log lines for two previously silent failures, a cache too small for its keys and a repository scan lock that fails for reasons other than contention. * The documentation now spells out that the cache directory must be pre-created, owned by the web server account and mode 0700. * Login forms carry autocomplete hints for password managers, and avatar images load lazily. Changed ....... * Atom feeds conform to the RFC. They are served as application/atom+xml with an XML declaration, invalid bytes are replaced instead of emitted, and every entry carries an author name. * Dates render as HTML time elements with strict ISO 8601 values. * Markup is fully HTML5. Complete documents everywhere, real table header cells, no XHTML self-closing slashes and no HTML comments in the output. The plain directory listing gets a doctype so browsers leave quirks mode. * cgit no longer sends Last-Modified, Expires or ETag. Front-end caching policy lives in the server configs, which now append headers rather than replace cgit's own. * Only a full object id qualifies for cache-static-ttl, since the id parameter accepts any rev git can resolve. * Percentages in the diffstat bars and language table print from integers, so a filter that switches the numeric locale cannot corrupt the output. * URL encoding is tightened. Ampersands and plus signs are percent-encoded in paths, and query strings are escaped at each sink instead of being pre-escaped. * The responsive breakpoints only hide or restack chrome and never change font sizes or gutters. Narrow screens drop the search box, branch switcher and author columns, and diff tables scroll inside their own box. * The client-side age refresher uses the same bucket arithmetic as the server, so refreshed ages no longer drift from rendered ones. * The example agefile hook was renamed to post-receive.cgit-age. Removed ....... * The repository homepage feature, including repo.homepage, the gitweb.homepage mapping and the homepage tab. * The post-update.update-server-info example hook. Fixed ..... * Error pages are no longer cached, so requesting a commit before it is pushed can no longer pin a 404 into the cache forever. * An abandoned cache fill no longer appends a second page to the response, publishes its lock file or leaves a stale copy to be served. * A crashed repository scan no longer freezes the index forever. Scan and cache locks are fcntl locks released by the kernel with the process, and lock holders re-verify their lock file after acquiring it, closing a race that could truncate a live file. * Blobs containing a NUL byte anywhere are treated as binary in tree and blame views, and the raw path substitutes replacement characters instead of truncating at the NUL. * An annotated tag whose tagger has no email no longer passes NULL to the email filter, repositories without an owner no longer render an empty link, single-page logs drop the pager, and filler rows use correct colspan values. * The dev preview server splits CGI headers at the earliest blank line, so DOS line endings in page output no longer swallow the document head. v2.4.0 (2026-08-08) ------------------- A performance and robustness release. Output is buffered instead of written fragment by fragment, the hot paths shed repeated work, and a cluster of fixes closes crashes reachable from repository-controlled content. Changed ....... * The bundled Git is updated from 2.54.0 to 2.55.0, with NO_RUST set so Cargo does not become a silent build requirement. * Page output is collected in a 64 KB buffer and written in whole blocks instead of one write per HTML fragment. * Blame and tree line numbers, hex dump rows and intra-line diff highlights are emitted in batches rather than per line or character. * The diff view renders each file while it is already open and replays it after the diffstat, replacing a second full tree walk. * Blame reuses a commit's rendered detail across its entries, the index resolves repository ages once before sorting, and the stats page computes its period labels once. * Side-by-side tab expansion is a single pass, where it was previously quadratic on tab-heavy lines. * Search queries are clamped to 512 characters, bounding the matching work one request can demand and the size of cache keys. * The sources compile as gnu17 so their meaning does not drift with compiler defaults, and the release script probes each hardening flag, preferring FORTIFY_SOURCE level 3. Fixed ..... * A repository under scan-path could supply its own module-link template, which was handed straight to printf. Surplus conversions could crash cgit or read stack memory into the page. Templates are now expanded manually. * Sorting branches by age crashed when a branch ref pointed at a tag or tree object. * Hunks whose header omits a length, as git writes for single-line hunks, were numbered from zero in side-by-side diffs and lost their links. * A request whose cache key was too long to read back could never hit, regenerating the page every time while still writing an unusable slot. Such requests now skip the cache and log it. * Git config isolation ran from a constructor attribute that some compilers silently discard. It now runs from main. * A memory leak of deferred side-by-side lines and an integer-truncation bug in the stats author ordering. v2.3.0 (2026-08-05) ------------------- A consolidation release focused on how the project is laid out, deployed and operated. Everything an operator ships or edits now lives under custom/, the server configs became complete standalone files, and the hooks and auth filters were hardened. Added ..... * New enable-relative-dates option, default 1. Set to 0 to always show calendar dates in the age columns instead of elapsed times. * New date-format option controlling those calendar dates, accepting git's date format names plus strftime formats. * New example hook post-receive.cgit-cache that clears cgit's output cache after a push, so new commits appear immediately instead of after the cache TTL. * New CGIT_EXTRA_CFLAGS make variable applying extra compiler flags to cgit's own objects only, not the bundled git. Changed ....... * The tree was reorganized. The git submodule moved to vendor/git, and the example cgitrc, Lua filters, hooks and server configs moved into custom/. Operators following old paths must update them. * Repositories with no commits get a dedicated page with working clone URLs instead of a bare notice with dead tabs. * A description file still holding git's "Unnamed repository" boilerplate is treated as no description. * The theme is applied before first paint, so pages no longer flash the wrong theme, and the header no longer shifts as the page loads. * The nginx, Apache and lighttpd configs are complete, runnable files carrying their own top-level directives, rather than snippets assuming a distro base. * The agefile hook uses committer dates from branches only, writes atomically and keeps its output readable by the web server. The update-server-info hook likewise forces a safe umask. * The auth filters' secret moved from /var/cache/cgit to /var/lib/cgit, so pruning the cache no longer invalidates every live session. * The dev preview server forwards cookies, referers and request bodies, so the auth filters can be exercised locally, and decodes escaped repository names. Removed ....... * The built-in help page and its enable-help option. * GitHub Actions CI, the release workflow and the make dist target. Fixed ..... * Ordinary working trees found by scan-path are listed as repo instead of repo/.git. * The auth filters match cookie names containing pattern magic characters, and auth-file tolerates a users file saved with CRLF line endings. v2.2.0 (2026-07-25) ------------------- A hardening release that puts explicit ceilings on the work a single request can provoke, reworks the statistics page, and overhauls the bundled Lua filters. Added ..... * The stats page gains a language breakdown, sizing the tree at HEAD by file extension without ever loading blob contents. * New enable-stats option, default 0, as the dedicated switch for the statistics page. * New max-patch-count option, default 50, bounding how many commits the patch view emits as a series. * New about-filter script about-render.lua, a server-side renderer for markdown, man pages and plain text, falling back to escaped text when its dependencies are missing. * New make dist target staging the release tarball, so local and released tarballs are identical. Changed ....... * max-stats no longer enables the stats page, it only bounds the coarsest period. Instances relying on it must now also set enable-stats=1. * The auth filters are hardened. Cookies are Secure by default, redirect targets are validated so a login cannot bounce to another origin, password checks are constant time even for unknown users, and a protected repository with no resolvable users denies everyone. * The avatar filters skip missing addresses instead of hashing garbage, normalize the rest, and expose size, style and URL settings. * link-commits.lua takes a user-editable list of pattern and URL rules instead of a hardcoded issue reference, resolving all matches in one pass. * The syntax highlighter falls back through an extension map when lexer detection fails and emits its plain-text fallback in slices instead of one full-size copy. * The masthead reserves the logo column so the header no longer shifts while the logo loads, and the logo and favicon were redrawn at higher resolution. Removed ....... * The client-side markdown renderer and the enable-markdown option. Rendered readmes now require about-filter pointed at about-render.lua. * The PDF documentation targets. Fixed ..... * A file name containing a quote could break out of the link attributes in side-by-side diffs. Paths are now percent-encoded. * A commit with no message no longer crashes the history views. * max-blob-size also bounds the diff path, so a huge blob is reported as binary instead of inflated into memory, and dangling refs are skipped instead of dereferenced. * The diff size caps are no longer silently disabled when follow is active. * The header branch switcher respects max-ref-count instead of emitting an option per branch on every page. * zstd snapshots run single-threaded, so one request cannot fan out across every core. * The stats walk is pruned by date instead of visiting all history, and a commit whose date cannot be represented is dropped rather than indexing a month table out of bounds. * The cache listing bounds its key output, and a malformed cgitrc line no longer discards the rest of the file. * The build no longer triggers a section-alignment warning on every macOS link. v2.1.0 (2026-07-19) ------------------- A release about behaving well on large repositories and under a strict Content-Security-Policy. Syntax highlighting moves out of the browser and into an optional server-side filter. Added ..... * New max-ref-count option, default 200, capping how many branches and tags each refs section lists, with independent pagination on the dedicated branch and tag pages. * New max-diff-files option, default 200. A commit touching more files renders only its diffstat, with a notice pointing at the per-file diffs and the patch view. * New max-diff-lines option, default 1000. A single file exceeding it within a whole-commit view links to its own diff page instead of rendering inline. Single-file diffs, rawdiff and patch are never capped. * An optional server-side syntax highlighter, syntax-highlight.lua, built on the Scintillua lexers with around 120 languages, degrading to plain escaped text when its dependencies are absent. * A built-in help page documenting the site's URL patterns, behind the new enable-help option. Removed again in v2.3.0. * URL fragments highlight the targeted source line in blob and blame views, support ranges like #n5-n12, and land the target mid-viewport. Changed ....... * The auto-submitting select controls drop their inline onchange handlers and are wired up from cgit.js, so the option forms work under a CSP without unsafe-inline. * A plaintext readme keeps its line structure instead of collapsing into a run-on paragraph. * Syntax highlighting of source views moved from the browser to the optional filter. Without a source-filter, code is served plain, and the client-side highlighter is deleted. * Font sizes were evened out across the interface, and the external-link icon follows the tab's text color in both themes. * The mobile layout hides the author and size columns, the search form and the branch switcher, so nothing forces sideways scrolling. Removed ....... * The owner-filter hook and its per-repository override. Owners always render as plain linked text. Fixed ..... * Release binaries no longer ship with an empty version string when git describe fails in a shallow clone. v2.0.0 (2026-07-16) ------------------- First release of this fork, cut from upstream cgit v1.3.1. The page chrome is rebuilt as semantic HTML with dark mode and a phone layout, the runtime sheds its external interpreters and crypto libraries, and around two dozen security and correctness fixes land. Added ..... * Built-in client-side syntax highlighting in cgit.js for roughly 33 languages, used when no source-filter is configured. * Built-in client-side markdown rendering for about pages behind the new enable-markdown option, restricted to safe link and image targets. * A light and dark theme with a toggle cycling auto, light and dark, persisted in localStorage and invisible without JavaScript. * A responsive layout for phones and small screens, with stacking panels and a repository index that collapses to name and age. * New enable-tree-group-dirs option listing directories before files in the tree view. * New enable-cache-list option, default 0, gating the previously unprotected cache listing page. * A fully commented example cgitrc listing every option at its default, and complete nginx, Apache and lighttpd examples with security headers. * An example post-update hook running update-server-info, needed because the built-in clone support speaks dumb HTTP. * tools/serve.py, a dependency-free local preview server, and tools/release-build.sh, a hardened Linux release build. * CI covering gcc and clang, the test suite under ASan and UBSan, sparse, and a hardened PIE build, plus a tag-triggered release workflow shipping hardened tarballs with checksums. * Accessible names on the search field, branch switcher and breadcrumb navigation, and contextual titles on the nav tabs. Changed ....... * max-blob-size defaults to 10 MB instead of unlimited, and now caps everything cgit reads into memory to serve, including plain and blob output. Oversized objects return a 413 page. * section-sort defaults to 0, so the order repositories are written in cgitrc is respected rather than alphabetized. * Repository age on the index is derived from HEAD instead of a hardcoded refs/heads/master, so repositories on main show an age. * The page chrome is semantic HTML instead of nested tables, keeping the existing class and id names so custom themes still match. * The filter scripts moved to a Lua-only set. simple-authentication.lua became auth-inline.lua, file-authentication.lua became auth-file.lua and commit-links.sh became link-commits.lua. * Lua is optional and autodetected through pkg-config, preferring LuaJIT. NO_LUA=1 forces a self-contained binary. * The sources were reorganized into source/, libraries/, assets/, extensions/, examples/, tools/ and tests/, with all generated output under build/. * The auth filters compare cookie HMACs in constant time, set SameSite=Lax, and strip header injection from Set-Cookie and Location values. Removed ....... * OpenSSL is no longer a build dependency, and libcurl is not required. * Every Python filter script, including the Pygments highlighter, the markdown and rst converters and the Python gravatar filter. * The Gentoo LDAP auth filter, owner-example.lua, about-formatting.sh and the man and txt about converters. * The unused name query parameter and the never-read cache-max-create-time and max-lock-attempts settings. Fixed ..... * An unauthenticated arbitrary file write through the log id parameter, which reached git's revision parser as an option. Revisions beginning with a dash are rejected. * Cache poisoning through a spoofed Host header. The scheme and host are now part of the cache key. * Stored XSS from about pages, which were written as raw HTML when no about-filter was configured. They are now escaped. * Two path traversals, one past the about-path prefix check via a sibling directory sharing a name prefix, and one through a crafted HEAD ref. * max-blob-size is enforced before a blob is read into memory, not after. * A signed-comparison bypass of the authentication POST size limit, and an unbounded history walk from a crafted ofs value, now clamped. * Every snapshot was undecompressable whenever a global git config existed, because git's error output was compressed into the archive. * Numerous crashes, among them out-of-bounds accesses on short paths and empty URLs, NULL dereferences on odd blobs and authorless commits, a division by zero in the diffstat and undefined ctype behavior on negative chars. * Truncated pages after stat-only diffs and binary blames, a 200 instead of a 404 for unknown blob paths, submodule hashes losing digits in diffs, pager links dropping search terms containing reserved characters, a missing updated element in empty atom feeds and negative ages from the age refresher.