diff options
| author | Bryce Kwon <bryce@brycekwon.com> | |
|---|---|---|
| committer | Bryce Kwon <bryce@brycekwon.com> | |
| commit | ||
| parent | ||
| tree | ||
| download | ||
Rework link-commits into a rules filter
| -rw-r--r-- | extensions/link-commits.lua | 154 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 file changed, 140 insertions, 14 deletions
diff --git a/extensions/link-commits.lua b/extensions/link-commits.lua index 160a12d..e7b17cc 100644 --- a/extensions/link-commits.lua +++ b/extensions/link-commits.lua @@ -1,27 +1,153 @@ --- Turns object hashes and issue references in commit messages into links. --- Use it with the commit-filter or repo.commit-filter setting and the lua: --- prefix. cgit hands the filter the message already HTML-escaped, so this --- only wraps matches in anchors. +-- cgit commit-filter that turns git object names and configurable text +-- references in commit messages into links. Use it with the commit-filter or +-- repo.commit-filter setting and the lua: prefix. +-- +-- commit-filter=lua:/path/to/link-commits.lua +-- +-- cgit hands the filter the message already HTML-escaped, so this only wraps +-- matches in anchors. No external dependencies. Runs on Lua 5.1 through 5.4 +-- and LuaJIT. +-- +-- Two kinds of thing are linked, object names (runs of hex that look like git +-- hashes) and any number of text-reference rules you define, each a pattern +-- and a URL. Both are configured in the block below. All matches are resolved +-- in a single left-to-right pass, so nothing is ever linked twice. -local buffer +-- +-- ===== CONFIGURATION ===== +-- + +-- Object names (git hashes). Handled specially, because the length rule cannot +-- be written as a plain Lua pattern. +-- +-- Recognition is by shape, since a commit-filter cannot ask the repository +-- whether a hash is real. Any hex run within the length bounds is linked, +-- whatever mix of digits and letters it has, so abbreviated and all-digit +-- hashes are both caught. The cost is that a long hex-looking number can now +-- and then link to an object that does not exist, which cgit renders as a +-- harmless "bad object name" page. Shape matching is inherently approximate, +-- the length bounds are the only filter. +local objects = { + -- Set false to stop linking bare hashes. + enabled = true, + -- A hex run within these lengths is linked. Git abbreviations run about 7 + -- to 12 characters, full names are 40 (sha1) or 64 (sha256). + min_length = 7, + max_length = 64, + -- Link target, %s is replaced with the matched hash. "./?id=%s" is relative + -- to the current page and works for the common virtual-root layout. + url = "./?id=%s", +} + +-- Text-reference rules. Each rule is a Lua pattern with ONE capture and a URL +-- where %s is replaced by that capture, percent-encoded. The whole match is +-- shown, the capture is what goes in the URL. Rules are tried in order and the +-- leftmost match on the line wins, so put more specific patterns first. Leave +-- the list empty to link only object names. +-- +-- Lua patterns are not regular expressions. There is no alternation and no +-- {n,m} repetition. %d is a digit, %a a letter, %w a letter or digit, %x a hex +-- digit, and a literal magic character is escaped with %, so a literal '-' is +-- '%-'. Reference: https://www.lua.org/manual/5.1/manual.html#5.4.1 +local rules = { + { pattern = "#(%d+)", url = "https://bugs.example.com/?bug=%s" }, + -- { pattern = "CVE%-(%d%d%d%d%-%d+)", url = "https://www.cve.org/CVERecord?id=CVE-%s" }, + -- { pattern = "!(%d+)", url = "https://gitlab.example.com/group/repo/-/merge_requests/%s" }, + -- { pattern = "RFC%s?(%d+)", url = "https://www.rfc-editor.org/rfc/rfc%s" }, +} + +-- +-- ========================= +-- + +local chunks = {} + +-- Percent-encode everything but the URL-unreserved characters, so a captured +-- value cannot break out of the href attribute or the URL. +local function url_encode(s) + return (string.gsub(s, "[^%w._~-]", function(c) + return string.format("%%%02X", string.byte(c)) + end)) +end + +-- Build one anchor. url_template has %s where the encoded capture goes, display +-- is the text shown. A function replacement is used so a '%' in the encoded +-- value is not treated as a gsub reference. +local function make_link(url_template, capture, display) + local encoded = url_encode(capture) + local href = string.gsub(url_template, "%%s", function() return encoded end) + return '<a href="' .. href .. '">' .. display .. '</a>' +end + +-- Collect every candidate match as {s, e, pri, link}. A lower pri wins a tie on +-- the same start position. +local function collect(text) + local cands = {} + for pri, rule in ipairs(rules) do + -- A malformed pattern is an operator error, skip that rule rather than + -- failing the whole page. + pcall(function() + local init = 1 + while init <= #text do + local s, e, cap = string.find(text, rule.pattern, init) + if not s then break end + if cap == nil then + cap = string.sub(text, s, e) + end + cands[#cands + 1] = { + s = s, e = e, pri = pri, + link = make_link(rule.url, cap, string.sub(text, s, e)), + } + init = (e >= s) and e + 1 or s + 1 + end + end) + end + if objects.enabled then + local objpri = #rules + 1 + local init = 1 + while init <= #text do + local s, e, run = string.find(text, "%f[%w](%x+)%f[%W]", init) + if not s then break end + if #run >= objects.min_length and #run <= objects.max_length then + cands[#cands + 1] = { + s = s, e = e, pri = objpri, + link = make_link(objects.url, run, run), + } + end + init = e + 1 + end + end + return cands +end function filter_open(...) - buffer = "" + chunks = {} end function filter_write(str) - buffer = buffer .. str + chunks[#chunks + 1] = str end function filter_close() - -- Link a 7 to 64 character object name to this repository. - local out = buffer:gsub("%f[%w](%x+)%f[%W]", function(hex) - if #hex >= 7 and #hex <= 64 then - return '<a href="./?id=' .. hex .. '">' .. hex .. '</a>' + local text = table.concat(chunks) + local cands = collect(text) + table.sort(cands, function(a, b) + if a.s ~= b.s then + return a.s < b.s end + return a.pri < b.pri end) - -- Link "#123" to a bug tracker. Point this at your own. - out = out:gsub("#(%d+)", '<a href="https://bugs.example.com/?bug=%1">#%1</a>') - html(out) + local out = {} + local i = 1 + for _, c in ipairs(cands) do + -- Skip a candidate that overlaps one already emitted. + if c.s >= i then + out[#out + 1] = string.sub(text, i, c.s - 1) + out[#out + 1] = c.link + i = c.e + 1 + end + end + out[#out + 1] = string.sub(text, i) + html(table.concat(out)) return 0 end |
