1 //! Builder-pattern objects for configuration various git operations.
2 
3 use libc::{c_char, c_int, c_uint, c_void, size_t};
4 use std::ffi::{CStr, CString};
5 use std::mem;
6 use std::path::Path;
7 use std::ptr;
8 
9 use crate::util::{self, Binding};
10 use crate::{panic, raw, Error, FetchOptions, IntoCString, Repository};
11 use crate::{CheckoutNotificationType, DiffFile, Remote};
12 
13 /// A builder struct which is used to build configuration for cloning a new git
14 /// repository.
15 ///
16 /// # Example
17 ///
18 /// Cloning using SSH:
19 ///
20 /// ```no_run
21 /// use git2::{Cred, Error, RemoteCallbacks};
22 /// use std::env;
23 /// use std::path::Path;
24 ///
25 ///   // Prepare callbacks.
26 ///   let mut callbacks = RemoteCallbacks::new();
27 ///   callbacks.credentials(|_url, username_from_url, _allowed_types| {
28 ///     Cred::ssh_key(
29 ///       username_from_url.unwrap(),
30 ///       None,
31 ///       std::path::Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())),
32 ///       None,
33 ///     )
34 ///   });
35 ///
36 ///   // Prepare fetch options.
37 ///   let mut fo = git2::FetchOptions::new();
38 ///   fo.remote_callbacks(callbacks);
39 ///
40 ///   // Prepare builder.
41 ///   let mut builder = git2::build::RepoBuilder::new();
42 ///   builder.fetch_options(fo);
43 ///
44 ///   // Clone the project.
45 ///   builder.clone(
46 ///     "git@github.com:rust-lang/git2-rs.git",
47 ///     Path::new("/tmp/git2-rs"),
48 ///   );
49 /// ```
50 pub struct RepoBuilder<'cb> {
51     bare: bool,
52     branch: Option<CString>,
53     local: bool,
54     hardlinks: bool,
55     checkout: Option<CheckoutBuilder<'cb>>,
56     fetch_opts: Option<FetchOptions<'cb>>,
57     clone_local: Option<CloneLocal>,
58     remote_create: Option<Box<RemoteCreate<'cb>>>,
59 }
60 
61 /// Type of callback passed to `RepoBuilder::remote_create`.
62 ///
63 /// The second and third arguments are the remote's name and the remote's url.
64 pub type RemoteCreate<'cb> =
65     dyn for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb;
66 
67 /// A builder struct for configuring checkouts of a repository.
68 pub struct CheckoutBuilder<'cb> {
69     their_label: Option<CString>,
70     our_label: Option<CString>,
71     ancestor_label: Option<CString>,
72     target_dir: Option<CString>,
73     paths: Vec<CString>,
74     path_ptrs: Vec<*const c_char>,
75     file_perm: Option<i32>,
76     dir_perm: Option<i32>,
77     disable_filters: bool,
78     checkout_opts: u32,
79     progress: Option<Box<Progress<'cb>>>,
80     notify: Option<Box<Notify<'cb>>>,
81     notify_flags: CheckoutNotificationType,
82 }
83 
84 /// Checkout progress notification callback.
85 ///
86 /// The first argument is the path for the notification, the next is the numver
87 /// of completed steps so far, and the final is the total number of steps.
88 pub type Progress<'a> = dyn FnMut(Option<&Path>, usize, usize) + 'a;
89 
90 /// Checkout notifications callback.
91 ///
92 /// The first argument is the notification type, the next is the path for the
93 /// the notification, followed by the baseline diff, target diff, and workdir diff.
94 ///
95 /// The callback must return a bool specifying whether the checkout should
96 /// continue.
97 pub type Notify<'a> = dyn FnMut(
98         CheckoutNotificationType,
99         Option<&Path>,
100         Option<DiffFile<'_>>,
101         Option<DiffFile<'_>>,
102         Option<DiffFile<'_>>,
103     ) -> bool
104     + 'a;
105 
106 impl<'cb> Default for RepoBuilder<'cb> {
default() -> Self107     fn default() -> Self {
108         Self::new()
109     }
110 }
111 
112 /// Options that can be passed to `RepoBuilder::clone_local`.
113 #[derive(Clone, Copy)]
114 pub enum CloneLocal {
115     /// Auto-detect (default)
116     ///
117     /// Here libgit2 will bypass the git-aware transport for local paths, but
118     /// use a normal fetch for `file://` urls.
119     Auto = raw::GIT_CLONE_LOCAL_AUTO as isize,
120 
121     /// Bypass the git-aware transport even for `file://` urls.
122     Local = raw::GIT_CLONE_LOCAL as isize,
123 
124     /// Never bypass the git-aware transport
125     None = raw::GIT_CLONE_NO_LOCAL as isize,
126 
127     /// Bypass the git-aware transport, but don't try to use hardlinks.
128     NoLinks = raw::GIT_CLONE_LOCAL_NO_LINKS as isize,
129 
130     #[doc(hidden)]
131     __Nonexhaustive = 0xff,
132 }
133 
134 impl<'cb> RepoBuilder<'cb> {
135     /// Creates a new repository builder with all of the default configuration.
136     ///
137     /// When ready, the `clone()` method can be used to clone a new repository
138     /// using this configuration.
new() -> RepoBuilder<'cb>139     pub fn new() -> RepoBuilder<'cb> {
140         crate::init();
141         RepoBuilder {
142             bare: false,
143             branch: None,
144             local: true,
145             clone_local: None,
146             hardlinks: true,
147             checkout: None,
148             fetch_opts: None,
149             remote_create: None,
150         }
151     }
152 
153     /// Indicate whether the repository will be cloned as a bare repository or
154     /// not.
bare(&mut self, bare: bool) -> &mut RepoBuilder<'cb>155     pub fn bare(&mut self, bare: bool) -> &mut RepoBuilder<'cb> {
156         self.bare = bare;
157         self
158     }
159 
160     /// Specify the name of the branch to check out after the clone.
161     ///
162     /// If not specified, the remote's default branch will be used.
branch(&mut self, branch: &str) -> &mut RepoBuilder<'cb>163     pub fn branch(&mut self, branch: &str) -> &mut RepoBuilder<'cb> {
164         self.branch = Some(CString::new(branch).unwrap());
165         self
166     }
167 
168     /// Configures options for bypassing the git-aware transport on clone.
169     ///
170     /// Bypassing it means that instead of a fetch libgit2 will copy the object
171     /// database directory instead of figuring out what it needs, which is
172     /// faster. If possible, it will hardlink the files to save space.
clone_local(&mut self, clone_local: CloneLocal) -> &mut RepoBuilder<'cb>173     pub fn clone_local(&mut self, clone_local: CloneLocal) -> &mut RepoBuilder<'cb> {
174         self.clone_local = Some(clone_local);
175         self
176     }
177 
178     /// Set the flag for bypassing the git aware transport mechanism for local
179     /// paths.
180     ///
181     /// If `true`, the git-aware transport will be bypassed for local paths. If
182     /// `false`, the git-aware transport will not be bypassed.
183     #[deprecated(note = "use `clone_local` instead")]
184     #[doc(hidden)]
local(&mut self, local: bool) -> &mut RepoBuilder<'cb>185     pub fn local(&mut self, local: bool) -> &mut RepoBuilder<'cb> {
186         self.local = local;
187         self
188     }
189 
190     /// Set the flag for whether hardlinks are used when using a local git-aware
191     /// transport mechanism.
192     #[deprecated(note = "use `clone_local` instead")]
193     #[doc(hidden)]
hardlinks(&mut self, links: bool) -> &mut RepoBuilder<'cb>194     pub fn hardlinks(&mut self, links: bool) -> &mut RepoBuilder<'cb> {
195         self.hardlinks = links;
196         self
197     }
198 
199     /// Configure the checkout which will be performed by consuming a checkout
200     /// builder.
with_checkout(&mut self, checkout: CheckoutBuilder<'cb>) -> &mut RepoBuilder<'cb>201     pub fn with_checkout(&mut self, checkout: CheckoutBuilder<'cb>) -> &mut RepoBuilder<'cb> {
202         self.checkout = Some(checkout);
203         self
204     }
205 
206     /// Options which control the fetch, including callbacks.
207     ///
208     /// The callbacks are used for reporting fetch progress, and for acquiring
209     /// credentials in the event they are needed.
fetch_options(&mut self, fetch_opts: FetchOptions<'cb>) -> &mut RepoBuilder<'cb>210     pub fn fetch_options(&mut self, fetch_opts: FetchOptions<'cb>) -> &mut RepoBuilder<'cb> {
211         self.fetch_opts = Some(fetch_opts);
212         self
213     }
214 
215     /// Configures a callback used to create the git remote, prior to its being
216     /// used to perform the clone operation.
remote_create<F>(&mut self, f: F) -> &mut RepoBuilder<'cb> where F: for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb,217     pub fn remote_create<F>(&mut self, f: F) -> &mut RepoBuilder<'cb>
218     where
219         F: for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb,
220     {
221         self.remote_create = Some(Box::new(f));
222         self
223     }
224 
225     /// Clone a remote repository.
226     ///
227     /// This will use the options configured so far to clone the specified url
228     /// into the specified local path.
clone(&mut self, url: &str, into: &Path) -> Result<Repository, Error>229     pub fn clone(&mut self, url: &str, into: &Path) -> Result<Repository, Error> {
230         let mut opts: raw::git_clone_options = unsafe { mem::zeroed() };
231         unsafe {
232             try_call!(raw::git_clone_init_options(
233                 &mut opts,
234                 raw::GIT_CLONE_OPTIONS_VERSION
235             ));
236         }
237         opts.bare = self.bare as c_int;
238         opts.checkout_branch = self
239             .branch
240             .as_ref()
241             .map(|s| s.as_ptr())
242             .unwrap_or(ptr::null());
243 
244         if let Some(ref local) = self.clone_local {
245             opts.local = *local as raw::git_clone_local_t;
246         } else {
247             opts.local = match (self.local, self.hardlinks) {
248                 (true, false) => raw::GIT_CLONE_LOCAL_NO_LINKS,
249                 (false, _) => raw::GIT_CLONE_NO_LOCAL,
250                 (true, _) => raw::GIT_CLONE_LOCAL_AUTO,
251             };
252         }
253 
254         if let Some(ref mut cbs) = self.fetch_opts {
255             opts.fetch_opts = cbs.raw();
256         }
257 
258         if let Some(ref mut c) = self.checkout {
259             unsafe {
260                 c.configure(&mut opts.checkout_opts);
261             }
262         }
263 
264         if let Some(ref mut callback) = self.remote_create {
265             opts.remote_cb = Some(remote_create_cb);
266             opts.remote_cb_payload = callback as *mut _ as *mut _;
267         }
268 
269         let url = CString::new(url)?;
270         // Normal file path OK (does not need Windows conversion).
271         let into = into.into_c_string()?;
272         let mut raw = ptr::null_mut();
273         unsafe {
274             try_call!(raw::git_clone(&mut raw, url, into, &opts));
275             Ok(Binding::from_raw(raw))
276         }
277     }
278 }
279 
remote_create_cb( out: *mut *mut raw::git_remote, repo: *mut raw::git_repository, name: *const c_char, url: *const c_char, payload: *mut c_void, ) -> c_int280 extern "C" fn remote_create_cb(
281     out: *mut *mut raw::git_remote,
282     repo: *mut raw::git_repository,
283     name: *const c_char,
284     url: *const c_char,
285     payload: *mut c_void,
286 ) -> c_int {
287     unsafe {
288         let repo = Repository::from_raw(repo);
289         let code = panic::wrap(|| {
290             let name = CStr::from_ptr(name).to_str().unwrap();
291             let url = CStr::from_ptr(url).to_str().unwrap();
292             let f = payload as *mut Box<RemoteCreate<'_>>;
293             match (*f)(&repo, name, url) {
294                 Ok(remote) => {
295                     *out = crate::remote::remote_into_raw(remote);
296                     0
297                 }
298                 Err(e) => e.raw_code(),
299             }
300         });
301         mem::forget(repo);
302         code.unwrap_or(-1)
303     }
304 }
305 
306 impl<'cb> Default for CheckoutBuilder<'cb> {
default() -> Self307     fn default() -> Self {
308         Self::new()
309     }
310 }
311 
312 impl<'cb> CheckoutBuilder<'cb> {
313     /// Creates a new builder for checkouts with all of its default
314     /// configuration.
new() -> CheckoutBuilder<'cb>315     pub fn new() -> CheckoutBuilder<'cb> {
316         crate::init();
317         CheckoutBuilder {
318             disable_filters: false,
319             dir_perm: None,
320             file_perm: None,
321             path_ptrs: Vec::new(),
322             paths: Vec::new(),
323             target_dir: None,
324             ancestor_label: None,
325             our_label: None,
326             their_label: None,
327             checkout_opts: raw::GIT_CHECKOUT_SAFE as u32,
328             progress: None,
329             notify: None,
330             notify_flags: CheckoutNotificationType::empty(),
331         }
332     }
333 
334     /// Indicate that this checkout should perform a dry run by checking for
335     /// conflicts but not make any actual changes.
dry_run(&mut self) -> &mut CheckoutBuilder<'cb>336     pub fn dry_run(&mut self) -> &mut CheckoutBuilder<'cb> {
337         self.checkout_opts &= !((1 << 4) - 1);
338         self.checkout_opts |= raw::GIT_CHECKOUT_NONE as u32;
339         self
340     }
341 
342     /// Take any action necessary to get the working directory to match the
343     /// target including potentially discarding modified files.
force(&mut self) -> &mut CheckoutBuilder<'cb>344     pub fn force(&mut self) -> &mut CheckoutBuilder<'cb> {
345         self.checkout_opts &= !((1 << 4) - 1);
346         self.checkout_opts |= raw::GIT_CHECKOUT_FORCE as u32;
347         self
348     }
349 
350     /// Indicate that the checkout should be performed safely, allowing new
351     /// files to be created but not overwriting extisting files or changes.
352     ///
353     /// This is the default.
safe(&mut self) -> &mut CheckoutBuilder<'cb>354     pub fn safe(&mut self) -> &mut CheckoutBuilder<'cb> {
355         self.checkout_opts &= !((1 << 4) - 1);
356         self.checkout_opts |= raw::GIT_CHECKOUT_SAFE as u32;
357         self
358     }
359 
flag(&mut self, bit: raw::git_checkout_strategy_t, on: bool) -> &mut CheckoutBuilder<'cb>360     fn flag(&mut self, bit: raw::git_checkout_strategy_t, on: bool) -> &mut CheckoutBuilder<'cb> {
361         if on {
362             self.checkout_opts |= bit as u32;
363         } else {
364             self.checkout_opts &= !(bit as u32);
365         }
366         self
367     }
368 
369     /// In safe mode, create files that don't exist.
370     ///
371     /// Defaults to false.
recreate_missing(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb>372     pub fn recreate_missing(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb> {
373         self.flag(raw::GIT_CHECKOUT_RECREATE_MISSING, allow)
374     }
375 
376     /// In safe mode, apply safe file updates even when there are conflicts
377     /// instead of canceling the checkout.
378     ///
379     /// Defaults to false.
allow_conflicts(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb>380     pub fn allow_conflicts(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb> {
381         self.flag(raw::GIT_CHECKOUT_ALLOW_CONFLICTS, allow)
382     }
383 
384     /// Remove untracked files from the working dir.
385     ///
386     /// Defaults to false.
remove_untracked(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb>387     pub fn remove_untracked(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb> {
388         self.flag(raw::GIT_CHECKOUT_REMOVE_UNTRACKED, remove)
389     }
390 
391     /// Remove ignored files from the working dir.
392     ///
393     /// Defaults to false.
remove_ignored(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb>394     pub fn remove_ignored(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb> {
395         self.flag(raw::GIT_CHECKOUT_REMOVE_IGNORED, remove)
396     }
397 
398     /// Only update the contents of files that already exist.
399     ///
400     /// If set, files will not be created or deleted.
401     ///
402     /// Defaults to false.
update_only(&mut self, update: bool) -> &mut CheckoutBuilder<'cb>403     pub fn update_only(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> {
404         self.flag(raw::GIT_CHECKOUT_UPDATE_ONLY, update)
405     }
406 
407     /// Prevents checkout from writing the updated files' information to the
408     /// index.
409     ///
410     /// Defaults to true.
update_index(&mut self, update: bool) -> &mut CheckoutBuilder<'cb>411     pub fn update_index(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> {
412         self.flag(raw::GIT_CHECKOUT_DONT_UPDATE_INDEX, !update)
413     }
414 
415     /// Indicate whether the index and git attributes should be refreshed from
416     /// disk before any operations.
417     ///
418     /// Defaults to true,
refresh(&mut self, refresh: bool) -> &mut CheckoutBuilder<'cb>419     pub fn refresh(&mut self, refresh: bool) -> &mut CheckoutBuilder<'cb> {
420         self.flag(raw::GIT_CHECKOUT_NO_REFRESH, !refresh)
421     }
422 
423     /// Skip files with unmerged index entries.
424     ///
425     /// Defaults to false.
skip_unmerged(&mut self, skip: bool) -> &mut CheckoutBuilder<'cb>426     pub fn skip_unmerged(&mut self, skip: bool) -> &mut CheckoutBuilder<'cb> {
427         self.flag(raw::GIT_CHECKOUT_SKIP_UNMERGED, skip)
428     }
429 
430     /// Indicate whether the checkout should proceed on conflicts by using the
431     /// stage 2 version of the file ("ours").
432     ///
433     /// Defaults to false.
use_ours(&mut self, ours: bool) -> &mut CheckoutBuilder<'cb>434     pub fn use_ours(&mut self, ours: bool) -> &mut CheckoutBuilder<'cb> {
435         self.flag(raw::GIT_CHECKOUT_USE_OURS, ours)
436     }
437 
438     /// Indicate whether the checkout should proceed on conflicts by using the
439     /// stage 3 version of the file ("theirs").
440     ///
441     /// Defaults to false.
use_theirs(&mut self, theirs: bool) -> &mut CheckoutBuilder<'cb>442     pub fn use_theirs(&mut self, theirs: bool) -> &mut CheckoutBuilder<'cb> {
443         self.flag(raw::GIT_CHECKOUT_USE_THEIRS, theirs)
444     }
445 
446     /// Indicate whether ignored files should be overwritten during the checkout.
447     ///
448     /// Defaults to true.
overwrite_ignored(&mut self, overwrite: bool) -> &mut CheckoutBuilder<'cb>449     pub fn overwrite_ignored(&mut self, overwrite: bool) -> &mut CheckoutBuilder<'cb> {
450         self.flag(raw::GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, !overwrite)
451     }
452 
453     /// Indicate whether a normal merge file should be written for conflicts.
454     ///
455     /// Defaults to false.
conflict_style_merge(&mut self, on: bool) -> &mut CheckoutBuilder<'cb>456     pub fn conflict_style_merge(&mut self, on: bool) -> &mut CheckoutBuilder<'cb> {
457         self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_MERGE, on)
458     }
459 
460     /// Specify for which notification types to invoke the notification
461     /// callback.
462     ///
463     /// Defaults to none.
notify_on( &mut self, notification_types: CheckoutNotificationType, ) -> &mut CheckoutBuilder<'cb>464     pub fn notify_on(
465         &mut self,
466         notification_types: CheckoutNotificationType,
467     ) -> &mut CheckoutBuilder<'cb> {
468         self.notify_flags = notification_types;
469         self
470     }
471 
472     /// Indicates whether to include common ancestor data in diff3 format files
473     /// for conflicts.
474     ///
475     /// Defaults to false.
conflict_style_diff3(&mut self, on: bool) -> &mut CheckoutBuilder<'cb>476     pub fn conflict_style_diff3(&mut self, on: bool) -> &mut CheckoutBuilder<'cb> {
477         self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_DIFF3, on)
478     }
479 
480     /// Indicate whether to apply filters like CRLF conversion.
disable_filters(&mut self, disable: bool) -> &mut CheckoutBuilder<'cb>481     pub fn disable_filters(&mut self, disable: bool) -> &mut CheckoutBuilder<'cb> {
482         self.disable_filters = disable;
483         self
484     }
485 
486     /// Set the mode with which new directories are created.
487     ///
488     /// Default is 0755
dir_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb>489     pub fn dir_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb> {
490         self.dir_perm = Some(perm);
491         self
492     }
493 
494     /// Set the mode with which new files are created.
495     ///
496     /// The default is 0644 or 0755 as dictated by the blob.
file_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb>497     pub fn file_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb> {
498         self.file_perm = Some(perm);
499         self
500     }
501 
502     /// Add a path to be checked out.
503     ///
504     /// If no paths are specified, then all files are checked out. Otherwise
505     /// only these specified paths are checked out.
path<T: IntoCString>(&mut self, path: T) -> &mut CheckoutBuilder<'cb>506     pub fn path<T: IntoCString>(&mut self, path: T) -> &mut CheckoutBuilder<'cb> {
507         let path = util::cstring_to_repo_path(path).unwrap();
508         self.path_ptrs.push(path.as_ptr());
509         self.paths.push(path);
510         self
511     }
512 
513     /// Set the directory to check out to
target_dir(&mut self, dst: &Path) -> &mut CheckoutBuilder<'cb>514     pub fn target_dir(&mut self, dst: &Path) -> &mut CheckoutBuilder<'cb> {
515         // Normal file path OK (does not need Windows conversion).
516         self.target_dir = Some(dst.into_c_string().unwrap());
517         self
518     }
519 
520     /// The name of the common ancestor side of conflicts
ancestor_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb>521     pub fn ancestor_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> {
522         self.ancestor_label = Some(CString::new(label).unwrap());
523         self
524     }
525 
526     /// The name of the common our side of conflicts
our_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb>527     pub fn our_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> {
528         self.our_label = Some(CString::new(label).unwrap());
529         self
530     }
531 
532     /// The name of the common their side of conflicts
their_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb>533     pub fn their_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> {
534         self.their_label = Some(CString::new(label).unwrap());
535         self
536     }
537 
538     /// Set a callback to receive notifications of checkout progress.
progress<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb> where F: FnMut(Option<&Path>, usize, usize) + 'cb,539     pub fn progress<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb>
540     where
541         F: FnMut(Option<&Path>, usize, usize) + 'cb,
542     {
543         self.progress = Some(Box::new(cb) as Box<Progress<'cb>>);
544         self
545     }
546 
547     /// Set a callback to receive checkout notifications.
548     ///
549     /// Callbacks are invoked prior to modifying any files on disk.
550     /// Returning `false` from the callback will cancel the checkout.
notify<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb> where F: FnMut( CheckoutNotificationType, Option<&Path>, Option<DiffFile<'_>>, Option<DiffFile<'_>>, Option<DiffFile<'_>>, ) -> bool + 'cb,551     pub fn notify<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb>
552     where
553         F: FnMut(
554                 CheckoutNotificationType,
555                 Option<&Path>,
556                 Option<DiffFile<'_>>,
557                 Option<DiffFile<'_>>,
558                 Option<DiffFile<'_>>,
559             ) -> bool
560             + 'cb,
561     {
562         self.notify = Some(Box::new(cb) as Box<Notify<'cb>>);
563         self
564     }
565 
566     /// Configure a raw checkout options based on this configuration.
567     ///
568     /// This method is unsafe as there is no guarantee that this structure will
569     /// outlive the provided checkout options.
configure(&mut self, opts: &mut raw::git_checkout_options)570     pub unsafe fn configure(&mut self, opts: &mut raw::git_checkout_options) {
571         opts.version = raw::GIT_CHECKOUT_OPTIONS_VERSION;
572         opts.disable_filters = self.disable_filters as c_int;
573         opts.dir_mode = self.dir_perm.unwrap_or(0) as c_uint;
574         opts.file_mode = self.file_perm.unwrap_or(0) as c_uint;
575 
576         if !self.path_ptrs.is_empty() {
577             opts.paths.strings = self.path_ptrs.as_ptr() as *mut _;
578             opts.paths.count = self.path_ptrs.len() as size_t;
579         }
580 
581         if let Some(ref c) = self.target_dir {
582             opts.target_directory = c.as_ptr();
583         }
584         if let Some(ref c) = self.ancestor_label {
585             opts.ancestor_label = c.as_ptr();
586         }
587         if let Some(ref c) = self.our_label {
588             opts.our_label = c.as_ptr();
589         }
590         if let Some(ref c) = self.their_label {
591             opts.their_label = c.as_ptr();
592         }
593         if self.progress.is_some() {
594             opts.progress_cb = Some(progress_cb);
595             opts.progress_payload = self as *mut _ as *mut _;
596         }
597         if self.notify.is_some() {
598             opts.notify_cb = Some(notify_cb);
599             opts.notify_payload = self as *mut _ as *mut _;
600             opts.notify_flags = self.notify_flags.bits() as c_uint;
601         }
602         opts.checkout_strategy = self.checkout_opts as c_uint;
603     }
604 }
605 
progress_cb( path: *const c_char, completed: size_t, total: size_t, data: *mut c_void, )606 extern "C" fn progress_cb(
607     path: *const c_char,
608     completed: size_t,
609     total: size_t,
610     data: *mut c_void,
611 ) {
612     panic::wrap(|| unsafe {
613         let payload = &mut *(data as *mut CheckoutBuilder<'_>);
614         let callback = match payload.progress {
615             Some(ref mut c) => c,
616             None => return,
617         };
618         let path = if path.is_null() {
619             None
620         } else {
621             Some(util::bytes2path(CStr::from_ptr(path).to_bytes()))
622         };
623         callback(path, completed as usize, total as usize)
624     });
625 }
626 
notify_cb( why: raw::git_checkout_notify_t, path: *const c_char, baseline: *const raw::git_diff_file, target: *const raw::git_diff_file, workdir: *const raw::git_diff_file, data: *mut c_void, ) -> c_int627 extern "C" fn notify_cb(
628     why: raw::git_checkout_notify_t,
629     path: *const c_char,
630     baseline: *const raw::git_diff_file,
631     target: *const raw::git_diff_file,
632     workdir: *const raw::git_diff_file,
633     data: *mut c_void,
634 ) -> c_int {
635     // pack callback etc
636     panic::wrap(|| unsafe {
637         let payload = &mut *(data as *mut CheckoutBuilder<'_>);
638         let callback = match payload.notify {
639             Some(ref mut c) => c,
640             None => return 0,
641         };
642         let path = if path.is_null() {
643             None
644         } else {
645             Some(util::bytes2path(CStr::from_ptr(path).to_bytes()))
646         };
647 
648         let baseline = if baseline.is_null() {
649             None
650         } else {
651             Some(DiffFile::from_raw(baseline))
652         };
653 
654         let target = if target.is_null() {
655             None
656         } else {
657             Some(DiffFile::from_raw(target))
658         };
659 
660         let workdir = if workdir.is_null() {
661             None
662         } else {
663             Some(DiffFile::from_raw(workdir))
664         };
665 
666         let why = CheckoutNotificationType::from_bits_truncate(why as u32);
667         let keep_going = callback(why, path, baseline, target, workdir);
668         if keep_going {
669             0
670         } else {
671             1
672         }
673     })
674     .unwrap_or(2)
675 }
676 
677 #[cfg(test)]
678 mod tests {
679     use super::{CheckoutBuilder, RepoBuilder};
680     use crate::{CheckoutNotificationType, Repository};
681     use std::fs;
682     use std::path::Path;
683     use tempfile::TempDir;
684 
685     #[test]
smoke()686     fn smoke() {
687         let r = RepoBuilder::new().clone("/path/to/nowhere", Path::new("foo"));
688         assert!(r.is_err());
689     }
690 
691     #[test]
smoke2()692     fn smoke2() {
693         let td = TempDir::new().unwrap();
694         Repository::init_bare(&td.path().join("bare")).unwrap();
695         let url = if cfg!(unix) {
696             format!("file://{}/bare", td.path().display())
697         } else {
698             format!(
699                 "file:///{}/bare",
700                 td.path().display().to_string().replace("\\", "/")
701             )
702         };
703 
704         let dst = td.path().join("foo");
705         RepoBuilder::new().clone(&url, &dst).unwrap();
706         fs::remove_dir_all(&dst).unwrap();
707         assert!(RepoBuilder::new().branch("foo").clone(&url, &dst).is_err());
708     }
709 
710     /// Issue regression test #365
711     #[test]
712     fn notify_callback() {
713         let td = TempDir::new().unwrap();
714         let cd = TempDir::new().unwrap();
715 
716         {
717             let repo = Repository::init(&td.path()).unwrap();
718 
719             let mut config = repo.config().unwrap();
720             config.set_str("user.name", "name").unwrap();
721             config.set_str("user.email", "email").unwrap();
722 
723             let mut index = repo.index().unwrap();
724             let p = Path::new(td.path()).join("file");
725             println!("using path {:?}", p);
726             fs::File::create(&p).unwrap();
727             index.add_path(&Path::new("file")).unwrap();
728             let id = index.write_tree().unwrap();
729 
730             let tree = repo.find_tree(id).unwrap();
731             let sig = repo.signature().unwrap();
732             repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, &[])
733                 .unwrap();
734         }
735 
736         let repo = Repository::open_bare(&td.path().join(".git")).unwrap();
737         let tree = repo
738             .revparse_single(&"master")
739             .unwrap()
740             .peel_to_tree()
741             .unwrap();
742         let mut index = repo.index().unwrap();
743         index.read_tree(&tree).unwrap();
744 
745         let mut checkout_opts = CheckoutBuilder::new();
746         checkout_opts.target_dir(&cd.path());
747         checkout_opts.notify_on(CheckoutNotificationType::all());
748         checkout_opts.notify(|_notif, _path, baseline, target, workdir| {
749             assert!(baseline.is_none());
750             assert_eq!(target.unwrap().path(), Some(Path::new("file")));
751             assert!(workdir.is_none());
752             true
753         });
754         repo.checkout_index(Some(&mut index), Some(&mut checkout_opts))
755             .unwrap();
756     }
757 }
758