1 use libc;
2 use std::marker;
3 use std::mem;
4 use std::ops::Range;
5 use std::ptr;
6 use std::str;
7 
8 use crate::util::Binding;
9 use crate::{raw, signature, Buf, Error, IntoCString, Mailmap, Object, Oid, Signature, Time, Tree};
10 
11 /// A structure to represent a git [commit][1]
12 ///
13 /// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
14 pub struct Commit<'repo> {
15     raw: *mut raw::git_commit,
16     _marker: marker::PhantomData<Object<'repo>>,
17 }
18 
19 /// An iterator over the parent commits of a commit.
20 ///
21 /// Aborts iteration when a commit cannot be found
22 pub struct Parents<'commit, 'repo> {
23     range: Range<usize>,
24     commit: &'commit Commit<'repo>,
25 }
26 
27 /// An iterator over the parent commits' ids of a commit.
28 ///
29 /// Aborts iteration when a commit cannot be found
30 pub struct ParentIds<'commit> {
31     range: Range<usize>,
32     commit: &'commit Commit<'commit>,
33 }
34 
35 impl<'repo> Commit<'repo> {
36     /// Get the id (SHA1) of a repository commit
id(&self) -> Oid37     pub fn id(&self) -> Oid {
38         unsafe { Binding::from_raw(raw::git_commit_id(&*self.raw)) }
39     }
40 
41     /// Get the id of the tree pointed to by this commit.
42     ///
43     /// No attempts are made to fetch an object from the ODB.
tree_id(&self) -> Oid44     pub fn tree_id(&self) -> Oid {
45         unsafe { Binding::from_raw(raw::git_commit_tree_id(&*self.raw)) }
46     }
47 
48     /// Get the tree pointed to by a commit.
tree(&self) -> Result<Tree<'repo>, Error>49     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     /// Get access to the underlying raw pointer.
raw(&self) -> *mut raw::git_commit58     pub fn raw(&self) -> *mut raw::git_commit {
59         self.raw
60     }
61 
62     /// Get the full message of a commit.
63     ///
64     /// The returned message will be slightly prettified by removing any
65     /// potential leading newlines.
66     ///
67     /// `None` will be returned if the message is not valid utf-8
message(&self) -> Option<&str>68     pub fn message(&self) -> Option<&str> {
69         str::from_utf8(self.message_bytes()).ok()
70     }
71 
72     /// Get the full message of a commit as a byte slice.
73     ///
74     /// The returned message will be slightly prettified by removing any
75     /// potential leading newlines.
message_bytes(&self) -> &[u8]76     pub fn message_bytes(&self) -> &[u8] {
77         unsafe { crate::opt_bytes(self, raw::git_commit_message(&*self.raw)).unwrap() }
78     }
79 
80     /// Get the encoding for the message of a commit, as a string representing a
81     /// standard encoding name.
82     ///
83     /// `None` will be returned if the encoding is not known
message_encoding(&self) -> Option<&str>84     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     /// Get the full raw message of a commit.
90     ///
91     /// `None` will be returned if the message is not valid utf-8
message_raw(&self) -> Option<&str>92     pub fn message_raw(&self) -> Option<&str> {
93         str::from_utf8(self.message_raw_bytes()).ok()
94     }
95 
96     /// Get the full raw message of a commit.
message_raw_bytes(&self) -> &[u8]97     pub fn message_raw_bytes(&self) -> &[u8] {
98         unsafe { crate::opt_bytes(self, raw::git_commit_message_raw(&*self.raw)).unwrap() }
99     }
100 
101     /// Get the full raw text of the commit header.
102     ///
103     /// `None` will be returned if the message is not valid utf-8
raw_header(&self) -> Option<&str>104     pub fn raw_header(&self) -> Option<&str> {
105         str::from_utf8(self.raw_header_bytes()).ok()
106     }
107 
108     /// Get an arbitrary header field.
header_field_bytes<T: IntoCString>(&self, field: T) -> Result<Buf, Error>109     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     /// Get the full raw text of the commit header.
raw_header_bytes(&self) -> &[u8]123     pub fn raw_header_bytes(&self) -> &[u8] {
124         unsafe { crate::opt_bytes(self, raw::git_commit_raw_header(&*self.raw)).unwrap() }
125     }
126 
127     /// Get the short "summary" of the git commit message.
128     ///
129     /// The returned message is the summary of the commit, comprising the first
130     /// paragraph of the message with whitespace trimmed and squashed.
131     ///
132     /// `None` may be returned if an error occurs or if the summary is not valid
133     /// utf-8.
summary(&self) -> Option<&str>134     pub fn summary(&self) -> Option<&str> {
135         self.summary_bytes().and_then(|s| str::from_utf8(s).ok())
136     }
137 
138     /// Get the short "summary" of the git commit message.
139     ///
140     /// The returned message is the summary of the commit, comprising the first
141     /// paragraph of the message with whitespace trimmed and squashed.
142     ///
143     /// `None` may be returned if an error occurs
summary_bytes(&self) -> Option<&[u8]>144     pub fn summary_bytes(&self) -> Option<&[u8]> {
145         unsafe { crate::opt_bytes(self, raw::git_commit_summary(self.raw)) }
146     }
147 
148     /// Get the commit time (i.e. committer time) of a commit.
149     ///
150     /// The first element of the tuple is the time, in seconds, since the epoch.
151     /// The second element is the offset, in minutes, of the time zone of the
152     /// committer's preferred time zone.
time(&self) -> Time153     pub fn time(&self) -> Time {
154         unsafe {
155             Time::new(
156                 raw::git_commit_time(&*self.raw) as i64,
157                 raw::git_commit_time_offset(&*self.raw) as i32,
158             )
159         }
160     }
161 
162     /// Creates a new iterator over the parents of this commit.
parents<'a>(&'a self) -> Parents<'a, 'repo>163     pub fn parents<'a>(&'a self) -> Parents<'a, 'repo> {
164         Parents {
165             range: 0..self.parent_count(),
166             commit: self,
167         }
168     }
169 
170     /// Creates a new iterator over the parents of this commit.
parent_ids(&self) -> ParentIds<'_>171     pub fn parent_ids(&self) -> ParentIds<'_> {
172         ParentIds {
173             range: 0..self.parent_count(),
174             commit: self,
175         }
176     }
177 
178     /// Get the author of this commit.
author(&self) -> Signature<'_>179     pub fn author(&self) -> Signature<'_> {
180         unsafe {
181             let ptr = raw::git_commit_author(&*self.raw);
182             signature::from_raw_const(self, ptr)
183         }
184     }
185 
186     /// Get the author of this commit, using the mailmap to map names and email
187     /// addresses to canonical real names and email addresses.
author_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error>188     pub fn author_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> {
189         let mut ret = ptr::null_mut();
190         unsafe {
191             try_call!(raw::git_commit_author_with_mailmap(
192                 &mut ret,
193                 &*self.raw,
194                 &*mailmap.raw()
195             ));
196             Ok(Binding::from_raw(ret))
197         }
198     }
199 
200     /// Get the committer of this commit.
committer(&self) -> Signature<'_>201     pub fn committer(&self) -> Signature<'_> {
202         unsafe {
203             let ptr = raw::git_commit_committer(&*self.raw);
204             signature::from_raw_const(self, ptr)
205         }
206     }
207 
208     /// Get the committer of this commit, using the mailmap to map names and email
209     /// addresses to canonical real names and email addresses.
committer_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error>210     pub fn committer_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> {
211         let mut ret = ptr::null_mut();
212         unsafe {
213             try_call!(raw::git_commit_committer_with_mailmap(
214                 &mut ret,
215                 &*self.raw,
216                 &*mailmap.raw()
217             ));
218             Ok(Binding::from_raw(ret))
219         }
220     }
221 
222     /// Amend this existing commit with all non-`None` values
223     ///
224     /// This creates a new commit that is exactly the same as the old commit,
225     /// except that any non-`None` values will be updated. The new commit has
226     /// the same parents as the old commit.
227     ///
228     /// For information about `update_ref`, see [`Repository::commit`].
229     ///
230     /// [`Repository::commit`]: struct.Repository.html#method.commit
amend( &self, update_ref: Option<&str>, author: Option<&Signature<'_>>, committer: Option<&Signature<'_>>, message_encoding: Option<&str>, message: Option<&str>, tree: Option<&Tree<'repo>>, ) -> Result<Oid, Error>231     pub fn amend(
232         &self,
233         update_ref: Option<&str>,
234         author: Option<&Signature<'_>>,
235         committer: Option<&Signature<'_>>,
236         message_encoding: Option<&str>,
237         message: Option<&str>,
238         tree: Option<&Tree<'repo>>,
239     ) -> Result<Oid, Error> {
240         let mut raw = raw::git_oid {
241             id: [0; raw::GIT_OID_RAWSZ],
242         };
243         let update_ref = crate::opt_cstr(update_ref)?;
244         let encoding = crate::opt_cstr(message_encoding)?;
245         let message = crate::opt_cstr(message)?;
246         unsafe {
247             try_call!(raw::git_commit_amend(
248                 &mut raw,
249                 self.raw(),
250                 update_ref,
251                 author.map(|s| s.raw()),
252                 committer.map(|s| s.raw()),
253                 encoding,
254                 message,
255                 tree.map(|t| t.raw())
256             ));
257             Ok(Binding::from_raw(&raw as *const _))
258         }
259     }
260 
261     /// Get the number of parents of this commit.
262     ///
263     /// Use the `parents` iterator to return an iterator over all parents.
parent_count(&self) -> usize264     pub fn parent_count(&self) -> usize {
265         unsafe { raw::git_commit_parentcount(&*self.raw) as usize }
266     }
267 
268     /// Get the specified parent of the commit.
269     ///
270     /// Use the `parents` iterator to return an iterator over all parents.
parent(&self, i: usize) -> Result<Commit<'repo>, Error>271     pub fn parent(&self, i: usize) -> Result<Commit<'repo>, Error> {
272         unsafe {
273             let mut raw = ptr::null_mut();
274             try_call!(raw::git_commit_parent(
275                 &mut raw,
276                 &*self.raw,
277                 i as libc::c_uint
278             ));
279             Ok(Binding::from_raw(raw))
280         }
281     }
282 
283     /// Get the specified parent id of the commit.
284     ///
285     /// This is different from `parent`, which will attempt to load the
286     /// parent commit from the ODB.
287     ///
288     /// Use the `parent_ids` iterator to return an iterator over all parents.
parent_id(&self, i: usize) -> Result<Oid, Error>289     pub fn parent_id(&self, i: usize) -> Result<Oid, Error> {
290         unsafe {
291             let id = raw::git_commit_parent_id(self.raw, i as libc::c_uint);
292             if id.is_null() {
293                 Err(Error::from_str("parent index out of bounds"))
294             } else {
295                 Ok(Binding::from_raw(id))
296             }
297         }
298     }
299 
300     /// Casts this Commit to be usable as an `Object`
as_object(&self) -> &Object<'repo>301     pub fn as_object(&self) -> &Object<'repo> {
302         unsafe { &*(self as *const _ as *const Object<'repo>) }
303     }
304 
305     /// Consumes Commit to be returned as an `Object`
into_object(self) -> Object<'repo>306     pub fn into_object(self) -> Object<'repo> {
307         assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
308         unsafe { mem::transmute(self) }
309     }
310 }
311 
312 impl<'repo> Binding for Commit<'repo> {
313     type Raw = *mut raw::git_commit;
from_raw(raw: *mut raw::git_commit) -> Commit<'repo>314     unsafe fn from_raw(raw: *mut raw::git_commit) -> Commit<'repo> {
315         Commit {
316             raw: raw,
317             _marker: marker::PhantomData,
318         }
319     }
raw(&self) -> *mut raw::git_commit320     fn raw(&self) -> *mut raw::git_commit {
321         self.raw
322     }
323 }
324 
325 impl<'repo> std::fmt::Debug for Commit<'repo> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>326     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
327         let mut ds = f.debug_struct("Commit");
328         ds.field("id", &self.id());
329         if let Some(summary) = self.summary() {
330             ds.field("summary", &summary);
331         }
332         ds.finish()
333     }
334 }
335 
336 /// Aborts iteration when a commit cannot be found
337 impl<'repo, 'commit> Iterator for Parents<'commit, 'repo> {
338     type Item = Commit<'repo>;
next(&mut self) -> Option<Commit<'repo>>339     fn next(&mut self) -> Option<Commit<'repo>> {
340         self.range.next().and_then(|i| self.commit.parent(i).ok())
341     }
size_hint(&self) -> (usize, Option<usize>)342     fn size_hint(&self) -> (usize, Option<usize>) {
343         self.range.size_hint()
344     }
345 }
346 
347 /// Aborts iteration when a commit cannot be found
348 impl<'repo, 'commit> DoubleEndedIterator for Parents<'commit, 'repo> {
next_back(&mut self) -> Option<Commit<'repo>>349     fn next_back(&mut self) -> Option<Commit<'repo>> {
350         self.range
351             .next_back()
352             .and_then(|i| self.commit.parent(i).ok())
353     }
354 }
355 
356 impl<'repo, 'commit> ExactSizeIterator for Parents<'commit, 'repo> {}
357 
358 /// Aborts iteration when a commit cannot be found
359 impl<'commit> Iterator for ParentIds<'commit> {
360     type Item = Oid;
next(&mut self) -> Option<Oid>361     fn next(&mut self) -> Option<Oid> {
362         self.range
363             .next()
364             .and_then(|i| self.commit.parent_id(i).ok())
365     }
size_hint(&self) -> (usize, Option<usize>)366     fn size_hint(&self) -> (usize, Option<usize>) {
367         self.range.size_hint()
368     }
369 }
370 
371 /// Aborts iteration when a commit cannot be found
372 impl<'commit> DoubleEndedIterator for ParentIds<'commit> {
next_back(&mut self) -> Option<Oid>373     fn next_back(&mut self) -> Option<Oid> {
374         self.range
375             .next_back()
376             .and_then(|i| self.commit.parent_id(i).ok())
377     }
378 }
379 
380 impl<'commit> ExactSizeIterator for ParentIds<'commit> {}
381 
382 impl<'repo> Clone for Commit<'repo> {
clone(&self) -> Self383     fn clone(&self) -> Self {
384         self.as_object().clone().into_commit().ok().unwrap()
385     }
386 }
387 
388 impl<'repo> Drop for Commit<'repo> {
drop(&mut self)389     fn drop(&mut self) {
390         unsafe { raw::git_commit_free(self.raw) }
391     }
392 }
393 
394 #[cfg(test)]
395 mod tests {
396     #[test]
smoke()397     fn smoke() {
398         let (_td, repo) = crate::test::repo_init();
399         let head = repo.head().unwrap();
400         let target = head.target().unwrap();
401         let commit = repo.find_commit(target).unwrap();
402         assert_eq!(commit.message(), Some("initial"));
403         assert_eq!(commit.id(), target);
404         commit.message_raw().unwrap();
405         commit.raw_header().unwrap();
406         commit.message_encoding();
407         commit.summary().unwrap();
408         commit.tree_id();
409         commit.tree().unwrap();
410         assert_eq!(commit.parents().count(), 0);
411 
412         let tree_header_bytes = commit.header_field_bytes("tree").unwrap();
413         assert_eq!(
414             crate::Oid::from_str(tree_header_bytes.as_str().unwrap()).unwrap(),
415             commit.tree_id()
416         );
417         assert_eq!(commit.author().name(), Some("name"));
418         assert_eq!(commit.author().email(), Some("email"));
419         assert_eq!(commit.committer().name(), Some("name"));
420         assert_eq!(commit.committer().email(), Some("email"));
421 
422         let sig = repo.signature().unwrap();
423         let tree = repo.find_tree(commit.tree_id()).unwrap();
424         let id = repo
425             .commit(Some("HEAD"), &sig, &sig, "bar", &tree, &[&commit])
426             .unwrap();
427         let head = repo.find_commit(id).unwrap();
428 
429         let new_head = head
430             .amend(Some("HEAD"), None, None, None, Some("new message"), None)
431             .unwrap();
432         let new_head = repo.find_commit(new_head).unwrap();
433         assert_eq!(new_head.message(), Some("new message"));
434         new_head.into_object();
435 
436         repo.find_object(target, None).unwrap().as_commit().unwrap();
437         repo.find_object(target, None)
438             .unwrap()
439             .into_commit()
440             .ok()
441             .unwrap();
442     }
443 }
444