mod about;
mod commit;
mod diff;
mod log;
mod refs;
mod smart_git;
mod snapshot;
mod summary;
mod tag;
mod tree;
use std::{
collections::BTreeMap,
ops::Deref,
path::{Path, PathBuf},
sync::{Arc, LazyLock},
};
use axum::{
body::Body,
handler::Handler,
http::{Request, StatusCode},
response::{IntoResponse, Response},
};
use path_clean::PathClean;
use self::{
about::handle as handle_about,
commit::handle as handle_commit,
diff::{handle as handle_diff, handle_plain as handle_patch},
log::handle as handle_log,
refs::handle as handle_refs,
smart_git::handle as handle_smart_git,
snapshot::handle as handle_snapshot,
summary::handle as handle_summary,
tag::handle as handle_tag,
tree::handle as handle_tree,
};
use crate::database::schema::tag::YokedString;
use crate::database::schema::{commit::YokedCommit, tag::YokedTag};
pub const DEFAULT_BRANCHES: [&str; 2] = ["refs/heads/master", "refs/heads/main"];
#[allow(clippy::trait_duplication_in_bounds)]
pub async fn service(mut request: Request<Body>) -> Response {
let scan_path = request
.extensions()
.get::<Arc<PathBuf>>()
.expect("scan_path missing");
let ParsedUri {
uri,
child_path,
action,
} = parse_uri(request.uri().path().trim_matches('/'));
let uri = Path::new(uri).clean();
let path = scan_path.join(&uri);
let db = request
.extensions()
.get::<Arc<rocksdb::DB>>()
.expect("db extension missing");
if path.as_os_str().is_empty()
|| !crate::database::schema::repository::Repository::exists(db, &uri).unwrap_or_default()
{
return RepositoryNotFound.into_response();
}
request.extensions_mut().insert(ChildPath(child_path));
request.extensions_mut().insert(Repository(uri));
request.extensions_mut().insert(RepositoryPath(path));
match action {
HandlerAction::About => handle_about.call(request, None::<()>).await,
HandlerAction::SmartGit => handle_smart_git.call(request, None::<()>).await,
HandlerAction::Refs => handle_refs.call(request, None::<()>).await,
HandlerAction::Log => handle_log.call(request, None::<()>).await,
HandlerAction::Tree => handle_tree.call(request, None::<()>).await,
HandlerAction::Commit => handle_commit.call(request, None::<()>).await,
HandlerAction::Diff => handle_diff.call(request, None::<()>).await,
HandlerAction::Patch => handle_patch.call(request, None::<()>).await,
HandlerAction::Tag => handle_tag.call(request, None::<()>).await,
HandlerAction::Snapshot => handle_snapshot.call(request, None::<()>).await,
HandlerAction::Summary => handle_summary.call(request, None::<()>).await,
}
}
#[derive(Debug, PartialEq, Eq)]
struct ParsedUri<'a> {
action: HandlerAction,
uri: &'a str,
child_path: Option<PathBuf>,
}
fn parse_uri(uri: &str) -> ParsedUri<'_> {
let mut uri_parts = memchr::memchr_iter(b'/', uri.as_bytes());
let original_uri = uri;
let (action, mut uri) = if let Some(idx) = uri_parts.next_back() {
(uri.get(idx + 1..), &uri[..idx])
} else {
(None, uri)
};
match action {
Some("about") => ParsedUri {
action: HandlerAction::About,
uri,
child_path: None,
},
Some("git-upload-pack") => ParsedUri {
action: HandlerAction::SmartGit,
uri,
child_path: None,
},
Some("refs") => {
if let Some(idx) = uri_parts.next_back() {
if uri.get(idx + 1..) == Some("info") {
ParsedUri {
action: HandlerAction::SmartGit,
uri: &uri[..idx],
child_path: None,
}
} else {
ParsedUri {
action: HandlerAction::Refs,
uri,
child_path: None,
}
}
} else {
ParsedUri {
action: HandlerAction::Refs,
uri,
child_path: None,
}
}
}
Some("log") => ParsedUri {
action: HandlerAction::Log,
uri,
child_path: None,
},
Some("tree") => ParsedUri {
action: HandlerAction::Tree,
uri,
child_path: None,
},
Some("commit") => ParsedUri {
action: HandlerAction::Commit,
uri,
child_path: None,
},
Some("diff") => ParsedUri {
action: HandlerAction::Diff,
uri,
child_path: None,
},
Some("patch") => ParsedUri {
action: HandlerAction::Patch,
uri,
child_path: None,
},
Some("tag") => ParsedUri {
action: HandlerAction::Tag,
uri,
child_path: None,
},
Some("snapshot") => ParsedUri {
action: HandlerAction::Snapshot,
uri,
child_path: None,
},
Some(_) => {
static TREE_FINDER: LazyLock<memchr::memmem::Finder> =
LazyLock::new(|| memchr::memmem::Finder::new(b"/tree/"));
uri = original_uri;
if let Some(idx) = TREE_FINDER.find(uri.as_bytes()) {
ParsedUri {
action: HandlerAction::Tree,
uri: &uri[..idx],
child_path: Some(Path::new(&uri[idx + 6..]).clean()),
}
} else {
ParsedUri {
action: HandlerAction::Summary,
uri,
child_path: None,
}
}
}
None => ParsedUri {
action: HandlerAction::Summary,
uri,
child_path: None,
},
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum HandlerAction {
About,
SmartGit,
Refs,
Log,
Tree,
Commit,
Diff,
Patch,
Tag,
Snapshot,
Summary,
}
#[derive(Clone)]
pub struct Repository(pub PathBuf);
impl Deref for Repository {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone)]
pub struct RepositoryPath(pub PathBuf);
#[derive(Clone)]
pub struct ChildPath(pub Option<PathBuf>);
impl Deref for RepositoryPath {
type Target = Path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
pub struct InvalidRequest;
impl IntoResponse for InvalidRequest {
fn into_response(self) -> Response {
(StatusCode::NOT_FOUND, "Invalid request").into_response()
}
}
pub struct RepositoryNotFound;
impl IntoResponse for RepositoryNotFound {
fn into_response(self) -> Response {
(StatusCode::NOT_FOUND, "Repository not found").into_response()
}
}
pub struct Error(anyhow::Error);
impl From<Arc<anyhow::Error>> for Error {
fn from(e: Arc<anyhow::Error>) -> Self {
Self(anyhow::Error::msg(format!("{e:?}")))
}
}
impl From<anyhow::Error> for Error {
fn from(e: anyhow::Error) -> Self {
Self(e)
}
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
(StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", self.0)).into_response()
}
}
pub struct Refs {
heads: BTreeMap<String, YokedCommit>,
tags: Vec<(YokedString, YokedTag)>,
}