diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:41 +0000 |
commit | 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 (patch) | |
tree | bdffd5d80c26cf4a7a518281a204be1ace85b4c1 /vendor/git2/src/reference.rs | |
parent | Releasing progress-linux version 1.70.0+dfsg1-9~progress7.99u1. (diff) | |
download | rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.tar.xz rustc-10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87.zip |
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/git2/src/reference.rs')
-rw-r--r-- | vendor/git2/src/reference.rs | 586 |
1 files changed, 586 insertions, 0 deletions
diff --git a/vendor/git2/src/reference.rs b/vendor/git2/src/reference.rs new file mode 100644 index 000000000..92eb18c63 --- /dev/null +++ b/vendor/git2/src/reference.rs @@ -0,0 +1,586 @@ +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::{ + call, 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(); + let mut valid: libc::c_int = 0; + unsafe { + call::c_try(raw::git_reference_name_is_valid( + &mut valid, + refname.as_ptr(), + )) + .unwrap(); + } + valid == 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)) + } + } + + /// Create a new reference with the same name as the given reference but a + /// different symbolic target. The reference must be a symbolic reference, + /// otherwise this will fail. + /// + /// The new reference will be written to disk, overwriting the given + /// reference. + /// + /// The target name will be checked for validity. See + /// [`Repository::reference_symbolic`] for rules about valid names. + /// + /// The message for the reflog will be ignored if the reference does not + /// belong in the standard set (HEAD, branches and remote-tracking + /// branches) and it does not have a reflog. + pub fn symbolic_set_target( + &mut self, + target: &str, + reflog_msg: &str, + ) -> Result<Reference<'repo>, Error> { + let mut raw = ptr::null_mut(); + let target = CString::new(target)?; + let msg = CString::new(reflog_msg)?; + unsafe { + try_call!(raw::git_reference_symbolic_set_target( + &mut raw, self.raw, target, 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 is_valid_name() { + assert!(Reference::is_valid_name("refs/foo")); + assert!(!Reference::is_valid_name("foo")); + assert!(Reference::is_valid_name("FOO_BAR")); + + assert!(!Reference::is_valid_name("foo")); + assert!(!Reference::is_valid_name("_FOO_BAR")); + } + + #[test] + #[should_panic] + fn is_valid_name_for_invalid_ref() { + Reference::is_valid_name("ab\012"); + } + + #[test] + fn smoke() { + 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); + let mut sym2 = repo + .reference_symbolic("refs/tags/tag2", "refs/heads/main", false, "test") + .unwrap() + .symbolic_set_target("refs/tags/tag1", "test") + .unwrap(); + assert_eq!(sym2.kind().unwrap(), ReferenceType::Symbolic); + assert_eq!(sym2.symbolic_target().unwrap(), "refs/tags/tag1"); + sym2.delete().unwrap(); + 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(); + } +} |