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, ReferenceFormat, ReferenceType, Repository, 12 Tag, Tree, 13 }; 14 15 // Not in the public header files (yet?), but a hard limit used by libgit2 16 // internally 17 const GIT_REFNAME_MAX: usize = 1024; 18 19 struct Refdb<'repo>(&'repo Repository); 20 21 /// A structure to represent a git [reference][1]. 22 /// 23 /// [1]: http://git-scm.com/book/en/Git-Internals-Git-References 24 pub struct Reference<'repo> { 25 raw: *mut raw::git_reference, 26 _marker: marker::PhantomData<Refdb<'repo>>, 27 } 28 29 /// An iterator over the references in a repository. 30 pub struct References<'repo> { 31 raw: *mut raw::git_reference_iterator, 32 _marker: marker::PhantomData<Refdb<'repo>>, 33 } 34 35 /// An iterator over the names of references in a repository. 36 pub struct ReferenceNames<'repo, 'references> { 37 inner: &'references mut References<'repo>, 38 } 39 40 impl<'repo> Reference<'repo> { 41 /// Ensure the reference name is well-formed. 42 /// 43 /// Validation is performed as if [`ReferenceFormat::ALLOW_ONELEVEL`] 44 /// was given to [`Reference::normalize_name`]. No normalization is 45 /// performed, however. 46 /// 47 /// ```rust 48 /// use git2::Reference; 49 /// 50 /// assert!(Reference::is_valid_name("HEAD")); 51 /// assert!(Reference::is_valid_name("refs/heads/main")); 52 /// 53 /// // But: 54 /// assert!(!Reference::is_valid_name("main")); 55 /// assert!(!Reference::is_valid_name("refs/heads/*")); 56 /// assert!(!Reference::is_valid_name("foo//bar")); 57 /// ``` 58 /// 59 /// [`ReferenceFormat::ALLOW_ONELEVEL`]: 60 /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL 61 /// [`Reference::normalize_name`]: struct.Reference#method.normalize_name is_valid_name(refname: &str) -> bool62 pub fn is_valid_name(refname: &str) -> bool { 63 crate::init(); 64 let refname = CString::new(refname).unwrap(); 65 unsafe { raw::git_reference_is_valid_name(refname.as_ptr()) == 1 } 66 } 67 68 /// Normalize reference name and check validity. 69 /// 70 /// This will normalize the reference name by collapsing runs of adjacent 71 /// slashes between name components into a single slash. It also validates 72 /// the name according to the following rules: 73 /// 74 /// 1. If [`ReferenceFormat::ALLOW_ONELEVEL`] is given, the name may 75 /// contain only capital letters and underscores, and must begin and end 76 /// with a letter. (e.g. "HEAD", "ORIG_HEAD"). 77 /// 2. The flag [`ReferenceFormat::REFSPEC_SHORTHAND`] has an effect 78 /// only when combined with [`ReferenceFormat::ALLOW_ONELEVEL`]. If 79 /// it is given, "shorthand" branch names (i.e. those not prefixed by 80 /// `refs/`, but consisting of a single word without `/` separators) 81 /// become valid. For example, "main" would be accepted. 82 /// 3. If [`ReferenceFormat::REFSPEC_PATTERN`] is given, the name may 83 /// contain a single `*` in place of a full pathname component (e.g. 84 /// `foo/*/bar`, `foo/bar*`). 85 /// 4. Names prefixed with "refs/" can be almost anything. You must avoid 86 /// the characters '~', '^', ':', '\\', '?', '[', and '*', and the 87 /// sequences ".." and "@{" which have special meaning to revparse. 88 /// 89 /// If the reference passes validation, it is returned in normalized form, 90 /// otherwise an [`Error`] with [`ErrorCode::InvalidSpec`] is returned. 91 /// 92 /// ```rust 93 /// use git2::{Reference, ReferenceFormat}; 94 /// 95 /// assert_eq!( 96 /// Reference::normalize_name( 97 /// "foo//bar", 98 /// ReferenceFormat::NORMAL 99 /// ) 100 /// .unwrap(), 101 /// "foo/bar".to_owned() 102 /// ); 103 /// 104 /// assert_eq!( 105 /// Reference::normalize_name( 106 /// "HEAD", 107 /// ReferenceFormat::ALLOW_ONELEVEL 108 /// ) 109 /// .unwrap(), 110 /// "HEAD".to_owned() 111 /// ); 112 /// 113 /// assert_eq!( 114 /// Reference::normalize_name( 115 /// "refs/heads/*", 116 /// ReferenceFormat::REFSPEC_PATTERN 117 /// ) 118 /// .unwrap(), 119 /// "refs/heads/*".to_owned() 120 /// ); 121 /// 122 /// assert_eq!( 123 /// Reference::normalize_name( 124 /// "main", 125 /// ReferenceFormat::ALLOW_ONELEVEL | ReferenceFormat::REFSPEC_SHORTHAND 126 /// ) 127 /// .unwrap(), 128 /// "main".to_owned() 129 /// ); 130 /// ``` 131 /// 132 /// [`ReferenceFormat::ALLOW_ONELEVEL`]: 133 /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL 134 /// [`ReferenceFormat::REFSPEC_SHORTHAND`]: 135 /// struct.ReferenceFormat#associatedconstant.REFSPEC_SHORTHAND 136 /// [`ReferenceFormat::REFSPEC_PATTERN`]: 137 /// struct.ReferenceFormat#associatedconstant.REFSPEC_PATTERN 138 /// [`Error`]: struct.Error 139 /// [`ErrorCode::InvalidSpec`]: enum.ErrorCode#variant.InvalidSpec normalize_name(refname: &str, flags: ReferenceFormat) -> Result<String, Error>140 pub fn normalize_name(refname: &str, flags: ReferenceFormat) -> Result<String, Error> { 141 crate::init(); 142 let mut dst = [0u8; GIT_REFNAME_MAX]; 143 let refname = CString::new(refname)?; 144 unsafe { 145 try_call!(raw::git_reference_normalize_name( 146 dst.as_mut_ptr() as *mut libc::c_char, 147 dst.len() as libc::size_t, 148 refname, 149 flags.bits() 150 )); 151 let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()]; 152 Ok(str::from_utf8(s).unwrap().to_owned()) 153 } 154 } 155 156 /// Get access to the underlying raw pointer. raw(&self) -> *mut raw::git_reference157 pub fn raw(&self) -> *mut raw::git_reference { 158 self.raw 159 } 160 161 /// Delete an existing reference. 162 /// 163 /// This method works for both direct and symbolic references. The reference 164 /// will be immediately removed on disk. 165 /// 166 /// This function will return an error if the reference has changed from the 167 /// time it was looked up. delete(&mut self) -> Result<(), Error>168 pub fn delete(&mut self) -> Result<(), Error> { 169 unsafe { 170 try_call!(raw::git_reference_delete(self.raw)); 171 } 172 Ok(()) 173 } 174 175 /// Check if a reference is a local branch. is_branch(&self) -> bool176 pub fn is_branch(&self) -> bool { 177 unsafe { raw::git_reference_is_branch(&*self.raw) == 1 } 178 } 179 180 /// Check if a reference is a note. is_note(&self) -> bool181 pub fn is_note(&self) -> bool { 182 unsafe { raw::git_reference_is_note(&*self.raw) == 1 } 183 } 184 185 /// Check if a reference is a remote tracking branch is_remote(&self) -> bool186 pub fn is_remote(&self) -> bool { 187 unsafe { raw::git_reference_is_remote(&*self.raw) == 1 } 188 } 189 190 /// Check if a reference is a tag is_tag(&self) -> bool191 pub fn is_tag(&self) -> bool { 192 unsafe { raw::git_reference_is_tag(&*self.raw) == 1 } 193 } 194 195 /// Get the reference type of a reference. 196 /// 197 /// If the type is unknown, then `None` is returned. kind(&self) -> Option<ReferenceType>198 pub fn kind(&self) -> Option<ReferenceType> { 199 ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) }) 200 } 201 202 /// Get the full name of a reference. 203 /// 204 /// Returns `None` if the name is not valid utf-8. name(&self) -> Option<&str>205 pub fn name(&self) -> Option<&str> { 206 str::from_utf8(self.name_bytes()).ok() 207 } 208 209 /// Get the full name of a reference. name_bytes(&self) -> &[u8]210 pub fn name_bytes(&self) -> &[u8] { 211 unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() } 212 } 213 214 /// Get the full shorthand of a reference. 215 /// 216 /// This will transform the reference name into a name "human-readable" 217 /// version. If no shortname is appropriate, it will return the full name. 218 /// 219 /// Returns `None` if the shorthand is not valid utf-8. shorthand(&self) -> Option<&str>220 pub fn shorthand(&self) -> Option<&str> { 221 str::from_utf8(self.shorthand_bytes()).ok() 222 } 223 224 /// Get the full shorthand of a reference. shorthand_bytes(&self) -> &[u8]225 pub fn shorthand_bytes(&self) -> &[u8] { 226 unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() } 227 } 228 229 /// Get the OID pointed to by a direct reference. 230 /// 231 /// Only available if the reference is direct (i.e. an object id reference, 232 /// not a symbolic one). target(&self) -> Option<Oid>233 pub fn target(&self) -> Option<Oid> { 234 unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) } 235 } 236 237 /// Return the peeled OID target of this reference. 238 /// 239 /// This peeled OID only applies to direct references that point to a hard 240 /// Tag object: it is the result of peeling such Tag. target_peel(&self) -> Option<Oid>241 pub fn target_peel(&self) -> Option<Oid> { 242 unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) } 243 } 244 245 /// Get full name to the reference pointed to by a symbolic reference. 246 /// 247 /// May return `None` if the reference is either not symbolic or not a 248 /// valid utf-8 string. symbolic_target(&self) -> Option<&str>249 pub fn symbolic_target(&self) -> Option<&str> { 250 self.symbolic_target_bytes() 251 .and_then(|s| str::from_utf8(s).ok()) 252 } 253 254 /// Get full name to the reference pointed to by a symbolic reference. 255 /// 256 /// Only available if the reference is symbolic. symbolic_target_bytes(&self) -> Option<&[u8]>257 pub fn symbolic_target_bytes(&self) -> Option<&[u8]> { 258 unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) } 259 } 260 261 /// Resolve a symbolic reference to a direct reference. 262 /// 263 /// This method iteratively peels a symbolic reference until it resolves to 264 /// a direct reference to an OID. 265 /// 266 /// If a direct reference is passed as an argument, a copy of that 267 /// reference is returned. resolve(&self) -> Result<Reference<'repo>, Error>268 pub fn resolve(&self) -> Result<Reference<'repo>, Error> { 269 let mut raw = ptr::null_mut(); 270 unsafe { 271 try_call!(raw::git_reference_resolve(&mut raw, &*self.raw)); 272 Ok(Binding::from_raw(raw)) 273 } 274 } 275 276 /// Peel a reference to an object 277 /// 278 /// This method recursively peels the reference until it reaches 279 /// an object of the specified type. peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error>280 pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> { 281 let mut raw = ptr::null_mut(); 282 unsafe { 283 try_call!(raw::git_reference_peel(&mut raw, self.raw, kind)); 284 Ok(Binding::from_raw(raw)) 285 } 286 } 287 288 /// Peel a reference to a blob 289 /// 290 /// This method recursively peels the reference until it reaches 291 /// a blob. peel_to_blob(&self) -> Result<Blob<'repo>, Error>292 pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> { 293 Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob)) 294 } 295 296 /// Peel a reference to a commit 297 /// 298 /// This method recursively peels the reference until it reaches 299 /// a commit. peel_to_commit(&self) -> Result<Commit<'repo>, Error>300 pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> { 301 Ok(self 302 .peel(ObjectType::Commit)? 303 .cast_or_panic(ObjectType::Commit)) 304 } 305 306 /// Peel a reference to a tree 307 /// 308 /// This method recursively peels the reference until it reaches 309 /// a tree. peel_to_tree(&self) -> Result<Tree<'repo>, Error>310 pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> { 311 Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree)) 312 } 313 314 /// Peel a reference to a tag 315 /// 316 /// This method recursively peels the reference until it reaches 317 /// a tag. peel_to_tag(&self) -> Result<Tag<'repo>, Error>318 pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> { 319 Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag)) 320 } 321 322 /// Rename an existing reference. 323 /// 324 /// This method works for both direct and symbolic references. 325 /// 326 /// If the force flag is not enabled, and there's already a reference with 327 /// the given name, the renaming will fail. rename( &mut self, new_name: &str, force: bool, msg: &str, ) -> Result<Reference<'repo>, Error>328 pub fn rename( 329 &mut self, 330 new_name: &str, 331 force: bool, 332 msg: &str, 333 ) -> Result<Reference<'repo>, Error> { 334 let mut raw = ptr::null_mut(); 335 let new_name = CString::new(new_name)?; 336 let msg = CString::new(msg)?; 337 unsafe { 338 try_call!(raw::git_reference_rename( 339 &mut raw, self.raw, new_name, force, msg 340 )); 341 Ok(Binding::from_raw(raw)) 342 } 343 } 344 345 /// Conditionally create a new reference with the same name as the given 346 /// reference but a different OID target. The reference must be a direct 347 /// reference, otherwise this will fail. 348 /// 349 /// The new reference will be written to disk, overwriting the given 350 /// reference. set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error>351 pub fn set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error> { 352 let mut raw = ptr::null_mut(); 353 let msg = CString::new(reflog_msg)?; 354 unsafe { 355 try_call!(raw::git_reference_set_target( 356 &mut raw, 357 self.raw, 358 id.raw(), 359 msg 360 )); 361 Ok(Binding::from_raw(raw)) 362 } 363 } 364 } 365 366 impl<'repo> PartialOrd for Reference<'repo> { partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering>367 fn partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering> { 368 Some(self.cmp(other)) 369 } 370 } 371 372 impl<'repo> Ord for Reference<'repo> { cmp(&self, other: &Reference<'repo>) -> Ordering373 fn cmp(&self, other: &Reference<'repo>) -> Ordering { 374 c_cmp_to_ordering(unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) }) 375 } 376 } 377 378 impl<'repo> PartialEq for Reference<'repo> { eq(&self, other: &Reference<'repo>) -> bool379 fn eq(&self, other: &Reference<'repo>) -> bool { 380 self.cmp(other) == Ordering::Equal 381 } 382 } 383 384 impl<'repo> Eq for Reference<'repo> {} 385 386 impl<'repo> Binding for Reference<'repo> { 387 type Raw = *mut raw::git_reference; from_raw(raw: *mut raw::git_reference) -> Reference<'repo>388 unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> { 389 Reference { 390 raw: raw, 391 _marker: marker::PhantomData, 392 } 393 } raw(&self) -> *mut raw::git_reference394 fn raw(&self) -> *mut raw::git_reference { 395 self.raw 396 } 397 } 398 399 impl<'repo> Drop for Reference<'repo> { drop(&mut self)400 fn drop(&mut self) { 401 unsafe { raw::git_reference_free(self.raw) } 402 } 403 } 404 405 impl<'repo> References<'repo> { 406 /// Consumes a `References` iterator to create an iterator over just the 407 /// name of some references. 408 /// 409 /// This is more efficient if only the names are desired of references as 410 /// the references themselves don't have to be allocated and deallocated. 411 /// 412 /// The returned iterator will yield strings as opposed to a `Reference`. names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a>413 pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> { 414 ReferenceNames { inner: self } 415 } 416 } 417 418 impl<'repo> Binding for References<'repo> { 419 type Raw = *mut raw::git_reference_iterator; from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo>420 unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> { 421 References { 422 raw: raw, 423 _marker: marker::PhantomData, 424 } 425 } raw(&self) -> *mut raw::git_reference_iterator426 fn raw(&self) -> *mut raw::git_reference_iterator { 427 self.raw 428 } 429 } 430 431 impl<'repo> Iterator for References<'repo> { 432 type Item = Result<Reference<'repo>, Error>; next(&mut self) -> Option<Result<Reference<'repo>, Error>>433 fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> { 434 let mut out = ptr::null_mut(); 435 unsafe { 436 try_call_iter!(raw::git_reference_next(&mut out, self.raw)); 437 Some(Ok(Binding::from_raw(out))) 438 } 439 } 440 } 441 442 impl<'repo> Drop for References<'repo> { drop(&mut self)443 fn drop(&mut self) { 444 unsafe { raw::git_reference_iterator_free(self.raw) } 445 } 446 } 447 448 impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> { 449 type Item = Result<&'references str, Error>; next(&mut self) -> Option<Result<&'references str, Error>>450 fn next(&mut self) -> Option<Result<&'references str, Error>> { 451 let mut out = ptr::null(); 452 unsafe { 453 try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw)); 454 let bytes = crate::opt_bytes(self, out).unwrap(); 455 let s = str::from_utf8(bytes).unwrap(); 456 Some(Ok(mem::transmute::<&str, &'references str>(s))) 457 } 458 } 459 } 460 461 #[cfg(test)] 462 mod tests { 463 use crate::{ObjectType, Reference, ReferenceType}; 464 465 #[test] smoke()466 fn smoke() { 467 assert!(Reference::is_valid_name("refs/foo")); 468 assert!(!Reference::is_valid_name("foo")); 469 } 470 471 #[test] smoke2()472 fn smoke2() { 473 let (_td, repo) = crate::test::repo_init(); 474 let mut head = repo.head().unwrap(); 475 assert!(head.is_branch()); 476 assert!(!head.is_remote()); 477 assert!(!head.is_tag()); 478 assert!(!head.is_note()); 479 480 // HEAD is a symbolic reference but git_repository_head resolves it 481 // so it is a GIT_REFERENCE_DIRECT. 482 assert_eq!(head.kind().unwrap(), ReferenceType::Direct); 483 484 assert!(head == repo.head().unwrap()); 485 assert_eq!(head.name(), Some("refs/heads/main")); 486 487 assert!(head == repo.find_reference("refs/heads/main").unwrap()); 488 assert_eq!( 489 repo.refname_to_id("refs/heads/main").unwrap(), 490 head.target().unwrap() 491 ); 492 493 assert!(head.symbolic_target().is_none()); 494 assert!(head.target_peel().is_none()); 495 496 assert_eq!(head.shorthand(), Some("main")); 497 assert!(head.resolve().unwrap() == head); 498 499 let mut tag1 = repo 500 .reference("refs/tags/tag1", head.target().unwrap(), false, "test") 501 .unwrap(); 502 assert!(tag1.is_tag()); 503 assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct); 504 505 let peeled_commit = tag1.peel(ObjectType::Commit).unwrap(); 506 assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap()); 507 assert_eq!(tag1.target().unwrap(), peeled_commit.id()); 508 509 tag1.delete().unwrap(); 510 511 let mut sym1 = repo 512 .reference_symbolic("refs/tags/tag1", "refs/heads/main", false, "test") 513 .unwrap(); 514 assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic); 515 sym1.delete().unwrap(); 516 517 { 518 assert!(repo.references().unwrap().count() == 1); 519 assert!(repo.references().unwrap().next().unwrap().unwrap() == head); 520 let mut names = repo.references().unwrap(); 521 let mut names = names.names(); 522 assert_eq!(names.next().unwrap().unwrap(), "refs/heads/main"); 523 assert!(names.next().is_none()); 524 assert!(repo.references_glob("foo").unwrap().count() == 0); 525 assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1); 526 } 527 528 let mut head = head.rename("refs/foo", true, "test").unwrap(); 529 head.delete().unwrap(); 530 } 531 } 532