gitscope
← rgit / 0.1.3 / 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,
 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}