🏡 index : ~doyle/rgit.git

author Jordan Doyle <jordan@doyle.la> 2025-01-08 15:21:05.0 +07:00:00
committer Jordan Doyle <jordan@doyle.la> 2025-01-08 15:21:05.0 +07:00:00
commit
91a9d8421108ad37e36d8bea6f34acafb594743f [patch]
tree
89826c7bf29c0473c35101968f065e894e6ca4bb
parent
73690ab5f75eaa37e4d52bcf64b40b7d3a343c4a
download
91a9d8421108ad37e36d8bea6f34acafb594743f.tar.gz

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(-)

diff --git a/src/git.rs b/src/git.rs
index 7751a81..a0cf052 100644
--- a/src/git.rs
+++ a/src/git.rs
@@ -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),
            // time: OffsetDateTime::from_unix_timestamp(v.time.seconds)?
            //     .to_offset(UtcOffset::from_whole_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
    }
}
diff --git a/src/methods/filters.rs b/src/methods/filters.rs
index 2724abe..a0e15f9 100644
--- a/src/methods/filters.rs
+++ a/src/methods/filters.rs
@@ -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 {
diff --git a/templates/repo/commit.html b/templates/repo/commit.html
index 017a89b..820dc61 100644
--- a/templates/repo/commit.html
+++ a/templates/repo/commit.html
@@ -14,23 +14,23 @@
    <tbody>
    <tr>
        <th>author</th>
        <td>{{ commit.author().name() }} &lt;{{ commit.author().email() }}&gt;</td>
        <td>{{ commit.author().time() }}</td>
        <td>{{ commit.get().author().name() }} &lt;{{ commit.get().author().email() }}&gt;</td>
        <td>{{ commit.get().author().time() }}</td>
    </tr>
    <tr>
        <th>committer</th>
        <td>{{ commit.committer().name() }} &lt;{{ commit.committer().email() }}&gt;</td>
        <td>{{ commit.committer().time() }}</td>
        <td>{{ commit.get().committer().name() }} &lt;{{ commit.get().committer().email() }}&gt;</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 }}
diff --git a/templates/repo/tag.html b/templates/repo/tag.html
index 94ef63c..b034f2a 100644
--- a/templates/repo/tag.html
+++ a/templates/repo/tag.html
@@ -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() }} &lt;{{ tagger.email() }}&gt;</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 %}
diff --git a/src/methods/repo/diff.rs b/src/methods/repo/diff.rs
index ed1ee87..d170173 100644
--- a/src/methods/repo/diff.rs
+++ a/src/methods/repo/diff.rs
@@ -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();

diff --git a/src/methods/repo/tag.rs b/src/methods/repo/tag.rs
index 0b7dab4..1baea7e 100644
--- a/src/methods/repo/tag.rs
+++ a/src/methods/repo/tag.rs
@@ -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>>,
}