don't
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(git-service): support `git archive` service

Signed-off-by: tjh <did:plc:65gha4t3avpfpzmvpbwovss7>

+120 -2
+17
crates/git-service/src/commands.rs
··· 3 3 use std::{ffi::OsStr, path::Path}; 4 4 use tokio::process::Command; 5 5 6 + pub fn upload_archive<S, P>(git: S, path: P) -> Command 7 + where 8 + S: AsRef<OsStr>, 9 + P: AsRef<Path>, 10 + { 11 + let path = path.as_ref(); 12 + 13 + let mut command = Command::new(git); 14 + command 15 + .env_clear() 16 + .current_dir(path) 17 + .args(["upload-archive"]) 18 + .arg(path.as_os_str()); 19 + 20 + command 21 + } 22 + 6 23 pub fn upload_pack_info_refs<S, P>(git: S, path: P) -> Command 7 24 where 8 25 S: AsRef<OsStr>,
+6 -1
crates/git-service/src/lib.rs
··· 3 3 pub mod types; 4 4 5 5 mod receive_pack; 6 + mod upload_archive; 6 7 mod upload_pack; 7 8 8 9 pub(crate) mod util; ··· 25 24 use axum::routing::{get, post}; 26 25 Router::<S>::new() 27 26 .route("/info/refs", get(info_refs::<S>)) 27 + .route( 28 + "/git-upload-archive", 29 + post(upload_archive::upload_archive::<S>), 30 + ) 28 31 .route("/git-upload-pack", post(upload_pack::upload_pack::<S>)) 29 32 .route("/git-receive-pack", post(receive_pack::receive_pack::<S>)) 30 33 } 31 34 32 35 /// Parameters for 'info/refs' 33 - #[derive(serde::Deserialize)] 36 + #[derive(Debug, serde::Deserialize)] 34 37 pub struct InfoRefsQuery { 35 38 pub service: GitService, 36 39 }
+13
crates/git-service/src/state.rs
··· 9 9 /// Maximum body size in bytes for requests. 10 10 const MAX_REQUEST_BODY_SIZE: usize = 1024 * 1024; 11 11 12 + /// Initialise the [`Command`] to use for serving "$GIT_URL/git-upload-archive" 13 + /// requests. 14 + fn init_upload_archive( 15 + &self, 16 + parts: &mut Parts, 17 + ) -> impl Future<Output = Result<Command, Self::Rejection>> + Send; 18 + 12 19 /// Initialise the [`Command`] to use for serving "$GIT_URL/info/refs?service=git-upload-pack" 13 20 /// requests. 14 21 fn init_upload_pack_advertisement( ··· 109 102 P: AsRef<std::path::Path> + Sync + 'static, 110 103 { 111 104 type Rejection = Error; 105 + 106 + async fn init_upload_archive(&self, parts: &mut Parts) -> Result<Command, Self::Rejection> { 107 + let repo_path = resolve_path(self.as_ref(), parts).await?; 108 + let command = crate::commands::upload_archive(GIT_COMMAND, repo_path); 109 + Ok(command) 110 + } 112 111 113 112 async fn init_upload_pack_advertisement( 114 113 &self,
+62
crates/git-service/src/upload_archive.rs
··· 1 + use std::process::Stdio; 2 + 3 + use axum::{ 4 + extract::{Request, State}, 5 + http::header::CONTENT_TYPE, 6 + response::IntoResponse, 7 + }; 8 + use axum_extra::body::AsyncReadBody; 9 + use tokio::{io::AsyncWriteExt as _, net::unix::pipe::Sender}; 10 + 11 + use crate::{state::GitServiceState, types::GitProtocol, util::SetOptionEnv as _}; 12 + 13 + const UPLOAD_ARCHIVE_RESULT: &str = "application/x-git-upload-archive-result"; 14 + 15 + pub async fn upload_archive<S>( 16 + State(state): State<S>, 17 + protocol: Option<GitProtocol>, 18 + req: Request, 19 + ) -> Result<impl IntoResponse, S::Rejection> 20 + where 21 + S: GitServiceState, 22 + S::Rejection: From<std::io::Error>, 23 + S::Rejection: From<axum::Error>, 24 + { 25 + tracing::warn!(?req); 26 + let (mut parts, body) = req.into_parts(); 27 + let mut command = state.init_upload_archive(&mut parts).await?; 28 + 29 + // The request body for git-upload-pack should be quite small, so just 30 + // buffer it. 31 + let mut request_body = axum::body::to_bytes(body, S::MAX_REQUEST_BODY_SIZE) 32 + .await 33 + .inspect_err(|error| tracing::error!(?error))?; 34 + 35 + let mut child = command 36 + .option_env("GIT_PROTOCOL", protocol.as_deref()) 37 + .stdin(Stdio::piped()) 38 + .stdout(Stdio::piped()) 39 + .stderr(Stdio::piped()) 40 + .spawn()?; 41 + 42 + let stdin = child.stdin.take().ok_or(std::io::Error::other( 43 + "failed to take stdin of child process", 44 + ))?; 45 + 46 + let stdout = child.stdout.take().ok_or(std::io::Error::other( 47 + "failed to take stdout of child process", 48 + ))?; 49 + 50 + // Copy the buffered request body to the child process's stdin. 51 + let mut sender = Sender::from_owned_fd(stdin.into_owned_fd()?)?; 52 + tokio::spawn(async move { 53 + if let Err(error) = sender.write_all_buf(&mut request_body).await { 54 + tracing::error!(?error, "error writing to child stdin"); 55 + } 56 + }); 57 + 58 + tokio::spawn(crate::util::await_child(child)); 59 + 60 + let headers = [(CONTENT_TYPE, UPLOAD_ARCHIVE_RESULT)]; 61 + Ok((headers, AsyncReadBody::new(stdout)).into_response()) 62 + }
+22 -1
crates/knot/src/model.rs
··· 68 68 impl GitServiceState for Knot { 69 69 type Rejection = Error; 70 70 71 + async fn init_upload_archive(&self, parts: &mut Parts) -> Result<Command, Self::Rejection> { 72 + use axum::extract::Path; 73 + let Path(repopath) = Path::<RepositoryPath>::from_request_parts(parts, &()).await?; 74 + let resolved = self.resolve_repo_path(&repopath).await.map_err(NotFound)?; 75 + let repo = self.open_repository(repopath).await.map_err(NotFound)?; 76 + let path = repo.path(); 77 + 78 + let mut command = Command::new("/usr/bin/git"); 79 + command 80 + .env_clear() 81 + .env(private::ENV_PRIVATE_ENDPOINTS, self.private_endpoints()) 82 + .env(private::ENV_REPO_DID, resolved.owner.as_str()) 83 + .env(private::ENV_REPO_RKEY, resolved.rkey.as_ref()) 84 + .env("GIT_CONFIG_GLOBAL", self.git_config_path()) 85 + .current_dir(path) 86 + .args(["upload-archive"]) 87 + .arg(path.as_os_str()); 88 + 89 + Ok(command) 90 + } 91 + 71 92 async fn init_upload_pack_advertisement( 72 93 &self, 73 94 parts: &mut Parts, ··· 140 119 .args(["upload-pack", "--strict", "--stateless-rpc"]) 141 120 .arg(path.as_os_str()); 142 121 143 - let command = git_service::commands::upload_pack("/usr/bin/git", repo.path()); 144 122 Ok(command) 145 123 } 146 124 ··· 185 165 "--stateless-rpc", 186 166 ]) 187 167 .arg(path.as_os_str()); 168 + 188 169 Ok(command) 189 170 } 190 171