gitscope
← rgit / 0.1.4 / 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::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::database::schema::tag::YokedString;
 41use crate::{
 42    database::schema::{commit::YokedCommit, tag::YokedTag},
 43    layers::UnwrapInfallible,
 44};
 45
 46pub const DEFAULT_BRANCHES: [&str; 2] = ["refs/heads/master", "refs/heads/main"];
 47
 48// this is some wicked, wicked abuse of axum right here...
 49#[allow(clippy::trait_duplication_in_bounds)] // clippy seems a bit.. lost
 50pub async fn service(mut request: Request<Body>) -> Response {
 51    let scan_path = request
 52        .extensions()
 53        .get::<Arc<PathBuf>>()
 54        .expect("scan_path missing");
 55
 56    let mut child_path = None;
 57
 58    macro_rules! h {
 59        ($handler:ident) => {
 60            BoxCloneService::new($handler.into_service())
 61        };
 62    }
 63
 64    let uri = request
 65        .uri()
 66        .path()
 67        .trim_start_matches('/')
 68        .trim_end_matches('/');
 69    let mut uri_parts = memchr::memchr_iter(b'/', uri.as_bytes());
 70
 71    let original_uri = uri;
 72    let (action, mut uri) = if let Some(idx) = uri_parts.next_back() {
 73        (uri.get(idx + 1..), &uri[..idx])
 74    } else {
 75        (None, uri)
 76    };
 77
 78    let mut service = match action {
 79        Some("about") => h!(handle_about),
 80        Some("git-upload-pack") => h!(handle_smart_git),
 81        Some("refs") => {
 82            if let Some(idx) = uri_parts.next_back() {
 83                if uri.get(idx + 1..) == Some("info") {
 84                    uri = &uri[..idx];
 85                    h!(handle_smart_git)
 86                } else {
 87                    h!(handle_refs)
 88                }
 89            } else {
 90                h!(handle_refs)
 91            }
 92        }
 93        Some("log") => h!(handle_log),
 94        Some("tree") => h!(handle_tree),
 95        Some("commit") => h!(handle_commit),
 96        Some("diff") => h!(handle_diff),
 97        Some("patch") => h!(handle_patch),
 98        Some("tag") => h!(handle_tag),
 99        Some("snapshot") => h!(handle_snapshot),
100        Some(_) => {
101            static TREE_FINDER: LazyLock<memchr::memmem::Finder> =
102                LazyLock::new(|| memchr::memmem::Finder::new(b"/tree/"));
103
104            uri = original_uri;
105
106            // match tree children
107            if let Some(idx) = TREE_FINDER.find(uri.as_bytes()) {
108                // 6 is the length of /tree/
109                child_path = Some(Path::new(&uri[idx + 6..]).clean());
110                uri = &uri[..idx];
111                h!(handle_tree)
112            } else {
113                h!(handle_summary)
114            }
115        }
116        None => h!(handle_summary),
117    };
118
119    let uri = Path::new(uri).clean();
120    let path = scan_path.join(&uri);
121
122    let db = request
123        .extensions()
124        .get::<Arc<rocksdb::DB>>()
125        .expect("db extension missing");
126    if path.as_os_str().is_empty()
127        || !crate::database::schema::repository::Repository::exists(db, &uri).unwrap_or_default()
128    {
129        return RepositoryNotFound.into_response();
130    }
131
132    request.extensions_mut().insert(ChildPath(child_path));
133    request.extensions_mut().insert(Repository(uri));
134    request.extensions_mut().insert(RepositoryPath(path));
135
136    service
137        .call(request)
138        .await
139        .unwrap_infallible()
140        .into_response()
141}
142
143#[derive(Clone)]
144pub struct Repository(pub PathBuf);
145
146impl Deref for Repository {
147    type Target = Path;
148
149    fn deref(&self) -> &Self::Target {
150        &self.0
151    }
152}
153
154#[derive(Clone)]
155pub struct RepositoryPath(pub PathBuf);
156
157#[derive(Clone)]
158pub struct ChildPath(pub Option<PathBuf>);
159
160impl Deref for RepositoryPath {
161    type Target = Path;
162
163    fn deref(&self) -> &Self::Target {
164        &self.0
165    }
166}
167
168pub type Result<T, E = Error> = std::result::Result<T, E>;
169
170pub struct InvalidRequest;
171
172impl IntoResponse for InvalidRequest {
173    fn into_response(self) -> Response {
174        (StatusCode::NOT_FOUND, "Invalid request").into_response()
175    }
176}
177
178pub struct RepositoryNotFound;
179
180impl IntoResponse for RepositoryNotFound {
181    fn into_response(self) -> Response {
182        (StatusCode::NOT_FOUND, "Repository not found").into_response()
183    }
184}
185
186pub struct Error(anyhow::Error);
187
188impl From<Arc<anyhow::Error>> for Error {
189    fn from(e: Arc<anyhow::Error>) -> Self {
190        Self(anyhow::Error::msg(format!("{e:?}")))
191    }
192}
193
194impl From<anyhow::Error> for Error {
195    fn from(e: anyhow::Error) -> Self {
196        Self(e)
197    }
198}
199
200impl IntoResponse for Error {
201    fn into_response(self) -> Response {
202        (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", self.0)).into_response()
203    }
204}
205
206pub struct Refs {
207    heads: BTreeMap<String, YokedCommit>,
208    tags: Vec<(YokedString, YokedTag)>,
209}