1use crate::core::{
2 find_repo_root, get_current_branch, read_head, set_head_detached, write_ref, Object, ObjectHash,
3};
4use crate::response::ResetResponse;
5use crate::storage::{Index, ObjectStore};
6use std::fs;
7
8pub fn execute(
9 target: String,
10 soft: bool,
11 hard: bool,
12) -> Result<ResetResponse, crate::errors::LitError> {
13 let repo_root = find_repo_root()?;
14 let store = ObjectStore::new(&repo_root);
15
16 let commit_hash = execute_resolve(&repo_root, &target)?;
18
19 let hash_obj = ObjectHash::from_hex(commit_hash.clone());
21 let commit = match store.read(&hash_obj)? {
22 Object::Commit(c) => c,
23 _ => return Err(format!("'{}' is not a commit", target).into()),
24 };
25
26 let mode = if soft {
27 "soft"
28 } else if hard {
29 "hard"
30 } else {
31 "mixed"
32 };
33
34 move_head(&repo_root, &commit_hash)?;
36
37 if mode != "soft" {
38 reset_index_to_tree(&repo_root, &store, &commit.tree)?;
40 }
41
42 if mode == "hard" {
43 reset_working_tree(&repo_root, &store, &commit.tree)?;
45 }
46
47 Ok(ResetResponse {
48 target: commit_hash[..16.min(commit_hash.len())].to_string(),
49 mode: mode.to_string(),
50 message: format!(
51 "HEAD is now at {} {}",
52 &commit_hash[..16.min(commit_hash.len())],
53 commit.message
54 ),
55 })
56}
57
58pub fn execute_resolve(
59 repo_root: &std::path::Path,
60 target: &str,
61) -> Result<String, crate::errors::LitError> {
62 if target.starts_with("HEAD~") || target.starts_with("HEAD^") {
64 let count: usize = target[5..].parse().unwrap_or(1);
65 let mut current = read_head(repo_root)?;
66 let store = ObjectStore::new(repo_root);
67
68 for _ in 0..count {
69 let hash = ObjectHash::from_hex(current);
70 let commit = match store.read(&hash)? {
71 Object::Commit(c) => c,
72 _ => return Err("Not a commit in history".into()),
73 };
74 current = commit
75 .parents
76 .first()
77 .ok_or("No parent commit")?
78 .to_string();
79 }
80 return Ok(current);
81 }
82
83 if target == "HEAD" {
85 return Ok(read_head(repo_root)?);
86 }
87
88 if let Ok(hash) = crate::core::read_ref(repo_root, &format!("heads/{}", target)) {
90 return Ok(hash);
91 }
92
93 if let Ok(hash) = crate::core::read_ref(repo_root, &format!("tags/{}", target)) {
95 return Ok(hash);
96 }
97
98 if target.len() >= 16 && target.chars().all(|c| c.is_ascii_hexdigit()) {
100 return Ok(target.to_string());
101 }
102
103 Err(format!("Cannot resolve '{}' to a commit", target).into())
104}
105
106fn move_head(
107 repo_root: &std::path::Path,
108 commit_hash: &str,
109) -> Result<(), crate::errors::LitError> {
110 match get_current_branch(repo_root) {
111 Ok(branch) => Ok(write_ref(
112 repo_root,
113 &format!("heads/{}", branch),
114 commit_hash,
115 )?),
116 Err(_) => Ok(set_head_detached(repo_root, commit_hash)?),
117 }
118}
119
120fn reset_index_to_tree(
121 repo_root: &std::path::Path,
122 store: &ObjectStore,
123 tree_hash: &ObjectHash,
124) -> Result<(), crate::errors::LitError> {
125 let tree = match store.read(tree_hash)? {
126 Object::Tree(t) => t,
127 _ => return Err("Not a tree".into()),
128 };
129
130 let mut index = Index::new();
131 for entry in &tree.entries {
132 index.add(
133 entry.name.clone(),
134 entry.hash.to_string(),
135 entry.mode.clone(),
136 );
137 }
138 index.save(repo_root).map_err(Into::into)
139}
140
141fn reset_working_tree(
142 repo_root: &std::path::Path,
143 store: &ObjectStore,
144 tree_hash: &ObjectHash,
145) -> Result<(), crate::errors::LitError> {
146 let tree = match store.read(tree_hash)? {
147 Object::Tree(t) => t,
148 _ => return Err("Not a tree".into()),
149 };
150
151 for entry in &tree.entries {
154 if entry.object_type == "blob" {
155 let blob = match store.read(&entry.hash)? {
156 Object::Blob(b) => b,
157 _ => continue,
158 };
159 let full_path = repo_root.join(&entry.name);
160 if let Some(parent) = full_path.parent() {
161 fs::create_dir_all(parent)
162 .map_err(|e| format!("Failed to create directory: {}", e))?;
163 }
164 fs::write(&full_path, &blob.content)
165 .map_err(|e| format!("Failed to write file: {}", e))?;
166 }
167 }
168 Ok(())
169}