use anyhow::{bail, Context, Result}; use celery::{error::TaskError, task::TaskResult}; use diesel::prelude::*; use std::{fs, path::Path}; use url::Url; use uuidv7::Uuid; use crate::{db, CONFIG}; fn get_repo_name(db_conn: &mut db::Connection, id: Uuid) -> Result<(String, String)> { let (user_id, name) = db::schema::repositories::table .select(( db::schema::repositories::user_id, db::schema::repositories::name, )) .filter(db::schema::repositories::id.eq(id)) .first::<(Uuid, String)>(db_conn)?; let user_name = db::schema::users::table .select(db::schema::users::name) .filter(db::schema::users::id.eq(user_id)) .first::(db_conn)?; Ok((user_name, name)) } fn repo_dir(user: &str, repo: &str) -> (String, String, String) { let parent = format!("{}/{}", CONFIG.repos_dir, user); let dir = format!("{}/{}", parent, repo); (parent, dir, format!("{}/{}.git", user, repo)) } fn do_task(parent_dir: &str, repo_dir: &str, full_name_path: &str) -> Result<()> { let path = Path::new(repo_dir); if path.exists() && path.is_dir() { let repo = git2::Repository::open(repo_dir)?; repo.find_remote("origin")? .fetch(&[&CONFIG.gitea_pull_branch], None, None)?; let fetch_head = repo.find_reference("FETCH_HEAD")?; let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?; let analysis = repo.merge_analysis(&[&fetch_commit])?; if !analysis.0.is_up_to_date() { if analysis.0.is_fast_forward() { let refname = format!("refs/heads/{}", CONFIG.gitea_pull_branch); let mut reference = repo.find_reference(&refname)?; reference.set_target(fetch_commit.id(), "Fast-Forward")?; repo.set_head(&refname)?; repo.checkout_head(Some(git2::build::CheckoutBuilder::default().force()))?; } else { bail!("Fast-forward only!"); } } } else { fs::create_dir_all(parent_dir)?; let repo = git2::Repository::clone( Url::parse(CONFIG.gitea_pull_url.as_str())? .join(full_name_path)? .as_str(), repo_dir, )?; let (object, reference) = repo.revparse_ext(&format!("remotes/origin/{}", CONFIG.gitea_pull_branch))?; repo.checkout_tree(&object, None)?; match reference { Some(gref) => repo.set_head(gref.name().context("Could not get ref name")?), None => repo.set_head_detached(object.id()), } .context("Failed to set HEAD")?; }; Ok(()) } #[celery::task] pub async fn get_repo(id: Uuid) -> TaskResult<()> { let db_conn = &mut match db::POOL.get() { Ok(x) => x, Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), }; let (user_name, repo_name) = match get_repo_name(db_conn, id) { Ok(x) => x, Err(e) => return Err(TaskError::UnexpectedError(format!("{:?}", e))), }; let (parent_dir, repo_dir, full_name_path) = repo_dir(&user_name, &repo_name); if let Err(e) = do_task(&parent_dir, &repo_dir, &full_name_path) { if let Err(err) = fs::remove_dir_all(repo_dir) { return Err(TaskError::UnexpectedError(format!("{:?}", err))); } return Err(TaskError::UnexpectedError(format!("{:?}", e))); } Ok(()) }