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