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