1 use std::cmp::Ordering; 2 use std::ffi::CString; 3 use std::marker; 4 use std::mem; 5 use std::ptr; 6 use std::str; 7 8 use crate::object::CastOrPanic; 9 use crate::util::{c_cmp_to_ordering, Binding}; 10 use crate::{ 11 raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceType, Repository, Tag, Tree, 12 }; 13 14 struct Refdb<'repo>(&'repo Repository); 15 16 /// A structure to represent a git [reference][1]. 17 /// 18 /// [1]: http://git-scm.com/book/en/Git-Internals-Git-References 19 pub struct Reference<'repo> { 20 raw: *mut raw::git_reference, 21 _marker: marker::PhantomData<Refdb<'repo>>, 22 } 23 24 /// An iterator over the references in a repository. 25 pub struct References<'repo> { 26 raw: *mut raw::git_reference_iterator, 27 _marker: marker::PhantomData<Refdb<'repo>>, 28 } 29 30 /// An iterator over the names of references in a repository. 31 pub struct ReferenceNames<'repo, 'references> { 32 inner: &'references mut References<'repo>, 33 } 34 35 impl<'repo> Reference<'repo> { 36 /// Ensure the reference name is well-formed. is_valid_name(refname: &str) -> bool37 pub fn is_valid_name(refname: &str) -> bool { 38 crate::init(); 39 let refname = CString::new(refname).unwrap(); 40 unsafe { raw::git_reference_is_valid_name(refname.as_ptr()) == 1 } 41 } 42 43 /// Get access to the underlying raw pointer. raw(&self) -> *mut raw::git_reference44 pub fn raw(&self) -> *mut raw::git_reference { 45 self.raw 46 } 47 48 /// Delete an existing reference. 49 /// 50 /// This method works for both direct and symbolic references. The reference 51 /// will be immediately removed on disk. 52 /// 53 /// This function will return an error if the reference has changed from the 54 /// time it was looked up. delete(&mut self) -> Result<(), Error>55 pub fn delete(&mut self) -> Result<(), Error> { 56 unsafe { 57 try_call!(raw::git_reference_delete(self.raw)); 58 } 59 Ok(()) 60 } 61 62 /// Check if a reference is a local branch. is_branch(&self) -> bool63 pub fn is_branch(&self) -> bool { 64 unsafe { raw::git_reference_is_branch(&*self.raw) == 1 } 65 } 66 67 /// Check if a reference is a note. is_note(&self) -> bool68 pub fn is_note(&self) -> bool { 69 unsafe { raw::git_reference_is_note(&*self.raw) == 1 } 70 } 71 72 /// Check if a reference is a remote tracking branch is_remote(&self) -> bool73 pub fn is_remote(&self) -> bool { 74 unsafe { raw::git_reference_is_remote(&*self.raw) == 1 } 75 } 76 77 /// Check if a reference is a tag is_tag(&self) -> bool78 pub fn is_tag(&self) -> bool { 79 unsafe { raw::git_reference_is_tag(&*self.raw) == 1 } 80 } 81 82 /// Get the reference type of a reference. 83 /// 84 /// If the type is unknown, then `None` is returned. kind(&self) -> Option<ReferenceType>85 pub fn kind(&self) -> Option<ReferenceType> { 86 ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) }) 87 } 88 89 /// Get the full name of a reference. 90 /// 91 /// Returns `None` if the name is not valid utf-8. name(&self) -> Option<&str>92 pub fn name(&self) -> Option<&str> { 93 str::from_utf8(self.name_bytes()).ok() 94 } 95 96 /// Get the full name of a reference. name_bytes(&self) -> &[u8]97 pub fn name_bytes(&self) -> &[u8] { 98 unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() } 99 } 100 101 /// Get the full shorthand of a reference. 102 /// 103 /// This will transform the reference name into a name "human-readable" 104 /// version. If no shortname is appropriate, it will return the full name. 105 /// 106 /// Returns `None` if the shorthand is not valid utf-8. shorthand(&self) -> Option<&str>107 pub fn shorthand(&self) -> Option<&str> { 108 str::from_utf8(self.shorthand_bytes()).ok() 109 } 110 111 /// Get the full shorthand of a reference. shorthand_bytes(&self) -> &[u8]112 pub fn shorthand_bytes(&self) -> &[u8] { 113 unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() } 114 } 115 116 /// Get the OID pointed to by a direct reference. 117 /// 118 /// Only available if the reference is direct (i.e. an object id reference, 119 /// not a symbolic one). target(&self) -> Option<Oid>120 pub fn target(&self) -> Option<Oid> { 121 unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) } 122 } 123 124 /// Return the peeled OID target of this reference. 125 /// 126 /// This peeled OID only applies to direct references that point to a hard 127 /// Tag object: it is the result of peeling such Tag. target_peel(&self) -> Option<Oid>128 pub fn target_peel(&self) -> Option<Oid> { 129 unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) } 130 } 131 132 /// Get full name to the reference pointed to by a symbolic reference. 133 /// 134 /// May return `None` if the reference is either not symbolic or not a 135 /// valid utf-8 string. symbolic_target(&self) -> Option<&str>136 pub fn symbolic_target(&self) -> Option<&str> { 137 self.symbolic_target_bytes() 138 .and_then(|s| str::from_utf8(s).ok()) 139 } 140 141 /// Get full name to the reference pointed to by a symbolic reference. 142 /// 143 /// Only available if the reference is symbolic. symbolic_target_bytes(&self) -> Option<&[u8]>144 pub fn symbolic_target_bytes(&self) -> Option<&[u8]> { 145 unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) } 146 } 147 148 /// Resolve a symbolic reference to a direct reference. 149 /// 150 /// This method iteratively peels a symbolic reference until it resolves to 151 /// a direct reference to an OID. 152 /// 153 /// If a direct reference is passed as an argument, a copy of that 154 /// reference is returned. resolve(&self) -> Result<Reference<'repo>, Error>155 pub fn resolve(&self) -> Result<Reference<'repo>, Error> { 156 let mut raw = ptr::null_mut(); 157 unsafe { 158 try_call!(raw::git_reference_resolve(&mut raw, &*self.raw)); 159 Ok(Binding::from_raw(raw)) 160 } 161 } 162 163 /// Peel a reference to an object 164 /// 165 /// This method recursively peels the reference until it reaches 166 /// an object of the specified type. peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error>167 pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> { 168 let mut raw = ptr::null_mut(); 169 unsafe { 170 try_call!(raw::git_reference_peel(&mut raw, self.raw, kind)); 171 Ok(Binding::from_raw(raw)) 172 } 173 } 174 175 /// Peel a reference to a blob 176 /// 177 /// This method recursively peels the reference until it reaches 178 /// a blob. peel_to_blob(&self) -> Result<Blob<'repo>, Error>179 pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> { 180 Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob)) 181 } 182 183 /// Peel a reference to a commit 184 /// 185 /// This method recursively peels the reference until it reaches 186 /// a commit. peel_to_commit(&self) -> Result<Commit<'repo>, Error>187 pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> { 188 Ok(self 189 .peel(ObjectType::Commit)? 190 .cast_or_panic(ObjectType::Commit)) 191 } 192 193 /// Peel a reference to a tree 194 /// 195 /// This method recursively peels the reference until it reaches 196 /// a tree. peel_to_tree(&self) -> Result<Tree<'repo>, Error>197 pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> { 198 Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree)) 199 } 200 201 /// Peel a reference to a tag 202 /// 203 /// This method recursively peels the reference until it reaches 204 /// a tag. peel_to_tag(&self) -> Result<Tag<'repo>, Error>205 pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> { 206 Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag)) 207 } 208 209 /// Rename an existing reference. 210 /// 211 /// This method works for both direct and symbolic references. 212 /// 213 /// If the force flag is not enabled, and there's already a reference with 214 /// the given name, the renaming will fail. rename( &mut self, new_name: &str, force: bool, msg: &str, ) -> Result<Reference<'repo>, Error>215 pub fn rename( 216 &mut self, 217 new_name: &str, 218 force: bool, 219 msg: &str, 220 ) -> Result<Reference<'repo>, Error> { 221 let mut raw = ptr::null_mut(); 222 let new_name = CString::new(new_name)?; 223 let msg = CString::new(msg)?; 224 unsafe { 225 try_call!(raw::git_reference_rename( 226 &mut raw, self.raw, new_name, force, msg 227 )); 228 Ok(Binding::from_raw(raw)) 229 } 230 } 231 232 /// Conditionally create a new reference with the same name as the given 233 /// reference but a different OID target. The reference must be a direct 234 /// reference, otherwise this will fail. 235 /// 236 /// The new reference will be written to disk, overwriting the given 237 /// reference. set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error>238 pub fn set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error> { 239 let mut raw = ptr::null_mut(); 240 let msg = CString::new(reflog_msg)?; 241 unsafe { 242 try_call!(raw::git_reference_set_target( 243 &mut raw, 244 self.raw, 245 id.raw(), 246 msg 247 )); 248 Ok(Binding::from_raw(raw)) 249 } 250 } 251 } 252 253 impl<'repo> PartialOrd for Reference<'repo> { partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering>254 fn partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering> { 255 Some(self.cmp(other)) 256 } 257 } 258 259 impl<'repo> Ord for Reference<'repo> { cmp(&self, other: &Reference<'repo>) -> Ordering260 fn cmp(&self, other: &Reference<'repo>) -> Ordering { 261 c_cmp_to_ordering(unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) }) 262 } 263 } 264 265 impl<'repo> PartialEq for Reference<'repo> { eq(&self, other: &Reference<'repo>) -> bool266 fn eq(&self, other: &Reference<'repo>) -> bool { 267 self.cmp(other) == Ordering::Equal 268 } 269 } 270 271 impl<'repo> Eq for Reference<'repo> {} 272 273 impl<'repo> Binding for Reference<'repo> { 274 type Raw = *mut raw::git_reference; from_raw(raw: *mut raw::git_reference) -> Reference<'repo>275 unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> { 276 Reference { 277 raw: raw, 278 _marker: marker::PhantomData, 279 } 280 } raw(&self) -> *mut raw::git_reference281 fn raw(&self) -> *mut raw::git_reference { 282 self.raw 283 } 284 } 285 286 impl<'repo> Drop for Reference<'repo> { drop(&mut self)287 fn drop(&mut self) { 288 unsafe { raw::git_reference_free(self.raw) } 289 } 290 } 291 292 impl<'repo> References<'repo> { 293 /// Consumes a `References` iterator to create an iterator over just the 294 /// name of some references. 295 /// 296 /// This is more efficient if only the names are desired of references as 297 /// the references themselves don't have to be allocated and deallocated. 298 /// 299 /// The returned iterator will yield strings as opposed to a `Reference`. names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a>300 pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> { 301 ReferenceNames { inner: self } 302 } 303 } 304 305 impl<'repo> Binding for References<'repo> { 306 type Raw = *mut raw::git_reference_iterator; from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo>307 unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> { 308 References { 309 raw: raw, 310 _marker: marker::PhantomData, 311 } 312 } raw(&self) -> *mut raw::git_reference_iterator313 fn raw(&self) -> *mut raw::git_reference_iterator { 314 self.raw 315 } 316 } 317 318 impl<'repo> Iterator for References<'repo> { 319 type Item = Result<Reference<'repo>, Error>; next(&mut self) -> Option<Result<Reference<'repo>, Error>>320 fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> { 321 let mut out = ptr::null_mut(); 322 unsafe { 323 try_call_iter!(raw::git_reference_next(&mut out, self.raw)); 324 Some(Ok(Binding::from_raw(out))) 325 } 326 } 327 } 328 329 impl<'repo> Drop for References<'repo> { drop(&mut self)330 fn drop(&mut self) { 331 unsafe { raw::git_reference_iterator_free(self.raw) } 332 } 333 } 334 335 impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> { 336 type Item = Result<&'references str, Error>; next(&mut self) -> Option<Result<&'references str, Error>>337 fn next(&mut self) -> Option<Result<&'references str, Error>> { 338 let mut out = ptr::null(); 339 unsafe { 340 try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw)); 341 let bytes = crate::opt_bytes(self, out).unwrap(); 342 let s = str::from_utf8(bytes).unwrap(); 343 Some(Ok(mem::transmute::<&str, &'references str>(s))) 344 } 345 } 346 } 347 348 #[cfg(test)] 349 mod tests { 350 use crate::{ObjectType, Reference, ReferenceType}; 351 352 #[test] smoke()353 fn smoke() { 354 assert!(Reference::is_valid_name("refs/foo")); 355 assert!(!Reference::is_valid_name("foo")); 356 } 357 358 #[test] smoke2()359 fn smoke2() { 360 let (_td, repo) = crate::test::repo_init(); 361 let mut head = repo.head().unwrap(); 362 assert!(head.is_branch()); 363 assert!(!head.is_remote()); 364 assert!(!head.is_tag()); 365 assert!(!head.is_note()); 366 367 // HEAD is a symbolic reference but git_repository_head resolves it 368 // so it is a GIT_REFERENCE_DIRECT. 369 assert_eq!(head.kind().unwrap(), ReferenceType::Direct); 370 371 assert!(head == repo.head().unwrap()); 372 assert_eq!(head.name(), Some("refs/heads/master")); 373 374 assert!(head == repo.find_reference("refs/heads/master").unwrap()); 375 assert_eq!( 376 repo.refname_to_id("refs/heads/master").unwrap(), 377 head.target().unwrap() 378 ); 379 380 assert!(head.symbolic_target().is_none()); 381 assert!(head.target_peel().is_none()); 382 383 assert_eq!(head.shorthand(), Some("master")); 384 assert!(head.resolve().unwrap() == head); 385 386 let mut tag1 = repo 387 .reference("refs/tags/tag1", head.target().unwrap(), false, "test") 388 .unwrap(); 389 assert!(tag1.is_tag()); 390 assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct); 391 392 let peeled_commit = tag1.peel(ObjectType::Commit).unwrap(); 393 assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap()); 394 assert_eq!(tag1.target().unwrap(), peeled_commit.id()); 395 396 tag1.delete().unwrap(); 397 398 let mut sym1 = repo 399 .reference_symbolic("refs/tags/tag1", "refs/heads/master", false, "test") 400 .unwrap(); 401 assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic); 402 sym1.delete().unwrap(); 403 404 { 405 assert!(repo.references().unwrap().count() == 1); 406 assert!(repo.references().unwrap().next().unwrap().unwrap() == head); 407 let mut names = repo.references().unwrap(); 408 let mut names = names.names(); 409 assert_eq!(names.next().unwrap().unwrap(), "refs/heads/master"); 410 assert!(names.next().is_none()); 411 assert!(repo.references_glob("foo").unwrap().count() == 0); 412 assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1); 413 } 414 415 let mut head = head.rename("refs/foo", true, "test").unwrap(); 416 head.delete().unwrap(); 417 } 418 } 419