use crate::{ parser::{GitReceivePackArgs, GitUploadPackArgs}, ShackleError, }; use git2::{Repository, RepositoryInitMode, RepositoryInitOptions}; use std::{fs, path::PathBuf, process::Command}; use user_info::{get_user_groups, get_username}; pub struct GitInitResult { pub path: PathBuf, } fn git_dir_prefix() -> PathBuf { PathBuf::from("git") } fn personal_git_dir() -> Result { let username = get_username().ok_or(ShackleError::UserReadError)?; Ok(git_dir_prefix().join(username)) } fn group_git_dir(group: &str) -> Result { let groups = get_user_groups(); if !groups.iter().any(|g| g == group) { Err(ShackleError::InvalidGroup) } else { Ok(git_dir_prefix().join(group)) } } fn is_valid_git_repo_path(path: &PathBuf) -> Result { let prefix = git_dir_prefix(); let relative_path = match path.strip_prefix(&prefix) { Ok(relative_path) => relative_path, Err(_) => { return Ok(false); } }; let mut it = relative_path.iter(); let group = it.next(); let repo_name = it.next(); let end = it.next(); match (group, repo_name, end) { (_, _, Some(_)) | (None, _, _) | (_, None, _) => Ok(false), (Some(group_name), Some(_repo_name), _) => { if relative_path.extension().map(|ext| ext == "git") != Some(true) { Ok(false) } else { let group_name = group_name.to_string_lossy(); let user_name = get_username(); let is_valid_personal_repo_path = user_name .map(|user_name| user_name == group_name) .unwrap_or(false); let user_groups = get_user_groups(); let is_valid_shared_repo_path = user_groups.iter().any(|group| group.as_ref() == group_name); Ok(is_valid_personal_repo_path || is_valid_shared_repo_path) } } } } pub fn init(repo_name: &str, group: &Option) -> Result { fn init_group(repo_name: &str, group: &str) -> Result { let path = group_git_dir(group)?.join(repo_name).with_extension("git"); Repository::init_opts( &path, &RepositoryInitOptions::new() .bare(true) .mode(RepositoryInitMode::SHARED_GROUP) .mkdir(true) .no_reinit(true), )?; Ok(GitInitResult { path }) } fn init_personal(repo_name: &str) -> Result { let path = personal_git_dir()?.join(repo_name).with_extension("git"); Repository::init_opts( &path, &RepositoryInitOptions::new() .bare(true) .mkdir(true) .no_reinit(true), )?; Ok(GitInitResult { path }) } match group { Some(group) => init_group(repo_name, group), None => init_personal(repo_name), } } pub struct RepoMetadata { pub path: PathBuf, pub description: String, } pub fn list() -> Result, ShackleError> { let mut results = Vec::new(); let personal_dir = personal_git_dir()?; if personal_dir.is_dir() { for dir in personal_dir.read_dir()? { let path = dir?.path(); let description_path = path.join("description"); let has_git_ext = path.extension().map_or(false, |ext| ext == "git"); let has_description = description_path.is_file(); // TODO: Read the config to tell if this is a shared repo if has_git_ext && has_description { let description = fs::read_to_string(description_path)?; results.push(RepoMetadata { path, description }); } } } // TODO: Do the same (more or less) for group repos Ok(results) } pub fn upload_pack(upload_pack_args: &GitUploadPackArgs) -> Result<(), ShackleError> { if !is_valid_git_repo_path(&upload_pack_args.directory)? { return Err(ShackleError::InvalidDirectory); } let mut command = Command::new("git-upload-pack"); if upload_pack_args.strict { command.arg("strict"); } if upload_pack_args.no_strict { command.arg("no-strict"); } if let Some(timeout) = upload_pack_args.timeout { command.args(["timeout", &timeout.to_string()]); } if upload_pack_args.stateless_rpc { command.arg("stateless-rpc"); } if upload_pack_args.advertise_refs { command.arg("advertise-refs"); } command.arg(&upload_pack_args.directory); command.spawn()?.wait()?; Ok(()) } pub fn receive_pack(receive_pack_args: &GitReceivePackArgs) -> Result<(), ShackleError> { if !is_valid_git_repo_path(&receive_pack_args.directory)? { return Err(ShackleError::InvalidDirectory); } let mut command = Command::new("git-receive-pack"); if receive_pack_args.http_backend_info_refs { command.arg("--http-backend-info-refs"); } command.arg(&receive_pack_args.directory); command.spawn()?.wait()?; Ok(()) }