use crate::bare::GitBare; use crate::error::GitResult; use crate::paginate; use crate::pb::*; impl GitBare { /// Find all refs pointing to a given OID. pub fn find_refs_by_oid(&self, request: FindRefsByOidRequest) -> GitResult { crate::sanitize::validate_revision(&request.oid)?; let mut args = vec![ "--git-dir".to_string(), self.bare_dir.to_string_lossy().into_owned(), "for-each-ref".to_string(), "--format=%(refname)%00%(objectname)%00%(symref)".to_string(), format!("--points-at={}", request.oid), ]; if let Some(ref filter) = request.filter { for prefix in &filter.prefixes { args.push(prefix.clone()); } if filter.limit > 0 { args.push(format!("--count={}", filter.limit)); } } let output = std::process::Command::new("git") .args(&args) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; let stdout = String::from_utf8_lossy(&output.stdout); let mut refs = Vec::new(); for line in stdout.lines() { let parts: Vec<&str> = line.split('\0').collect(); if parts.len() >= 2 { let ref_name = parts[0].to_string(); let oid = parts[1].to_string(); let symref = parts.get(2).map(|s| s.to_string()).unwrap_or_default(); refs.push(FoundRef { ref_name, target_oid: oid, symbolic: !symref.is_empty(), symbolic_target: symref, }); } } Ok(FindRefsByOidResponse { refs }) } /// List refs with optional prefix/pagination/sorting. pub fn list_all_refs(&self, request: ListRefsRequest) -> GitResult { let mut args = vec![ "--git-dir".to_string(), self.bare_dir.to_string_lossy().into_owned(), "for-each-ref".to_string(), "--format=%(refname)%00%(objectname)%00%(symref)".to_string(), ]; // Sort direction let sort_prefix = match SortDirection::try_from(request.sort_direction) { Ok(SortDirection::Asc) => "", Ok(SortDirection::Desc) | _ => "-", }; args.push(format!("--sort={sort_prefix}refname")); // Containing OIDs filter if let Some(first_oid) = request.containing_oids.first() { args.push(format!("--points-at={first_oid}")); } // Prefix or pattern if !request.prefixes.is_empty() { for prefix in &request.prefixes { args.push(prefix.clone()); } } let output = std::process::Command::new("git") .args(&args) .output() .map_err(|e| crate::error::GitError::CommandFailed { status_code: None, stderr: e.to_string(), })?; let stdout = String::from_utf8_lossy(&output.stdout); let mut all_refs: Vec = Vec::new(); for line in stdout.lines() { let parts: Vec<&str> = line.split('\0').collect(); if parts.len() >= 2 { let ref_name = parts[0].to_string(); let oid = parts[1].to_string(); let symref = parts.get(2).map(|s| s.to_string()).unwrap_or_default(); // Apply glob pattern filter if set if !request.pattern.is_empty() && !simple_glob_match(&request.pattern, &ref_name) { continue; } all_refs.push(FoundRef { ref_name, target_oid: oid, symbolic: !symref.is_empty(), symbolic_target: symref, }); } } let _total = all_refs.len() as u64; let (paged, page_info) = paginate::paginate(&all_refs, request.pagination.as_ref()); Ok(ListRefsResponse { refs: paged, page_info: Some(page_info), }) } } /// Simple glob match. Supports `*` (any chars) and `?` (single char). fn simple_glob_match(pattern: &str, name: &str) -> bool { let pat_bytes = pattern.as_bytes(); let name_bytes = name.as_bytes(); let mut pi = 0; let mut ni = 0; let mut star_pi = None; let mut star_ni = 0; while ni < name_bytes.len() || pi < pat_bytes.len() { if pi < pat_bytes.len() && pat_bytes[pi] == b'*' { star_pi = Some(pi); star_ni = ni; pi += 1; } else if pi < pat_bytes.len() && ni < name_bytes.len() && (pat_bytes[pi] == b'?' || pat_bytes[pi] == name_bytes[ni]) { pi += 1; ni += 1; } else if let Some(sp) = star_pi { pi = sp + 1; star_ni += 1; ni = star_ni; } else { return false; } } true }