Skip to main content

lit/commands/
commit.rs

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    // Get author
21    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    // Build tree from index
30    let tree_hash = build_tree_from_index(&index, &store)?;
31
32    // Get parent commit(s)
33    let parents = match read_head(&repo_root) {
34        Ok(parent_hash) => vec![ObjectHash::from_hex(parent_hash)],
35        Err(_) => vec![], // First commit
36    };
37
38    let parent_str = parents.first().map(|p| p.to_string());
39
40    // Create commit object
41    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    // Update the current branch reference
52    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    // Group files by directory
76    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            // Root level file
83            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            // Nested file
90            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    // Build root tree
100    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    // Add subdirectories
114    for dir in tree_map.keys() {
115        if !dir.is_empty() {
116            // Create subtree (simplified - doesn't handle deep nesting)
117            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    // Write root tree
143    let tree_object = Object::Tree(root_tree);
144    store.write(&tree_object).map_err(Into::into)
145}