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, 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