| use std::cmp::Ordering; |
| use std::ffi::CString; |
| use std::marker; |
| use std::mem; |
| use std::ptr; |
| use std::str; |
| |
| use crate::object::CastOrPanic; |
| use crate::util::{c_cmp_to_ordering, Binding}; |
| use crate::{ |
| raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceFormat, ReferenceType, Repository, |
| Tag, Tree, |
| }; |
| |
| // Not in the public header files (yet?), but a hard limit used by libgit2 |
| // internally |
| const GIT_REFNAME_MAX: usize = 1024; |
| |
| struct Refdb<'repo>(&'repo Repository); |
| |
| /// A structure to represent a git [reference][1]. |
| /// |
| /// [1]: http://git-scm.com/book/en/Git-Internals-Git-References |
| pub struct Reference<'repo> { |
| raw: *mut raw::git_reference, |
| _marker: marker::PhantomData<Refdb<'repo>>, |
| } |
| |
| /// An iterator over the references in a repository. |
| pub struct References<'repo> { |
| raw: *mut raw::git_reference_iterator, |
| _marker: marker::PhantomData<Refdb<'repo>>, |
| } |
| |
| /// An iterator over the names of references in a repository. |
| pub struct ReferenceNames<'repo, 'references> { |
| inner: &'references mut References<'repo>, |
| } |
| |
| impl<'repo> Reference<'repo> { |
| /// Ensure the reference name is well-formed. |
| /// |
| /// Validation is performed as if [`ReferenceFormat::ALLOW_ONELEVEL`] |
| /// was given to [`Reference::normalize_name`]. No normalization is |
| /// performed, however. |
| /// |
| /// ```rust |
| /// use git2::Reference; |
| /// |
| /// assert!(Reference::is_valid_name("HEAD")); |
| /// assert!(Reference::is_valid_name("refs/heads/main")); |
| /// |
| /// // But: |
| /// assert!(!Reference::is_valid_name("main")); |
| /// assert!(!Reference::is_valid_name("refs/heads/*")); |
| /// assert!(!Reference::is_valid_name("foo//bar")); |
| /// ``` |
| /// |
| /// [`ReferenceFormat::ALLOW_ONELEVEL`]: |
| /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL |
| /// [`Reference::normalize_name`]: struct.Reference#method.normalize_name |
| pub fn is_valid_name(refname: &str) -> bool { |
| crate::init(); |
| let refname = CString::new(refname).unwrap(); |
| unsafe { raw::git_reference_is_valid_name(refname.as_ptr()) == 1 } |
| } |
| |
| /// Normalize reference name and check validity. |
| /// |
| /// This will normalize the reference name by collapsing runs of adjacent |
| /// slashes between name components into a single slash. It also validates |
| /// the name according to the following rules: |
| /// |
| /// 1. If [`ReferenceFormat::ALLOW_ONELEVEL`] is given, the name may |
| /// contain only capital letters and underscores, and must begin and end |
| /// with a letter. (e.g. "HEAD", "ORIG_HEAD"). |
| /// 2. The flag [`ReferenceFormat::REFSPEC_SHORTHAND`] has an effect |
| /// only when combined with [`ReferenceFormat::ALLOW_ONELEVEL`]. If |
| /// it is given, "shorthand" branch names (i.e. those not prefixed by |
| /// `refs/`, but consisting of a single word without `/` separators) |
| /// become valid. For example, "main" would be accepted. |
| /// 3. If [`ReferenceFormat::REFSPEC_PATTERN`] is given, the name may |
| /// contain a single `*` in place of a full pathname component (e.g. |
| /// `foo/*/bar`, `foo/bar*`). |
| /// 4. Names prefixed with "refs/" can be almost anything. You must avoid |
| /// the characters '~', '^', ':', '\\', '?', '[', and '*', and the |
| /// sequences ".." and "@{" which have special meaning to revparse. |
| /// |
| /// If the reference passes validation, it is returned in normalized form, |
| /// otherwise an [`Error`] with [`ErrorCode::InvalidSpec`] is returned. |
| /// |
| /// ```rust |
| /// use git2::{Reference, ReferenceFormat}; |
| /// |
| /// assert_eq!( |
| /// Reference::normalize_name( |
| /// "foo//bar", |
| /// ReferenceFormat::NORMAL |
| /// ) |
| /// .unwrap(), |
| /// "foo/bar".to_owned() |
| /// ); |
| /// |
| /// assert_eq!( |
| /// Reference::normalize_name( |
| /// "HEAD", |
| /// ReferenceFormat::ALLOW_ONELEVEL |
| /// ) |
| /// .unwrap(), |
| /// "HEAD".to_owned() |
| /// ); |
| /// |
| /// assert_eq!( |
| /// Reference::normalize_name( |
| /// "refs/heads/*", |
| /// ReferenceFormat::REFSPEC_PATTERN |
| /// ) |
| /// .unwrap(), |
| /// "refs/heads/*".to_owned() |
| /// ); |
| /// |
| /// assert_eq!( |
| /// Reference::normalize_name( |
| /// "main", |
| /// ReferenceFormat::ALLOW_ONELEVEL | ReferenceFormat::REFSPEC_SHORTHAND |
| /// ) |
| /// .unwrap(), |
| /// "main".to_owned() |
| /// ); |
| /// ``` |
| /// |
| /// [`ReferenceFormat::ALLOW_ONELEVEL`]: |
| /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL |
| /// [`ReferenceFormat::REFSPEC_SHORTHAND`]: |
| /// struct.ReferenceFormat#associatedconstant.REFSPEC_SHORTHAND |
| /// [`ReferenceFormat::REFSPEC_PATTERN`]: |
| /// struct.ReferenceFormat#associatedconstant.REFSPEC_PATTERN |
| /// [`Error`]: struct.Error |
| /// [`ErrorCode::InvalidSpec`]: enum.ErrorCode#variant.InvalidSpec |
| pub fn normalize_name(refname: &str, flags: ReferenceFormat) -> Result<String, Error> { |
| crate::init(); |
| let mut dst = [0u8; GIT_REFNAME_MAX]; |
| let refname = CString::new(refname)?; |
| unsafe { |
| try_call!(raw::git_reference_normalize_name( |
| dst.as_mut_ptr() as *mut libc::c_char, |
| dst.len() as libc::size_t, |
| refname, |
| flags.bits() |
| )); |
| let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()]; |
| Ok(str::from_utf8(s).unwrap().to_owned()) |
| } |
| } |
| |
| /// Get access to the underlying raw pointer. |
| pub fn raw(&self) -> *mut raw::git_reference { |
| self.raw |
| } |
| |
| /// Delete an existing reference. |
| /// |
| /// This method works for both direct and symbolic references. The reference |
| /// will be immediately removed on disk. |
| /// |
| /// This function will return an error if the reference has changed from the |
| /// time it was looked up. |
| pub fn delete(&mut self) -> Result<(), Error> { |
| unsafe { |
| try_call!(raw::git_reference_delete(self.raw)); |
| } |
| Ok(()) |
| } |
| |
| /// Check if a reference is a local branch. |
| pub fn is_branch(&self) -> bool { |
| unsafe { raw::git_reference_is_branch(&*self.raw) == 1 } |
| } |
| |
| /// Check if a reference is a note. |
| pub fn is_note(&self) -> bool { |
| unsafe { raw::git_reference_is_note(&*self.raw) == 1 } |
| } |
| |
| /// Check if a reference is a remote tracking branch |
| pub fn is_remote(&self) -> bool { |
| unsafe { raw::git_reference_is_remote(&*self.raw) == 1 } |
| } |
| |
| /// Check if a reference is a tag |
| pub fn is_tag(&self) -> bool { |
| unsafe { raw::git_reference_is_tag(&*self.raw) == 1 } |
| } |
| |
| /// Get the reference type of a reference. |
| /// |
| /// If the type is unknown, then `None` is returned. |
| pub fn kind(&self) -> Option<ReferenceType> { |
| ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) }) |
| } |
| |
| /// Get the full name of a reference. |
| /// |
| /// Returns `None` if the name is not valid utf-8. |
| pub fn name(&self) -> Option<&str> { |
| str::from_utf8(self.name_bytes()).ok() |
| } |
| |
| /// Get the full name of a reference. |
| pub fn name_bytes(&self) -> &[u8] { |
| unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() } |
| } |
| |
| /// Get the full shorthand of a reference. |
| /// |
| /// This will transform the reference name into a name "human-readable" |
| /// version. If no shortname is appropriate, it will return the full name. |
| /// |
| /// Returns `None` if the shorthand is not valid utf-8. |
| pub fn shorthand(&self) -> Option<&str> { |
| str::from_utf8(self.shorthand_bytes()).ok() |
| } |
| |
| /// Get the full shorthand of a reference. |
| pub fn shorthand_bytes(&self) -> &[u8] { |
| unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() } |
| } |
| |
| /// Get the OID pointed to by a direct reference. |
| /// |
| /// Only available if the reference is direct (i.e. an object id reference, |
| /// not a symbolic one). |
| pub fn target(&self) -> Option<Oid> { |
| unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) } |
| } |
| |
| /// Return the peeled OID target of this reference. |
| /// |
| /// This peeled OID only applies to direct references that point to a hard |
| /// Tag object: it is the result of peeling such Tag. |
| pub fn target_peel(&self) -> Option<Oid> { |
| unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) } |
| } |
| |
| /// Get full name to the reference pointed to by a symbolic reference. |
| /// |
| /// May return `None` if the reference is either not symbolic or not a |
| /// valid utf-8 string. |
| pub fn symbolic_target(&self) -> Option<&str> { |
| self.symbolic_target_bytes() |
| .and_then(|s| str::from_utf8(s).ok()) |
| } |
| |
| /// Get full name to the reference pointed to by a symbolic reference. |
| /// |
| /// Only available if the reference is symbolic. |
| pub fn symbolic_target_bytes(&self) -> Option<&[u8]> { |
| unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) } |
| } |
| |
| /// Resolve a symbolic reference to a direct reference. |
| /// |
| /// This method iteratively peels a symbolic reference until it resolves to |
| /// a direct reference to an OID. |
| /// |
| /// If a direct reference is passed as an argument, a copy of that |
| /// reference is returned. |
| pub fn resolve(&self) -> Result<Reference<'repo>, Error> { |
| let mut raw = ptr::null_mut(); |
| unsafe { |
| try_call!(raw::git_reference_resolve(&mut raw, &*self.raw)); |
| Ok(Binding::from_raw(raw)) |
| } |
| } |
| |
| /// Peel a reference to an object |
| /// |
| /// This method recursively peels the reference until it reaches |
| /// an object of the specified type. |
| pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> { |
| let mut raw = ptr::null_mut(); |
| unsafe { |
| try_call!(raw::git_reference_peel(&mut raw, self.raw, kind)); |
| Ok(Binding::from_raw(raw)) |
| } |
| } |
| |
| /// Peel a reference to a blob |
| /// |
| /// This method recursively peels the reference until it reaches |
| /// a blob. |
| pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> { |
| Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob)) |
| } |
| |
| /// Peel a reference to a commit |
| /// |
| /// This method recursively peels the reference until it reaches |
| /// a commit. |
| pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> { |
| Ok(self |
| .peel(ObjectType::Commit)? |
| .cast_or_panic(ObjectType::Commit)) |
| } |
| |
| /// Peel a reference to a tree |
| /// |
| /// This method recursively peels the reference until it reaches |
| /// a tree. |
| pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> { |
| Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree)) |
| } |
| |
| /// Peel a reference to a tag |
| /// |
| /// This method recursively peels the reference until it reaches |
| /// a tag. |
| pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> { |
| Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag)) |
| } |
| |
| /// Rename an existing reference. |
| /// |
| /// This method works for both direct and symbolic references. |
| /// |
| /// If the force flag is not enabled, and there's already a reference with |
| /// the given name, the renaming will fail. |
| pub fn rename( |
| &mut self, |
| new_name: &str, |
| force: bool, |
| msg: &str, |
| ) -> Result<Reference<'repo>, Error> { |
| let mut raw = ptr::null_mut(); |
| let new_name = CString::new(new_name)?; |
| let msg = CString::new(msg)?; |
| unsafe { |
| try_call!(raw::git_reference_rename( |
| &mut raw, self.raw, new_name, force, msg |
| )); |
| Ok(Binding::from_raw(raw)) |
| } |
| } |
| |
| /// Conditionally create a new reference with the same name as the given |
| /// reference but a different OID target. The reference must be a direct |
| /// reference, otherwise this will fail. |
| /// |
| /// The new reference will be written to disk, overwriting the given |
| /// reference. |
| pub fn set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error> { |
| let mut raw = ptr::null_mut(); |
| let msg = CString::new(reflog_msg)?; |
| unsafe { |
| try_call!(raw::git_reference_set_target( |
| &mut raw, |
| self.raw, |
| id.raw(), |
| msg |
| )); |
| Ok(Binding::from_raw(raw)) |
| } |
| } |
| } |
| |
| impl<'repo> PartialOrd for Reference<'repo> { |
| fn partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering> { |
| Some(self.cmp(other)) |
| } |
| } |
| |
| impl<'repo> Ord for Reference<'repo> { |
| fn cmp(&self, other: &Reference<'repo>) -> Ordering { |
| c_cmp_to_ordering(unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) }) |
| } |
| } |
| |
| impl<'repo> PartialEq for Reference<'repo> { |
| fn eq(&self, other: &Reference<'repo>) -> bool { |
| self.cmp(other) == Ordering::Equal |
| } |
| } |
| |
| impl<'repo> Eq for Reference<'repo> {} |
| |
| impl<'repo> Binding for Reference<'repo> { |
| type Raw = *mut raw::git_reference; |
| unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> { |
| Reference { |
| raw, |
| _marker: marker::PhantomData, |
| } |
| } |
| fn raw(&self) -> *mut raw::git_reference { |
| self.raw |
| } |
| } |
| |
| impl<'repo> Drop for Reference<'repo> { |
| fn drop(&mut self) { |
| unsafe { raw::git_reference_free(self.raw) } |
| } |
| } |
| |
| impl<'repo> References<'repo> { |
| /// Consumes a `References` iterator to create an iterator over just the |
| /// name of some references. |
| /// |
| /// This is more efficient if only the names are desired of references as |
| /// the references themselves don't have to be allocated and deallocated. |
| /// |
| /// The returned iterator will yield strings as opposed to a `Reference`. |
| pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> { |
| ReferenceNames { inner: self } |
| } |
| } |
| |
| impl<'repo> Binding for References<'repo> { |
| type Raw = *mut raw::git_reference_iterator; |
| unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> { |
| References { |
| raw, |
| _marker: marker::PhantomData, |
| } |
| } |
| fn raw(&self) -> *mut raw::git_reference_iterator { |
| self.raw |
| } |
| } |
| |
| impl<'repo> Iterator for References<'repo> { |
| type Item = Result<Reference<'repo>, Error>; |
| fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> { |
| let mut out = ptr::null_mut(); |
| unsafe { |
| try_call_iter!(raw::git_reference_next(&mut out, self.raw)); |
| Some(Ok(Binding::from_raw(out))) |
| } |
| } |
| } |
| |
| impl<'repo> Drop for References<'repo> { |
| fn drop(&mut self) { |
| unsafe { raw::git_reference_iterator_free(self.raw) } |
| } |
| } |
| |
| impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> { |
| type Item = Result<&'references str, Error>; |
| fn next(&mut self) -> Option<Result<&'references str, Error>> { |
| let mut out = ptr::null(); |
| unsafe { |
| try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw)); |
| let bytes = crate::opt_bytes(self, out).unwrap(); |
| let s = str::from_utf8(bytes).unwrap(); |
| Some(Ok(mem::transmute::<&str, &'references str>(s))) |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use crate::{ObjectType, Reference, ReferenceType}; |
| |
| #[test] |
| fn smoke() { |
| assert!(Reference::is_valid_name("refs/foo")); |
| assert!(!Reference::is_valid_name("foo")); |
| } |
| |
| #[test] |
| fn smoke2() { |
| let (_td, repo) = crate::test::repo_init(); |
| let mut head = repo.head().unwrap(); |
| assert!(head.is_branch()); |
| assert!(!head.is_remote()); |
| assert!(!head.is_tag()); |
| assert!(!head.is_note()); |
| |
| // HEAD is a symbolic reference but git_repository_head resolves it |
| // so it is a GIT_REFERENCE_DIRECT. |
| assert_eq!(head.kind().unwrap(), ReferenceType::Direct); |
| |
| assert!(head == repo.head().unwrap()); |
| assert_eq!(head.name(), Some("refs/heads/main")); |
| |
| assert!(head == repo.find_reference("refs/heads/main").unwrap()); |
| assert_eq!( |
| repo.refname_to_id("refs/heads/main").unwrap(), |
| head.target().unwrap() |
| ); |
| |
| assert!(head.symbolic_target().is_none()); |
| assert!(head.target_peel().is_none()); |
| |
| assert_eq!(head.shorthand(), Some("main")); |
| assert!(head.resolve().unwrap() == head); |
| |
| let mut tag1 = repo |
| .reference("refs/tags/tag1", head.target().unwrap(), false, "test") |
| .unwrap(); |
| assert!(tag1.is_tag()); |
| assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct); |
| |
| let peeled_commit = tag1.peel(ObjectType::Commit).unwrap(); |
| assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap()); |
| assert_eq!(tag1.target().unwrap(), peeled_commit.id()); |
| |
| tag1.delete().unwrap(); |
| |
| let mut sym1 = repo |
| .reference_symbolic("refs/tags/tag1", "refs/heads/main", false, "test") |
| .unwrap(); |
| assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic); |
| sym1.delete().unwrap(); |
| |
| { |
| assert!(repo.references().unwrap().count() == 1); |
| assert!(repo.references().unwrap().next().unwrap().unwrap() == head); |
| let mut names = repo.references().unwrap(); |
| let mut names = names.names(); |
| assert_eq!(names.next().unwrap().unwrap(), "refs/heads/main"); |
| assert!(names.next().is_none()); |
| assert!(repo.references_glob("foo").unwrap().count() == 0); |
| assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1); |
| } |
| |
| let mut head = head.rename("refs/foo", true, "test").unwrap(); |
| head.delete().unwrap(); |
| } |
| } |