1 use super::{get_head, utils::repo, CommitId};
2 use crate::error::Result;
3 use git2::{ErrorCode, ObjectType, Repository, Signature};
4 use scopetime::scope_time;
5
6 ///
amend( repo_path: &str, id: CommitId, msg: &str, ) -> Result<CommitId>7 pub fn amend(
8 repo_path: &str,
9 id: CommitId,
10 msg: &str,
11 ) -> Result<CommitId> {
12 scope_time!("amend");
13
14 let repo = repo(repo_path)?;
15 let commit = repo.find_commit(id.into())?;
16
17 let mut index = repo.index()?;
18 let tree_id = index.write_tree()?;
19 let tree = repo.find_tree(tree_id)?;
20
21 let new_id = commit.amend(
22 Some("HEAD"),
23 None,
24 None,
25 None,
26 Some(msg),
27 Some(&tree),
28 )?;
29
30 Ok(CommitId::new(new_id))
31 }
32
33 /// Wrap Repository::signature to allow unknown user.name.
34 ///
35 /// See <https://github.com/extrawurst/gitui/issues/79>.
signature_allow_undefined_name( repo: &Repository, ) -> std::result::Result<Signature<'_>, git2::Error>36 fn signature_allow_undefined_name(
37 repo: &Repository,
38 ) -> std::result::Result<Signature<'_>, git2::Error> {
39 match repo.signature() {
40 Err(e) if e.code() == ErrorCode::NotFound => {
41 let config = repo.config()?;
42 Signature::now(
43 config.get_str("user.name").unwrap_or("unknown"),
44 config.get_str("user.email")?,
45 )
46 }
47
48 v => v,
49 }
50 }
51
52 /// this does not run any git hooks
commit(repo_path: &str, msg: &str) -> Result<CommitId>53 pub fn commit(repo_path: &str, msg: &str) -> Result<CommitId> {
54 scope_time!("commit");
55
56 let repo = repo(repo_path)?;
57
58 let signature = signature_allow_undefined_name(&repo)?;
59 let mut index = repo.index()?;
60 let tree_id = index.write_tree()?;
61 let tree = repo.find_tree(tree_id)?;
62
63 let parents = if let Ok(id) = get_head(repo_path) {
64 vec![repo.find_commit(id.into())?]
65 } else {
66 Vec::new()
67 };
68
69 let parents = parents.iter().collect::<Vec<_>>();
70
71 Ok(repo
72 .commit(
73 Some("HEAD"),
74 &signature,
75 &signature,
76 msg,
77 &tree,
78 parents.as_slice(),
79 )?
80 .into())
81 }
82
83 /// Tag a commit.
84 ///
85 /// This function will return an `Err(…)` variant if the tag’s name is refused
86 /// by git or if the tag already exists.
tag( repo_path: &str, commit_id: &CommitId, tag: &str, ) -> Result<CommitId>87 pub fn tag(
88 repo_path: &str,
89 commit_id: &CommitId,
90 tag: &str,
91 ) -> Result<CommitId> {
92 scope_time!("tag");
93
94 let repo = repo(repo_path)?;
95
96 let signature = signature_allow_undefined_name(&repo)?;
97 let object_id = commit_id.get_oid();
98 let target =
99 repo.find_object(object_id, Some(ObjectType::Commit))?;
100
101 Ok(repo.tag(tag, &target, &signature, "", false)?.into())
102 }
103
104 #[cfg(test)]
105 mod tests {
106
107 use crate::error::Result;
108 use crate::sync::{
109 commit, get_commit_details, get_commit_files, stage_add_file,
110 tags::get_tags,
111 tests::{get_statuses, repo_init, repo_init_empty},
112 utils::get_head,
113 LogWalker,
114 };
115 use commit::{amend, tag};
116 use git2::Repository;
117 use std::{fs::File, io::Write, path::Path};
118
count_commits(repo: &Repository, max: usize) -> usize119 fn count_commits(repo: &Repository, max: usize) -> usize {
120 let mut items = Vec::new();
121 let mut walk = LogWalker::new(&repo);
122 walk.read(&mut items, max).unwrap();
123 items.len()
124 }
125
126 #[test]
test_commit()127 fn test_commit() {
128 let file_path = Path::new("foo");
129 let (_td, repo) = repo_init().unwrap();
130 let root = repo.path().parent().unwrap();
131 let repo_path = root.as_os_str().to_str().unwrap();
132
133 File::create(&root.join(file_path))
134 .unwrap()
135 .write_all(b"test\nfoo")
136 .unwrap();
137
138 assert_eq!(get_statuses(repo_path), (1, 0));
139
140 stage_add_file(repo_path, file_path).unwrap();
141
142 assert_eq!(get_statuses(repo_path), (0, 1));
143
144 commit(repo_path, "commit msg").unwrap();
145
146 assert_eq!(get_statuses(repo_path), (0, 0));
147 }
148
149 #[test]
test_commit_in_empty_repo()150 fn test_commit_in_empty_repo() {
151 let file_path = Path::new("foo");
152 let (_td, repo) = repo_init_empty().unwrap();
153 let root = repo.path().parent().unwrap();
154 let repo_path = root.as_os_str().to_str().unwrap();
155
156 assert_eq!(get_statuses(repo_path), (0, 0));
157
158 File::create(&root.join(file_path))
159 .unwrap()
160 .write_all(b"test\nfoo")
161 .unwrap();
162
163 assert_eq!(get_statuses(repo_path), (1, 0));
164
165 stage_add_file(repo_path, file_path).unwrap();
166
167 assert_eq!(get_statuses(repo_path), (0, 1));
168
169 commit(repo_path, "commit msg").unwrap();
170
171 assert_eq!(get_statuses(repo_path), (0, 0));
172 }
173
174 #[test]
test_amend() -> Result<()>175 fn test_amend() -> Result<()> {
176 let file_path1 = Path::new("foo");
177 let file_path2 = Path::new("foo2");
178 let (_td, repo) = repo_init_empty()?;
179 let root = repo.path().parent().unwrap();
180 let repo_path = root.as_os_str().to_str().unwrap();
181
182 File::create(&root.join(file_path1))?.write_all(b"test1")?;
183
184 stage_add_file(repo_path, file_path1)?;
185 let id = commit(repo_path, "commit msg")?;
186
187 assert_eq!(count_commits(&repo, 10), 1);
188
189 File::create(&root.join(file_path2))?.write_all(b"test2")?;
190
191 stage_add_file(repo_path, file_path2)?;
192
193 let new_id = amend(repo_path, id, "amended")?;
194
195 assert_eq!(count_commits(&repo, 10), 1);
196
197 let details = get_commit_details(repo_path, new_id)?;
198 assert_eq!(details.message.unwrap().subject, "amended");
199
200 let files = get_commit_files(repo_path, new_id)?;
201
202 assert_eq!(files.len(), 2);
203
204 let head = get_head(repo_path)?;
205
206 assert_eq!(head, new_id);
207
208 Ok(())
209 }
210
211 #[test]
test_tag() -> Result<()>212 fn test_tag() -> Result<()> {
213 let file_path = Path::new("foo");
214 let (_td, repo) = repo_init_empty().unwrap();
215 let root = repo.path().parent().unwrap();
216 let repo_path = root.as_os_str().to_str().unwrap();
217
218 File::create(&root.join(file_path))?
219 .write_all(b"test\nfoo")?;
220
221 stage_add_file(repo_path, file_path)?;
222
223 let new_id = commit(repo_path, "commit msg")?;
224
225 tag(repo_path, &new_id, "tag")?;
226
227 assert_eq!(
228 get_tags(repo_path).unwrap()[&new_id],
229 vec!["tag"]
230 );
231
232 assert!(matches!(tag(repo_path, &new_id, "tag"), Err(_)));
233
234 assert_eq!(
235 get_tags(repo_path).unwrap()[&new_id],
236 vec!["tag"]
237 );
238
239 tag(repo_path, &new_id, "second-tag")?;
240
241 assert_eq!(
242 get_tags(repo_path).unwrap()[&new_id],
243 vec!["second-tag", "tag"]
244 );
245
246 Ok(())
247 }
248 }
249