Use deserialised BStrs directly from gix
Diff
src/git.rs | 222 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
src/methods/filters.rs | 2 +-
templates/repo/commit.html | 18 +++++++++---------
templates/repo/tag.html | 12 +++++++-----
src/methods/repo/diff.rs | 11 ++++++-----
src/methods/repo/tag.rs | 3 ++-
6 files changed, 166 insertions(+), 102 deletions(-)
@@ -8,20 +8,21 @@
bstr::{BStr, BString, ByteSlice, ByteVec},
diff::blob::{platform::prepare_diff::Operation, Sink},
object::{tree::EntryKind, Kind},
objs::tree::EntryRef,
objs::{tree::EntryRef, CommitRef, TagRef},
prelude::TreeEntryRefExt,
traverse::tree::visit::Action,
url::Scheme,
ObjectId, ThreadSafeRepository, Url,
};
use itertools::Itertools;
use itertools::{Either, Itertools};
use moka::future::Cache;
use std::borrow::Cow;
use std::{
borrow::Cow,
collections::{BTreeMap, VecDeque},
ffi::OsStr,
fmt::{self, Arguments, Write},
io::ErrorKind,
iter::Copied,
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
@@ -30,8 +31,10 @@
use tar::Builder;
use time::{OffsetDateTime, UtcOffset};
use tracing::{error, instrument, warn};
use yoke::{Yoke, Yokeable};
use crate::{
methods::filters::DisplayHexBuffer,
syntax_highlight::{format_file, format_file_inner, ComrakHighlightAdapter, FileIdentifier},
unified_diff_builder::{Callback, UnifiedDiffBuilder},
};
@@ -272,7 +275,7 @@
}
#[instrument(skip(self))]
pub async fn tag_info(self: Arc<Self>) -> Result<DetailedTag> {
pub async fn tag_info(self: Arc<Self>) -> Result<Yoke<DetailedTag<'static>, Vec<u8>>> {
tokio::task::spawn_blocking(move || {
let tag_name = self.branch.clone().context("no tag given")?;
let repo = self.repo.to_thread_local();
@@ -281,25 +284,25 @@
.find_reference(&format!("refs/tags/{tag_name}"))
.context("Given tag does not exist in repository")?
.peel_to_tag()
.context("Couldn't get to a tag from the given reference")?;
let tag_target = tag
.target_id()
.context("Couldn't find tagged object")?
.object()?;
let tagged_object = match tag_target.kind {
Kind::Commit => Some(TaggedObject::Commit(tag_target.id.to_string())),
Kind::Tree => Some(TaggedObject::Tree(tag_target.id.to_string())),
_ => None,
};
let tag_info = tag.decode()?;
Ok(DetailedTag {
name: tag_name,
tagger: tag_info.tagger.map(TryInto::try_into).transpose()?,
message: tag_info.message.to_string(),
tagged_object,
.context("Couldn't get to a tag from the given reference")?
.detach()
.data;
Yoke::try_attach_to_cart(tag, move |tag| {
let tag = TagRef::from_bytes(tag)?;
let tagged_object = match tag.target_kind {
Kind::Commit => Some(TaggedObject::Commit(tag.target)),
Kind::Tree => Some(TaggedObject::Tree(tag.target)),
_ => None,
};
Ok::<_, anyhow::Error>(DetailedTag {
name: tag_name,
tagger: tag.tagger.map(TryInto::try_into).transpose()?,
tagged_object,
message: tag.message,
})
})
})
.await
@@ -393,10 +396,16 @@
.context("Couldn't find commit HEAD of repository refers to")?;
let (diff_output, diff_stats) = fetch_diff_and_stats(&repo, &commit, highlighted)?;
let mut commit = Commit::try_from(commit)?;
commit.diff_stats = diff_stats;
commit.diff = diff_output;
Ok(commit)
let oid = take_oid(commit.id);
let inner = Yoke::try_attach_to_cart(commit.detach().data, |commit| {
CommitInner::new(CommitRef::from_bytes(commit)?, oid)
})?;
Ok(Commit {
inner,
diff_stats,
diff: diff_output,
})
})
.await
.context("Failed to join Tokio task")?
@@ -479,16 +488,28 @@
let (diff_output, diff_stats) =
fetch_diff_and_stats(&repo, &commit, highlighted)?;
let mut commit = Commit::try_from(commit)?;
commit.diff_stats = diff_stats;
commit.diff = diff_output;
let oid = take_oid(commit.id);
Ok(Arc::new(commit))
let inner = Yoke::try_attach_to_cart(commit.detach().data, |commit| {
CommitInner::new(CommitRef::from_bytes(commit)?, oid)
})?;
Ok(Arc::new(Commit {
inner,
diff_stats,
diff: diff_output,
}))
})
.await
.context("Failed to join Tokio task")?
})
.await
}
}
fn take_oid(v: ObjectId) -> [u8; 20] {
match v {
ObjectId::Sha1(v) => v,
}
}
@@ -682,33 +703,33 @@
}
#[derive(Debug)]
pub enum TaggedObject {
Commit(String),
Tree(String),
pub enum TaggedObject<'a> {
Commit(&'a BStr),
Tree(&'a BStr),
}
#[derive(Debug)]
pub struct DetailedTag {
#[derive(Debug, Yokeable)]
pub struct DetailedTag<'a> {
pub name: Arc<str>,
pub tagger: Option<CommitUser>,
pub message: String,
pub tagged_object: Option<TaggedObject>,
pub tagger: Option<CommitUser<'a>>,
pub message: &'a BStr,
pub tagged_object: Option<TaggedObject<'a>>,
}
#[derive(Debug)]
pub struct CommitUser {
name: String,
email: String,
pub struct CommitUser<'a> {
name: &'a BStr,
email: &'a BStr,
time: (i64, i32),
}
impl TryFrom<SignatureRef<'_>> for CommitUser {
impl<'a> TryFrom<SignatureRef<'a>> for CommitUser<'a> {
type Error = anyhow::Error;
fn try_from(v: SignatureRef<'_>) -> Result<Self> {
fn try_from(v: SignatureRef<'a>) -> Result<Self> {
Ok(CommitUser {
name: v.name.to_string(),
email: v.email.to_string(),
name: v.name,
email: v.email,
time: (v.time.seconds, v.time.offset),
@@ -716,12 +737,12 @@
}
}
impl CommitUser {
pub fn name(&self) -> &str {
impl CommitUser<'_> {
pub fn name(&self) -> &BStr {
&self.name
}
pub fn email(&self) -> &str {
pub fn email(&self) -> &BStr {
&self.email
}
@@ -734,63 +755,102 @@
#[derive(Debug)]
pub struct Commit {
author: CommitUser,
committer: CommitUser,
oid: String,
tree: String,
parents: Vec<String>,
summary: String,
body: String,
inner: yoke::Yoke<CommitInner<'static>, Vec<u8>>,
pub diff_stats: String,
pub diff: String,
}
impl TryFrom<gix::Commit<'_>> for Commit {
type Error = anyhow::Error;
impl Commit {
pub fn get(&self) -> &CommitInner<'_> {
self.inner.get()
}
}
fn try_from(commit: gix::Commit<'_>) -> Result<Self> {
let message = commit.message()?;
Ok(Commit {
author: CommitUser::try_from(commit.author()?)?,
committer: CommitUser::try_from(commit.committer()?)?,
oid: commit.id().to_string(),
tree: commit.tree_id()?.to_string(),
parents: commit.parent_ids().map(|v| v.to_string()).collect(),
summary: message.summary().to_string(),
body: message.body.map_or_else(String::new, ToString::to_string),
diff_stats: String::with_capacity(0),
diff: String::with_capacity(0),
#[derive(Debug, Yokeable)]
pub struct CommitInner<'a> {
author: CommitUser<'a>,
committer: CommitUser<'a>,
oid: [u8; 20],
tree: &'a BStr,
parents: SmallVec<&'a BStr>,
summary: Cow<'a, BStr>,
body: &'a BStr,
}
#[derive(Debug)]
enum SmallVec<T> {
None,
One(T),
Many(Vec<T>),
}
impl<T: Copy> SmallVec<T> {
fn iter(
&self,
) -> Either<std::iter::Empty<T>, Either<std::iter::Once<T>, Copied<std::slice::Iter<T>>>> {
match self {
Self::None => Either::Left(std::iter::empty()),
Self::One(v) => Either::Right(Either::Left(std::iter::once(*v))),
Self::Many(v) => Either::Right(Either::Right(v.iter().copied())),
}
}
}
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)?,
oid,
tree: commit.tree,
parents: commit
.parents
.into_inner()
.map(|[v]| SmallVec::One(v))
.unwrap_or_else(|inner| {
if inner.is_empty() {
SmallVec::None
} else {
SmallVec::Many(inner.into_vec())
}
}),
summary: message.summary(),
body: message.body.unwrap_or_else(|| BStr::new("")),
})
}
}
impl Commit {
pub fn author(&self) -> &CommitUser {
impl CommitInner<'_> {
pub fn author(&self) -> &CommitUser<'_> {
&self.author
}
pub fn committer(&self) -> &CommitUser {
pub fn committer(&self) -> &CommitUser<'_> {
&self.committer
}
pub fn oid(&self) -> &str {
&self.oid
pub fn oid(&self) -> DisplayHexBuffer<20> {
let mut buf = const_hex::Buffer::new();
buf.format(&self.oid);
DisplayHexBuffer(buf)
}
pub fn tree(&self) -> &str {
pub fn tree(&self) -> &BStr {
&self.tree
}
pub fn parents(&self) -> impl Iterator<Item = &str> {
self.parents.iter().map(String::as_str)
pub fn parents(&self) -> impl Iterator<Item = &BStr> {
self.parents.iter()
}
pub fn summary(&self) -> &str {
pub fn summary(&self) -> &BStr {
&self.summary
}
pub fn body(&self) -> &str {
pub fn body(&self) -> &BStr {
&self.body
}
}
@@ -41,7 +41,7 @@
Ok(unix_mode::to_string(u32::from(*s)))
}
pub struct DisplayHexBuffer<const N: usize>(const_hex::Buffer<N>);
pub struct DisplayHexBuffer<const N: usize>(pub const_hex::Buffer<N>);
impl<const N: usize> Display for DisplayHexBuffer<N> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -14,23 +14,23 @@
<tbody>
<tr>
<th>author</th>
<td>{{ commit.author().name() }} <{{ commit.author().email() }}></td>
<td>{{ commit.author().time() }}</td>
<td>{{ commit.get().author().name() }} <{{ commit.get().author().email() }}></td>
<td>{{ commit.get().author().time() }}</td>
</tr>
<tr>
<th>committer</th>
<td>{{ commit.committer().name() }} <{{ commit.committer().email() }}></td>
<td>{{ commit.committer().time() }}</td>
<td>{{ commit.get().committer().name() }} <{{ commit.get().committer().email() }}></td>
<td>{{ commit.get().committer().time() }}</td>
</tr>
<tr>
<th>commit</th>
<td colspan="2"><pre><a href="/{{ repo.display() }}/commit?id={{ commit.oid() }}{% call link::maybe_branch_suffix(branch) %}" class="no-style">{{ commit.oid() }}</a> <a href="/{{ repo.display() }}/patch?id={{ commit.oid() }}">[patch]</a></pre></td>
<td colspan="2"><pre><a href="/{{ repo.display() }}/commit?id={{ commit.get().oid() }}{% call link::maybe_branch_suffix(branch) %}" class="no-style">{{ commit.get().oid() }}</a> <a href="/{{ repo.display() }}/patch?id={{ commit.get().oid() }}">[patch]</a></pre></td>
</tr>
<tr>
<th>tree</th>
<td colspan="2"><pre><a href="/{{ repo.display() }}/tree?id={{ commit.tree() }}{% call link::maybe_branch_suffix(branch) %}" class="no-style">{{ commit.tree() }}</a></pre></td>
<td colspan="2"><pre><a href="/{{ repo.display() }}/tree?id={{ commit.get().tree() }}{% call link::maybe_branch_suffix(branch) %}" class="no-style">{{ commit.get().tree() }}</a></pre></td>
</tr>
{%- for parent in commit.parents() %}
{%- for parent in commit.get().parents() %}
<tr>
<th>parent</th>
<td colspan="2"><pre><a href="/{{ repo.display() }}/commit?id={{ parent }}{% call link::maybe_branch_suffix(branch) %}" class="no-style">{{ parent }}</a></pre></td>
@@ -44,8 +44,8 @@
</table>
</div>
<h2>{{ commit.summary() }}</h2>
<pre>{{ commit.body() }}</pre>
<h2>{{ commit.get().summary() }}</h2>
<pre>{{ commit.get().body() }}</pre>
<h3>Diff</h3>
<pre class="diff">{{ commit.diff_stats|safe }}
@@ -7,9 +7,9 @@
<tbody>
<tr>
<th>tag name</th>
<td>{{ tag.name }}</td>
<td>{{ tag.get().name }}</td>
</tr>
{% if let Some(tagger) = tag.tagger %}
{% if let Some(tagger) = tag.get().tagger %}
<tr>
<th>tag date</th>
<td>{{ tagger.time() }}</td>
@@ -19,7 +19,7 @@
<td>{{ tagger.name() }} <{{ tagger.email() }}></td>
</tr>
{% endif %}
{% if let Some(tagged_object) = tag.tagged_object %}
{% if let Some(tagged_object) = tag.get().tagged_object %}
<tr>
<th>tagged object</th>
<td>
@@ -34,11 +34,13 @@
{% endif %}
<tr>
<th>download</th>
<td colspan="2"><pre><a href="/{{ repo.display() }}/snapshot?h={{ tag.name }}">{{ tag.name }}.tar.gz</a></pre></td>
<td colspan="2">
<pre><a href="/{{ repo.display() }}/snapshot?h={{ tag.get().name }}">{{ tag.get().name }}.tar.gz</a></pre>
</td>
</tr>
</tbody>
</table>
</div>
<pre class="h2-first-line">{{ tag.message }}</pre>
<pre class="h2-first-line">{{ tag.get().message }}</pre>
{% endblock %}
@@ -68,18 +68,19 @@
let mut data = BytesMut::new();
writeln!(data, "From {} Mon Sep 17 00:00:00 2001", commit.oid()).unwrap();
writeln!(data, "From {} Mon Sep 17 00:00:00 2001", commit.get().oid()).unwrap();
writeln!(
data,
"From: {} <{}>",
commit.author().name(),
commit.author().email()
commit.get().author().name(),
commit.get().author().email()
)
.unwrap();
write!(data, "Date: ").unwrap();
let mut writer = data.writer();
commit
.get()
.author()
.time()
.format_into(&mut writer, &Rfc2822)
@@ -87,9 +88,9 @@
let mut data = writer.into_inner();
writeln!(data).unwrap();
writeln!(data, "Subject: [PATCH] {}\n", commit.summary()).unwrap();
writeln!(data, "Subject: [PATCH] {}\n", commit.get().summary()).unwrap();
write!(data, "{}", commit.body()).unwrap();
write!(data, "{}", commit.get().body()).unwrap();
writeln!(data, "---").unwrap();
@@ -1,8 +1,9 @@
use std::sync::Arc;
use askama::Template;
use axum::{extract::Query, response::IntoResponse, Extension};
use serde::Deserialize;
use yoke::Yoke;
use crate::{
git::DetailedTag,
@@ -24,7 +25,7 @@
#[template(path = "repo/tag.html")]
pub struct View {
repo: Repository,
tag: DetailedTag,
tag: Yoke<DetailedTag<'static>, Vec<u8>>,
branch: Option<Arc<str>>,
}