Skip to main content

lit/commands/
uncommit.rs

1use crate::core::{find_repo_root, get_current_branch, read_head, write_ref, Object, ObjectHash};
2use crate::response::CommitResponse;
3use crate::storage::ObjectStore;
4
5/// Uncommit the last commit, keeping changes in the working tree.
6/// With --discard, also drops the committed content.
7pub fn execute(discard: bool) -> Result<CommitResponse, crate::errors::LitError> {
8    let repo_root = find_repo_root()?;
9    let store = ObjectStore::new(&repo_root);
10
11    let head_hash = read_head(&repo_root)?;
12    let head_obj = store.read(&ObjectHash::from_hex(head_hash.clone()))?;
13
14    let old_commit = match head_obj {
15        Object::Commit(c) => c,
16        _ => return Err("HEAD is not a commit".into()),
17    };
18
19    // Get the parent commit hash
20    let parent_hash = old_commit
21        .parents
22        .first()
23        .ok_or_else(|| crate::errors::LitError::general("Cannot uncommit the initial commit"))?
24        .to_string();
25
26    let branch = get_current_branch(&repo_root).unwrap_or_else(|_| "main".to_string());
27
28    // Move branch pointer back to parent
29    write_ref(&repo_root, &format!("heads/{}", branch), &parent_hash)?;
30
31    if !discard {
32        // Re-stage the files from the uncommitted commit's tree
33        // (In a full implementation, this would restore the index from the tree diff)
34        // For now, the files remain in the working directory
35    }
36
37    Ok(CommitResponse {
38        hash: parent_hash.clone(),
39        short_hash: parent_hash[..8.min(parent_hash.len())].to_string(),
40        tree: old_commit.tree.to_string(),
41        parent: old_commit.parents.get(1).map(|p| p.to_string()),
42        author: old_commit.author.clone(),
43        message: format!(
44            "Uncommitted: {}{}",
45            old_commit.message,
46            if discard { " (discarded)" } else { "" }
47        ),
48        timestamp: old_commit.timestamp,
49    })
50}