1use std::path::Path;
2
3use crate::error::{GitError, Result};
4use crate::hash::GitHash;
5
6#[must_use]
10pub fn list_refs(git_dir: &Path) -> Vec<(String, GitHash)> {
11 let mut out: Vec<(String, GitHash)> = Vec::new();
12
13 let refs_root = git_dir.join("refs");
15 collect_loose_refs(&refs_root, "refs", git_dir, &mut out);
16
17 if let Ok(text) = std::fs::read_to_string(git_dir.join("packed-refs")) {
20 for line in text.lines() {
21 let line = line.trim();
22 if line.is_empty() || line.starts_with('#') || line.starts_with('^') {
23 continue;
24 }
25 if let Some((sha, name)) = line.split_once(' ') {
26 if out.iter().any(|(n, _)| n == name) {
27 continue; }
29 if let Ok(hash) = GitHash::from_hex(sha.trim()) {
30 out.push((name.trim().to_string(), hash));
31 }
32 }
33 }
34 }
35
36 if let Ok(hash) = resolve_ref(git_dir, "HEAD") {
38 if !out.iter().any(|(n, _)| n == "HEAD") {
39 out.push(("HEAD".to_string(), hash));
40 }
41 }
42
43 out
44}
45
46fn collect_loose_refs(dir: &Path, prefix: &str, git_dir: &Path, out: &mut Vec<(String, GitHash)>) {
48 let Ok(entries) = std::fs::read_dir(dir) else {
49 return;
50 };
51 for entry in entries.flatten() {
52 let name = entry.file_name();
53 let Some(name) = name.to_str() else {
54 continue;
55 };
56 let refname = format!("{prefix}/{name}");
57 let path = entry.path();
58 if path.is_dir() {
59 collect_loose_refs(&path, &refname, git_dir, out);
60 } else if let Ok(hash) = resolve_ref(git_dir, &refname) {
61 out.push((refname, hash));
62 }
63 }
64}
65
66pub fn resolve_ref(git_dir: &Path, refname: &str) -> Result<GitHash> {
73 if refname.len() == 40 && refname.chars().all(|c| c.is_ascii_hexdigit()) {
74 return GitHash::from_hex(refname);
75 }
76
77 let ref_path = git_dir.join(refname);
78 let content = std::fs::read_to_string(&ref_path).map_err(|e| {
79 if e.kind() == std::io::ErrorKind::NotFound {
80 GitError::RefNotFound(refname.to_string())
81 } else {
82 GitError::Io(e)
83 }
84 })?;
85
86 let content = content.trim();
87
88 if let Some(target) = content.strip_prefix("ref: ") {
90 return resolve_ref(git_dir, target);
91 }
92
93 GitHash::from_hex(content)
94 .map_err(|_| GitError::RefNotFound(format!("{refname}: invalid hash {content:?}")))
95}