]> git.proxmox.com Git - cargo.git/blob - vendor/git2-0.6.8/src/build.rs
New upstream version 0.23.0
[cargo.git] / vendor / git2-0.6.8 / src / build.rs
1 //! Builder-pattern objects for configuration various git operations.
2
3 use std::ffi::{CStr, CString};
4 use std::mem;
5 use std::path::Path;
6 use std::ptr;
7 use libc::{c_char, size_t, c_void, c_uint, c_int};
8
9 use {raw, panic, Error, Repository, FetchOptions, IntoCString};
10 use {CheckoutNotificationType, DiffFile};
11 use util::{self, Binding};
12
13 /// A builder struct which is used to build configuration for cloning a new git
14 /// repository.
15 pub struct RepoBuilder<'cb> {
16 bare: bool,
17 branch: Option<CString>,
18 local: bool,
19 hardlinks: bool,
20 checkout: Option<CheckoutBuilder<'cb>>,
21 fetch_opts: Option<FetchOptions<'cb>>,
22 }
23
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>,
30 paths: Vec<CString>,
31 path_ptrs: Vec<*const c_char>,
32 file_perm: Option<i32>,
33 dir_perm: Option<i32>,
34 disable_filters: bool,
35 checkout_opts: u32,
36 progress: Option<Box<Progress<'cb>>>,
37 notify: Option<Box<Notify<'cb>>>,
38 notify_flags: CheckoutNotificationType,
39 }
40
41 /// Checkout progress notification callback.
42 ///
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;
46
47 /// Checkout notifications callback.
48 ///
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.
51 ///
52 /// The callback must return a bool specifying whether the checkout should
53 /// continue.
54 pub type Notify<'a> = FnMut(CheckoutNotificationType, Option<&Path>, DiffFile,
55 DiffFile, DiffFile) -> bool + 'a;
56
57
58 impl<'cb> Default for RepoBuilder<'cb> {
59 fn default() -> Self {
60 Self::new()
61 }
62 }
63
64 impl<'cb> RepoBuilder<'cb> {
65 /// Creates a new repository builder with all of the default configuration.
66 ///
67 /// When ready, the `clone()` method can be used to clone a new repository
68 /// using this configuration.
69 pub fn new() -> RepoBuilder<'cb> {
70 ::init();
71 RepoBuilder {
72 bare: false,
73 branch: None,
74 local: true,
75 hardlinks: true,
76 checkout: None,
77 fetch_opts: None,
78 }
79 }
80
81 /// Indicate whether the repository will be cloned as a bare repository or
82 /// not.
83 pub fn bare(&mut self, bare: bool) -> &mut RepoBuilder<'cb> {
84 self.bare = bare;
85 self
86 }
87
88 /// Specify the name of the branch to check out after the clone.
89 ///
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());
93 self
94 }
95
96 /// Set the flag for bypassing the git aware transport mechanism for local
97 /// paths.
98 ///
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> {
102 self.local = local;
103 self
104 }
105
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;
110 self
111 }
112
113 /// Configure the checkout which will be performed by consuming a checkout
114 /// builder.
115 pub fn with_checkout(&mut self, checkout: CheckoutBuilder<'cb>)
116 -> &mut RepoBuilder<'cb> {
117 self.checkout = Some(checkout);
118 self
119 }
120
121 /// Options which control the fetch, including callbacks.
122 ///
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);
128 self
129 }
130
131 /// Clone a remote repository.
132 ///
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() };
137 unsafe {
138 try_call!(raw::git_clone_init_options(&mut opts,
139 raw::GIT_CLONE_OPTIONS_VERSION));
140 }
141 opts.bare = self.bare as c_int;
142 opts.checkout_branch = self.branch.as_ref().map(|s| {
143 s.as_ptr()
144 }).unwrap_or(ptr::null());
145
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,
150 };
151 opts.checkout_opts.checkout_strategy =
152 raw::GIT_CHECKOUT_SAFE as c_uint;
153
154 if let Some(ref mut cbs) = self.fetch_opts {
155 opts.fetch_opts = cbs.raw();
156 }
157
158 if let Some(ref mut c) = self.checkout {
159 unsafe {
160 c.configure(&mut opts.checkout_opts);
161 }
162 }
163
164 let url = try!(CString::new(url));
165 let into = try!(into.into_c_string());
166 let mut raw = ptr::null_mut();
167 unsafe {
168 try_call!(raw::git_clone(&mut raw, url, into, &opts));
169 Ok(Binding::from_raw(raw))
170 }
171 }
172 }
173
174 impl<'cb> Default for CheckoutBuilder<'cb> {
175 fn default() -> Self {
176 Self::new()
177 }
178 }
179
180 impl<'cb> CheckoutBuilder<'cb> {
181 /// Creates a new builder for checkouts with all of its default
182 /// configuration.
183 pub fn new() -> CheckoutBuilder<'cb> {
184 ::init();
185 CheckoutBuilder {
186 disable_filters: false,
187 dir_perm: None,
188 file_perm: None,
189 path_ptrs: Vec::new(),
190 paths: Vec::new(),
191 target_dir: None,
192 ancestor_label: None,
193 our_label: None,
194 their_label: None,
195 checkout_opts: raw::GIT_CHECKOUT_SAFE as u32,
196 progress: None,
197 notify: None,
198 notify_flags: CheckoutNotificationType::empty(),
199 }
200 }
201
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;
207 self
208 }
209
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;
215 self
216 }
217
218 /// Indicate that the checkout should be performed safely, allowing new
219 /// files to be created but not overwriting extisting files or changes.
220 ///
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;
225 self
226 }
227
228 fn flag(&mut self, bit: raw::git_checkout_strategy_t,
229 on: bool) -> &mut CheckoutBuilder<'cb> {
230 if on {
231 self.checkout_opts |= bit as u32;
232 } else {
233 self.checkout_opts &= !(bit as u32);
234 }
235 self
236 }
237
238 /// In safe mode, create files that don't exist.
239 ///
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)
243 }
244
245 /// In safe mode, apply safe file updates even when there are conflicts
246 /// instead of canceling the checkout.
247 ///
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)
251 }
252
253 /// Remove untracked files from the working dir.
254 ///
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)
259 }
260
261 /// Remove ignored files from the working dir.
262 ///
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)
266 }
267
268 /// Only update the contents of files that already exist.
269 ///
270 /// If set, files will not be created or deleted.
271 ///
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)
275 }
276
277 /// Prevents checkout from writing the updated files' information to the
278 /// index.
279 ///
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)
283 }
284
285 /// Indicate whether the index and git attributes should be refreshed from
286 /// disk before any operations.
287 ///
288 /// Defaults to true,
289 pub fn refresh(&mut self, refresh: bool) -> &mut CheckoutBuilder<'cb> {
290 self.flag(raw::GIT_CHECKOUT_NO_REFRESH, !refresh)
291 }
292
293 /// Skip files with unmerged index entries.
294 ///
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)
298 }
299
300 /// Indicate whether the checkout should proceed on conflicts by using the
301 /// stage 2 version of the file ("ours").
302 ///
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)
306 }
307
308 /// Indicate whether the checkout should proceed on conflicts by using the
309 /// stage 3 version of the file ("theirs").
310 ///
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)
314 }
315
316 /// Indicate whether ignored files should be overwritten during the checkout.
317 ///
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)
322 }
323
324 /// Indicate whether a normal merge file should be written for conflicts.
325 ///
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)
330 }
331
332 /// Specify for which notification types to invoke the notification
333 /// callback.
334 ///
335 /// Defaults to none.
336 pub fn notify_on(&mut self, notification_types: CheckoutNotificationType)
337 -> &mut CheckoutBuilder<'cb> {
338 self.notify_flags = notification_types;
339 self
340 }
341
342 /// Indicates whether to include common ancestor data in diff3 format files
343 /// for conflicts.
344 ///
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)
349 }
350
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;
355 self
356 }
357
358 /// Set the mode with which new directories are created.
359 ///
360 /// Default is 0755
361 pub fn dir_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb> {
362 self.dir_perm = Some(perm);
363 self
364 }
365
366 /// Set the mode with which new files are created.
367 ///
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);
371 self
372 }
373
374 /// Add a path to be checked out.
375 ///
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);
383 self
384 }
385
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());
389 self
390 }
391
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());
395 self
396 }
397
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());
401 self
402 }
403
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());
407 self
408 }
409
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>>);
414 self
415 }
416
417 /// Set a callback to receive checkout notifications.
418 ///
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
424 {
425 self.notify = Some(Box::new(cb) as Box<Notify<'cb>>);
426 self
427 }
428
429 /// Configure a raw checkout options based on this configuration.
430 ///
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;
438
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;
442 }
443
444 if let Some(ref c) = self.target_dir {
445 opts.target_directory = c.as_ptr();
446 }
447 if let Some(ref c) = self.ancestor_label {
448 opts.ancestor_label = c.as_ptr();
449 }
450 if let Some(ref c) = self.our_label {
451 opts.our_label = c.as_ptr();
452 }
453 if let Some(ref c) = self.their_label {
454 opts.their_label = c.as_ptr();
455 }
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 _;
460 }
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;
466 }
467 opts.checkout_strategy = self.checkout_opts as c_uint;
468 }
469 }
470
471 extern fn progress_cb(path: *const c_char,
472 completed: size_t,
473 total: size_t,
474 data: *mut c_void) {
475 panic::wrap(|| unsafe {
476 let payload = &mut *(data as *mut CheckoutBuilder);
477 let callback = match payload.progress {
478 Some(ref mut c) => c,
479 None => return,
480 };
481 let path = if path.is_null() {
482 None
483 } else {
484 Some(util::bytes2path(CStr::from_ptr(path).to_bytes()))
485 };
486 callback(path, completed as usize, total as usize)
487 });
488 }
489
490 extern fn notify_cb(why: raw::git_checkout_notify_t,
491 path: *const c_char,
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 {
496 // pack callback etc
497 panic::wrap(|| unsafe {
498 let payload = &mut *(data as *mut CheckoutBuilder);
499 let callback = match payload.notify {
500 Some(ref mut c) => c,
501 None => return 0,
502 };
503 let path = if path.is_null() {
504 None
505 } else {
506 Some(util::bytes2path(CStr::from_ptr(path).to_bytes()))
507 };
508
509 let why = CheckoutNotificationType::from_bits_truncate(why as u32);
510 let keep_going = callback(why,
511 path,
512 DiffFile::from_raw(baseline),
513 DiffFile::from_raw(target),
514 DiffFile::from_raw(workdir));
515 if keep_going {0} else {1}
516 }).unwrap_or(2)
517 }
518
519 #[cfg(test)]
520 mod tests {
521 use std::fs;
522 use std::path::Path;
523 use tempdir::TempDir;
524 use super::RepoBuilder;
525 use Repository;
526
527 #[test]
528 fn smoke() {
529 let r = RepoBuilder::new().clone("/path/to/nowhere", Path::new("foo"));
530 assert!(r.is_err());
531 }
532
533 #[test]
534 fn smoke2() {
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())
539 } else {
540 format!("file:///{}/bare", td.path().display().to_string()
541 .replace("\\", "/"))
542 };
543
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());
554 }
555
556 }