1use crate::core::{find_repo_root, read_ref, update_head, write_ref, Object, ObjectHash, Tree};
2use crate::response::CheckoutResponse;
3use crate::storage::{Index, ObjectStore};
4use std::fs;
5use std::path::Path;
6
7pub fn execute(
8 target: String,
9 create_new: bool,
10) -> Result<CheckoutResponse, crate::errors::LitError> {
11 let repo_root = find_repo_root()?;
12
13 if create_new {
14 use crate::core::read_head;
15 let head_hash = read_head(&repo_root)?;
16 write_ref(&repo_root, &format!("heads/{}", target), &head_hash)?;
17 update_head(&repo_root, &target)?;
18 checkout_commit(&repo_root, &ObjectHash::from_hex(head_hash))?;
19 Ok(CheckoutResponse {
20 target,
21 is_new_branch: true,
22 is_detached: false,
23 })
24 } else if let Ok(commit_hash) = read_ref(&repo_root, &format!("heads/{}", target)) {
25 update_head(&repo_root, &target)?;
26 checkout_commit(&repo_root, &ObjectHash::from_hex(commit_hash))?;
27 Ok(CheckoutResponse {
28 target,
29 is_new_branch: false,
30 is_detached: false,
31 })
32 } else {
33 let hash = ObjectHash::from_hex(target.clone());
34 checkout_commit(&repo_root, &hash)?;
35 use crate::core::set_head_detached;
36 set_head_detached(&repo_root, &target)?;
37 Ok(CheckoutResponse {
38 target,
39 is_new_branch: false,
40 is_detached: true,
41 })
42 }
43}
44
45fn checkout_commit(
46 repo_root: &Path,
47 commit_hash: &ObjectHash,
48) -> Result<(), crate::errors::LitError> {
49 let store = ObjectStore::new(repo_root);
50
51 let commit = match store.read(commit_hash)? {
53 Object::Commit(c) => c,
54 _ => return Err("Not a commit".into()),
55 };
56
57 let tree = match store.read(&commit.tree)? {
59 Object::Tree(t) => t,
60 _ => return Err("Not a tree".into()),
61 };
62
63 let mut index = Index::new();
74 checkout_tree(repo_root, &tree, &store, "", &mut index)?;
75 index.save(repo_root)?;
76
77 Ok(())
78}
79
80fn checkout_tree(
83 repo_root: &Path,
84 tree: &Tree,
85 store: &ObjectStore,
86 prefix: &str,
87 index: &mut Index,
88) -> Result<(), crate::errors::LitError> {
89 for entry in &tree.entries {
90 let path = if prefix.is_empty() {
91 entry.name.clone()
92 } else {
93 format!("{}/{}", prefix, entry.name)
94 };
95
96 let full_path = repo_root.join(&path);
97
98 match entry.object_type.as_str() {
99 "blob" => {
100 let blob = match store.read(&entry.hash)? {
102 Object::Blob(b) => b,
103 _ => return Err("Expected blob".into()),
104 };
105
106 if let Some(parent) = full_path.parent() {
107 fs::create_dir_all(parent)
108 .map_err(|e| format!("Failed to create directory: {}", e))?;
109 }
110
111 fs::write(&full_path, &blob.content)
112 .map_err(|e| format!("Failed to write file: {}", e))?;
113
114 index.add(path, entry.hash.to_string(), entry.mode.clone());
115 }
116 "tree" => {
117 let subtree = match store.read(&entry.hash)? {
119 Object::Tree(t) => t,
120 _ => return Err("Expected tree".into()),
121 };
122
123 checkout_tree(repo_root, &subtree, store, &path, index)?;
124 }
125 _ => {}
126 }
127 }
128
129 Ok(())
130}