use crate::error::VcsError;
use crate::hash::ObjectId;
use crate::store::{HeadState, Store};
pub fn create_branch(
store: &mut dyn Store,
name: &str,
commit_id: ObjectId,
) -> Result<(), VcsError> {
let ref_name = format!("refs/heads/{name}");
if store.get_ref(&ref_name)?.is_some() {
return Err(VcsError::BranchExists {
name: name.to_owned(),
});
}
store.set_ref(&ref_name, commit_id)
}
pub fn delete_branch(store: &mut dyn Store, name: &str) -> Result<(), VcsError> {
let ref_name = format!("refs/heads/{name}");
store.delete_ref(&ref_name)
}
pub fn list_branches(store: &dyn Store) -> Result<Vec<(String, ObjectId)>, VcsError> {
let refs = store.list_refs("refs/heads/")?;
Ok(refs
.into_iter()
.map(|(full_name, id)| {
let name = full_name
.strip_prefix("refs/heads/")
.unwrap_or(&full_name)
.to_owned();
(name, id)
})
.collect())
}
pub fn create_tag(store: &mut dyn Store, name: &str, commit_id: ObjectId) -> Result<(), VcsError> {
let ref_name = format!("refs/tags/{name}");
if store.get_ref(&ref_name)?.is_some() {
return Err(VcsError::BranchExists {
name: name.to_owned(),
});
}
store.set_ref(&ref_name, commit_id)
}
pub fn delete_tag(store: &mut dyn Store, name: &str) -> Result<(), VcsError> {
let ref_name = format!("refs/tags/{name}");
store.delete_ref(&ref_name)
}
pub fn list_tags(store: &dyn Store) -> Result<Vec<(String, ObjectId)>, VcsError> {
let refs = store.list_refs("refs/tags/")?;
Ok(refs
.into_iter()
.map(|(full_name, id)| {
let name = full_name
.strip_prefix("refs/tags/")
.unwrap_or(&full_name)
.to_owned();
(name, id)
})
.collect())
}
pub fn checkout_branch(store: &mut dyn Store, name: &str) -> Result<(), VcsError> {
let ref_name = format!("refs/heads/{name}");
if store.get_ref(&ref_name)?.is_none() {
return Err(VcsError::RefNotFound {
name: name.to_owned(),
});
}
store.set_head(HeadState::Branch(name.to_owned()))
}
pub fn checkout_detached(store: &mut dyn Store, commit_id: ObjectId) -> Result<(), VcsError> {
store.set_head(HeadState::Detached(commit_id))
}
pub fn resolve_ref(store: &dyn Store, target: &str) -> Result<ObjectId, VcsError> {
if target.len() == 64 {
if let Ok(id) = target.parse::<ObjectId>() {
return Ok(id);
}
}
if target == "HEAD" {
return crate::store::resolve_head(store)?.ok_or_else(|| VcsError::RefNotFound {
name: "HEAD".to_owned(),
});
}
let branch_ref = format!("refs/heads/{target}");
if let Some(id) = store.get_ref(&branch_ref)? {
return Ok(id);
}
let tag_ref = format!("refs/tags/{target}");
if let Some(id) = store.get_ref(&tag_ref)? {
return Ok(id);
}
Err(VcsError::RefNotFound {
name: target.to_owned(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::MemStore;
use crate::error::VcsError;
#[test]
fn branch_create_list_delete() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = ObjectId::from_bytes([1; 32]);
create_branch(&mut store, "feature", id)?;
let branches = list_branches(&store)?;
assert_eq!(branches.len(), 1);
assert_eq!(branches[0].0, "feature");
assert_eq!(branches[0].1, id);
assert!(create_branch(&mut store, "feature", id).is_err());
delete_branch(&mut store, "feature")?;
assert!(list_branches(&store)?.is_empty());
Ok(())
}
#[test]
fn tag_create_list_delete() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = ObjectId::from_bytes([2; 32]);
create_tag(&mut store, "v1.0", id)?;
let tags = list_tags(&store)?;
assert_eq!(tags.len(), 1);
assert_eq!(tags[0].0, "v1.0");
delete_tag(&mut store, "v1.0")?;
assert!(list_tags(&store)?.is_empty());
Ok(())
}
#[test]
fn checkout_branch_test() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = ObjectId::from_bytes([1; 32]);
store.set_ref("refs/heads/dev", id)?;
checkout_branch(&mut store, "dev")?;
assert_eq!(store.get_head()?, HeadState::Branch("dev".into()));
Ok(())
}
#[test]
fn checkout_nonexistent_branch_fails() {
let mut store = MemStore::new();
assert!(checkout_branch(&mut store, "nonexistent").is_err());
}
#[test]
fn resolve_ref_branch() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = ObjectId::from_bytes([3; 32]);
store.set_ref("refs/heads/main", id)?;
assert_eq!(resolve_ref(&store, "main")?, id);
Ok(())
}
#[test]
fn resolve_ref_tag() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = ObjectId::from_bytes([4; 32]);
store.set_ref("refs/tags/v1.0", id)?;
assert_eq!(resolve_ref(&store, "v1.0")?, id);
Ok(())
}
#[test]
fn resolve_ref_hex() -> Result<(), VcsError> {
let store = MemStore::new();
let id = ObjectId::from_bytes([5; 32]);
let hex = id.to_string();
assert_eq!(resolve_ref(&store, &hex)?, id);
Ok(())
}
#[test]
fn resolve_ref_head() -> Result<(), VcsError> {
let mut store = MemStore::new();
let id = ObjectId::from_bytes([6; 32]);
store.set_ref("refs/heads/main", id)?;
assert_eq!(resolve_ref(&store, "HEAD")?, id);
Ok(())
}
#[test]
fn resolve_ref_nonexistent() {
let store = MemStore::new();
assert!(resolve_ref(&store, "nonexistent").is_err());
}
}