1use crate::core::{
2 find_repo_root, get_current_branch, read_head, write_ref, Commit, Object, ObjectHash, Tree,
3};
4use crate::response::CommitResponse;
5use crate::storage::{Index, ObjectStore};
6use std::collections::HashMap;
7
8pub fn execute(
9 message: String,
10 author: Option<String>,
11) -> Result<CommitResponse, crate::errors::LitError> {
12 let repo_root = find_repo_root()?;
13 let store = ObjectStore::new(&repo_root);
14 let index = Index::load(&repo_root)?;
15
16 if index.entries.is_empty() {
17 return Err("Nothing to commit (staging area is empty)".into());
18 }
19
20 let author_name = if let Some(a) = author {
22 a
23 } else {
24 std::env::var("USER")
25 .or_else(|_| std::env::var("USERNAME"))
26 .unwrap_or_else(|_| "Unknown".to_string())
27 };
28
29 let tree_hash = build_tree_from_index(&index, &store)?;
31
32 let parents = match read_head(&repo_root) {
34 Ok(parent_hash) => vec![ObjectHash::from_hex(parent_hash)],
35 Err(_) => vec![], };
37
38 let parent_str = parents.first().map(|p| p.to_string());
39
40 let commit = Commit::new(
42 tree_hash.clone(),
43 parents,
44 author_name.clone(),
45 message.clone(),
46 );
47 let timestamp = commit.timestamp;
48 let commit_object = Object::Commit(commit);
49 let commit_hash = store.write(&commit_object)?;
50
51 let branch = get_current_branch(&repo_root).unwrap_or_else(|_| "main".to_string());
53
54 write_ref(
55 &repo_root,
56 &format!("heads/{}", branch),
57 commit_hash.as_str(),
58 )?;
59
60 Ok(CommitResponse {
61 hash: commit_hash.to_string(),
62 short_hash: commit_hash.short().to_string(),
63 tree: tree_hash.to_string(),
64 parent: parent_str,
65 author: author_name,
66 message,
67 timestamp,
68 })
69}
70
71fn build_tree_from_index(
72 index: &Index,
73 store: &ObjectStore,
74) -> Result<ObjectHash, crate::errors::LitError> {
75 let mut tree_map: HashMap<String, Vec<(String, String, String)>> = HashMap::new();
77
78 for entry in index.sorted_entries() {
79 let parts: Vec<&str> = entry.path.split('/').collect();
80
81 if parts.len() == 1 {
82 tree_map.entry("".to_string()).or_default().push((
84 parts[0].to_string(),
85 entry.hash.clone(),
86 entry.mode.clone(),
87 ));
88 } else {
89 let dir = parts[0].to_string();
91 tree_map.entry(dir).or_default().push((
92 parts[1..].join("/"),
93 entry.hash.clone(),
94 entry.mode.clone(),
95 ));
96 }
97 }
98
99 let mut root_tree = Tree::new();
101
102 if let Some(root_files) = tree_map.get("") {
103 for (name, hash, mode) in root_files {
104 root_tree.add_entry(
105 mode.clone(),
106 name.clone(),
107 ObjectHash::from_hex(hash.clone()),
108 "blob".to_string(),
109 );
110 }
111 }
112
113 for dir in tree_map.keys() {
115 if !dir.is_empty() {
116 let mut subtree = Tree::new();
118
119 if let Some(files) = tree_map.get(dir) {
120 for (name, hash, mode) in files {
121 subtree.add_entry(
122 mode.clone(),
123 name.clone(),
124 ObjectHash::from_hex(hash.clone()),
125 "blob".to_string(),
126 );
127 }
128 }
129
130 let subtree_object = Object::Tree(subtree);
131 let subtree_hash = store.write(&subtree_object)?;
132
133 root_tree.add_entry(
134 "040000".to_string(),
135 dir.clone(),
136 subtree_hash,
137 "tree".to_string(),
138 );
139 }
140 }
141
142 let tree_object = Object::Tree(root_tree);
144 store.write(&tree_object).map_err(Into::into)
145}