141 lines
4.8 KiB
Rust
141 lines
4.8 KiB
Rust
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::<Option<String>, 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<String, String> {
|
|
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<String, String>) {
|
|
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;
|
|
}
|
|
}
|