1use crate::core::{
2 find_repo_root, get_current_branch, read_head, write_ref, Commit, Object, ObjectHash, Tree,
3};
4use crate::response::RevertResponse;
5use crate::storage::ObjectStore;
6
7pub fn execute(target: String) -> Result<RevertResponse, crate::errors::LitError> {
8 let repo_root = find_repo_root()?;
9 let store = ObjectStore::new(&repo_root);
10
11 let commit_hash = crate::commands::reset::execute_resolve(&repo_root, &target)?;
13 let hash_obj = ObjectHash::from_hex(commit_hash.clone());
14
15 let commit = match store.read(&hash_obj)? {
16 Object::Commit(c) => c,
17 _ => return Err(format!("'{}' is not a commit", target).into()),
18 };
19
20 let parent_hash = commit
22 .parents
23 .first()
24 .ok_or("Cannot revert a root commit")?;
25
26 let parent_commit = match store.read(parent_hash)? {
27 Object::Commit(c) => c,
28 _ => return Err("Parent is not a commit".into()),
29 };
30
31 let head_hash = read_head(&repo_root)?;
33 let head_obj = ObjectHash::from_hex(head_hash.clone());
34 let head_commit = match store.read(&head_obj)? {
35 Object::Commit(c) => c,
36 _ => return Err("HEAD is not a commit".into()),
37 };
38
39 let commit_tree = load_tree_files(&store, &commit.tree)?;
41 let parent_tree = load_tree_files(&store, &parent_commit.tree)?;
42 let head_tree = load_tree_files(&store, &head_commit.tree)?;
43
44 let mut new_tree = Tree::new();
45 let mut reverted_files = Vec::new();
46
47 for (path, (hash, mode)) in &head_tree {
49 let commit_version = commit_tree.get(path);
50 let parent_version = parent_tree.get(path);
51
52 match (commit_version, parent_version) {
53 (Some(_), None) => {
55 reverted_files.push(path.clone());
56 continue; }
58 (Some(cv), Some(pv)) if cv.0 != pv.0 => {
60 reverted_files.push(path.clone());
61 new_tree.add_entry(
62 pv.1.clone(),
63 path.clone(),
64 ObjectHash::from_hex(pv.0.clone()),
65 "blob".to_string(),
66 );
67 }
68 _ => {
70 new_tree.add_entry(
71 mode.clone(),
72 path.clone(),
73 ObjectHash::from_hex(hash.clone()),
74 "blob".to_string(),
75 );
76 }
77 }
78 }
79
80 for (path, (hash, mode)) in &parent_tree {
82 if !commit_tree.contains_key(path) && !head_tree.contains_key(path) {
83 reverted_files.push(path.clone());
84 new_tree.add_entry(
85 mode.clone(),
86 path.clone(),
87 ObjectHash::from_hex(hash.clone()),
88 "blob".to_string(),
89 );
90 }
91 }
92
93 let tree_hash = store.write(&Object::Tree(new_tree))?;
94
95 let author = std::env::var("USER")
96 .or_else(|_| std::env::var("USERNAME"))
97 .unwrap_or_else(|_| "Unknown".to_string());
98
99 let revert_commit = Commit::new(
100 tree_hash,
101 vec![ObjectHash::from_hex(head_hash)],
102 author,
103 format!("Revert \"{}\"", commit.message),
104 );
105
106 let revert_hash = store.write(&Object::Commit(revert_commit))?;
107
108 let branch = get_current_branch(&repo_root).unwrap_or_else(|_| "main".to_string());
110 write_ref(
111 &repo_root,
112 &format!("heads/{}", branch),
113 revert_hash.as_str(),
114 )?;
115
116 Ok(RevertResponse {
117 reverted_commit: commit_hash[..16.min(commit_hash.len())].to_string(),
118 new_commit: revert_hash.short(),
119 files_changed: reverted_files.len(),
120 message: format!(
121 "Reverted commit {} in {}",
122 &commit_hash[..16.min(commit_hash.len())],
123 revert_hash.short()
124 ),
125 })
126}
127
128fn load_tree_files(
129 store: &ObjectStore,
130 tree_hash: &ObjectHash,
131) -> Result<std::collections::HashMap<String, (String, String)>, String> {
132 let tree = match store.read(tree_hash)? {
133 Object::Tree(t) => t,
134 _ => return Err("Not a tree".into()),
135 };
136
137 let mut files = std::collections::HashMap::new();
138 for entry in &tree.entries {
139 files.insert(
140 entry.name.clone(),
141 (entry.hash.to_string(), entry.mode.clone()),
142 );
143 }
144 Ok(files)
145}