changeset_git/repository/
commit.rs1use crate::{CommitInfo, GitError, Result};
2
3use super::Repository;
4
5impl Repository {
6 pub fn reset_to_parent(&self) -> Result<()> {
10 let head_commit = self.inner.head()?.peel_to_commit()?;
11 let parent = head_commit
12 .parent(0)
13 .map_err(|source| GitError::NoParentCommit { source })?;
14 self.inner
15 .reset(parent.as_object(), git2::ResetType::Soft, None)?;
16 Ok(())
17 }
18
19 pub fn commit(&self, message: &str) -> Result<CommitInfo> {
23 let sig = self.inner.signature()?;
24 let mut index = self.inner.index()?;
25 let tree_id = index.write_tree()?;
26 let tree = self.inner.find_tree(tree_id)?;
27
28 let parent = self.inner.head().ok().and_then(|h| h.peel_to_commit().ok());
29
30 let parents: Vec<&git2::Commit<'_>> = parent.iter().collect();
31
32 let commit_oid = self
33 .inner
34 .commit(Some("HEAD"), &sig, &sig, message, &tree, &parents)?;
35
36 let sha = commit_oid.to_string();
37
38 Ok(CommitInfo::new(sha, message.to_string()))
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::super::tests::setup_test_repo;
45 use crate::GitError;
46 use std::fs;
47 use std::path::Path;
48
49 #[test]
50 fn create_commit() -> anyhow::Result<()> {
51 let (dir, repo) = setup_test_repo()?;
52
53 fs::write(dir.path().join("file.txt"), "content")?;
54 repo.stage_files(&[Path::new("file.txt")])?;
55
56 let commit_info = repo.commit("Test commit message")?;
57
58 assert!(!commit_info.sha().is_empty());
59 assert_eq!(commit_info.message(), "Test commit message");
60
61 let head = repo.inner.head()?.peel_to_commit()?;
62 assert_eq!(commit_info.sha(), &head.id().to_string());
63
64 Ok(())
65 }
66
67 #[test]
68 fn commit_with_multiline_message() -> anyhow::Result<()> {
69 let (dir, repo) = setup_test_repo()?;
70
71 fs::write(dir.path().join("file.txt"), "content")?;
72 repo.stage_files(&[Path::new("file.txt")])?;
73
74 let message = "Summary line\n\nDetailed description\nwith multiple lines";
75 let commit_info = repo.commit(message)?;
76
77 let head = repo.inner.head()?.peel_to_commit()?;
78 assert_eq!(head.message(), Some(message));
79 assert_eq!(commit_info.message(), message);
80
81 Ok(())
82 }
83
84 #[test]
85 fn reset_to_parent_undoes_last_commit() -> anyhow::Result<()> {
86 let (dir, repo) = setup_test_repo()?;
87
88 let initial_head = repo.inner.head()?.peel_to_commit()?.id();
89
90 fs::write(dir.path().join("file.txt"), "content")?;
91 repo.stage_files(&[Path::new("file.txt")])?;
92 repo.commit("Second commit")?;
93
94 let after_commit_head = repo.inner.head()?.peel_to_commit()?.id();
95 assert_ne!(initial_head, after_commit_head);
96
97 repo.reset_to_parent()?;
98
99 let after_reset_head = repo.inner.head()?.peel_to_commit()?.id();
100 assert_eq!(initial_head, after_reset_head);
101
102 Ok(())
103 }
104
105 #[test]
106 fn reset_to_parent_on_initial_commit_fails() -> anyhow::Result<()> {
107 let (_dir, repo) = setup_test_repo()?;
108
109 let result = repo.reset_to_parent();
110
111 assert!(result.is_err());
112 let err = result.expect_err("expected NoParentCommit error");
113 assert!(matches!(err, GitError::NoParentCommit { .. }));
114
115 Ok(())
116 }
117}