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