wayver's git archive


a simple self-hosted git server
git clone https://git.wayver.dev/bile

src/utils/filters.rs@375565f690b958e08f589a7fee998ad5f47a70d0

raw
Date Commit Message Author Files + -
2026-02-17 21:07 initial mvp wayverd 74 10800 0
...

1use git2::{Commit, Signature, Time};
2use jiff::{
3    Timestamp,
4    tz::{self, TimeZone},
5};
6use num_conv::Truncate as _;
7
8use crate::utils::git::Repository;
9
10#[askama::filter_fn]
11#[tracing::instrument(skip_all)]
12pub fn format_datetime<'f>(
13    time: Time,
14    _: &dyn askama::Values,
15    format: &'f str,
16) -> askama::Result<jiff::fmt::strtime::Display<'f>> {
17    let tz = TimeZone::fixed(tz::offset((time.offset_minutes() / 60).truncate::<i8>()));
18
19    let timestamp = Timestamp::from_second(time.seconds()).map_err(|err| {
20        tracing::error!(err=%err, "failed to convert seconds into a timestamp");
21
22        askama::Error::Fmt
23    })?;
24
25    let zoned = timestamp.to_zoned(tz);
26
27    let format = zoned.strftime(format);
28
29    Ok(format)
30}
31
32#[askama::filter_fn]
33pub fn unix_perms(m: i32, _: &dyn askama::Values) -> askama::Result<String> {
34    // https://unix.stackexchange.com/questions/450480/file-permission-with-six-bytes-in-git-what-does-it-mean
35    // Git doesn't store arbitrary modes, only a subset of the values are
36    // allowed. Since the number of possible values is quite small, it is
37    // easiest to exhaustively match them.
38    Ok(match m {
39        0o040_000 => "drwxr-xr-x", // directory
40        0o100_755 => "-rwxr-xr-x", // regular file, executable
41        0o100_644 => "-rw-r--r--", // regular file, default umask
42        0o120_000 => "lrwxrwxrwx", // symlink
43        0o160_000 => "m---------", // submodule
44        _ => unreachable!("unknown file mode"),
45    }
46    .into())
47}
48
49#[askama::filter_fn]
50pub fn repo_name<'r>(repo: &'r Repository, _: &dyn askama::Values) -> askama::Result<&'r str> {
51    repo.name().ok_or(askama::Error::Fmt)
52}
53
54#[askama::filter_fn]
55pub fn description(repo: &Repository, _: &dyn askama::Values) -> askama::Result<String> {
56    Ok(repo.description())
57}
58
59#[askama::filter_fn]
60#[tracing::instrument(skip_all)]
61pub fn last_modified(repo: &Repository, _: &dyn askama::Values) -> askama::Result<Time> {
62    repo.last_modified().map_err(|err| {
63        tracing::error!(err=?err, "failed to get repo's last modified date");
64        askama::Error::Fmt
65    })
66}
67
68#[askama::filter_fn]
69pub fn repo_owner(repo: &Repository, _: &dyn askama::Values) -> askama::Result<String> {
70    Ok(repo.owner())
71}
72
73#[askama::filter_fn]
74pub fn signature_email_link(
75    signature: &Signature<'_>,
76    _: &dyn askama::Values,
77) -> askama::Result<String> {
78    let Some(email) = signature.email() else {
79        return Ok(signature.to_string());
80    };
81
82    Ok(format!(
83        "<a href=\"mailto:{}\">{}</a>",
84        email,
85        signature.name().unwrap_or("&#65533;")
86    ))
87}
88
89#[askama::filter_fn]
90#[tracing::instrument(skip_all)]
91pub fn short_id(commit: &Commit<'_>, _: &dyn askama::Values) -> askama::Result<String> {
92    let id = match commit.as_object().short_id() {
93        Ok(id) => id,
94        Err(err) => {
95            tracing::error!(err=?err, "failed to get short id of commit");
96
97            return Err(askama::Error::Fmt);
98        }
99    };
100
101    id.as_str().map(str::to_string).ok_or(askama::Error::Fmt)
102}
103