commit.body: Make links clickable in web interface
Diff
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(-)
@@ -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]]
@@ -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"] }
@@ -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},
};
fn escape(text: &str, dest: &mut Vec<u8>) {
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),
}
}
}
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"<a href=\"");
escape(&url, &mut bytes);
bytes.extend_from_slice(b"\" target=\"_blank\">");
escape(span.as_str(), &mut bytes);
bytes.extend_from_slice(b"</a>");
},
_ => {
escape(span.as_str(), &mut bytes);
}
}
}
bytes.into()
}
type ReadmeCacheKey = (PathBuf, Option<Arc<str>>);
@@ -700,7 +740,6 @@
impl<'a> CommitInner<'a> {
pub fn new(commit: gix::worktree::object::CommitRef<'a>, oid: [u8; 20]) -> Result<Self> {
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)
}
}
@@ -92,6 +92,9 @@
#[clap(long, env="REQUEST_TIMEOUT", default_value_t = Duration::from_secs(10).into())]
request_timeout: humantime::Duration,
#[clap(long, env="BUG_TRACKER_PREFIX")]
bug_tracker_prefix: Option<String>,
}
#[derive(Debug, Clone, Copy)]
@@ -45,7 +45,7 @@
</div>
<h2>{{ commit.get().summary() }}</h2>
<pre>{{ commit.get().body() }}</pre>
<pre>{{ commit.get().body_html_links()|escape("none") }}</pre>
<h3>Diff</h3>
<pre class="diff">{{ commit.diff_stats|safe }}