Skip to content

Add bindings for git_reference_normalize_name #620

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions libgit2-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1814,6 +1814,15 @@ git_enum! {
}
}

git_enum! {
pub enum git_reference_format_t {
GIT_REFERENCE_FORMAT_NORMAL = 0,
GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL = 1 << 0,
GIT_REFERENCE_FORMAT_REFSPEC_PATTERN = 1 << 1,
GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND = 1 << 2,
}
}

extern "C" {
// threads
pub fn git_libgit2_init() -> c_int;
Expand Down Expand Up @@ -2278,6 +2287,12 @@ extern "C" {
) -> c_int;
pub fn git_reference_has_log(repo: *mut git_repository, name: *const c_char) -> c_int;
pub fn git_reference_ensure_log(repo: *mut git_repository, name: *const c_char) -> c_int;
pub fn git_reference_normalize_name(
buffer_out: *mut c_char,
buffer_size: size_t,
name: *const c_char,
flags: u32,
) -> c_int;

// stash
pub fn git_stash_save(
Expand Down
34 changes: 34 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,40 @@ impl DiffFlags {
is_bit_set!(exists, DiffFlags::EXISTS);
}

bitflags! {
/// Options for [`Reference::normalize_name`].
pub struct ReferenceFormat: u32 {
/// No particular normalization.
const NORMAL = raw::GIT_REFERENCE_FORMAT_NORMAL as u32;
/// Constrol whether one-level refname are accepted (i.e., refnames that
/// do not contain multiple `/`-separated components). Those are
/// expected to be written only using uppercase letters and underscore
/// (e.g. `HEAD`, `FETCH_HEAD`).
const ALLOW_ONELEVEL = raw::GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL as u32;
/// Interpret the provided name as a reference pattern for a refspec (as
/// used with remote repositories). If this option is enabled, the name
/// is allowed to contain a single `*` in place of a full pathname
/// components (e.g., `foo/*/bar` but not `foo/bar*`).
const REFSPEC_PATTERN = raw::GIT_REFERENCE_FORMAT_REFSPEC_PATTERN as u32;
/// Interpret the name as part of a refspec in shorthand form so the
/// `ALLOW_ONELEVEL` naming rules aren't enforced and `master` becomes a
/// valid name.
const REFSPEC_SHORTHAND = raw::GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND as u32;
}
}

impl ReferenceFormat {
is_bit_set!(is_allow_onelevel, ReferenceFormat::ALLOW_ONELEVEL);
is_bit_set!(is_refspec_pattern, ReferenceFormat::REFSPEC_PATTERN);
is_bit_set!(is_refspec_shorthand, ReferenceFormat::REFSPEC_SHORTHAND);
}

impl Default for ReferenceFormat {
fn default() -> Self {
ReferenceFormat::NORMAL
}
}

#[cfg(test)]
mod tests {
use super::ObjectType;
Expand Down
115 changes: 114 additions & 1 deletion src/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ use std::str;
use crate::object::CastOrPanic;
use crate::util::{c_cmp_to_ordering, Binding};
use crate::{
raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceType, Repository, Tag, Tree,
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].
Expand All @@ -34,12 +39,120 @@ pub struct ReferenceNames<'repo, 'references> {

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/master"));
///
/// // But:
/// assert!(!Reference::is_valid_name("master"));
/// 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, "master" 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(
/// "master",
/// ReferenceFormat::ALLOW_ONELEVEL | ReferenceFormat::REFSPEC_SHORTHAND
/// )
/// .unwrap(),
/// "master".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
Expand Down