Skip to content

Commit 95c243f

Browse files
authored
Add bindings for git_reference_normalize_name (rust-lang#620)
* Add bindings for `git_reference_normalize_name` * fixup! Add bindings for `git_reference_normalize_name` * Fix type * GIT_REFNAME_MAX is not in the includes
1 parent 831faaf commit 95c243f

File tree

3 files changed

+163
-1
lines changed

3 files changed

+163
-1
lines changed

libgit2-sys/lib.rs

+15
Original file line numberDiff line numberDiff line change
@@ -1814,6 +1814,15 @@ git_enum! {
18141814
}
18151815
}
18161816

1817+
git_enum! {
1818+
pub enum git_reference_format_t {
1819+
GIT_REFERENCE_FORMAT_NORMAL = 0,
1820+
GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL = 1 << 0,
1821+
GIT_REFERENCE_FORMAT_REFSPEC_PATTERN = 1 << 1,
1822+
GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND = 1 << 2,
1823+
}
1824+
}
1825+
18171826
extern "C" {
18181827
// threads
18191828
pub fn git_libgit2_init() -> c_int;
@@ -2278,6 +2287,12 @@ extern "C" {
22782287
) -> c_int;
22792288
pub fn git_reference_has_log(repo: *mut git_repository, name: *const c_char) -> c_int;
22802289
pub fn git_reference_ensure_log(repo: *mut git_repository, name: *const c_char) -> c_int;
2290+
pub fn git_reference_normalize_name(
2291+
buffer_out: *mut c_char,
2292+
buffer_size: size_t,
2293+
name: *const c_char,
2294+
flags: u32,
2295+
) -> c_int;
22812296

22822297
// stash
22832298
pub fn git_stash_save(

src/lib.rs

+34
Original file line numberDiff line numberDiff line change
@@ -1403,6 +1403,40 @@ impl DiffFlags {
14031403
is_bit_set!(exists, DiffFlags::EXISTS);
14041404
}
14051405

1406+
bitflags! {
1407+
/// Options for [`Reference::normalize_name`].
1408+
pub struct ReferenceFormat: u32 {
1409+
/// No particular normalization.
1410+
const NORMAL = raw::GIT_REFERENCE_FORMAT_NORMAL as u32;
1411+
/// Constrol whether one-level refname are accepted (i.e., refnames that
1412+
/// do not contain multiple `/`-separated components). Those are
1413+
/// expected to be written only using uppercase letters and underscore
1414+
/// (e.g. `HEAD`, `FETCH_HEAD`).
1415+
const ALLOW_ONELEVEL = raw::GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL as u32;
1416+
/// Interpret the provided name as a reference pattern for a refspec (as
1417+
/// used with remote repositories). If this option is enabled, the name
1418+
/// is allowed to contain a single `*` in place of a full pathname
1419+
/// components (e.g., `foo/*/bar` but not `foo/bar*`).
1420+
const REFSPEC_PATTERN = raw::GIT_REFERENCE_FORMAT_REFSPEC_PATTERN as u32;
1421+
/// Interpret the name as part of a refspec in shorthand form so the
1422+
/// `ALLOW_ONELEVEL` naming rules aren't enforced and `master` becomes a
1423+
/// valid name.
1424+
const REFSPEC_SHORTHAND = raw::GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND as u32;
1425+
}
1426+
}
1427+
1428+
impl ReferenceFormat {
1429+
is_bit_set!(is_allow_onelevel, ReferenceFormat::ALLOW_ONELEVEL);
1430+
is_bit_set!(is_refspec_pattern, ReferenceFormat::REFSPEC_PATTERN);
1431+
is_bit_set!(is_refspec_shorthand, ReferenceFormat::REFSPEC_SHORTHAND);
1432+
}
1433+
1434+
impl Default for ReferenceFormat {
1435+
fn default() -> Self {
1436+
ReferenceFormat::NORMAL
1437+
}
1438+
}
1439+
14061440
#[cfg(test)]
14071441
mod tests {
14081442
use super::ObjectType;

src/reference.rs

+114-1
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,14 @@ use std::str;
88
use crate::object::CastOrPanic;
99
use crate::util::{c_cmp_to_ordering, Binding};
1010
use crate::{
11-
raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceType, Repository, Tag, Tree,
11+
raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceFormat, ReferenceType, Repository,
12+
Tag, Tree,
1213
};
1314

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+
1419
struct Refdb<'repo>(&'repo Repository);
1520

1621
/// A structure to represent a git [reference][1].
@@ -34,12 +39,120 @@ pub struct ReferenceNames<'repo, 'references> {
3439

3540
impl<'repo> Reference<'repo> {
3641
/// 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/master"));
52+
///
53+
/// // But:
54+
/// assert!(!Reference::is_valid_name("master"));
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
3762
pub fn is_valid_name(refname: &str) -> bool {
3863
crate::init();
3964
let refname = CString::new(refname).unwrap();
4065
unsafe { raw::git_reference_is_valid_name(refname.as_ptr()) == 1 }
4166
}
4267

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, "master" 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+
/// "master",
125+
/// ReferenceFormat::ALLOW_ONELEVEL | ReferenceFormat::REFSPEC_SHORTHAND
126+
/// )
127+
/// .unwrap(),
128+
/// "master".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
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+
43156
/// Get access to the underlying raw pointer.
44157
pub fn raw(&self) -> *mut raw::git_reference {
45158
self.raw

0 commit comments

Comments
 (0)