summaryrefslogtreecommitdiffstats
path: root/vendor/git2/src/util.rs
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/git2/src/util.rs')
-rw-r--r--vendor/git2/src/util.rs342
1 files changed, 342 insertions, 0 deletions
diff --git a/vendor/git2/src/util.rs b/vendor/git2/src/util.rs
new file mode 100644
index 000000000..5f735bc00
--- /dev/null
+++ b/vendor/git2/src/util.rs
@@ -0,0 +1,342 @@
+use libc::{c_char, c_int, size_t};
+use std::cmp::Ordering;
+use std::ffi::{CString, OsStr, OsString};
+use std::iter::IntoIterator;
+use std::path::{Component, Path, PathBuf};
+
+use crate::{raw, Error};
+
+#[doc(hidden)]
+pub trait IsNull {
+ fn is_ptr_null(&self) -> bool;
+}
+impl<T> IsNull for *const T {
+ fn is_ptr_null(&self) -> bool {
+ self.is_null()
+ }
+}
+impl<T> IsNull for *mut T {
+ fn is_ptr_null(&self) -> bool {
+ self.is_null()
+ }
+}
+
+#[doc(hidden)]
+pub trait Binding: Sized {
+ type Raw;
+
+ unsafe fn from_raw(raw: Self::Raw) -> Self;
+ fn raw(&self) -> Self::Raw;
+
+ unsafe fn from_raw_opt<T>(raw: T) -> Option<Self>
+ where
+ T: Copy + IsNull,
+ Self: Binding<Raw = T>,
+ {
+ if raw.is_ptr_null() {
+ None
+ } else {
+ Some(Binding::from_raw(raw))
+ }
+ }
+}
+
+/// Converts an iterator of repo paths into a git2-compatible array of cstrings.
+///
+/// Only use this for repo-relative paths or pathspecs.
+///
+/// See `iter2cstrs` for more details.
+pub fn iter2cstrs_paths<T, I>(
+ iter: I,
+) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error>
+where
+ T: IntoCString,
+ I: IntoIterator<Item = T>,
+{
+ let cstrs = iter
+ .into_iter()
+ .map(|i| fixup_windows_path(i.into_c_string()?))
+ .collect::<Result<Vec<CString>, _>>()?;
+ iter2cstrs(cstrs)
+}
+
+/// Converts an iterator of things into a git array of c-strings.
+///
+/// Returns a tuple `(cstrings, pointers, git_strarray)`. The first two values
+/// should not be dropped before `git_strarray`.
+pub fn iter2cstrs<T, I>(
+ iter: I,
+) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error>
+where
+ T: IntoCString,
+ I: IntoIterator<Item = T>,
+{
+ let cstrs = iter
+ .into_iter()
+ .map(|i| i.into_c_string())
+ .collect::<Result<Vec<CString>, _>>()?;
+ let ptrs = cstrs.iter().map(|i| i.as_ptr()).collect::<Vec<_>>();
+ let raw = raw::git_strarray {
+ strings: ptrs.as_ptr() as *mut _,
+ count: ptrs.len() as size_t,
+ };
+ Ok((cstrs, ptrs, raw))
+}
+
+#[cfg(unix)]
+pub fn bytes2path(b: &[u8]) -> &Path {
+ use std::os::unix::prelude::*;
+ Path::new(OsStr::from_bytes(b))
+}
+#[cfg(windows)]
+pub fn bytes2path(b: &[u8]) -> &Path {
+ use std::str;
+ Path::new(str::from_utf8(b).unwrap())
+}
+
+/// A class of types that can be converted to C strings.
+///
+/// These types are represented internally as byte slices and it is quite rare
+/// for them to contain an interior 0 byte.
+pub trait IntoCString {
+ /// Consume this container, converting it into a CString
+ fn into_c_string(self) -> Result<CString, Error>;
+}
+
+impl<'a, T: IntoCString + Clone> IntoCString for &'a T {
+ fn into_c_string(self) -> Result<CString, Error> {
+ self.clone().into_c_string()
+ }
+}
+
+impl<'a> IntoCString for &'a str {
+ fn into_c_string(self) -> Result<CString, Error> {
+ Ok(CString::new(self)?)
+ }
+}
+
+impl IntoCString for String {
+ fn into_c_string(self) -> Result<CString, Error> {
+ Ok(CString::new(self.into_bytes())?)
+ }
+}
+
+impl IntoCString for CString {
+ fn into_c_string(self) -> Result<CString, Error> {
+ Ok(self)
+ }
+}
+
+impl<'a> IntoCString for &'a Path {
+ fn into_c_string(self) -> Result<CString, Error> {
+ let s: &OsStr = self.as_ref();
+ s.into_c_string()
+ }
+}
+
+impl IntoCString for PathBuf {
+ fn into_c_string(self) -> Result<CString, Error> {
+ let s: OsString = self.into();
+ s.into_c_string()
+ }
+}
+
+impl<'a> IntoCString for &'a OsStr {
+ fn into_c_string(self) -> Result<CString, Error> {
+ self.to_os_string().into_c_string()
+ }
+}
+
+impl IntoCString for OsString {
+ #[cfg(unix)]
+ fn into_c_string(self) -> Result<CString, Error> {
+ use std::os::unix::prelude::*;
+ let s: &OsStr = self.as_ref();
+ Ok(CString::new(s.as_bytes())?)
+ }
+ #[cfg(windows)]
+ fn into_c_string(self) -> Result<CString, Error> {
+ match self.to_str() {
+ Some(s) => s.into_c_string(),
+ None => Err(Error::from_str(
+ "only valid unicode paths are accepted on windows",
+ )),
+ }
+ }
+}
+
+impl<'a> IntoCString for &'a [u8] {
+ fn into_c_string(self) -> Result<CString, Error> {
+ Ok(CString::new(self)?)
+ }
+}
+
+impl IntoCString for Vec<u8> {
+ fn into_c_string(self) -> Result<CString, Error> {
+ Ok(CString::new(self)?)
+ }
+}
+
+pub fn into_opt_c_string<S>(opt_s: Option<S>) -> Result<Option<CString>, Error>
+where
+ S: IntoCString,
+{
+ match opt_s {
+ None => Ok(None),
+ Some(s) => Ok(Some(s.into_c_string()?)),
+ }
+}
+
+pub fn c_cmp_to_ordering(cmp: c_int) -> Ordering {
+ match cmp {
+ 0 => Ordering::Equal,
+ n if n < 0 => Ordering::Less,
+ _ => Ordering::Greater,
+ }
+}
+
+/// Converts a path to a CString that is usable by the libgit2 API.
+///
+/// Checks if it is a relative path.
+///
+/// On Windows, this also requires the path to be valid Unicode, and translates
+/// back slashes to forward slashes.
+pub fn path_to_repo_path(path: &Path) -> Result<CString, Error> {
+ macro_rules! err {
+ ($msg:literal, $path:expr) => {
+ return Err(Error::from_str(&format!($msg, $path.display())))
+ };
+ }
+ match path.components().next() {
+ None => return Err(Error::from_str("repo path should not be empty")),
+ Some(Component::Prefix(_)) => err!(
+ "repo path `{}` should be relative, not a windows prefix",
+ path
+ ),
+ Some(Component::RootDir) => err!("repo path `{}` should be relative", path),
+ Some(Component::CurDir) => err!("repo path `{}` should not start with `.`", path),
+ Some(Component::ParentDir) => err!("repo path `{}` should not start with `..`", path),
+ Some(Component::Normal(_)) => {}
+ }
+ #[cfg(windows)]
+ {
+ match path.to_str() {
+ None => {
+ return Err(Error::from_str(
+ "only valid unicode paths are accepted on windows",
+ ))
+ }
+ Some(s) => return fixup_windows_path(s),
+ }
+ }
+ #[cfg(not(windows))]
+ {
+ path.into_c_string()
+ }
+}
+
+pub fn cstring_to_repo_path<T: IntoCString>(path: T) -> Result<CString, Error> {
+ fixup_windows_path(path.into_c_string()?)
+}
+
+#[cfg(windows)]
+fn fixup_windows_path<P: Into<Vec<u8>>>(path: P) -> Result<CString, Error> {
+ let mut bytes: Vec<u8> = path.into();
+ for i in 0..bytes.len() {
+ if bytes[i] == b'\\' {
+ bytes[i] = b'/';
+ }
+ }
+ Ok(CString::new(bytes)?)
+}
+
+#[cfg(not(windows))]
+fn fixup_windows_path(path: CString) -> Result<CString, Error> {
+ Ok(path)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ macro_rules! assert_err {
+ ($path:expr, $msg:expr) => {
+ match path_to_repo_path(Path::new($path)) {
+ Ok(_) => panic!("expected `{}` to err", $path),
+ Err(e) => assert_eq!(e.message(), $msg),
+ }
+ };
+ }
+
+ macro_rules! assert_repo_path_ok {
+ ($path:expr) => {
+ assert_repo_path_ok!($path, $path)
+ };
+ ($path:expr, $expect:expr) => {
+ assert_eq!(
+ path_to_repo_path(Path::new($path)),
+ Ok(CString::new($expect).unwrap())
+ );
+ };
+ }
+
+ #[test]
+ #[cfg(windows)]
+ fn path_to_repo_path_translate() {
+ assert_repo_path_ok!("foo");
+ assert_repo_path_ok!("foo/bar");
+ assert_repo_path_ok!(r"foo\bar", "foo/bar");
+ assert_repo_path_ok!(r"foo\bar\", "foo/bar/");
+ }
+
+ #[test]
+ fn path_to_repo_path_no_weird() {
+ assert_err!("", "repo path should not be empty");
+ assert_err!("./foo", "repo path `./foo` should not start with `.`");
+ assert_err!("../foo", "repo path `../foo` should not start with `..`");
+ }
+
+ #[test]
+ #[cfg(not(windows))]
+ fn path_to_repo_path_no_absolute() {
+ assert_err!("/", "repo path `/` should be relative");
+ assert_repo_path_ok!("foo/bar");
+ }
+
+ #[test]
+ #[cfg(windows)]
+ fn path_to_repo_path_no_absolute() {
+ assert_err!(
+ r"c:",
+ r"repo path `c:` should be relative, not a windows prefix"
+ );
+ assert_err!(
+ r"c:\",
+ r"repo path `c:\` should be relative, not a windows prefix"
+ );
+ assert_err!(
+ r"c:temp",
+ r"repo path `c:temp` should be relative, not a windows prefix"
+ );
+ assert_err!(
+ r"\\?\UNC\a\b\c",
+ r"repo path `\\?\UNC\a\b\c` should be relative, not a windows prefix"
+ );
+ assert_err!(
+ r"\\?\c:\foo",
+ r"repo path `\\?\c:\foo` should be relative, not a windows prefix"
+ );
+ assert_err!(
+ r"\\.\COM42",
+ r"repo path `\\.\COM42` should be relative, not a windows prefix"
+ );
+ assert_err!(
+ r"\\a\b",
+ r"repo path `\\a\b` should be relative, not a windows prefix"
+ );
+ assert_err!(r"\", r"repo path `\` should be relative");
+ assert_err!(r"/", r"repo path `/` should be relative");
+ assert_err!(r"\foo", r"repo path `\foo` should be relative");
+ assert_err!(r"/foo", r"repo path `/foo` should be relative");
+ }
+}