]> git.proxmox.com Git - cargo.git/blob - vendor/git2-0.6.8/src/commit.rs
New upstream version 0.23.0
[cargo.git] / vendor / git2-0.6.8 / src / commit.rs
1 use std::marker;
2 use std::mem;
3 use std::ops::Range;
4 use std::ptr;
5 use std::str;
6 use libc;
7
8 use {raw, signature, Oid, Error, Signature, Tree, Time, Object};
9 use util::Binding;
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 pub struct Parents<'commit, 'repo: 'commit> {
21 range: Range<usize>,
22 commit: &'commit Commit<'repo>,
23 }
24
25 /// An iterator over the parent commits' ids of a commit.
26 pub struct ParentIds<'commit> {
27 range: Range<usize>,
28 commit: &'commit Commit<'commit>,
29 }
30
31 impl<'repo> Commit<'repo> {
32 /// Get the id (SHA1) of a repository commit
33 pub fn id(&self) -> Oid {
34 unsafe { Binding::from_raw(raw::git_commit_id(&*self.raw)) }
35 }
36
37 /// Get the id of the tree pointed to by this commit.
38 ///
39 /// No attempts are made to fetch an object from the ODB.
40 pub fn tree_id(&self) -> Oid {
41 unsafe { Binding::from_raw(raw::git_commit_tree_id(&*self.raw)) }
42 }
43
44 /// Get the tree pointed to by a commit.
45 pub fn tree(&self) -> Result<Tree<'repo>, Error> {
46 let mut ret = ptr::null_mut();
47 unsafe {
48 try_call!(raw::git_commit_tree(&mut ret, &*self.raw));
49 Ok(Binding::from_raw(ret))
50 }
51 }
52
53 /// Get access to the underlying raw pointer.
54 pub fn raw(&self) -> *mut raw::git_commit { self.raw }
55
56 /// Get the full message of a commit.
57 ///
58 /// The returned message will be slightly prettified by removing any
59 /// potential leading newlines.
60 ///
61 /// `None` will be returned if the message is not valid utf-8
62 pub fn message(&self) -> Option<&str> {
63 str::from_utf8(self.message_bytes()).ok()
64 }
65
66 /// Get the full message of a commit as a byte slice.
67 ///
68 /// The returned message will be slightly prettified by removing any
69 /// potential leading newlines.
70 pub fn message_bytes(&self) -> &[u8] {
71 unsafe {
72 ::opt_bytes(self, raw::git_commit_message(&*self.raw)).unwrap()
73 }
74 }
75
76 /// Get the encoding for the message of a commit, as a string representing a
77 /// standard encoding name.
78 ///
79 /// `None` will be returned if the encoding is not known
80 pub fn message_encoding(&self) -> Option<&str> {
81 let bytes = unsafe {
82 ::opt_bytes(self, raw::git_commit_message(&*self.raw))
83 };
84 bytes.map(|b| str::from_utf8(b).unwrap())
85 }
86
87 /// Get the full raw message of a commit.
88 ///
89 /// `None` will be returned if the message is not valid utf-8
90 pub fn message_raw(&self) -> Option<&str> {
91 str::from_utf8(self.message_raw_bytes()).ok()
92 }
93
94 /// Get the full raw message of a commit.
95 pub fn message_raw_bytes(&self) -> &[u8] {
96 unsafe {
97 ::opt_bytes(self, raw::git_commit_message_raw(&*self.raw)).unwrap()
98 }
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
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.
109 pub fn raw_header_bytes(&self) -> &[u8] {
110 unsafe {
111 ::opt_bytes(self, raw::git_commit_raw_header(&*self.raw)).unwrap()
112 }
113 }
114
115 /// Get the short "summary" of the git commit message.
116 ///
117 /// The returned message is the summary of the commit, comprising the first
118 /// paragraph of the message with whitespace trimmed and squashed.
119 ///
120 /// `None` may be returned if an error occurs or if the summary is not valid
121 /// utf-8.
122 pub fn summary(&mut self) -> Option<&str> {
123 self.summary_bytes().and_then(|s| str::from_utf8(s).ok())
124 }
125
126 /// Get the short "summary" of the git commit message.
127 ///
128 /// The returned message is the summary of the commit, comprising the first
129 /// paragraph of the message with whitespace trimmed and squashed.
130 ///
131 /// `None` may be returned if an error occurs
132 pub fn summary_bytes(&mut self) -> Option<&[u8]> {
133 unsafe { ::opt_bytes(self, raw::git_commit_summary(self.raw)) }
134 }
135
136 /// Get the commit time (i.e. committer time) of a commit.
137 ///
138 /// The first element of the tuple is the time, in seconds, since the epoch.
139 /// The second element is the offset, in minutes, of the time zone of the
140 /// committer's preferred time zone.
141 pub fn time(&self) -> Time {
142 unsafe {
143 Time::new(raw::git_commit_time(&*self.raw) as i64,
144 raw::git_commit_time_offset(&*self.raw) as i32)
145 }
146 }
147
148 /// Creates a new iterator over the parents of this commit.
149 pub fn parents<'a>(&'a self) -> Parents<'a, 'repo> {
150 let max = unsafe { raw::git_commit_parentcount(&*self.raw) as usize };
151 Parents { range: 0..max, commit: self }
152 }
153
154 /// Creates a new iterator over the parents of this commit.
155 pub fn parent_ids(&self) -> ParentIds {
156 let max = unsafe { raw::git_commit_parentcount(&*self.raw) as usize };
157 ParentIds { range: 0..max, commit: self }
158 }
159
160 /// Get the author of this commit.
161 pub fn author(&self) -> Signature {
162 unsafe {
163 let ptr = raw::git_commit_author(&*self.raw);
164 signature::from_raw_const(self, ptr)
165 }
166 }
167
168 /// Get the committer of this commit.
169 pub fn committer(&self) -> Signature {
170 unsafe {
171 let ptr = raw::git_commit_committer(&*self.raw);
172 signature::from_raw_const(self, ptr)
173 }
174 }
175
176 /// Amend this existing commit with all non-`None` values
177 ///
178 /// This creates a new commit that is exactly the same as the old commit,
179 /// except that any non-`None` values will be updated. The new commit has
180 /// the same parents as the old commit.
181 ///
182 /// For information about `update_ref`, see [`Repository::commit`].
183 ///
184 /// [`Repository::commit`]: struct.Repository.html#method.commit
185 pub fn amend(&self,
186 update_ref: Option<&str>,
187 author: Option<&Signature>,
188 committer: Option<&Signature>,
189 message_encoding: Option<&str>,
190 message: Option<&str>,
191 tree: Option<&Tree<'repo>>) -> Result<Oid, Error> {
192 let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ] };
193 let update_ref = try!(::opt_cstr(update_ref));
194 let encoding = try!(::opt_cstr(message_encoding));
195 let message = try!(::opt_cstr(message));
196 unsafe {
197 try_call!(raw::git_commit_amend(&mut raw,
198 self.raw(),
199 update_ref,
200 author.map(|s| s.raw()),
201 committer.map(|s| s.raw()),
202 encoding,
203 message,
204 tree.map(|t| t.raw())));
205 Ok(Binding::from_raw(&raw as *const _))
206 }
207 }
208
209 /// Get the specified parent of the commit.
210 ///
211 /// Use the `parents` iterator to return an iterator over all parents.
212 pub fn parent(&self, i: usize) -> Result<Commit<'repo>, Error> {
213 unsafe {
214 let mut raw = ptr::null_mut();
215 try_call!(raw::git_commit_parent(&mut raw, &*self.raw,
216 i as libc::c_uint));
217 Ok(Binding::from_raw(raw))
218 }
219 }
220
221 /// Get the specified parent id of the commit.
222 ///
223 /// This is different from `parent`, which will attemptstempt to load the
224 /// parent commit from the ODB.
225 ///
226 /// Use the `parent_ids` iterator to return an iterator over all parents.
227 pub fn parent_id(&self, i: usize) -> Result<Oid, Error> {
228 unsafe {
229 let id = raw::git_commit_parent_id(self.raw, i as libc::c_uint);
230 if id.is_null() {
231 Err(Error::from_str("parent index out of bounds"))
232 } else {
233 Ok(Binding::from_raw(id))
234 }
235 }
236 }
237
238 /// Casts this Commit to be usable as an `Object`
239 pub fn as_object(&self) -> &Object<'repo> {
240 unsafe {
241 &*(self as *const _ as *const Object<'repo>)
242 }
243 }
244
245 /// Consumes Commit to be returned as an `Object`
246 pub fn into_object(self) -> Object<'repo> {
247 assert_eq!(mem::size_of_val(&self), mem::size_of::<Object>());
248 unsafe {
249 mem::transmute(self)
250 }
251 }
252 }
253
254 impl<'repo> Binding for Commit<'repo> {
255 type Raw = *mut raw::git_commit;
256 unsafe fn from_raw(raw: *mut raw::git_commit) -> Commit<'repo> {
257 Commit {
258 raw: raw,
259 _marker: marker::PhantomData,
260 }
261 }
262 fn raw(&self) -> *mut raw::git_commit { self.raw }
263 }
264
265
266 impl<'repo, 'commit> Iterator for Parents<'commit, 'repo> {
267 type Item = Commit<'repo>;
268 fn next(&mut self) -> Option<Commit<'repo>> {
269 self.range.next().map(|i| self.commit.parent(i).unwrap())
270 }
271 fn size_hint(&self) -> (usize, Option<usize>) { self.range.size_hint() }
272 }
273
274 impl<'repo, 'commit> DoubleEndedIterator for Parents<'commit, 'repo> {
275 fn next_back(&mut self) -> Option<Commit<'repo>> {
276 self.range.next_back().map(|i| self.commit.parent(i).unwrap())
277 }
278 }
279
280 impl<'repo, 'commit> ExactSizeIterator for Parents<'commit, 'repo> {}
281
282 impl<'commit> Iterator for ParentIds<'commit> {
283 type Item = Oid;
284 fn next(&mut self) -> Option<Oid> {
285 self.range.next().map(|i| self.commit.parent_id(i).unwrap())
286 }
287 fn size_hint(&self) -> (usize, Option<usize>) { self.range.size_hint() }
288 }
289
290 impl<'commit> DoubleEndedIterator for ParentIds<'commit> {
291 fn next_back(&mut self) -> Option<Oid> {
292 self.range.next_back().map(|i| self.commit.parent_id(i).unwrap())
293 }
294 }
295
296 impl<'commit> ExactSizeIterator for ParentIds<'commit> {}
297
298 impl<'repo> Drop for Commit<'repo> {
299 fn drop(&mut self) {
300 unsafe { raw::git_commit_free(self.raw) }
301 }
302 }
303
304 #[cfg(test)]
305 mod tests {
306 #[test]
307 fn smoke() {
308 let (_td, repo) = ::test::repo_init();
309 let head = repo.head().unwrap();
310 let target = head.target().unwrap();
311 let mut commit = repo.find_commit(target).unwrap();
312 assert_eq!(commit.message(), Some("initial"));
313 assert_eq!(commit.id(), target);
314 commit.message_raw().unwrap();
315 commit.raw_header().unwrap();
316 commit.message_encoding();
317 commit.summary().unwrap();
318 commit.tree_id();
319 commit.tree().unwrap();
320 assert_eq!(commit.parents().count(), 0);
321
322 assert_eq!(commit.author().name(), Some("name"));
323 assert_eq!(commit.author().email(), Some("email"));
324 assert_eq!(commit.committer().name(), Some("name"));
325 assert_eq!(commit.committer().email(), Some("email"));
326
327 let sig = repo.signature().unwrap();
328 let tree = repo.find_tree(commit.tree_id()).unwrap();
329 let id = repo.commit(Some("HEAD"), &sig, &sig, "bar", &tree,
330 &[&commit]).unwrap();
331 let head = repo.find_commit(id).unwrap();
332
333 let new_head = head.amend(Some("HEAD"), None, None, None,
334 Some("new message"), None).unwrap();
335 let new_head = repo.find_commit(new_head).unwrap();
336 assert_eq!(new_head.message(), Some("new message"));
337 new_head.into_object();
338
339 repo.find_object(target, None).unwrap().as_commit().unwrap();
340 repo.find_object(target, None).unwrap().into_commit().ok().unwrap();
341 }
342 }
343