Skip to content

Commit

Permalink
feat: Add functionality of git command into serve command, but leave …
Browse files Browse the repository at this point in the history
…it also as separate command since its removal will break current setup
  • Loading branch information
BojanG99 committed Jan 22, 2025
1 parent 7b51d53 commit c4df927
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 0 deletions.
111 changes: 111 additions & 0 deletions src/server/git.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//! Legacy git microserver.
use actix_web::{get, route, web, App, HttpResponse, HttpServer, Responder};
use git2::{self, ErrorCode};
use std::path::PathBuf;
use tracing_actix_web::TracingLogger;

use super::errors::{CliError, HTTPError, StelaeError};
use crate::utils::git::{Repo, GIT_REQUEST_NOT_FOUND};
use crate::utils::http::get_contenttype;
use crate::{server::tracing::StelaeRootSpanBuilder, utils::paths::clean_path};

/// Global, read-only state passed into the actix app
struct AppState {
/// path to the Stelae archive
archive_path: PathBuf,
}

/// Root index path
#[get("/")]
async fn index() -> &'static str {
"Welcome to Stelae"
}

/// Just for development purposes at the moment
#[get("{path}")]
async fn misc(path: web::Path<String>) -> actix_web::Result<&'static str, StelaeError> {
match path.as_str() {
"error" => Err(StelaeError::GitError),
_ => Ok("\u{2728}"),
}
}

/// Return the content in the stelae archive in the `{namespace}/{name}`
/// repo at the `commitish` commit at the `remainder` path.
/// Return 404 if any are not found or there are any errors.
#[route(
"/{namespace}/{name}/{commitish}{remainder:/+([^{}]*?)?/*}",
method = "GET",
method = "HEAD"
)]
#[tracing::instrument(name = "Retrieving a Git blob", skip(path, data))]
async fn get_blob(
path: web::Path<(String, String, String, String)>,
data: web::Data<AppState>,
) -> impl Responder {
let (namespace, name, commitish, remainder) = path.into_inner();
let archive_path = &data.archive_path;
let blob = Repo::find_blob(archive_path, &namespace, &name, &remainder, &commitish);
let blob_path = clean_path(&remainder);
let contenttype = get_contenttype(&blob_path);
match blob {
Ok(content) => HttpResponse::Ok().insert_header(contenttype).body(content),
Err(error) => blob_error_response(&error, &namespace, &name),
}
}

/// A centralised place to match potentially unsafe internal errors to safe user-facing error responses
#[expect(
clippy::wildcard_enum_match_arm,
reason = "Default to wildcard match in case of unexpected errors"
)]
#[tracing::instrument(name = "Error with Git blob request", skip(error, namespace, name))]
fn blob_error_response(error: &anyhow::Error, namespace: &str, name: &str) -> HttpResponse {
tracing::error!("{error}",);
if let Some(git_error) = error.downcast_ref::<git2::Error>() {
return match git_error.code() {
// TODO: check this is the right error
ErrorCode::NotFound => {
HttpResponse::NotFound().body(format!("repo {namespace}/{name} does not exist"))
}
_ => HttpResponse::InternalServerError().body("Unexpected Git error"),
};
}
match error {
// TODO: Obviously it's better to use custom `Error` types
_ if error.to_string() == GIT_REQUEST_NOT_FOUND => {
HttpResponse::NotFound().body(HTTPError::NotFound.to_string())
}
_ => HttpResponse::InternalServerError().body(HTTPError::InternalServerError.to_string()),
}
}

/// Serve git repositories in the Stelae archive.
#[actix_web::main] // or #[tokio::main]
pub async fn serve_git(
raw_archive_path: &str,
archive_path: PathBuf,
port: u16,
) -> Result<(), CliError> {
let bind = "127.0.0.1";
let message = "Serving content from the Stelae archive at";
tracing::info!("{message} '{raw_archive_path}' on http://{bind}:{port}.",);

HttpServer::new(move || {
App::new()
.wrap(TracingLogger::<StelaeRootSpanBuilder>::new())
.service(index)
.service(misc)
.service(get_blob)
.app_data(web::Data::new(AppState {
archive_path: archive_path.clone(),
}))
})
.bind((bind, port))?
.run()
.await
.map_err(|err| {
tracing::error!("Error running Git server: {err:?}");
CliError::GenericError
})
}
1 change: 1 addition & 0 deletions src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
pub mod api;
pub mod app;
pub mod errors;
pub mod git;
pub mod tracing;
8 changes: 8 additions & 0 deletions src/utils/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use crate::history::changes;
use crate::server::app::serve_archive;
use crate::server::errors::CliError;
use crate::server::git::serve_git;
use crate::utils::archive::find_archive_path;
use clap::Parser;
use std::env;
Expand Down Expand Up @@ -40,6 +41,12 @@ struct Cli {
/// Subcommands for the Stelae CLI
#[derive(Clone, clap::Subcommand)]
enum Subcommands {
/// Serve git repositories in the Stelae archive
Git {
/// Port on which to serve the archive.
#[arg(short, long, default_value_t = 8080)]
port: u16,
},
/// Serve documents in a Stelae archive.
Serve {
/// Port on which to serve the archive.
Expand Down Expand Up @@ -108,6 +115,7 @@ fn init_tracing(archive_path: &Path) {
/// This function returns the generic `CliError`, based on which we exit with a known exit code.
fn execute_command(cli: &Cli, archive_path: PathBuf) -> Result<(), CliError> {
match cli.subcommands {
Subcommands::Git { port } => serve_git(&cli.archive_path, archive_path, port),
Subcommands::Serve { port, individual } => {
serve_archive(&cli.archive_path, archive_path, port, individual)
}
Expand Down

0 comments on commit c4df927

Please sign in to comment.