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) -> String {
55 self.inner
56 .config()
57 .and_then(|config| config.get_string("gitweb.owner"))
58 .unwrap_or_default()
59 }
60
61 #[must_use]
62 pub(crate) fn path(&self) -> &Path {
63 self.inner.path()
64 }
65
66 #[must_use]
67 pub(crate) fn readme(&self, syntaxes: &SyntaxSet) -> String {
68 use askama::filters::Escaper as _;
69
70 enum ReadmeFormat {
71 Plaintext,
72 Html,
73 Markdown,
74 }
75
76 let mut format = ReadmeFormat::Plaintext;
77
78 self.inner
79 .revparse_single("HEAD:readme")
80 .or_else(|_| self.inner.revparse_single("HEAD:README.txt"))
81 .or_else(|_| self.inner.revparse_single("HEAD:readme.txt"))
82 .or_else(|_| self.inner.revparse_single("HEAD:README.txt"))
83 .or_else(|_| {
84 format = ReadmeFormat::Markdown;
85 self.inner.revparse_single("HEAD:readme.md")
86 })
87 .or_else(|_| self.inner.revparse_single("HEAD:README.mdown"))
88 .or_else(|_| self.inner.revparse_single("HEAD:readme.mdown"))
89 .or_else(|_| self.inner.revparse_single("HEAD:README.mdown"))
90 .or_else(|_| self.inner.revparse_single("HEAD:readme.markdown"))
91 .or_else(|_| self.inner.revparse_single("HEAD:README.markdown"))
92 .or_else(|_| {
93 format = ReadmeFormat::Html;
94 self.inner.revparse_single("HEAD:readme.html")
95 })
96 .or_else(|_| self.inner.revparse_single("HEAD:README.html"))
97 .or_else(|_| self.inner.revparse_single("HEAD:readme.htm"))
98 .or_else(|_| self.inner.revparse_single("HEAD:README.htm"))
99 .ok()
100 .and_then(|readme| readme.into_blob().ok())
101 .map(|blob| {
102 let text = str::from_utf8(blob.content()).unwrap_or_default();
103
104 match format {
106 ReadmeFormat::Plaintext => {
108 let mut output = "<pre>".to_string();
109 if let Err(err) = askama::filters::Html.write_escaped_str(&mut output, text)
110 {
111 tracing::error!(err=?err, "failed to write escaped plaintext readme");
112 }
113 output.push_str("</pre>");
114 output
115 }
116 ReadmeFormat::Html => text.to_string(),
118 ReadmeFormat::Markdown => crate::utils::markdown::render(syntaxes, text),
120 }
121 })
122 .unwrap_or_default()
123 }
124
125 #[tracing::instrument(skip_all)]
126 pub(crate) fn ref_or_head_shorthand(&self, r#ref: Option<&Ref>) -> Result<String> {
127 let head = self.head()?;
128
129 let spec = r#ref
130 .map(|r| r.0.clone())
131 .or_else(move || head.shorthand().map(str::to_string))
132 .context("failed to get repo ref spec")?;
133
134 Ok(spec)
135 }
136}
137