1 use libc::{c_char, c_int, size_t};
2 use std::cmp::Ordering;
3 use std::ffi::{CString, OsStr, OsString};
4 use std::iter::IntoIterator;
5 use std::path::{Component, Path, PathBuf};
6
7 use crate::{raw, Error};
8
9 #[doc(hidden)]
10 pub trait IsNull {
is_ptr_null(&self) -> bool11 fn is_ptr_null(&self) -> bool;
12 }
13 impl<T> IsNull for *const T {
is_ptr_null(&self) -> bool14 fn is_ptr_null(&self) -> bool {
15 self.is_null()
16 }
17 }
18 impl<T> IsNull for *mut T {
is_ptr_null(&self) -> bool19 fn is_ptr_null(&self) -> bool {
20 self.is_null()
21 }
22 }
23
24 #[doc(hidden)]
25 pub trait Binding: Sized {
26 type Raw;
27
from_raw(raw: Self::Raw) -> Self28 unsafe fn from_raw(raw: Self::Raw) -> Self;
raw(&self) -> Self::Raw29 fn raw(&self) -> Self::Raw;
30
from_raw_opt<T>(raw: T) -> Option<Self> where T: Copy + IsNull, Self: Binding<Raw = T>,31 unsafe fn from_raw_opt<T>(raw: T) -> Option<Self>
32 where
33 T: Copy + IsNull,
34 Self: Binding<Raw = T>,
35 {
36 if raw.is_ptr_null() {
37 None
38 } else {
39 Some(Binding::from_raw(raw))
40 }
41 }
42 }
43
44 /// Converts an iterator of repo paths into a git2-compatible array of cstrings.
45 ///
46 /// Only use this for repo-relative paths or pathspecs.
47 ///
48 /// See `iter2cstrs` for more details.
iter2cstrs_paths<T, I>( iter: I, ) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error> where T: IntoCString, I: IntoIterator<Item = T>,49 pub fn iter2cstrs_paths<T, I>(
50 iter: I,
51 ) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error>
52 where
53 T: IntoCString,
54 I: IntoIterator<Item = T>,
55 {
56 let cstrs = iter
57 .into_iter()
58 .map(|i| fixup_windows_path(i.into_c_string()?))
59 .collect::<Result<Vec<CString>, _>>()?;
60 iter2cstrs(cstrs)
61 }
62
63 /// Converts an iterator of things into a git array of c-strings.
64 ///
65 /// Returns a tuple `(cstrings, pointers, git_strarray)`. The first two values
66 /// should not be dropped before `git_strarray`.
iter2cstrs<T, I>( iter: I, ) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error> where T: IntoCString, I: IntoIterator<Item = T>,67 pub fn iter2cstrs<T, I>(
68 iter: I,
69 ) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error>
70 where
71 T: IntoCString,
72 I: IntoIterator<Item = T>,
73 {
74 let cstrs = iter
75 .into_iter()
76 .map(|i| i.into_c_string())
77 .collect::<Result<Vec<CString>, _>>()?;
78 let ptrs = cstrs.iter().map(|i| i.as_ptr()).collect::<Vec<_>>();
79 let raw = raw::git_strarray {
80 strings: ptrs.as_ptr() as *mut _,
81 count: ptrs.len() as size_t,
82 };
83 Ok((cstrs, ptrs, raw))
84 }
85
86 #[cfg(unix)]
bytes2path(b: &[u8]) -> &Path87 pub fn bytes2path(b: &[u8]) -> &Path {
88 use std::os::unix::prelude::*;
89 Path::new(OsStr::from_bytes(b))
90 }
91 #[cfg(windows)]
bytes2path(b: &[u8]) -> &Path92 pub fn bytes2path(b: &[u8]) -> &Path {
93 use std::str;
94 Path::new(str::from_utf8(b).unwrap())
95 }
96
97 /// A class of types that can be converted to C strings.
98 ///
99 /// These types are represented internally as byte slices and it is quite rare
100 /// for them to contain an interior 0 byte.
101 pub trait IntoCString {
102 /// Consume this container, converting it into a CString
into_c_string(self) -> Result<CString, Error>103 fn into_c_string(self) -> Result<CString, Error>;
104 }
105
106 impl<'a, T: IntoCString + Clone> IntoCString for &'a T {
into_c_string(self) -> Result<CString, Error>107 fn into_c_string(self) -> Result<CString, Error> {
108 self.clone().into_c_string()
109 }
110 }
111
112 impl<'a> IntoCString for &'a str {
into_c_string(self) -> Result<CString, Error>113 fn into_c_string(self) -> Result<CString, Error> {
114 Ok(CString::new(self)?)
115 }
116 }
117
118 impl IntoCString for String {
into_c_string(self) -> Result<CString, Error>119 fn into_c_string(self) -> Result<CString, Error> {
120 Ok(CString::new(self.into_bytes())?)
121 }
122 }
123
124 impl IntoCString for CString {
into_c_string(self) -> Result<CString, Error>125 fn into_c_string(self) -> Result<CString, Error> {
126 Ok(self)
127 }
128 }
129
130 impl<'a> IntoCString for &'a Path {
into_c_string(self) -> Result<CString, Error>131 fn into_c_string(self) -> Result<CString, Error> {
132 let s: &OsStr = self.as_ref();
133 s.into_c_string()
134 }
135 }
136
137 impl IntoCString for PathBuf {
into_c_string(self) -> Result<CString, Error>138 fn into_c_string(self) -> Result<CString, Error> {
139 let s: OsString = self.into();
140 s.into_c_string()
141 }
142 }
143
144 impl<'a> IntoCString for &'a OsStr {
into_c_string(self) -> Result<CString, Error>145 fn into_c_string(self) -> Result<CString, Error> {
146 self.to_os_string().into_c_string()
147 }
148 }
149
150 impl IntoCString for OsString {
151 #[cfg(unix)]
into_c_string(self) -> Result<CString, Error>152 fn into_c_string(self) -> Result<CString, Error> {
153 use std::os::unix::prelude::*;
154 let s: &OsStr = self.as_ref();
155 Ok(CString::new(s.as_bytes())?)
156 }
157 #[cfg(windows)]
into_c_string(self) -> Result<CString, Error>158 fn into_c_string(self) -> Result<CString, Error> {
159 match self.to_str() {
160 Some(s) => s.into_c_string(),
161 None => Err(Error::from_str(
162 "only valid unicode paths are accepted on windows",
163 )),
164 }
165 }
166 }
167
168 impl<'a> IntoCString for &'a [u8] {
into_c_string(self) -> Result<CString, Error>169 fn into_c_string(self) -> Result<CString, Error> {
170 Ok(CString::new(self)?)
171 }
172 }
173
174 impl IntoCString for Vec<u8> {
into_c_string(self) -> Result<CString, Error>175 fn into_c_string(self) -> Result<CString, Error> {
176 Ok(CString::new(self)?)
177 }
178 }
179
into_opt_c_string<S>(opt_s: Option<S>) -> Result<Option<CString>, Error> where S: IntoCString,180 pub fn into_opt_c_string<S>(opt_s: Option<S>) -> Result<Option<CString>, Error>
181 where
182 S: IntoCString,
183 {
184 match opt_s {
185 None => Ok(None),
186 Some(s) => Ok(Some(s.into_c_string()?)),
187 }
188 }
189
c_cmp_to_ordering(cmp: c_int) -> Ordering190 pub fn c_cmp_to_ordering(cmp: c_int) -> Ordering {
191 match cmp {
192 0 => Ordering::Equal,
193 n if n < 0 => Ordering::Less,
194 _ => Ordering::Greater,
195 }
196 }
197
198 /// Converts a path to a CString that is usable by the libgit2 API.
199 ///
200 /// Checks if it is a relative path.
201 ///
202 /// On Windows, this also requires the path to be valid unicode, and translates
203 /// back slashes to forward slashes.
path_to_repo_path(path: &Path) -> Result<CString, Error>204 pub fn path_to_repo_path(path: &Path) -> Result<CString, Error> {
205 macro_rules! err {
206 ($msg:literal, $path:expr) => {
207 return Err(Error::from_str(&format!($msg, $path.display())))
208 };
209 }
210 match path.components().next() {
211 None => return Err(Error::from_str("repo path should not be empty")),
212 Some(Component::Prefix(_)) => err!(
213 "repo path `{}` should be relative, not a windows prefix",
214 path
215 ),
216 Some(Component::RootDir) => err!("repo path `{}` should be relative", path),
217 Some(Component::CurDir) => err!("repo path `{}` should not start with `.`", path),
218 Some(Component::ParentDir) => err!("repo path `{}` should not start with `..`", path),
219 Some(Component::Normal(_)) => {}
220 }
221 #[cfg(windows)]
222 {
223 match path.to_str() {
224 None => {
225 return Err(Error::from_str(
226 "only valid unicode paths are accepted on windows",
227 ))
228 }
229 Some(s) => return fixup_windows_path(s),
230 }
231 }
232 #[cfg(not(windows))]
233 {
234 path.into_c_string()
235 }
236 }
237
cstring_to_repo_path<T: IntoCString>(path: T) -> Result<CString, Error>238 pub fn cstring_to_repo_path<T: IntoCString>(path: T) -> Result<CString, Error> {
239 fixup_windows_path(path.into_c_string()?)
240 }
241
242 #[cfg(windows)]
fixup_windows_path<P: Into<Vec<u8>>>(path: P) -> Result<CString, Error>243 fn fixup_windows_path<P: Into<Vec<u8>>>(path: P) -> Result<CString, Error> {
244 let mut bytes: Vec<u8> = path.into();
245 for i in 0..bytes.len() {
246 if bytes[i] == b'\\' {
247 bytes[i] = b'/';
248 }
249 }
250 Ok(CString::new(bytes)?)
251 }
252
253 #[cfg(not(windows))]
fixup_windows_path(path: CString) -> Result<CString, Error>254 fn fixup_windows_path(path: CString) -> Result<CString, Error> {
255 Ok(path)
256 }
257
258 #[cfg(test)]
259 mod tests {
260 use super::*;
261
262 macro_rules! assert_err {
263 ($path:expr, $msg:expr) => {
264 match path_to_repo_path(Path::new($path)) {
265 Ok(_) => panic!("expected `{}` to err", $path),
266 Err(e) => assert_eq!(e.message(), $msg),
267 }
268 };
269 }
270
271 macro_rules! assert_repo_path_ok {
272 ($path:expr) => {
273 assert_repo_path_ok!($path, $path)
274 };
275 ($path:expr, $expect:expr) => {
276 assert_eq!(
277 path_to_repo_path(Path::new($path)),
278 Ok(CString::new($expect).unwrap())
279 );
280 };
281 }
282
283 #[test]
284 #[cfg(windows)]
path_to_repo_path_translate()285 fn path_to_repo_path_translate() {
286 assert_repo_path_ok!("foo");
287 assert_repo_path_ok!("foo/bar");
288 assert_repo_path_ok!(r"foo\bar", "foo/bar");
289 assert_repo_path_ok!(r"foo\bar\", "foo/bar/");
290 }
291
292 #[test]
path_to_repo_path_no_weird()293 fn path_to_repo_path_no_weird() {
294 assert_err!("", "repo path should not be empty");
295 assert_err!("./foo", "repo path `./foo` should not start with `.`");
296 assert_err!("../foo", "repo path `../foo` should not start with `..`");
297 }
298
299 #[test]
300 #[cfg(not(windows))]
path_to_repo_path_no_absolute()301 fn path_to_repo_path_no_absolute() {
302 assert_err!("/", "repo path `/` should be relative");
303 assert_repo_path_ok!("foo/bar");
304 }
305
306 #[test]
307 #[cfg(windows)]
path_to_repo_path_no_absolute()308 fn path_to_repo_path_no_absolute() {
309 assert_err!(
310 r"c:",
311 r"repo path `c:` should be relative, not a windows prefix"
312 );
313 assert_err!(
314 r"c:\",
315 r"repo path `c:\` should be relative, not a windows prefix"
316 );
317 assert_err!(
318 r"c:temp",
319 r"repo path `c:temp` should be relative, not a windows prefix"
320 );
321 assert_err!(
322 r"\\?\UNC\a\b\c",
323 r"repo path `\\?\UNC\a\b\c` should be relative, not a windows prefix"
324 );
325 assert_err!(
326 r"\\?\c:\foo",
327 r"repo path `\\?\c:\foo` should be relative, not a windows prefix"
328 );
329 assert_err!(
330 r"\\.\COM42",
331 r"repo path `\\.\COM42` should be relative, not a windows prefix"
332 );
333 assert_err!(
334 r"\\a\b",
335 r"repo path `\\a\b` should be relative, not a windows prefix"
336 );
337 assert_err!(r"\", r"repo path `\` should be relative");
338 assert_err!(r"/", r"repo path `/` should be relative");
339 assert_err!(r"\foo", r"repo path `\foo` should be relative");
340 assert_err!(r"/foo", r"repo path `/foo` should be relative");
341 }
342 }
343