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