1 //! Builder-pattern objects for configuration various git operations.
3 use std
::ffi
::{CStr, CString}
;
7 use libc
::{c_char, size_t, c_void, c_uint, c_int}
;
9 use {raw, panic, Error, Repository, FetchOptions, IntoCString}
;
10 use {CheckoutNotificationType, DiffFile}
;
11 use util
::{self, Binding}
;
13 /// A builder struct which is used to build configuration for cloning a new git
15 pub struct RepoBuilder
<'cb
> {
17 branch
: Option
<CString
>,
20 checkout
: Option
<CheckoutBuilder
<'cb
>>,
21 fetch_opts
: Option
<FetchOptions
<'cb
>>,
24 /// A builder struct for configuring checkouts of a repository.
25 pub struct CheckoutBuilder
<'cb
> {
26 their_label
: Option
<CString
>,
27 our_label
: Option
<CString
>,
28 ancestor_label
: Option
<CString
>,
29 target_dir
: Option
<CString
>,
31 path_ptrs
: Vec
<*const c_char
>,
32 file_perm
: Option
<i32>,
33 dir_perm
: Option
<i32>,
34 disable_filters
: bool
,
36 progress
: Option
<Box
<Progress
<'cb
>>>,
37 notify
: Option
<Box
<Notify
<'cb
>>>,
38 notify_flags
: CheckoutNotificationType
,
41 /// Checkout progress notification callback.
43 /// The first argument is the path for the notification, the next is the numver
44 /// of completed steps so far, and the final is the total number of steps.
45 pub type Progress
<'a
> = FnMut(Option
<&Path
>, usize, usize) + 'a
;
47 /// Checkout notifications callback.
49 /// The first argument is the notification type, the next is the path for the
50 /// the notification, followed by the baseline diff, target diff, and workdir diff.
52 /// The callback must return a bool specifying whether the checkout should
54 pub type Notify
<'a
> = FnMut(CheckoutNotificationType
, Option
<&Path
>, DiffFile
,
55 DiffFile
, DiffFile
) -> bool
+ 'a
;
58 impl<'cb
> Default
for RepoBuilder
<'cb
> {
59 fn default() -> Self {
64 impl<'cb
> RepoBuilder
<'cb
> {
65 /// Creates a new repository builder with all of the default configuration.
67 /// When ready, the `clone()` method can be used to clone a new repository
68 /// using this configuration.
69 pub fn new() -> RepoBuilder
<'cb
> {
81 /// Indicate whether the repository will be cloned as a bare repository or
83 pub fn bare(&mut self, bare
: bool
) -> &mut RepoBuilder
<'cb
> {
88 /// Specify the name of the branch to check out after the clone.
90 /// If not specified, the remote's default branch will be used.
91 pub fn branch(&mut self, branch
: &str) -> &mut RepoBuilder
<'cb
> {
92 self.branch
= Some(CString
::new(branch
).unwrap());
96 /// Set the flag for bypassing the git aware transport mechanism for local
99 /// If `true`, the git-aware transport will be bypassed for local paths. If
100 /// `false`, the git-aware transport will not be bypassed.
101 pub fn local(&mut self, local
: bool
) -> &mut RepoBuilder
<'cb
> {
106 /// Set the flag for whether hardlinks are used when using a local git-aware
107 /// transport mechanism.
108 pub fn hardlinks(&mut self, links
: bool
) -> &mut RepoBuilder
<'cb
> {
109 self.hardlinks
= links
;
113 /// Configure the checkout which will be performed by consuming a checkout
115 pub fn with_checkout(&mut self, checkout
: CheckoutBuilder
<'cb
>)
116 -> &mut RepoBuilder
<'cb
> {
117 self.checkout
= Some(checkout
);
121 /// Options which control the fetch, including callbacks.
123 /// The callbacks are used for reporting fetch progress, and for acquiring
124 /// credentials in the event they are needed.
125 pub fn fetch_options(&mut self, fetch_opts
: FetchOptions
<'cb
>)
126 -> &mut RepoBuilder
<'cb
> {
127 self.fetch_opts
= Some(fetch_opts
);
131 /// Clone a remote repository.
133 /// This will use the options configured so far to clone the specified url
134 /// into the specified local path.
135 pub fn clone(&mut self, url
: &str, into
: &Path
) -> Result
<Repository
, Error
> {
136 let mut opts
: raw
::git_clone_options
= unsafe { mem::zeroed() }
;
138 try_call
!(raw
::git_clone_init_options(&mut opts
,
139 raw
::GIT_CLONE_OPTIONS_VERSION
));
141 opts
.bare
= self.bare
as c_int
;
142 opts
.checkout_branch
= self.branch
.as_ref().map(|s
| {
144 }).unwrap_or(ptr
::null());
146 opts
.local
= match (self.local
, self.hardlinks
) {
147 (true, false) => raw
::GIT_CLONE_LOCAL_NO_LINKS
,
148 (false, _
) => raw
::GIT_CLONE_NO_LOCAL
,
149 (true, _
) => raw
::GIT_CLONE_LOCAL_AUTO
,
151 opts
.checkout_opts
.checkout_strategy
=
152 raw
::GIT_CHECKOUT_SAFE
as c_uint
;
154 if let Some(ref mut cbs
) = self.fetch_opts
{
155 opts
.fetch_opts
= cbs
.raw();
158 if let Some(ref mut c
) = self.checkout
{
160 c
.configure(&mut opts
.checkout_opts
);
164 let url
= try
!(CString
::new(url
));
165 let into
= try
!(into
.into_c_string());
166 let mut raw
= ptr
::null_mut();
168 try_call
!(raw
::git_clone(&mut raw
, url
, into
, &opts
));
169 Ok(Binding
::from_raw(raw
))
174 impl<'cb
> Default
for CheckoutBuilder
<'cb
> {
175 fn default() -> Self {
180 impl<'cb
> CheckoutBuilder
<'cb
> {
181 /// Creates a new builder for checkouts with all of its default
183 pub fn new() -> CheckoutBuilder
<'cb
> {
186 disable_filters
: false,
189 path_ptrs
: Vec
::new(),
192 ancestor_label
: None
,
195 checkout_opts
: raw
::GIT_CHECKOUT_SAFE
as u32,
198 notify_flags
: CheckoutNotificationType
::empty(),
202 /// Indicate that this checkout should perform a dry run by checking for
203 /// conflicts but not make any actual changes.
204 pub fn dry_run(&mut self) -> &mut CheckoutBuilder
<'cb
> {
205 self.checkout_opts
&= !((1 << 4) - 1);
206 self.checkout_opts
|= raw
::GIT_CHECKOUT_NONE
as u32;
210 /// Take any action necessary to get the working directory to match the
211 /// target including potentially discarding modified files.
212 pub fn force(&mut self) -> &mut CheckoutBuilder
<'cb
> {
213 self.checkout_opts
&= !((1 << 4) - 1);
214 self.checkout_opts
|= raw
::GIT_CHECKOUT_FORCE
as u32;
218 /// Indicate that the checkout should be performed safely, allowing new
219 /// files to be created but not overwriting extisting files or changes.
221 /// This is the default.
222 pub fn safe(&mut self) -> &mut CheckoutBuilder
<'cb
> {
223 self.checkout_opts
&= !((1 << 4) - 1);
224 self.checkout_opts
|= raw
::GIT_CHECKOUT_SAFE
as u32;
228 fn flag(&mut self, bit
: raw
::git_checkout_strategy_t
,
229 on
: bool
) -> &mut CheckoutBuilder
<'cb
> {
231 self.checkout_opts
|= bit
as u32;
233 self.checkout_opts
&= !(bit
as u32);
238 /// In safe mode, create files that don't exist.
240 /// Defaults to false.
241 pub fn recreate_missing(&mut self, allow
: bool
) -> &mut CheckoutBuilder
<'cb
> {
242 self.flag(raw
::GIT_CHECKOUT_RECREATE_MISSING
, allow
)
245 /// In safe mode, apply safe file updates even when there are conflicts
246 /// instead of canceling the checkout.
248 /// Defaults to false.
249 pub fn allow_conflicts(&mut self, allow
: bool
) -> &mut CheckoutBuilder
<'cb
> {
250 self.flag(raw
::GIT_CHECKOUT_ALLOW_CONFLICTS
, allow
)
253 /// Remove untracked files from the working dir.
255 /// Defaults to false.
256 pub fn remove_untracked(&mut self, remove
: bool
)
257 -> &mut CheckoutBuilder
<'cb
> {
258 self.flag(raw
::GIT_CHECKOUT_REMOVE_UNTRACKED
, remove
)
261 /// Remove ignored files from the working dir.
263 /// Defaults to false.
264 pub fn remove_ignored(&mut self, remove
: bool
) -> &mut CheckoutBuilder
<'cb
> {
265 self.flag(raw
::GIT_CHECKOUT_REMOVE_IGNORED
, remove
)
268 /// Only update the contents of files that already exist.
270 /// If set, files will not be created or deleted.
272 /// Defaults to false.
273 pub fn update_only(&mut self, update
: bool
) -> &mut CheckoutBuilder
<'cb
> {
274 self.flag(raw
::GIT_CHECKOUT_UPDATE_ONLY
, update
)
277 /// Prevents checkout from writing the updated files' information to the
280 /// Defaults to true.
281 pub fn update_index(&mut self, update
: bool
) -> &mut CheckoutBuilder
<'cb
> {
282 self.flag(raw
::GIT_CHECKOUT_DONT_UPDATE_INDEX
, !update
)
285 /// Indicate whether the index and git attributes should be refreshed from
286 /// disk before any operations.
288 /// Defaults to true,
289 pub fn refresh(&mut self, refresh
: bool
) -> &mut CheckoutBuilder
<'cb
> {
290 self.flag(raw
::GIT_CHECKOUT_NO_REFRESH
, !refresh
)
293 /// Skip files with unmerged index entries.
295 /// Defaults to false.
296 pub fn skip_unmerged(&mut self, skip
: bool
) -> &mut CheckoutBuilder
<'cb
> {
297 self.flag(raw
::GIT_CHECKOUT_SKIP_UNMERGED
, skip
)
300 /// Indicate whether the checkout should proceed on conflicts by using the
301 /// stage 2 version of the file ("ours").
303 /// Defaults to false.
304 pub fn use_ours(&mut self, ours
: bool
) -> &mut CheckoutBuilder
<'cb
> {
305 self.flag(raw
::GIT_CHECKOUT_USE_OURS
, ours
)
308 /// Indicate whether the checkout should proceed on conflicts by using the
309 /// stage 3 version of the file ("theirs").
311 /// Defaults to false.
312 pub fn use_theirs(&mut self, theirs
: bool
) -> &mut CheckoutBuilder
<'cb
> {
313 self.flag(raw
::GIT_CHECKOUT_USE_THEIRS
, theirs
)
316 /// Indicate whether ignored files should be overwritten during the checkout.
318 /// Defaults to true.
319 pub fn overwrite_ignored(&mut self, overwrite
: bool
)
320 -> &mut CheckoutBuilder
<'cb
> {
321 self.flag(raw
::GIT_CHECKOUT_DONT_OVERWRITE_IGNORED
, !overwrite
)
324 /// Indicate whether a normal merge file should be written for conflicts.
326 /// Defaults to false.
327 pub fn conflict_style_merge(&mut self, on
: bool
)
328 -> &mut CheckoutBuilder
<'cb
> {
329 self.flag(raw
::GIT_CHECKOUT_CONFLICT_STYLE_MERGE
, on
)
332 /// Specify for which notification types to invoke the notification
335 /// Defaults to none.
336 pub fn notify_on(&mut self, notification_types
: CheckoutNotificationType
)
337 -> &mut CheckoutBuilder
<'cb
> {
338 self.notify_flags
= notification_types
;
342 /// Indicates whether to include common ancestor data in diff3 format files
345 /// Defaults to false.
346 pub fn conflict_style_diff3(&mut self, on
: bool
)
347 -> &mut CheckoutBuilder
<'cb
> {
348 self.flag(raw
::GIT_CHECKOUT_CONFLICT_STYLE_DIFF3
, on
)
351 /// Indicate whether to apply filters like CRLF conversion.
352 pub fn disable_filters(&mut self, disable
: bool
)
353 -> &mut CheckoutBuilder
<'cb
> {
354 self.disable_filters
= disable
;
358 /// Set the mode with which new directories are created.
361 pub fn dir_perm(&mut self, perm
: i32) -> &mut CheckoutBuilder
<'cb
> {
362 self.dir_perm
= Some(perm
);
366 /// Set the mode with which new files are created.
368 /// The default is 0644 or 0755 as dictated by the blob.
369 pub fn file_perm(&mut self, perm
: i32) -> &mut CheckoutBuilder
<'cb
> {
370 self.file_perm
= Some(perm
);
374 /// Add a path to be checked out.
376 /// If no paths are specified, then all files are checked out. Otherwise
377 /// only these specified paths are checked out.
378 pub fn path
<T
: IntoCString
>(&mut self, path
: T
)
379 -> &mut CheckoutBuilder
<'cb
> {
380 let path
= path
.into_c_string().unwrap();
381 self.path_ptrs
.push(path
.as_ptr());
382 self.paths
.push(path
);
386 /// Set the directory to check out to
387 pub fn target_dir(&mut self, dst
: &Path
) -> &mut CheckoutBuilder
<'cb
> {
388 self.target_dir
= Some(dst
.into_c_string().unwrap());
392 /// The name of the common ancestor side of conflicts
393 pub fn ancestor_label(&mut self, label
: &str) -> &mut CheckoutBuilder
<'cb
> {
394 self.ancestor_label
= Some(CString
::new(label
).unwrap());
398 /// The name of the common our side of conflicts
399 pub fn our_label(&mut self, label
: &str) -> &mut CheckoutBuilder
<'cb
> {
400 self.our_label
= Some(CString
::new(label
).unwrap());
404 /// The name of the common their side of conflicts
405 pub fn their_label(&mut self, label
: &str) -> &mut CheckoutBuilder
<'cb
> {
406 self.their_label
= Some(CString
::new(label
).unwrap());
410 /// Set a callback to receive notifications of checkout progress.
411 pub fn progress
<F
>(&mut self, cb
: F
) -> &mut CheckoutBuilder
<'cb
>
412 where F
: FnMut(Option
<&Path
>, usize, usize) + 'cb
{
413 self.progress
= Some(Box
::new(cb
) as Box
<Progress
<'cb
>>);
417 /// Set a callback to receive checkout notifications.
419 /// Callbacks are invoked prior to modifying any files on disk.
420 /// Returning `false` from the callback will cancel the checkout.
421 pub fn notify
<F
>(&mut self, cb
: F
) -> &mut CheckoutBuilder
<'cb
>
422 where F
: FnMut(CheckoutNotificationType
, Option
<&Path
>, DiffFile
,
423 DiffFile
, DiffFile
) -> bool
+ 'cb
425 self.notify
= Some(Box
::new(cb
) as Box
<Notify
<'cb
>>);
429 /// Configure a raw checkout options based on this configuration.
431 /// This method is unsafe as there is no guarantee that this structure will
432 /// outlive the provided checkout options.
433 pub unsafe fn configure(&mut self, opts
: &mut raw
::git_checkout_options
) {
434 opts
.version
= raw
::GIT_CHECKOUT_OPTIONS_VERSION
;
435 opts
.disable_filters
= self.disable_filters
as c_int
;
436 opts
.dir_mode
= self.dir_perm
.unwrap_or(0) as c_uint
;
437 opts
.file_mode
= self.file_perm
.unwrap_or(0) as c_uint
;
439 if !self.path_ptrs
.is_empty() {
440 opts
.paths
.strings
= self.path_ptrs
.as_ptr() as *mut _
;
441 opts
.paths
.count
= self.path_ptrs
.len() as size_t
;
444 if let Some(ref c
) = self.target_dir
{
445 opts
.target_directory
= c
.as_ptr();
447 if let Some(ref c
) = self.ancestor_label
{
448 opts
.ancestor_label
= c
.as_ptr();
450 if let Some(ref c
) = self.our_label
{
451 opts
.our_label
= c
.as_ptr();
453 if let Some(ref c
) = self.their_label
{
454 opts
.their_label
= c
.as_ptr();
456 if self.progress
.is_some() {
457 let f
: raw
::git_checkout_progress_cb
= progress_cb
;
458 opts
.progress_cb
= Some(f
);
459 opts
.progress_payload
= self as *mut _
as *mut _
;
461 if self.notify
.is_some() {
462 let f
: raw
::git_checkout_notify_cb
= notify_cb
;
463 opts
.notify_cb
= Some(f
);
464 opts
.notify_payload
= self as *mut _
as *mut _
;
465 opts
.notify_flags
= self.notify_flags
.bits() as c_uint
;
467 opts
.checkout_strategy
= self.checkout_opts
as c_uint
;
471 extern fn progress_cb(path
: *const c_char
,
475 panic
::wrap(|| unsafe {
476 let payload
= &mut *(data
as *mut CheckoutBuilder
);
477 let callback
= match payload
.progress
{
478 Some(ref mut c
) => c
,
481 let path
= if path
.is_null() {
484 Some(util
::bytes2path(CStr
::from_ptr(path
).to_bytes()))
486 callback(path
, completed
as usize, total
as usize)
490 extern fn notify_cb(why
: raw
::git_checkout_notify_t
,
492 baseline
: *const raw
::git_diff_file
,
493 target
: *const raw
::git_diff_file
,
494 workdir
: *const raw
::git_diff_file
,
495 data
: *mut c_void
) -> c_int
{
497 panic
::wrap(|| unsafe {
498 let payload
= &mut *(data
as *mut CheckoutBuilder
);
499 let callback
= match payload
.notify
{
500 Some(ref mut c
) => c
,
503 let path
= if path
.is_null() {
506 Some(util
::bytes2path(CStr
::from_ptr(path
).to_bytes()))
509 let why
= CheckoutNotificationType
::from_bits_truncate(why
as u32);
510 let keep_going
= callback(why
,
512 DiffFile
::from_raw(baseline
),
513 DiffFile
::from_raw(target
),
514 DiffFile
::from_raw(workdir
));
515 if keep_going {0}
else {1}
523 use tempdir
::TempDir
;
524 use super::RepoBuilder
;
529 let r
= RepoBuilder
::new().clone("/path/to/nowhere", Path
::new("foo"));
535 let td
= TempDir
::new("test").unwrap();
536 Repository
::init_bare(&td
.path().join("bare")).unwrap();
537 let url
= if cfg
!(unix
) {
538 format
!("file://{}/bare", td
.path().display())
540 format
!("file:///{}/bare", td
.path().display().to_string()
544 let dst
= td
.path().join("foo");
545 RepoBuilder
::new().clone(&url
, &dst
).unwrap();
546 fs
::remove_dir_all(&dst
).unwrap();
547 RepoBuilder
::new().local(false).clone(&url
, &dst
).unwrap();
548 fs
::remove_dir_all(&dst
).unwrap();
549 RepoBuilder
::new().local(false).hardlinks(false).bare(true)
550 .clone(&url
, &dst
).unwrap();
551 fs
::remove_dir_all(&dst
).unwrap();
552 assert
!(RepoBuilder
::new().branch("foo")
553 .clone(&url
, &dst
).is_err());