use crate::GitError; use crate::hook::sync::HookMetaDataSync; use db::database::AppTransaction; use models::system::notify; use sea_orm::*; use std::collections::HashMap; use std::process::Command; impl HookMetaDataSync { pub async fn run_fsck_and_rollback_if_corrupt( &self, txn: &AppTransaction, ) -> Result<(), GitError> { let snapshot = self.snapshot_refs(); let storage_path = self.repo.storage_path.clone(); let logger = self.logger.clone(); let fsck_errors = tokio::task::spawn_blocking(move || { let output = Command::new("git") .arg("-C") .arg(&storage_path) .arg("fsck") .arg("--full") .output() .map_err(|e| GitError::IoError(format!("git fsck failed: {}", e)))?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).to_string(); let stdout = String::from_utf8_lossy(&output.stdout).to_string(); slog::warn!( logger, "git fsck failed with code {:?}. stdout: {}, stderr: {}", output.status.code(), stdout, stderr ); return Ok(Some(format!("{}\n{}", stdout, stderr))); } Ok::, GitError>(None) }) .await .map_err(|e| GitError::Internal(format!("spawn_blocking join error: {}", e)))??; if let Some(errors) = fsck_errors { self.rollback_refs(&snapshot).await; let notification = notify::ActiveModel { user: Set(self.repo.created_by), title: Set(format!( "Repository sync rollback triggered: {}", self.repo.repo_name )), description: Set(Some("Repository integrity check failed".to_string())), content: Set(format!( "Repository {} sync failed and has been rolled back.\nError: {}", self.repo.repo_name, errors )), kind: Set(1), created_at: Set(chrono::Utc::now()), ..Default::default() }; notify::Entity::insert(notification) .exec(txn) .await .map_err(|e| GitError::IoError(format!("failed to insert notification: {}", e)))?; return Err(GitError::Internal(format!( "repository corruption detected: {}", errors ))); } Ok(()) } fn snapshot_refs(&self) -> HashMap { let mut snapshot = HashMap::new(); let repo = self.domain.repo(); if let Ok(refs) = repo.references() { for r in refs.flatten() { let name = match r.name() { Some(n) => n.to_string(), None => continue, }; let oid = match r.target() { Some(o) => o.to_string(), None => continue, }; if name.starts_with("refs/heads/") || name.starts_with("refs/tags/") { snapshot.insert(name, oid); } } } snapshot } async fn rollback_refs(&self, snapshot: &HashMap) { let storage_path = self.repo.storage_path.clone(); let logger = self.logger.clone(); let refs: Vec<(String, String)> = snapshot .iter() .map(|(k, v)| (k.clone(), v.clone())) .collect(); let _ = tokio::task::spawn_blocking(move || { for (ref_name, oid) in &refs { let status = Command::new("git") .arg("-C") .arg(&storage_path) .arg("update-ref") .arg("-m") .arg("rollback: integrity check failed") .arg(ref_name) .arg(oid) .arg("HEAD") .status(); match status { Ok(s) if s.success() => { slog::info!(logger, "rolled back ref {} to {}", ref_name, oid); } Ok(s) => { slog::error!( logger, "failed to rollback ref {}: git exited with {:?}", ref_name, s.code() ); } Err(e) => { slog::error!(logger, "failed to rollback ref {}: {}", ref_name, e); } } } }) .await; } }