From 3737c5ea10662a0b5972e217d40449b85f01ddb5 Mon Sep 17 00:00:00 2001 From: Alexander von Gluck IV Date: Mon, 15 Dec 2025 15:05:35 -0600 Subject: [PATCH] commit.body: Make links clickable in web interface --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + src/git.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 +++ templates/repo/commit.html | 2 +- 5 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48edc78..3ad819c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -777,6 +777,7 @@ "httparse", "humantime", "itertools 0.12.1", + "linkify", "md5", "memchr", "moka", @@ -1941,6 +1942,15 @@ "cc", "pkg-config", "vcpkg", +] + +[[package]] +name = "linkify" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" +dependencies = [ + "memchr", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 91c916a..b34830a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ httparse = "1.10" humantime = "2.2" itertools = "0.12.1" +linkify = "0.10.0" md5 = "0.8" memchr = "2.7" moka = { version = "0.12.11", features = ["future"] } diff --git a/src/git.rs b/src/git.rs index d2ac9bd..e37026a 100644 --- a/src/git.rs +++ b/src/git.rs @@ -14,6 +14,7 @@ traverse::tree::visit::Action, }; use itertools::Either; +use linkify::{LinkFinder, LinkKind}; use moka::future::Cache; use std::{ borrow::Cow, @@ -32,11 +33,50 @@ use tracing::{error, instrument, warn}; use yoke::{Yoke, Yokeable}; + use crate::{ methods::filters::DisplayHexBuffer, syntax_highlight::{ComrakHighlightAdapter, FileIdentifier, format_file, format_file_inner}, unified_diff_builder::{Callback, UnifiedDiffBuilder}, }; + +/// Escape essential html +fn escape(text: &str, dest: &mut Vec) { + for c in text.bytes() { + match c { + b'&' => dest.extend_from_slice(b"&"), + b'<' => dest.extend_from_slice(b"<"), + b'>' => dest.extend_from_slice(b">"), + b'"' => dest.extend_from_slice(b"""), + b'\'' => dest.extend_from_slice(b"'"), + _ => dest.push(c), + } + } +} + +/// Find links in a BStr, make them clickable +pub fn linkify(text: &BStr) -> BString { + let mut link_finder = LinkFinder::new(); + link_finder.url_must_have_scheme(true); + + let mut bytes = Vec::new(); + for span in link_finder.spans(&text.to_string()) { + match span.kind() { + Some(LinkKind::Url) => { + let url = span.as_str().to_string(); + bytes.extend_from_slice(b""); + escape(span.as_str(), &mut bytes); + bytes.extend_from_slice(b""); + }, + _ => { + escape(span.as_str(), &mut bytes); + } + } + } + bytes.into() +} type ReadmeCacheKey = (PathBuf, Option>); @@ -700,7 +740,6 @@ impl<'a> CommitInner<'a> { pub fn new(commit: gix::worktree::object::CommitRef<'a>, oid: [u8; 20]) -> Result { let message = commit.message(); - Ok(CommitInner { author: CommitUser::try_from(commit.author)?, committer: CommitUser::try_from(commit.committer)?, @@ -752,6 +791,10 @@ pub fn body(&self) -> &BStr { self.body + } + + pub fn body_html_links(&self) -> BString { + linkify(self.body) } } diff --git a/src/main.rs b/src/main.rs index 2bfbdd4..490aceb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,6 +92,9 @@ /// Configures the request timeout. #[clap(long, env="REQUEST_TIMEOUT", default_value_t = Duration::from_secs(10).into())] request_timeout: humantime::Duration, + /// Configure an optional bug tracker prefix URL to link ticket numbers to (#XYZ) + #[clap(long, env="BUG_TRACKER_PREFIX")] + bug_tracker_prefix: Option, } #[derive(Debug, Clone, Copy)] diff --git a/templates/repo/commit.html b/templates/repo/commit.html index 820dc61..535f1ed 100644 --- a/templates/repo/commit.html +++ b/templates/repo/commit.html @@ -45,7 +45,7 @@

{{ commit.get().summary() }}

-
{{ commit.get().body() }}
+
{{ commit.get().body_html_links()|escape("none") }}

Diff

{{ commit.diff_stats|safe }}
--
gitore 0.2.3