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}