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