src/git/core.rs@main
raw
1use std::path::Path;
2
3use git2::{Reference, Time};
4use syntect::parsing::SyntaxSet;
5
6use crate::{error::Context as _, error::Result, git::Repository, http::extractor::Ref};
7
8impl Repository {
9 #[must_use]
10 pub(crate) fn description(&self) -> String {
11 let content = std::fs::read_to_string(self.path().join("description")).unwrap_or_default();
12
13 let first = content.lines().next().unwrap_or_default();
14
15 first.to_string()
16 }
17
18 #[tracing::instrument(skip_all)]
19 pub(crate) fn head(&self) -> Result<Reference<'_>> {
20 let head = self.inner.head()?;
21
22 Ok(head)
23 }
24
25 #[tracing::instrument(skip_all)]
26 pub(crate) fn is_empty(&self) -> Result<bool> {
27 Ok(self.inner.is_empty()?)
28 }
29
30 #[must_use]
31 pub(crate) fn is_shallow(&self) -> bool {
32 self.inner.is_shallow()
33 }
34
35 #[tracing::instrument(skip_all)]
36 pub(crate) fn last_modified(&self) -> Result<Time> {
37 let head = self.head()?;
38 let commit = head.peel_to_commit()?;
39 let time = commit.committer().when();
40
41 Ok(time)
42 }
43
44 pub(crate) fn name(&self) -> Option<&str> {
45 self.inner
46 .workdir()
47 .unwrap_or_else(|| self.inner.path())
49 .file_name()
50 .and_then(std::ffi::OsStr::to_str)
51 }
52
53 #[must_use]
54 pub(crate) fn owner(&self) -> Option<String> {
55 self.inner
56 .config()
57 .and_then(|config| config.get_string("gitweb.owner"))
58 .ok()
59 }
60
61 #[must_use]
62 pub(crate) fn section(&self) -> Option<String> {
63 self.inner
64 .config()
65 .and_then(|config| config.get_string("bile.section"))
66 .ok()
67 }
68
69 #[must_use]
70 pub(crate) fn path(&self) -> &Path {
71 self.inner.path()
72 }
73
74 #[must_use]
75 pub(crate) fn readme(&self, syntaxes: &SyntaxSet) -> String {
76 use askama::filters::Escaper as _;
77
78 enum ReadmeFormat {
79 Plaintext,
80 Html,
81 Markdown,
82 }
83
84 let mut format = ReadmeFormat::Plaintext;
85
86 self.inner
87 .revparse_single("HEAD:readme")
88 .or_else(|_| self.inner.revparse_single("HEAD:README.txt"))
89 .or_else(|_| self.inner.revparse_single("HEAD:readme.txt"))
90 .or_else(|_| self.inner.revparse_single("HEAD:README.txt"))
91 .or_else(|_| {
92 format = ReadmeFormat::Markdown;
93 self.inner.revparse_single("HEAD:readme.md")
94 })
95 .or_else(|_| self.inner.revparse_single("HEAD:README.md"))
96 .or_else(|_| self.inner.revparse_single("HEAD:readme.mdown"))
97 .or_else(|_| self.inner.revparse_single("HEAD:README.mdown"))
98 .or_else(|_| self.inner.revparse_single("HEAD:readme.markdown"))
99 .or_else(|_| self.inner.revparse_single("HEAD:README.markdown"))
100 .or_else(|_| {
101 format = ReadmeFormat::Html;
102 self.inner.revparse_single("HEAD:readme.html")
103 })
104 .or_else(|_| self.inner.revparse_single("HEAD:README.html"))
105 .or_else(|_| self.inner.revparse_single("HEAD:readme.htm"))
106 .or_else(|_| self.inner.revparse_single("HEAD:README.htm"))
107 .ok()
108 .and_then(|readme| readme.into_blob().ok())
109 .map(|blob| {
110 let text = str::from_utf8(blob.content()).unwrap_or_default();
111
112 match format {
114 ReadmeFormat::Plaintext => {
116 let mut output = "<pre>".to_string();
117 if let Err(err) = askama::filters::Html.write_escaped_str(&mut output, text)
118 {
119 tracing::error!(err=?err, "failed to write escaped plaintext readme");
120 }
121 output.push_str("</pre>");
122 output
123 }
124 ReadmeFormat::Html => text.to_string(),
126 ReadmeFormat::Markdown => crate::utils::markdown::render(syntaxes, text),
128 }
129 })
130 .unwrap_or_default()
131 }
132
133 #[tracing::instrument(skip_all)]
134 pub(crate) fn ref_or_head_shorthand(&self, r#ref: Option<&Ref>) -> Result<String> {
135 let head = self.head()?;
136
137 let spec = r#ref
138 .map(|r| r.0.clone())
139 .or_else(move || head.shorthand().map(str::to_string))
140 .context("failed to get repo ref spec")?;
141
142 Ok(spec)
143 }
144}
145