use crate::GitError; use crate::hook::sync::HookMetaDataSync; use db::database::AppTransaction; use models::repos::repo; use models::repos::repo_branch; use sea_orm::prelude::Expr; use sea_orm::*; use std::collections::HashSet; impl HookMetaDataSync { pub async fn sync_refs(&self, txn: &AppTransaction) -> Result<(), GitError> { let repo_id = self.repo.id; let now = chrono::Utc::now(); let existing: Vec = repo_branch::Entity::find() .filter(repo_branch::Column::Repo.eq(repo_id)) .all(txn) .await .map_err(|e| GitError::IoError(format!("failed to query branches: {}", e)))?; let mut existing_names: HashSet = existing.iter().map(|r| r.name.clone()).collect(); let references = self .domain .repo() .references() .map_err(|e| GitError::Internal(e.to_string()))?; // Auto-detect first local branch when default_branch is empty let mut auto_detected_branch: Option = None; for reference in references { let reference = reference.map_err(|e| GitError::Internal(e.to_string()))?; let name = reference .name() .ok_or_else(|| GitError::RefNotFound("unnamed ref".into()))? .to_string(); let shorthand = reference.shorthand().unwrap_or("").to_string(); let target_oid = match reference.target() { Some(oid) => oid.to_string(), None => continue, }; let is_branch = reference.is_branch(); let is_remote = reference.is_remote(); // Detect first local branch if no default is set if self.repo.default_branch.is_empty() && is_branch && !is_remote && auto_detected_branch.is_none() { auto_detected_branch = Some(shorthand.clone()); } let upstream = if is_branch && !is_remote { reference .shorthand() .map(|short| format!("refs/remotes/{{}}/{}", short)) } else { None }; if existing_names.contains(&name) { existing_names.remove(&name); repo_branch::Entity::update_many() .filter(repo_branch::Column::Repo.eq(repo_id)) .filter(repo_branch::Column::Name.eq(&name)) .col_expr(repo_branch::Column::Oid, Expr::value(&target_oid)) .col_expr(repo_branch::Column::Upstream, Expr::value(upstream)) .col_expr( repo_branch::Column::Head, Expr::value(is_branch && shorthand == self.repo.default_branch), ) .col_expr(repo_branch::Column::UpdatedAt, Expr::value(now)) .exec(txn) .await .map_err(|e| GitError::IoError(format!("failed to update branch: {}", e)))?; } else { let new_branch = repo_branch::ActiveModel { repo: Set(repo_id), name: Set(name), oid: Set(target_oid), upstream: Set(upstream), head: Set(is_branch && shorthand == self.repo.default_branch), created_at: Set(now), updated_at: Set(now), ..Default::default() }; new_branch .insert(txn) .await .map_err(|e| GitError::IoError(format!("failed to insert branch: {}", e)))?; } } if !existing_names.is_empty() { repo_branch::Entity::delete_many() .filter(repo_branch::Column::Repo.eq(repo_id)) .filter(repo_branch::Column::Name.is_in(existing_names)) .exec(txn) .await .map_err(|e| { GitError::IoError(format!("failed to delete stale branches: {}", e)) })?; } // Persist auto-detected default branch and update head flags if let Some(ref branch_name) = auto_detected_branch { // 1. Update the repo's default_branch repo::Entity::update_many() .filter(repo::Column::Id.eq(repo_id)) .col_expr( repo::Column::DefaultBranch, Expr::value(branch_name.clone()), ) .exec(txn) .await .map_err(|e| GitError::IoError(format!("failed to set default branch: {}", e)))?; // 2. Clear head on all branches repo_branch::Entity::update_many() .filter(repo_branch::Column::Repo.eq(repo_id)) .col_expr(repo_branch::Column::Head, Expr::value(false)) .exec(txn) .await .map_err(|e| GitError::IoError(format!("failed to clear head flags: {}", e)))?; // 3. Set head = true for the detected branch (it was inserted above) repo_branch::Entity::update_many() .filter(repo_branch::Column::Repo.eq(repo_id)) .filter(repo_branch::Column::Name.eq(branch_name)) .col_expr(repo_branch::Column::Head, Expr::value(true)) .exec(txn) .await .map_err(|e| GitError::IoError(format!("failed to set head flag: {}", e)))?; } Ok(()) } }