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, Error, 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 the full raw text of the commit header.
raw_header_bytes(&self) -> &[u8]109     pub fn raw_header_bytes(&self) -> &[u8] {
110         unsafe { crate::opt_bytes(self, raw::git_commit_raw_header(&*self.raw)).unwrap() }
111     }
112 
113     /// Get the short "summary" of the git commit message.
114     ///
115     /// The returned message is the summary of the commit, comprising the first
116     /// paragraph of the message with whitespace trimmed and squashed.
117     ///
118     /// `None` may be returned if an error occurs or if the summary is not valid
119     /// utf-8.
summary(&self) -> Option<&str>120     pub fn summary(&self) -> Option<&str> {
121         self.summary_bytes().and_then(|s| str::from_utf8(s).ok())
122     }
123 
124     /// Get the short "summary" of the git commit message.
125     ///
126     /// The returned message is the summary of the commit, comprising the first
127     /// paragraph of the message with whitespace trimmed and squashed.
128     ///
129     /// `None` may be returned if an error occurs
summary_bytes(&self) -> Option<&[u8]>130     pub fn summary_bytes(&self) -> Option<&[u8]> {
131         unsafe { crate::opt_bytes(self, raw::git_commit_summary(self.raw)) }
132     }
133 
134     /// Get the commit time (i.e. committer time) of a commit.
135     ///
136     /// The first element of the tuple is the time, in seconds, since the epoch.
137     /// The second element is the offset, in minutes, of the time zone of the
138     /// committer's preferred time zone.
time(&self) -> Time139     pub fn time(&self) -> Time {
140         unsafe {
141             Time::new(
142                 raw::git_commit_time(&*self.raw) as i64,
143                 raw::git_commit_time_offset(&*self.raw) as i32,
144             )
145         }
146     }
147 
148     /// Creates a new iterator over the parents of this commit.
parents<'a>(&'a self) -> Parents<'a, 'repo>149     pub fn parents<'a>(&'a self) -> Parents<'a, 'repo> {
150         Parents {
151             range: 0..self.parent_count(),
152             commit: self,
153         }
154     }
155 
156     /// Creates a new iterator over the parents of this commit.
parent_ids(&self) -> ParentIds<'_>157     pub fn parent_ids(&self) -> ParentIds<'_> {
158         ParentIds {
159             range: 0..self.parent_count(),
160             commit: self,
161         }
162     }
163 
164     /// Get the author of this commit.
author(&self) -> Signature<'_>165     pub fn author(&self) -> Signature<'_> {
166         unsafe {
167             let ptr = raw::git_commit_author(&*self.raw);
168             signature::from_raw_const(self, ptr)
169         }
170     }
171 
172     /// Get the committer of this commit.
committer(&self) -> Signature<'_>173     pub fn committer(&self) -> Signature<'_> {
174         unsafe {
175             let ptr = raw::git_commit_committer(&*self.raw);
176             signature::from_raw_const(self, ptr)
177         }
178     }
179 
180     /// Amend this existing commit with all non-`None` values
181     ///
182     /// This creates a new commit that is exactly the same as the old commit,
183     /// except that any non-`None` values will be updated. The new commit has
184     /// the same parents as the old commit.
185     ///
186     /// For information about `update_ref`, see [`Repository::commit`].
187     ///
188     /// [`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>189     pub fn amend(
190         &self,
191         update_ref: Option<&str>,
192         author: Option<&Signature<'_>>,
193         committer: Option<&Signature<'_>>,
194         message_encoding: Option<&str>,
195         message: Option<&str>,
196         tree: Option<&Tree<'repo>>,
197     ) -> Result<Oid, Error> {
198         let mut raw = raw::git_oid {
199             id: [0; raw::GIT_OID_RAWSZ],
200         };
201         let update_ref = crate::opt_cstr(update_ref)?;
202         let encoding = crate::opt_cstr(message_encoding)?;
203         let message = crate::opt_cstr(message)?;
204         unsafe {
205             try_call!(raw::git_commit_amend(
206                 &mut raw,
207                 self.raw(),
208                 update_ref,
209                 author.map(|s| s.raw()),
210                 committer.map(|s| s.raw()),
211                 encoding,
212                 message,
213                 tree.map(|t| t.raw())
214             ));
215             Ok(Binding::from_raw(&raw as *const _))
216         }
217     }
218 
219     /// Get the number of parents of this commit.
220     ///
221     /// Use the `parents` iterator to return an iterator over all parents.
parent_count(&self) -> usize222     pub fn parent_count(&self) -> usize {
223         unsafe { raw::git_commit_parentcount(&*self.raw) as usize }
224     }
225 
226     /// Get the specified parent of the commit.
227     ///
228     /// Use the `parents` iterator to return an iterator over all parents.
parent(&self, i: usize) -> Result<Commit<'repo>, Error>229     pub fn parent(&self, i: usize) -> Result<Commit<'repo>, Error> {
230         unsafe {
231             let mut raw = ptr::null_mut();
232             try_call!(raw::git_commit_parent(
233                 &mut raw,
234                 &*self.raw,
235                 i as libc::c_uint
236             ));
237             Ok(Binding::from_raw(raw))
238         }
239     }
240 
241     /// Get the specified parent id of the commit.
242     ///
243     /// This is different from `parent`, which will attempt to load the
244     /// parent commit from the ODB.
245     ///
246     /// Use the `parent_ids` iterator to return an iterator over all parents.
parent_id(&self, i: usize) -> Result<Oid, Error>247     pub fn parent_id(&self, i: usize) -> Result<Oid, Error> {
248         unsafe {
249             let id = raw::git_commit_parent_id(self.raw, i as libc::c_uint);
250             if id.is_null() {
251                 Err(Error::from_str("parent index out of bounds"))
252             } else {
253                 Ok(Binding::from_raw(id))
254             }
255         }
256     }
257 
258     /// Casts this Commit to be usable as an `Object`
as_object(&self) -> &Object<'repo>259     pub fn as_object(&self) -> &Object<'repo> {
260         unsafe { &*(self as *const _ as *const Object<'repo>) }
261     }
262 
263     /// Consumes Commit to be returned as an `Object`
into_object(self) -> Object<'repo>264     pub fn into_object(self) -> Object<'repo> {
265         assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
266         unsafe { mem::transmute(self) }
267     }
268 }
269 
270 impl<'repo> Binding for Commit<'repo> {
271     type Raw = *mut raw::git_commit;
from_raw(raw: *mut raw::git_commit) -> Commit<'repo>272     unsafe fn from_raw(raw: *mut raw::git_commit) -> Commit<'repo> {
273         Commit {
274             raw: raw,
275             _marker: marker::PhantomData,
276         }
277     }
raw(&self) -> *mut raw::git_commit278     fn raw(&self) -> *mut raw::git_commit {
279         self.raw
280     }
281 }
282 
283 impl<'repo> std::fmt::Debug for Commit<'repo> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>284     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
285         let mut ds = f.debug_struct("Commit");
286         ds.field("id", &self.id());
287         if let Some(summary) = self.summary() {
288             ds.field("summary", &summary);
289         }
290         ds.finish()
291     }
292 }
293 
294 /// Aborts iteration when a commit cannot be found
295 impl<'repo, 'commit> Iterator for Parents<'commit, 'repo> {
296     type Item = Commit<'repo>;
next(&mut self) -> Option<Commit<'repo>>297     fn next(&mut self) -> Option<Commit<'repo>> {
298         self.range.next().and_then(|i| self.commit.parent(i).ok())
299     }
size_hint(&self) -> (usize, Option<usize>)300     fn size_hint(&self) -> (usize, Option<usize>) {
301         self.range.size_hint()
302     }
303 }
304 
305 /// Aborts iteration when a commit cannot be found
306 impl<'repo, 'commit> DoubleEndedIterator for Parents<'commit, 'repo> {
next_back(&mut self) -> Option<Commit<'repo>>307     fn next_back(&mut self) -> Option<Commit<'repo>> {
308         self.range
309             .next_back()
310             .and_then(|i| self.commit.parent(i).ok())
311     }
312 }
313 
314 impl<'repo, 'commit> ExactSizeIterator for Parents<'commit, 'repo> {}
315 
316 /// Aborts iteration when a commit cannot be found
317 impl<'commit> Iterator for ParentIds<'commit> {
318     type Item = Oid;
next(&mut self) -> Option<Oid>319     fn next(&mut self) -> Option<Oid> {
320         self.range
321             .next()
322             .and_then(|i| self.commit.parent_id(i).ok())
323     }
size_hint(&self) -> (usize, Option<usize>)324     fn size_hint(&self) -> (usize, Option<usize>) {
325         self.range.size_hint()
326     }
327 }
328 
329 /// Aborts iteration when a commit cannot be found
330 impl<'commit> DoubleEndedIterator for ParentIds<'commit> {
next_back(&mut self) -> Option<Oid>331     fn next_back(&mut self) -> Option<Oid> {
332         self.range
333             .next_back()
334             .and_then(|i| self.commit.parent_id(i).ok())
335     }
336 }
337 
338 impl<'commit> ExactSizeIterator for ParentIds<'commit> {}
339 
340 impl<'repo> Clone for Commit<'repo> {
clone(&self) -> Self341     fn clone(&self) -> Self {
342         self.as_object().clone().into_commit().ok().unwrap()
343     }
344 }
345 
346 impl<'repo> Drop for Commit<'repo> {
drop(&mut self)347     fn drop(&mut self) {
348         unsafe { raw::git_commit_free(self.raw) }
349     }
350 }
351 
352 #[cfg(test)]
353 mod tests {
354     #[test]
smoke()355     fn smoke() {
356         let (_td, repo) = crate::test::repo_init();
357         let head = repo.head().unwrap();
358         let target = head.target().unwrap();
359         let commit = repo.find_commit(target).unwrap();
360         assert_eq!(commit.message(), Some("initial"));
361         assert_eq!(commit.id(), target);
362         commit.message_raw().unwrap();
363         commit.raw_header().unwrap();
364         commit.message_encoding();
365         commit.summary().unwrap();
366         commit.tree_id();
367         commit.tree().unwrap();
368         assert_eq!(commit.parents().count(), 0);
369 
370         assert_eq!(commit.author().name(), Some("name"));
371         assert_eq!(commit.author().email(), Some("email"));
372         assert_eq!(commit.committer().name(), Some("name"));
373         assert_eq!(commit.committer().email(), Some("email"));
374 
375         let sig = repo.signature().unwrap();
376         let tree = repo.find_tree(commit.tree_id()).unwrap();
377         let id = repo
378             .commit(Some("HEAD"), &sig, &sig, "bar", &tree, &[&commit])
379             .unwrap();
380         let head = repo.find_commit(id).unwrap();
381 
382         let new_head = head
383             .amend(Some("HEAD"), None, None, None, Some("new message"), None)
384             .unwrap();
385         let new_head = repo.find_commit(new_head).unwrap();
386         assert_eq!(new_head.message(), Some("new message"));
387         new_head.into_object();
388 
389         repo.find_object(target, None).unwrap().as_commit().unwrap();
390         repo.find_object(target, None)
391             .unwrap()
392             .into_commit()
393             .ok()
394             .unwrap();
395     }
396 }
397