don't
5
fork

Configure Feed

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

refactor(knot): implement helper method for running git commands on repo

Signed-off-by: tjh <x@tjh.dev>

tjh 6c302b4a 930848f7

+111 -108
+26 -85
crates/knot/src/model.rs
··· 18 18 use tokio::process::Command; 19 19 20 20 use crate::{ 21 + model::repository::TangledRepository, 21 22 private, 22 - public::git::{Error, GitAuthorization, NotFound}, 23 + public::git::{Error, GitAuthorization}, 23 24 services::authorization::{AuthorizationClaimsStore, AuthorizationClaimsStoreError}, 24 - types::repository_path::RepositoryPath, 25 25 }; 26 26 27 27 pub use knot_state::KnotState; ··· 76 76 type Rejection = Error; 77 77 78 78 async fn init_upload_archive(&self, parts: &mut Parts) -> Result<Command, Self::Rejection> { 79 - use axum::extract::Path; 80 - let Path(repo_path) = Path::<RepositoryPath>::from_request_parts(parts, &()).await?; 81 - let repo_key = self.resolve_repo_key(&repo_path).await.map_err(NotFound)?; 82 - let repo = self.open_repository(&repo_key).await.map_err(NotFound)?; 83 - let path = repo.path(); 79 + let repository = TangledRepository::from_git_request(parts, self).await?; 80 + let mut command = repository.git(); 81 + command.args(["upload-archive"]).arg(repository.path()); 84 82 85 - let mut command = Command::new("/usr/bin/git"); 86 - command 87 - .env_clear() 88 - .env(private::ENV_PRIVATE_ENDPOINTS, self.private_endpoints()) 89 - .env(private::ENV_REPO_DID, repo_key.owner.as_str()) 90 - .env(private::ENV_REPO_RKEY, repo_key.rkey) 91 - .env("GIT_CONFIG_GLOBAL", self.git_config_path()) 92 - .current_dir(path) 93 - .args(["upload-archive"]) 94 - .arg(path.as_os_str()); 95 - 96 - Ok(command) 83 + Ok(command.into()) 97 84 } 98 85 99 86 async fn init_upload_pack_advertisement( 100 87 &self, 101 88 parts: &mut Parts, 102 89 ) -> Result<tokio::process::Command, Self::Rejection> { 103 - use axum::extract::Path; 104 - let Path(repo_path) = Path::<RepositoryPath>::from_request_parts(parts, &()).await?; 105 - let repo_key = self.resolve_repo_key(&repo_path).await.map_err(NotFound)?; 106 - let repo = self.open_repository(&repo_key).await.map_err(NotFound)?; 107 - let path = repo.path(); 108 - 109 - let mut command = Command::new("/usr/bin/git"); 90 + let repository = TangledRepository::from_git_request(parts, self).await?; 91 + let mut command = repository.git(); 110 92 command 111 - .env_clear() 112 - .env(private::ENV_PRIVATE_ENDPOINTS, self.private_endpoints()) 113 - .env(private::ENV_REPO_DID, repo_key.owner.as_str()) 114 - .env(private::ENV_REPO_RKEY, repo_key.rkey) 115 - .env("GIT_CONFIG_GLOBAL", self.git_config_path()) 116 - .current_dir(path) 117 93 .args([ 118 94 "upload-pack", 119 95 "--http-backend-info-refs", ··· 97 121 "--strict", 98 122 "--timeout=10", 99 123 ]) 100 - .arg(path.as_os_str()); 124 + .arg(repository.path()); 101 125 102 - Ok(command) 126 + Ok(command.into()) 103 127 } 104 128 105 129 async fn init_upload_pack( 106 130 &self, 107 131 parts: &mut Parts, 108 132 ) -> Result<tokio::process::Command, Self::Rejection> { 109 - use axum::extract::Path; 110 - let Path(repo_path) = Path::<RepositoryPath>::from_request_parts(parts, &()).await?; 111 - let repo_key = self.resolve_repo_key(&repo_path).await.map_err(NotFound)?; 112 - let repo = self.open_repository(&repo_key).await.map_err(NotFound)?; 113 - let path = repo.path(); 114 - 115 - let mut command = Command::new("/usr/bin/git"); 133 + let repository = TangledRepository::from_git_request(parts, self).await?; 134 + let mut command = repository.git(); 116 135 command 117 - .env_clear() 118 - .env(private::ENV_PRIVATE_ENDPOINTS, self.private_endpoints()) 119 - .env(private::ENV_REPO_DID, repo_key.owner.as_str()) 120 - .env(private::ENV_REPO_RKEY, repo_key.rkey) 121 - .env("GIT_CONFIG_GLOBAL", self.git_config_path()) 122 - .current_dir(path) 123 136 .args(["upload-pack", "--strict", "--stateless-rpc"]) 124 - .arg(path.as_os_str()); 137 + .arg(repository.path()); 125 138 126 - Ok(command) 139 + Ok(command.into()) 127 140 } 128 141 129 142 async fn init_receive_pack_advertisement( ··· 120 155 parts: &mut Parts, 121 156 ) -> Result<tokio::process::Command, Self::Rejection> { 122 157 let GitAuthorization(auth) = GitAuthorization::from_request_parts(parts, self).await?; 158 + let repository = TangledRepository::from_git_request(parts, self).await?; 123 159 124 - use axum::extract::Path; 125 - let Path(repo_path) = Path::<RepositoryPath>::from_request_parts(parts, &()).await?; 126 - let repo_key = self.resolve_repo_key(&repo_path).await.map_err(NotFound)?; 127 - 128 - if !self.can_push(&repo_key, &auth.iss).await { 160 + if !self.can_push(repository.repository_key(), &auth.iss).await { 129 161 tracing::error!(did = %auth.iss, "push denied"); 130 162 return Err(Error::forbidden( 131 163 self, ··· 133 171 ))?; 134 172 } 135 173 136 - let repo = self.open_repository(&repo_key).await.map_err(NotFound)?; 137 - let path = repo.path(); 138 - 139 - let nonce_seed = self.generate_push_seed(&repo_key); 140 - let mut command = Command::new("/usr/bin/git"); 174 + let nonce_seed = self.generate_push_seed(repository.repository_key()); 175 + let mut command = repository.git(); 141 176 command 142 - .env_clear() 143 - .env(private::ENV_PRIVATE_ENDPOINTS, self.private_endpoints()) 144 - .env(private::ENV_REPO_DID, repo_key.owner.as_str()) 145 - .env(private::ENV_REPO_RKEY, repo_key.rkey) 146 177 .env(private::ENV_USER_DID, auth.iss.as_str()) 147 - .env("GIT_CONFIG_GLOBAL", self.git_config_path()) 148 - .current_dir(path) 149 178 .args([ 150 179 "-c", 151 180 &nonce_seed, ··· 144 191 "--http-backend-info-refs", 145 192 "--stateless-rpc", 146 193 ]) 147 - .arg(path.as_os_str()); 194 + .arg(repository.path()); 148 195 149 - Ok(command) 196 + Ok(command.into()) 150 197 } 151 198 152 199 async fn init_receive_pack( ··· 154 201 parts: &mut Parts, 155 202 ) -> Result<tokio::process::Command, Self::Rejection> { 156 203 let GitAuthorization(auth) = GitAuthorization::from_request_parts(parts, self).await?; 204 + let repository = TangledRepository::from_git_request(parts, self).await?; 157 205 158 - use axum::extract::Path; 159 - let Path(repo_path) = Path::<RepositoryPath>::from_request_parts(parts, &()).await?; 160 - let repo_key = self.resolve_repo_key(&repo_path).await.map_err(NotFound)?; 161 - 162 - if !self.can_push(&repo_key, &auth.iss).await { 206 + if !self.can_push(repository.repository_key(), &auth.iss).await { 163 207 tracing::error!(did = %auth.iss, "push denied"); 164 208 return Err(Error::forbidden( 165 209 self, ··· 166 216 ), 167 217 ))?; 168 218 } 169 - 170 - let repo = self.open_repository(&repo_key).await.map_err(NotFound)?; 171 - let path = repo.path(); 172 219 173 220 let allowed_signers_path = std::env::current_dir() 174 221 .unwrap() ··· 178 231 allowed_signers_option.push("gpg.ssh.allowedSignersFile="); 179 232 allowed_signers_option.push(&allowed_signers_path); 180 233 181 - let nonce_seed = self.generate_push_seed(&repo_key); 182 - let mut command = Command::new("/usr/bin/git"); 234 + let nonce_seed = self.generate_push_seed(repository.repository_key()); 235 + let mut command = repository.git(); 183 236 command 184 - .env_clear() 185 - .env(private::ENV_PRIVATE_ENDPOINTS, self.private_endpoints()) 186 - .env(private::ENV_REPO_DID, repo_key.owner.as_str()) 187 - .env(private::ENV_REPO_RKEY, repo_key.rkey) 188 237 .env(private::ENV_USER_DID, auth.iss.as_str()) 189 - .env("GIT_CONFIG_GLOBAL", self.git_config_path()) 190 - .current_dir(path) 191 238 .args(["-c", &nonce_seed, "-c"]) 192 239 .arg(&allowed_signers_option) 193 240 .args(["receive-pack", "--stateless-rpc"]) 194 - .arg(path.as_os_str()); 241 + .arg(repository.path()); 195 242 196 - Ok(command) 243 + Ok(command.into()) 197 244 } 198 245 }
+73 -11
crates/knot/src/model/repository.rs
··· 1 1 mod merge_check; 2 2 3 - use std::{borrow::Cow, collections::HashSet}; 3 + use std::{borrow::Cow, collections::HashSet, path::Path, process::Command}; 4 4 5 5 use axum::{ 6 6 Json, ··· 15 15 model::{convert, errors, nicediff}, 16 16 public::xrpc::{XrpcError, XrpcQuery, XrpcResponse, XrpcResult}, 17 17 types::{ 18 + repository_key::RepositoryKey, 18 19 repository_path::RepositoryPath, 19 20 sh::tangled::repo::{blob, branches, compare, diff, log, tags}, 20 21 }, ··· 26 25 #[derive(Debug)] 27 26 pub struct TangledRepository { 28 27 knot: Knot, 28 + repo_key: RepositoryKey, 29 29 repository: gix::Repository, 30 30 } 31 31 32 - impl From<(Knot, gix::Repository)> for TangledRepository { 33 - fn from((knot, repository): (Knot, gix::Repository)) -> Self { 34 - Self { knot, repository } 32 + impl From<(Knot, RepositoryKey, gix::Repository)> for TangledRepository { 33 + fn from((knot, repo_key, repository): (Knot, RepositoryKey, gix::Repository)) -> Self { 34 + Self { 35 + knot, 36 + repo_key, 37 + repository, 38 + } 35 39 } 36 40 } 37 41 38 42 impl TangledRepository { 43 + pub fn path(&self) -> &Path { 44 + self.repository.path() 45 + } 46 + 47 + pub fn repository_key(&self) -> &RepositoryKey { 48 + &self.repo_key 49 + } 50 + 39 51 /// Resolve a revspec into a [`(gix::Commit, bool)`] tuple. 40 52 /// 41 53 /// The boolean value indicates whether the revspec is immutable (ie. if ··· 219 205 } 220 206 221 207 pub fn compare(&self, params: compare::Input) -> XrpcResult<Json<compare::Output>> { 222 - use std::process::Command; 223 - 224 208 let (rev1, rev1_immutable) = self.resolve_revspec(Some(&params.rev1))?; 225 209 let (rev2, rev2_immutable) = self.resolve_revspec(Some(&params.rev2))?; 226 210 ··· 240 228 241 229 let mut format_patch_raw = String::new(); 242 230 for commit in commits { 243 - let output = Command::new("/usr/bin/git") 244 - .env_clear() 245 - .arg("-C") 246 - .arg(self.repository.path()) 231 + let output = self 232 + .git() 247 233 .arg("format-patch") 248 234 .arg("-1") 249 235 .arg(commit.id.to_hex().to_string()) ··· 501 491 .map_err(errors::RepoNotFound)? 502 492 .to_thread_local(); 503 493 504 - Ok(Self { knot, repository }) 494 + Ok(Self { 495 + knot, 496 + repo_key, 497 + repository, 498 + }) 499 + } 500 + } 501 + 502 + impl TangledRepository { 503 + pub async fn from_git_request<S: Send + Sync>( 504 + parts: &mut axum::http::request::Parts, 505 + state: &S, 506 + ) -> Result<Self, crate::public::git::Error> 507 + where 508 + Knot: axum::extract::FromRef<S>, 509 + { 510 + use crate::public::git::NotFound; 511 + use axum::extract::Path; 512 + 513 + let knot = Knot::from_ref(state); 514 + let Path(repo_path) = Path::<RepositoryPath>::from_request_parts(parts, &()).await?; 515 + let repo_key = knot.resolve_repo_key(&repo_path).await.map_err(NotFound)?; 516 + 517 + let repository = knot 518 + .open_repository(&repo_key) 519 + .await 520 + .map_err(NotFound)? 521 + .to_thread_local(); 522 + 523 + Ok(Self { 524 + knot, 525 + repo_key, 526 + repository, 527 + }) 528 + } 529 + } 530 + 531 + impl TangledRepository { 532 + /// Initialise a [`Command`] for running git with the appropriate environment and working 533 + /// directory for the repository. 534 + /// 535 + pub fn git(&self) -> Command { 536 + use crate::private::{ENV_PRIVATE_ENDPOINTS, ENV_REPO_DID, ENV_REPO_RKEY}; 537 + 538 + let mut command = Command::new("/usr/bin/git"); 539 + command 540 + .current_dir(self.path()) 541 + .env_clear() 542 + .env("GIT_CONFIG_GLOBAL", self.knot.git_config_path()) 543 + .env(ENV_PRIVATE_ENDPOINTS, self.knot.private_endpoints()) 544 + .env(ENV_REPO_DID, self.repo_key.owner_str()) 545 + .env(ENV_REPO_RKEY, &self.repo_key.rkey); 546 + command 505 547 } 506 548 }
+11 -11
crates/knot/src/model/repository/merge_check.rs
··· 2 2 borrow::Cow, 3 3 io::{self, Write as _}, 4 4 path::{Path, PathBuf}, 5 - process::{Command, Stdio}, 5 + process::Stdio, 6 6 }; 7 7 8 8 use axum::Json; ··· 15 15 16 16 impl super::TangledRepository { 17 17 pub fn merge_check(&self, params: Input) -> Result<XrpcResponse<Json<Output>>, XrpcError> { 18 - let worktree = TempWorktree::new(&self.repository).map_err(errors::Internal)?; 18 + let worktree = TempWorktree::new(&self).map_err(errors::Internal)?; 19 19 20 20 let stdin = Stdio::piped(); 21 21 let stderr = Stdio::piped(); 22 - let mut child = Command::new("/usr/bin/git") 22 + let mut child = self 23 + .git() 23 24 .arg("-C") 24 25 .arg(worktree.path()) 25 26 .arg("apply") ··· 98 97 99 98 #[derive(Debug)] 100 99 struct TempWorktree<'repo> { 101 - repo: &'repo gix::Repository, 100 + repo: &'repo super::TangledRepository, 102 101 name: String, 103 102 path: PathBuf, 104 103 } 105 104 106 105 impl<'repo> TempWorktree<'repo> { 107 - fn new(repo: &'repo gix::Repository) -> io::Result<Self> { 106 + fn new(repo: &'repo super::TangledRepository) -> io::Result<Self> { 108 107 let mut name = String::with_capacity("merge-check-".len() + 16); 109 108 let random_bytes: [u8; 8] = rand::random(); 110 109 ··· 112 111 data_encoding::BASE32_NOPAD_VISUAL.encode_append(&random_bytes, &mut name); 113 112 name = name.to_lowercase(); 114 113 115 - let output = Command::new("/usr/bin/git") 116 - .arg("-C") 117 - .arg(repo.path()) 114 + let output = repo 115 + .git() 118 116 .arg("worktree") 119 117 .arg("add") 120 118 .arg("--detach") ··· 139 139 impl<'repo> Drop for TempWorktree<'repo> { 140 140 fn drop(&mut self) { 141 141 tracing::debug!(?self, "removing temporary worktree"); 142 - if let Err(error) = Command::new("/usr/bin/git") 143 - .arg("-C") 144 - .arg(self.repo.path()) 142 + if let Err(error) = self 143 + .repo 144 + .git() 145 145 .arg("worktree") 146 146 .arg("remove") 147 147 .arg(&self.name)
+1 -1
crates/knot/src/public/xrpc/sh/tangled/repo.rs
··· 230 230 .map_err(errors::RepoNotFound)? 231 231 .to_thread_local(); 232 232 233 - let repository: TangledRepository = (knot.clone(), repo).into(); 233 + let repository: TangledRepository = (knot.clone(), repo_key, repo).into(); 234 234 235 235 knot.pool() 236 236 .spawn_async(move || repository.merge_check(params))