use std::collections::HashSet; use db::{database::AppDatabase, sqlx}; use model::repos::RepoLfsObjectModel; use uuid::Uuid; use crate::{bare::GitBare, errors::GitError}; #[tracing::instrument(skip(db, bare), fields(repo_id = %repo_id))] pub async fn sync_lfs_objects( db: &AppDatabase, bare: &GitBare, repo_id: Uuid, ) -> Result<(), GitError> { let pool = db.writer(); let existing: Vec = sqlx::query_as::<_, RepoLfsObjectModel>( "SELECT repo, oid, size_bytes, storage_key, created_at FROM repo_lfs_object WHERE repo = $1" ) .bind(repo_id) .fetch_all(pool) .await .map_err(|e| GitError::Internal(format!("failed to query lfs objects: {}", e)))?; let mut existing_oids: HashSet = existing.into_iter().map(|o| o.oid).collect(); let lfs_dir = bare.bare_dir.join(".lfs").join("objects"); if !lfs_dir.exists() { if !existing_oids.is_empty() { let oids_vec: Vec = existing_oids.into_iter().collect(); sqlx::query( "DELETE FROM repo_lfs_object WHERE repo = $1 AND oid = ANY($2)", ) .bind(repo_id) .bind(&oids_vec) .execute(pool) .await .map_err(|e| { GitError::Internal(format!( "failed to delete stale lfs objects: {}", e )) })?; } return Ok(()); } let now = chrono::Utc::now(); let mut new_objects: Vec<(String, i64, String)> = Vec::new(); if let Ok(prefix_entries) = std::fs::read_dir(&lfs_dir) { for prefix_entry in prefix_entries.flatten() { if let Ok(oid_entries) = std::fs::read_dir(prefix_entry.path()) { for oid_entry in oid_entries.flatten() { let oid_str = oid_entry.file_name().to_string_lossy().to_string(); if existing_oids.contains(&oid_str) { existing_oids.remove(&oid_str); continue; } let path = oid_entry.path(); let size_bytes = match std::fs::metadata(&path) { Ok(meta) => meta.len() as i64, Err(_) => continue, }; let storage_key = path.to_string_lossy().to_string(); new_objects.push((oid_str, size_bytes, storage_key)); } } } } for (oid, size_bytes, storage_key) in &new_objects { sqlx::query( "INSERT INTO repo_lfs_object (repo, oid, size_bytes, storage_key, created_at) VALUES ($1, $2, $3, $4, $5)" ) .bind(repo_id) .bind(oid) .bind(*size_bytes) .bind(storage_key) .bind(now) .execute(pool) .await .map_err(|e| GitError::Internal(format!("failed to insert lfs object: {}", e)))?; } if !existing_oids.is_empty() { let oids_vec: Vec = existing_oids.into_iter().collect(); sqlx::query( "DELETE FROM repo_lfs_object WHERE repo = $1 AND oid = ANY($2)", ) .bind(repo_id) .bind(&oids_vec) .execute(pool) .await .map_err(|e| { GitError::Internal(format!( "failed to delete stale lfs objects: {}", e )) })?; } Ok(()) }