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