1use cadvm_store::{Category, ObjectId};
4
5use crate::error::{CoreError, Result};
6use crate::repo::Repository;
7
8pub fn resolve(repo: &Repository, spec: &str) -> Result<ObjectId> {
16 let spec = spec.trim();
17
18 if spec == "HEAD" || spec.starts_with("HEAD~") || spec.starts_with("HEAD^") {
20 return resolve_head_relative(repo, spec);
21 }
22
23 if repo.branch_exists(spec) {
25 return repo
26 .read_ref(spec)?
27 .ok_or_else(|| CoreError::EmptyBranch(spec.to_string()));
28 }
29
30 resolve_hash(repo, spec)
32}
33
34fn resolve_head_relative(repo: &Repository, spec: &str) -> Result<ObjectId> {
35 let n: usize = if spec == "HEAD" {
36 0
37 } else if let Some(rest) = spec.strip_prefix("HEAD~") {
38 rest.parse()
39 .map_err(|_| CoreError::UnknownRevision(spec.to_string()))?
40 } else if let Some(rest) = spec.strip_prefix("HEAD^") {
41 if rest.is_empty() {
43 1
44 } else {
45 rest.parse()
46 .map_err(|_| CoreError::UnknownRevision(spec.to_string()))?
47 }
48 } else {
49 return Err(CoreError::UnknownRevision(spec.to_string()));
50 };
51
52 let mut current = repo
53 .head_commit_id()?
54 .ok_or_else(|| CoreError::UnknownRevision("HEAD".to_string()))?;
55 for _ in 0..n {
56 let commit = repo.read_commit(¤t)?;
57 current = commit
58 .parents
59 .into_iter()
60 .next()
61 .ok_or(CoreError::NoParent)?;
62 }
63 Ok(current)
64}
65
66fn resolve_hash(repo: &Repository, spec: &str) -> Result<ObjectId> {
67 let hex = spec.strip_prefix("blake3:").unwrap_or(spec);
69 let hex_lower = hex.to_ascii_lowercase();
70
71 if !hex_lower.bytes().all(|b| b.is_ascii_hexdigit()) || hex_lower.is_empty() {
72 return Err(CoreError::UnknownRevision(spec.to_string()));
73 }
74
75 if hex_lower.len() == 64 {
77 if let Ok(id) = spec.parse::<ObjectId>() {
78 if repo.store().has(Category::Commit, &id) {
79 return Ok(id);
80 }
81 }
82 return Err(CoreError::UnknownRevision(spec.to_string()));
83 }
84
85 let matches: Vec<ObjectId> = repo
87 .store()
88 .list(Category::Commit)?
89 .into_iter()
90 .filter(|id| id.hex().starts_with(&hex_lower))
91 .collect();
92
93 match matches.len() {
94 0 => Err(CoreError::UnknownRevision(spec.to_string())),
95 1 => Ok(matches.into_iter().next().unwrap()),
96 count => Err(CoreError::AmbiguousRevision {
97 prefix: spec.to_string(),
98 count,
99 }),
100 }
101}
102
103pub fn commit_history(repo: &Repository, head: &ObjectId) -> Result<Vec<crate::model::Commit>> {
105 let mut out = Vec::new();
106 let mut current = Some(head.clone());
107 while let Some(id) = current {
108 let commit = repo.read_commit(&id)?;
109 current = commit.parents.first().cloned();
110 out.push(commit);
111 }
112 Ok(out)
113}