gitscope
← rgit / main / src/methods/repo/mod.rs
https://dev.kronoz.win/r/rgit.git
src/methods/repo/mod.rs · Rust
Blame Raw
  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, LazyLock},
 17};
 18
 19use axum::{
 20    body::Body,
 21    handler::Handler,
 22    http::{Request, StatusCode},
 23    response::{IntoResponse, Response},
 24};
 25use path_clean::PathClean;
 26
 27use self::{
 28    about::handle as handle_about,
 29    commit::handle as handle_commit,
 30    diff::{handle as handle_diff, handle_plain as handle_patch},
 31    log::handle as handle_log,
 32    refs::handle as handle_refs,
 33    smart_git::handle as handle_smart_git,
 34    snapshot::handle as handle_snapshot,
 35    summary::handle as handle_summary,
 36    tag::handle as handle_tag,
 37    tree::handle as handle_tree,
 38};
 39use crate::database::schema::{
 40    commit::YokedCommit,
 41    tag::{YokedString, YokedTag},
 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(mut request: Request<Body>) -> Response {
 49    let scan_path = request
 50        .extensions()
 51        .get::<Arc<PathBuf>>()
 52        .expect("scan_path missing");
 53
 54    let ParsedUri {
 55        uri,
 56        child_path,
 57        action,
 58    } = parse_uri(request.uri().path().trim_matches('/'));
 59
 60    let uri = Path::new(uri).clean();
 61    let path = scan_path.join(&uri);
 62
 63    let db = request
 64        .extensions()
 65        .get::<Arc<rocksdb::DB>>()
 66        .expect("db extension missing");
 67    if path.as_os_str().is_empty()
 68        || !crate::database::schema::repository::Repository::exists(db, &uri).unwrap_or_default()
 69    {
 70        return RepositoryNotFound.into_response();
 71    }
 72
 73    request.extensions_mut().insert(ChildPath(child_path));
 74    request.extensions_mut().insert(Repository(uri));
 75    request.extensions_mut().insert(RepositoryPath(path));
 76
 77    match action {
 78        HandlerAction::About => handle_about.call(request, None::<()>).await,
 79        HandlerAction::SmartGit => handle_smart_git.call(request, None::<()>).await,
 80        HandlerAction::Refs => handle_refs.call(request, None::<()>).await,
 81        HandlerAction::Log => handle_log.call(request, None::<()>).await,
 82        HandlerAction::Tree => handle_tree.call(request, None::<()>).await,
 83        HandlerAction::Commit => handle_commit.call(request, None::<()>).await,
 84        HandlerAction::Diff => handle_diff.call(request, None::<()>).await,
 85        HandlerAction::Patch => handle_patch.call(request, None::<()>).await,
 86        HandlerAction::Tag => handle_tag.call(request, None::<()>).await,
 87        HandlerAction::Snapshot => handle_snapshot.call(request, None::<()>).await,
 88        HandlerAction::Summary => handle_summary.call(request, None::<()>).await,
 89    }
 90}
 91
 92#[derive(Debug, PartialEq, Eq)]
 93struct ParsedUri<'a> {
 94    action: HandlerAction,
 95    uri: &'a str,
 96    child_path: Option<PathBuf>,
 97}
 98
 99fn parse_uri(uri: &str) -> ParsedUri<'_> {
100    let mut uri_parts = memchr::memchr_iter(b'/', uri.as_bytes());
101
102    let original_uri = uri;
103    let (action, mut uri) = if let Some(idx) = uri_parts.next_back() {
104        (uri.get(idx + 1..), &uri[..idx])
105    } else {
106        (None, uri)
107    };
108
109    match action {
110        Some("about") => ParsedUri {
111            action: HandlerAction::About,
112            uri,
113            child_path: None,
114        },
115        Some("git-upload-pack") => ParsedUri {
116            action: HandlerAction::SmartGit,
117            uri,
118            child_path: None,
119        },
120        Some("refs") => {
121            if let Some(idx) = uri_parts.next_back() {
122                if uri.get(idx + 1..) == Some("info") {
123                    ParsedUri {
124                        action: HandlerAction::SmartGit,
125                        uri: &uri[..idx],
126                        child_path: None,
127                    }
128                } else {
129                    ParsedUri {
130                        action: HandlerAction::Refs,
131                        uri,
132                        child_path: None,
133                    }
134                }
135            } else {
136                ParsedUri {
137                    action: HandlerAction::Refs,
138                    uri,
139                    child_path: None,
140                }
141            }
142        }
143        Some("log") => ParsedUri {
144            action: HandlerAction::Log,
145            uri,
146            child_path: None,
147        },
148        Some("tree") => ParsedUri {
149            action: HandlerAction::Tree,
150            uri,
151            child_path: None,
152        },
153        Some("commit") => ParsedUri {
154            action: HandlerAction::Commit,
155            uri,
156            child_path: None,
157        },
158        Some("diff") => ParsedUri {
159            action: HandlerAction::Diff,
160            uri,
161            child_path: None,
162        },
163        Some("patch") => ParsedUri {
164            action: HandlerAction::Patch,
165            uri,
166            child_path: None,
167        },
168        Some("tag") => ParsedUri {
169            action: HandlerAction::Tag,
170            uri,
171            child_path: None,
172        },
173        Some("snapshot") => ParsedUri {
174            action: HandlerAction::Snapshot,
175            uri,
176            child_path: None,
177        },
178        Some(_) => {
179            static TREE_FINDER: LazyLock<memchr::memmem::Finder> =
180                LazyLock::new(|| memchr::memmem::Finder::new(b"/tree/"));
181
182            uri = original_uri;
183
184            // match tree children
185            if let Some(idx) = TREE_FINDER.find(uri.as_bytes()) {
186                ParsedUri {
187                    action: HandlerAction::Tree,
188                    uri: &uri[..idx],
189                    // 6 is the length of /tree/
190                    child_path: Some(Path::new(&uri[idx + 6..]).clean()),
191                }
192            } else {
193                ParsedUri {
194                    action: HandlerAction::Summary,
195                    uri,
196                    child_path: None,
197                }
198            }
199        }
200        None => ParsedUri {
201            action: HandlerAction::Summary,
202            uri,
203            child_path: None,
204        },
205    }
206}
207
208#[derive(Copy, Clone, Debug, PartialEq, Eq)]
209enum HandlerAction {
210    About,
211    SmartGit,
212    Refs,
213    Log,
214    Tree,
215    Commit,
216    Diff,
217    Patch,
218    Tag,
219    Snapshot,
220    Summary,
221}
222
223#[derive(Clone)]
224pub struct Repository(pub PathBuf);
225
226impl Deref for Repository {
227    type Target = Path;
228
229    fn deref(&self) -> &Self::Target {
230        &self.0
231    }
232}
233
234#[derive(Clone)]
235pub struct RepositoryPath(pub PathBuf);
236
237#[derive(Clone)]
238pub struct ChildPath(pub Option<PathBuf>);
239
240impl Deref for RepositoryPath {
241    type Target = Path;
242
243    fn deref(&self) -> &Self::Target {
244        &self.0
245    }
246}
247
248pub type Result<T, E = Error> = std::result::Result<T, E>;
249
250pub struct InvalidRequest;
251
252impl IntoResponse for InvalidRequest {
253    fn into_response(self) -> Response {
254        (StatusCode::NOT_FOUND, "Invalid request").into_response()
255    }
256}
257
258pub struct RepositoryNotFound;
259
260impl IntoResponse for RepositoryNotFound {
261    fn into_response(self) -> Response {
262        (StatusCode::NOT_FOUND, "Repository not found").into_response()
263    }
264}
265
266pub struct Error(anyhow::Error);
267
268impl From<Arc<anyhow::Error>> for Error {
269    fn from(e: Arc<anyhow::Error>) -> Self {
270        Self(anyhow::Error::msg(format!("{e:?}")))
271    }
272}
273
274impl From<anyhow::Error> for Error {
275    fn from(e: anyhow::Error) -> Self {
276        Self(e)
277    }
278}
279
280impl From<Error> for anyhow::Error {
281    fn from(value: Error) -> Self {
282        value.0
283    }
284}
285
286impl IntoResponse for Error {
287    fn into_response(self) -> Response {
288        (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", self.0)).into_response()
289    }
290}
291
292pub struct Refs {
293    heads: BTreeMap<String, YokedCommit>,
294    tags: Vec<(YokedString, YokedTag)>,
295}