1use std::iter::FusedIterator;
2use std::marker;
3use std::mem;
4use std::ops::Range;
5use std::ptr;
6use std::str;
7
8use crate::util::Binding;
9use crate::{raw, signature, Buf, Error, IntoCString, Mailmap, Object, Oid, Signature, Time, Tree};
10
11pub struct Commit<'repo> {
15 raw: *mut raw::git_commit,
16 _marker: marker::PhantomData<Object<'repo>>,
17}
18
19pub struct Parents<'commit, 'repo> {
23 range: Range<usize>,
24 commit: &'commit Commit<'repo>,
25}
26
27pub struct ParentIds<'commit> {
31 range: Range<usize>,
32 commit: &'commit Commit<'commit>,
33}
34
35impl<'repo> Commit<'repo> {
36 pub fn id(&self) -> Oid {
38 unsafe { Binding::from_raw(raw::git_commit_id(&*self.raw)) }
39 }
40
41 pub fn tree_id(&self) -> Oid {
45 unsafe { Binding::from_raw(raw::git_commit_tree_id(&*self.raw)) }
46 }
47
48 pub fn tree(&self) -> Result<Tree<'repo>, Error> {
50 let mut ret = ptr::null_mut();
51 unsafe {
52 try_call!(raw::git_commit_tree(&mut ret, &*self.raw));
53 Ok(Binding::from_raw(ret))
54 }
55 }
56
57 pub fn raw(&self) -> *mut raw::git_commit {
59 self.raw
60 }
61
62 pub fn message(&self) -> Option<&str> {
69 str::from_utf8(self.message_bytes()).ok()
70 }
71
72 pub fn message_bytes(&self) -> &[u8] {
77 unsafe { crate::opt_bytes(self, raw::git_commit_message(&*self.raw)).unwrap() }
78 }
79
80 pub fn message_encoding(&self) -> Option<&str> {
85 let bytes = unsafe { crate::opt_bytes(self, raw::git_commit_message_encoding(&*self.raw)) };
86 bytes.and_then(|b| str::from_utf8(b).ok())
87 }
88
89 pub fn message_raw(&self) -> Option<&str> {
93 str::from_utf8(self.message_raw_bytes()).ok()
94 }
95
96 pub fn message_raw_bytes(&self) -> &[u8] {
98 unsafe { crate::opt_bytes(self, raw::git_commit_message_raw(&*self.raw)).unwrap() }
99 }
100
101 pub fn raw_header(&self) -> Option<&str> {
105 str::from_utf8(self.raw_header_bytes()).ok()
106 }
107
108 pub fn header_field_bytes<T: IntoCString>(&self, field: T) -> Result<Buf, Error> {
110 let buf = Buf::new();
111 let raw_field = field.into_c_string()?;
112 unsafe {
113 try_call!(raw::git_commit_header_field(
114 buf.raw(),
115 &*self.raw,
116 raw_field
117 ));
118 }
119 Ok(buf)
120 }
121
122 pub fn raw_header_bytes(&self) -> &[u8] {
124 unsafe { crate::opt_bytes(self, raw::git_commit_raw_header(&*self.raw)).unwrap() }
125 }
126
127 pub fn summary(&self) -> Option<&str> {
135 self.summary_bytes().and_then(|s| str::from_utf8(s).ok())
136 }
137
138 pub fn summary_bytes(&self) -> Option<&[u8]> {
145 unsafe { crate::opt_bytes(self, raw::git_commit_summary(self.raw)) }
146 }
147
148 pub fn body(&self) -> Option<&str> {
157 self.body_bytes().and_then(|s| str::from_utf8(s).ok())
158 }
159
160 pub fn body_bytes(&self) -> Option<&[u8]> {
168 unsafe { crate::opt_bytes(self, raw::git_commit_body(self.raw)) }
169 }
170
171 pub fn time(&self) -> Time {
177 unsafe {
178 Time::new(
179 raw::git_commit_time(&*self.raw) as i64,
180 raw::git_commit_time_offset(&*self.raw) as i32,
181 )
182 }
183 }
184
185 pub fn parents<'a>(&'a self) -> Parents<'a, 'repo> {
187 Parents {
188 range: 0..self.parent_count(),
189 commit: self,
190 }
191 }
192
193 pub fn parent_ids(&self) -> ParentIds<'_> {
195 ParentIds {
196 range: 0..self.parent_count(),
197 commit: self,
198 }
199 }
200
201 pub fn author(&self) -> Signature<'_> {
203 unsafe {
204 let ptr = raw::git_commit_author(&*self.raw);
205 signature::from_raw_const(self, ptr)
206 }
207 }
208
209 pub fn author_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> {
212 let mut ret = ptr::null_mut();
213 unsafe {
214 try_call!(raw::git_commit_author_with_mailmap(
215 &mut ret,
216 &*self.raw,
217 &*mailmap.raw()
218 ));
219 Ok(Binding::from_raw(ret))
220 }
221 }
222
223 pub fn committer(&self) -> Signature<'_> {
225 unsafe {
226 let ptr = raw::git_commit_committer(&*self.raw);
227 signature::from_raw_const(self, ptr)
228 }
229 }
230
231 pub fn committer_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> {
234 let mut ret = ptr::null_mut();
235 unsafe {
236 try_call!(raw::git_commit_committer_with_mailmap(
237 &mut ret,
238 &*self.raw,
239 &*mailmap.raw()
240 ));
241 Ok(Binding::from_raw(ret))
242 }
243 }
244
245 pub fn amend(
255 &self,
256 update_ref: Option<&str>,
257 author: Option<&Signature<'_>>,
258 committer: Option<&Signature<'_>>,
259 message_encoding: Option<&str>,
260 message: Option<&str>,
261 tree: Option<&Tree<'repo>>,
262 ) -> Result<Oid, Error> {
263 let mut raw = raw::git_oid {
264 id: [0; raw::GIT_OID_RAWSZ],
265 };
266 let update_ref = crate::opt_cstr(update_ref)?;
267 let encoding = crate::opt_cstr(message_encoding)?;
268 let message = crate::opt_cstr(message)?;
269 unsafe {
270 try_call!(raw::git_commit_amend(
271 &mut raw,
272 self.raw(),
273 update_ref,
274 author.map(|s| s.raw()),
275 committer.map(|s| s.raw()),
276 encoding,
277 message,
278 tree.map(|t| t.raw())
279 ));
280 Ok(Binding::from_raw(&raw as *const _))
281 }
282 }
283
284 pub fn parent_count(&self) -> usize {
288 unsafe { raw::git_commit_parentcount(&*self.raw) as usize }
289 }
290
291 pub fn parent(&self, i: usize) -> Result<Commit<'repo>, Error> {
295 unsafe {
296 let mut raw = ptr::null_mut();
297 try_call!(raw::git_commit_parent(
298 &mut raw,
299 &*self.raw,
300 i as libc::c_uint
301 ));
302 Ok(Binding::from_raw(raw))
303 }
304 }
305
306 pub fn parent_id(&self, i: usize) -> Result<Oid, Error> {
313 unsafe {
314 let id = raw::git_commit_parent_id(self.raw, i as libc::c_uint);
315 if id.is_null() {
316 Err(Error::from_str("parent index out of bounds"))
317 } else {
318 Ok(Binding::from_raw(id))
319 }
320 }
321 }
322
323 pub fn as_object(&self) -> &Object<'repo> {
325 unsafe { &*(self as *const _ as *const Object<'repo>) }
326 }
327
328 pub fn into_object(self) -> Object<'repo> {
330 assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
331 unsafe { mem::transmute(self) }
332 }
333}
334
335impl<'repo> Binding for Commit<'repo> {
336 type Raw = *mut raw::git_commit;
337 unsafe fn from_raw(raw: *mut raw::git_commit) -> Commit<'repo> {
338 Commit {
339 raw,
340 _marker: marker::PhantomData,
341 }
342 }
343 fn raw(&self) -> *mut raw::git_commit {
344 self.raw
345 }
346}
347
348impl<'repo> std::fmt::Debug for Commit<'repo> {
349 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
350 let mut ds = f.debug_struct("Commit");
351 ds.field("id", &self.id());
352 if let Some(summary) = self.summary() {
353 ds.field("summary", &summary);
354 }
355 ds.finish()
356 }
357}
358
359impl<'repo, 'commit> Iterator for Parents<'commit, 'repo> {
361 type Item = Commit<'repo>;
362 fn next(&mut self) -> Option<Commit<'repo>> {
363 self.range.next().and_then(|i| self.commit.parent(i).ok())
364 }
365 fn size_hint(&self) -> (usize, Option<usize>) {
366 self.range.size_hint()
367 }
368}
369
370impl<'repo, 'commit> DoubleEndedIterator for Parents<'commit, 'repo> {
372 fn next_back(&mut self) -> Option<Commit<'repo>> {
373 self.range
374 .next_back()
375 .and_then(|i| self.commit.parent(i).ok())
376 }
377}
378
379impl<'repo, 'commit> FusedIterator for Parents<'commit, 'repo> {}
380
381impl<'repo, 'commit> ExactSizeIterator for Parents<'commit, 'repo> {}
382
383impl<'commit> Iterator for ParentIds<'commit> {
385 type Item = Oid;
386 fn next(&mut self) -> Option<Oid> {
387 self.range
388 .next()
389 .and_then(|i| self.commit.parent_id(i).ok())
390 }
391 fn size_hint(&self) -> (usize, Option<usize>) {
392 self.range.size_hint()
393 }
394}
395
396impl<'commit> DoubleEndedIterator for ParentIds<'commit> {
398 fn next_back(&mut self) -> Option<Oid> {
399 self.range
400 .next_back()
401 .and_then(|i| self.commit.parent_id(i).ok())
402 }
403}
404
405impl<'commit> FusedIterator for ParentIds<'commit> {}
406
407impl<'commit> ExactSizeIterator for ParentIds<'commit> {}
408
409impl<'repo> Clone for Commit<'repo> {
410 fn clone(&self) -> Self {
411 self.as_object().clone().into_commit().ok().unwrap()
412 }
413}
414
415impl<'repo> Drop for Commit<'repo> {
416 fn drop(&mut self) {
417 unsafe { raw::git_commit_free(self.raw) }
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 #[test]
424 fn smoke() {
425 let (_td, repo) = crate::test::repo_init();
426 let head = repo.head().unwrap();
427 let target = head.target().unwrap();
428 let commit = repo.find_commit(target).unwrap();
429 assert_eq!(commit.message(), Some("initial\n\nbody"));
430 assert_eq!(commit.body(), Some("body"));
431 assert_eq!(commit.id(), target);
432 commit.message_raw().unwrap();
433 commit.raw_header().unwrap();
434 commit.message_encoding();
435 commit.summary().unwrap();
436 commit.body().unwrap();
437 commit.tree_id();
438 commit.tree().unwrap();
439 assert_eq!(commit.parents().count(), 0);
440
441 let tree_header_bytes = commit.header_field_bytes("tree").unwrap();
442 assert_eq!(
443 crate::Oid::from_str(tree_header_bytes.as_str().unwrap()).unwrap(),
444 commit.tree_id()
445 );
446 assert_eq!(commit.author().name(), Some("name"));
447 assert_eq!(commit.author().email(), Some("email"));
448 assert_eq!(commit.committer().name(), Some("name"));
449 assert_eq!(commit.committer().email(), Some("email"));
450
451 let sig = repo.signature().unwrap();
452 let tree = repo.find_tree(commit.tree_id()).unwrap();
453 let id = repo
454 .commit(Some("HEAD"), &sig, &sig, "bar", &tree, &[&commit])
455 .unwrap();
456 let head = repo.find_commit(id).unwrap();
457
458 let new_head = head
459 .amend(Some("HEAD"), None, None, None, Some("new message"), None)
460 .unwrap();
461 let new_head = repo.find_commit(new_head).unwrap();
462 assert_eq!(new_head.message(), Some("new message"));
463 new_head.into_object();
464
465 repo.find_object(target, None).unwrap().as_commit().unwrap();
466 repo.find_object(target, None)
467 .unwrap()
468 .into_commit()
469 .ok()
470 .unwrap();
471 }
472}