//! Copyright (c) 2022-2026 GitDataAi All rights reserved. use crate::bare::GitBare; use crate::error::GitResult; use crate::pb::*; impl GitBare { /// Update multiple refs atomically using `git update-ref --stdin`. pub fn update_references( &self, request: UpdateReferencesRequest, ) -> GitResult { let mut stdin_input = String::new(); for update in &request.updates { crate::sanitize::validate_ref_name(&update.ref_name)?; crate::sanitize::validate_revision(&update.new_oid)?; if !update.old_oid.is_empty() { crate::sanitize::validate_revision(&update.old_oid)?; stdin_input.push_str(&format!( "update {} {} {}\n", update.ref_name, update.new_oid, update.old_oid )); } else { stdin_input.push_str(&format!("update {} {}\n", update.ref_name, update.new_oid)); } } if stdin_input.is_empty() { return Ok(UpdateReferencesResponse::default()); } let mut child = std::process::Command::new("git") .args([ "--git-dir", &self.bare_dir.to_string_lossy(), "update-ref", "--stdin", ]) .stdin(std::process::Stdio::piped()) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .spawn() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; if let Some(ref mut stdin) = child.stdin { use std::io::Write; stdin.write_all(stdin_input.as_bytes()).map_err(|e| { crate::error::GitError::CommandFailed { status_code: None, stderr: format!("failed to write stdin: {e}"), } })?; } drop(child.stdin.take()); let output = child .wait_with_output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); if !output.status.success() { return Ok(UpdateReferencesResponse { failed_refs: request.updates.iter().map(|u| u.ref_name.clone()).collect(), error: stderr.trim().to_string(), }); } Ok(UpdateReferencesResponse::default()) } /// Delete refs in bulk. pub fn delete_refs(&self, request: DeleteRefsRequest) -> GitResult { let mut failed = Vec::new(); let mut error_msg = String::new(); for ref_name in &request.ref_names { crate::sanitize::validate_ref_name(ref_name)?; let output = std::process::Command::new("git") .args([ "--git-dir", &self.bare_dir.to_string_lossy(), "update-ref", "-d", ref_name, ]) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; if !output.status.success() { failed.push(ref_name.clone()); if error_msg.is_empty() { error_msg = String::from_utf8_lossy(&output.stderr).trim().to_string(); } } } Ok(DeleteRefsResponse { failed_refs: failed, error: error_msg, }) } /// Write a single ref with optional expected-old-oid check. pub fn write_ref(&self, request: WriteRefRequest) -> GitResult { crate::sanitize::validate_ref_name(&request.ref_name)?; crate::sanitize::validate_revision(&request.new_oid)?; let mut args = vec![ "--git-dir".to_string(), self.bare_dir.to_string_lossy().into_owned(), "update-ref".to_string(), request.ref_name.clone(), request.new_oid.clone(), ]; if !request.old_oid.is_empty() { crate::sanitize::validate_revision(&request.old_oid)?; args.push(request.old_oid.clone()); } let output = std::process::Command::new("git") .args(&args) .stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; if !output.status.success() { return Ok(WriteRefResponse { ok: false, error: String::from_utf8_lossy(&output.stderr).trim().to_string(), }); } Ok(WriteRefResponse { ok: true, error: String::new(), }) } /// Check if a ref exists. pub fn ref_exists(&self, request: RefExistsRequest) -> GitResult { crate::sanitize::validate_ref_name(&request.ref_name)?; let repo = self.gix_repo()?; let exists = repo .try_find_reference(&request.ref_name) .ok() .flatten() .is_some(); Ok(RefExistsResponse { exists }) } /// Find the default branch name. pub fn find_default_branch_name(&self) -> GitResult { let result = std::process::Command::new("git") .args([ "--git-dir", &self.bare_dir.to_string_lossy(), "symbolic-ref", "HEAD", ]) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; if !result.status.success() { return Err(crate::error::GitError::CommandFailed { status_code: result.status.code(), stderr: String::from_utf8_lossy(&result.stderr).trim().to_string(), }); } let name = String::from_utf8_lossy(&result.stdout) .trim() .strip_prefix("refs/heads/") .map(|b| b.to_string()) .unwrap_or_default(); Ok(FindDefaultBranchNameResponse { name }) } }