1mod about;
2mod commit;
3mod diff;
4mod log;
5mod refs;
6mod smart_git;
7mod summary;
8mod tag;
9mod tree;
10
11use std::{
12 collections::BTreeMap,
13 fmt::Debug,
14 ops::Deref,
15 path::{Path, PathBuf},
16 sync::Arc,
17};
18
19use axum::{
20 body::HttpBody,
21 handler::HandlerWithoutStateExt,
22 http::{Request, StatusCode},
23 response::{IntoResponse, Response},
24};
25use path_clean::PathClean;
26use tower::{util::BoxCloneService, Service};
27
28use self::{
29 about::handle as handle_about,
30 commit::handle as handle_commit,
31 diff::{handle as handle_diff, handle_plain as handle_patch},
32 log::handle as handle_log,
33 refs::handle as handle_refs,
34 smart_git::handle as handle_smart_git,
35 summary::handle as handle_summary,
36 tag::handle as handle_tag,
37 tree::handle as handle_tree,
38};
39use crate::{
40 database::schema::{commit::YokedCommit, tag::YokedTag},
41 layers::UnwrapInfallible,
42};
43
44pub const DEFAULT_BRANCHES: [&str; 2] = ["refs/heads/master", "refs/heads/main"];
45
46// this is some wicked, wicked abuse of axum right here...
47#[allow(clippy::trait_duplication_in_bounds)] // clippy seems a bit.. lost
48pub async fn service<ReqBody>(mut request: Request<ReqBody>) -> Response
49where
50 ReqBody: HttpBody + Send + Debug + 'static,
51 <ReqBody as HttpBody>::Data: Send + Sync,
52 bytes::Bytes: From<ReqBody::Data>,
53 <ReqBody as HttpBody>::Error: std::error::Error + Send + Sync,
54{
55 let scan_path = request
56 .extensions()
57 .get::<Arc<PathBuf>>()
58 .expect("scan_path missing");
59
60 let mut uri_parts: Vec<&str> = request
61 .uri()
62 .path()
63 .trim_start_matches('/')
64 .trim_end_matches('/')
65 .split('/')
66 .collect();
67
68 let mut child_path = None;
69
70 macro_rules! h {
71 ($handler:ident) => {
72 BoxCloneService::new($handler.into_service())
73 };
74 }
75
76 let mut service = match uri_parts.pop() {
77 Some("about") => h!(handle_about),
78 // TODO: https://man.archlinux.org/man/git-http-backend.1.en
79 // TODO: GIT_PROTOCOL
80 Some("refs") if uri_parts.last() == Some(&"info") => {
81 uri_parts.pop();
82 h!(handle_smart_git)
83 }
84 Some("git-upload-pack") => h!(handle_smart_git),
85 Some("refs") => h!(handle_refs),
86 Some("log") => h!(handle_log),
87 Some("tree") => h!(handle_tree),
88 Some("commit") => h!(handle_commit),
89 Some("diff") => h!(handle_diff),
90 Some("patch") => h!(handle_patch),
91 Some("tag") => h!(handle_tag),
92 Some(v) => {
93 uri_parts.push(v);
94
95 // match tree children
96 if uri_parts.iter().any(|v| *v == "tree") {
97 // TODO: this needs fixing up so it doesn't accidentally match repos that have
98 // `tree` in their path
99 let mut reconstructed_path = Vec::new();
100
101 while let Some(part) = uri_parts.pop() {
102 if part == "tree" {
103 break;
104 }
105
106 // TODO: FIXME
107 reconstructed_path.insert(0, part);
108 }
109
110 child_path = Some(reconstructed_path.into_iter().collect::<PathBuf>().clean());
111
112 h!(handle_tree)
113 } else {
114 h!(handle_summary)
115 }
116 }
117 None => panic!("not found"),
118 };
119
120 let uri = uri_parts.into_iter().collect::<PathBuf>().clean();
121 let path = scan_path.join(&uri);
122
123 request.extensions_mut().insert(ChildPath(child_path));
124 request.extensions_mut().insert(Repository(uri));
125 request.extensions_mut().insert(RepositoryPath(path));
126
127 service
128 .call(request)
129 .await
130 .unwrap_infallible()
131 .into_response()
132}
133
134#[derive(Clone)]
135pub struct Repository(pub PathBuf);
136
137impl Deref for Repository {
138 type Target = Path;
139
140 fn deref(&self) -> &Self::Target {
141 &self.0
142 }
143}
144
145#[derive(Clone)]
146pub struct RepositoryPath(pub PathBuf);
147
148#[derive(Clone)]
149pub struct ChildPath(pub Option<PathBuf>);
150
151impl Deref for RepositoryPath {
152 type Target = Path;
153
154 fn deref(&self) -> &Self::Target {
155 &self.0
156 }
157}
158
159pub type Result<T, E = Error> = std::result::Result<T, E>;
160
161pub struct Error(anyhow::Error);
162
163impl From<Arc<anyhow::Error>> for Error {
164 fn from(e: Arc<anyhow::Error>) -> Self {
165 Self(anyhow::Error::msg(format!("{e:?}")))
166 }
167}
168
169impl From<anyhow::Error> for Error {
170 fn from(e: anyhow::Error) -> Self {
171 Self(e)
172 }
173}
174
175impl IntoResponse for Error {
176 fn into_response(self) -> Response {
177 (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", self.0)).into_response()
178 }
179}
180
181pub struct Refs {
182 heads: BTreeMap<String, YokedCommit>,
183 tags: Vec<(String, YokedTag)>,
184}