use std::ffi::{self, CString};
use std::io;
use std::mem;
use std::path::BytesContainer;
use libc::{c_char, size_t, c_void, c_uint, c_int};
use {raw, Signature, Error, Repository, RemoteCallbacks, panic};
use util::Binding;
pub struct RepoBuilder<'cb> {
bare: bool,
branch: Option<CString>,
sig: Option<Signature<'static>>,
local: bool,
hardlinks: bool,
checkout: Option<CheckoutBuilder<'cb>>,
callbacks: Option<RemoteCallbacks<'cb>>,
}
pub struct CheckoutBuilder<'cb> {
their_label: Option<CString>,
our_label: Option<CString>,
ancestor_label: Option<CString>,
target_dir: Option<CString>,
paths: Vec<CString>,
path_ptrs: Vec<*const c_char>,
file_perm: Option<io::FilePermission>,
dir_perm: Option<io::FilePermission>,
disable_filters: bool,
checkout_opts: u32,
progress: Option<Box<Progress<'cb>>>,
}
pub type Progress<'a> = FnMut(&[u8], usize, usize) + 'a;
impl<'cb> RepoBuilder<'cb> {
pub fn new() -> RepoBuilder<'cb> {
::init();
RepoBuilder {
bare: false,
branch: None,
sig: None,
local: true,
hardlinks: true,
checkout: None,
callbacks: None,
}
}
pub fn bare(&mut self, bare: bool) -> &mut RepoBuilder<'cb> {
self.bare = bare;
self
}
pub fn branch(&mut self, branch: &str) -> &mut RepoBuilder<'cb> {
self.branch = Some(CString::from_slice(branch.as_bytes()));
self
}
pub fn signature(&mut self, sig: Signature<'static>) -> &mut RepoBuilder<'cb> {
self.sig = Some(sig);
self
}
pub fn local(&mut self, local: bool) -> &mut RepoBuilder<'cb> {
self.local = local;
self
}
pub fn hardlinks(&mut self, links: bool) -> &mut RepoBuilder<'cb> {
self.hardlinks = links;
self
}
pub fn with_checkout(&mut self, checkout: CheckoutBuilder<'cb>)
-> &mut RepoBuilder<'cb> {
self.checkout = Some(checkout);
self
}
pub fn remote_callbacks(&mut self, callbacks: RemoteCallbacks<'cb>)
-> &mut RepoBuilder<'cb> {
self.callbacks = Some(callbacks);
self
}
pub fn clone(&mut self, url: &str, into: &Path) -> Result<Repository, Error> {
let mut opts: raw::git_clone_options = unsafe { mem::zeroed() };
unsafe {
try_call!(raw::git_clone_init_options(&mut opts,
raw::GIT_CLONE_OPTIONS_VERSION));
}
opts.bare = self.bare as c_int;
opts.checkout_branch = self.branch.as_ref().map(|s| {
s.as_ptr()
}).unwrap_or(0 as *const _);
opts.signature = self.sig.as_ref().map(|s| {
s.raw()
}).unwrap_or(0 as *mut _);
opts.local = match (self.local, self.hardlinks) {
(true, false) => raw::GIT_CLONE_LOCAL_NO_LINKS,
(false, _) => raw::GIT_CLONE_NO_LOCAL,
(true, _) => raw::GIT_CLONE_LOCAL_AUTO,
};
opts.checkout_opts.checkout_strategy =
raw::GIT_CHECKOUT_SAFE_CREATE as c_uint;
match self.callbacks {
Some(ref mut cbs) => {
opts.remote_callbacks = cbs.raw();
},
None => {}
}
match self.checkout {
Some(ref mut c) => unsafe { c.configure(&mut opts.checkout_opts) },
None => {}
}
let url = CString::from_slice(url.as_bytes());
let into = CString::from_slice(into.as_vec());
let mut raw = 0 as *mut raw::git_repository;
unsafe {
try_call!(raw::git_clone(&mut raw, url, into, &opts));
Ok(Binding::from_raw(raw))
}
}
}
impl<'cb> CheckoutBuilder<'cb> {
pub fn new() -> CheckoutBuilder<'cb> {
::init();
CheckoutBuilder {
disable_filters: false,
dir_perm: None,
file_perm: None,
path_ptrs: Vec::new(),
paths: Vec::new(),
target_dir: None,
ancestor_label: None,
our_label: None,
their_label: None,
checkout_opts: raw::GIT_CHECKOUT_SAFE_CREATE as u32,
progress: None,
}
}
pub fn dry_run(&mut self) -> &mut CheckoutBuilder<'cb> {
self.checkout_opts &= !((1 << 4) - 1);
self.checkout_opts |= raw::GIT_CHECKOUT_NONE as u32;
self
}
pub fn force(&mut self) -> &mut CheckoutBuilder<'cb> {
self.checkout_opts &= !((1 << 4) - 1);
self.checkout_opts |= raw::GIT_CHECKOUT_FORCE as u32;
self
}
pub fn safe(&mut self) -> &mut CheckoutBuilder<'cb> {
self.checkout_opts &= !((1 << 4) - 1);
self.checkout_opts |= raw::GIT_CHECKOUT_SAFE_CREATE as u32;
self
}
fn flag(&mut self, bit: raw::git_checkout_strategy_t,
on: bool) -> &mut CheckoutBuilder<'cb> {
if on {
self.checkout_opts |= bit as u32;
} else {
self.checkout_opts &= !(bit as u32);
}
self
}
pub fn allow_conflicts(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_ALLOW_CONFLICTS, allow)
}
pub fn remove_untracked(&mut self, remove: bool)
-> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_REMOVE_UNTRACKED, remove)
}
pub fn remove_ignored(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_REMOVE_IGNORED, remove)
}
pub fn update_only(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_UPDATE_ONLY, update)
}
pub fn update_index(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_DONT_UPDATE_INDEX, !update)
}
pub fn refresh(&mut self, refresh: bool) -> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_NO_REFRESH, !refresh)
}
pub fn skip_unmerged(&mut self, skip: bool) -> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_SKIP_UNMERGED, skip)
}
pub fn use_ours(&mut self, ours: bool) -> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_USE_OURS, ours)
}
pub fn use_theirs(&mut self, theirs: bool) -> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_USE_THEIRS, theirs)
}
pub fn overwrite_ignored(&mut self, overwrite: bool)
-> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, !overwrite)
}
pub fn conflict_style_merge(&mut self, on: bool)
-> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_MERGE, on)
}
pub fn conflict_style_diff3(&mut self, on: bool)
-> &mut CheckoutBuilder<'cb> {
self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_DIFF3, on)
}
pub fn disable_filters(&mut self, disable: bool)
-> &mut CheckoutBuilder<'cb> {
self.disable_filters = disable;
self
}
pub fn dir_perm(&mut self, perm: io::FilePermission)
-> &mut CheckoutBuilder<'cb> {
self.dir_perm = Some(perm);
self
}
pub fn file_perm(&mut self, perm: io::FilePermission)
-> &mut CheckoutBuilder<'cb> {
self.file_perm = Some(perm);
self
}
pub fn path<T: BytesContainer>(&mut self, path: T)
-> &mut CheckoutBuilder<'cb> {
let path = CString::from_slice(path.container_as_bytes());
self.path_ptrs.push(path.as_ptr());
self.paths.push(path);
self
}
pub fn target_dir(&mut self, dst: Path) -> &mut CheckoutBuilder<'cb> {
self.target_dir = Some(CString::from_slice(dst.as_vec()));
self
}
pub fn ancestor_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> {
self.ancestor_label = Some(CString::from_slice(label.as_bytes()));
self
}
pub fn our_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> {
self.our_label = Some(CString::from_slice(label.as_bytes()));
self
}
pub fn their_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> {
self.their_label = Some(CString::from_slice(label.as_bytes()));
self
}
pub fn progress<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb>
where F: FnMut(&[u8], usize, usize) + 'cb {
self.progress = Some(Box::new(cb) as Box<Progress<'cb>>);
self
}
pub unsafe fn configure(&mut self, opts: &mut raw::git_checkout_options) {
opts.version = raw::GIT_CHECKOUT_OPTIONS_VERSION;
opts.disable_filters = self.disable_filters as c_int;
opts.dir_mode = self.dir_perm.map(|p| p.bits()).unwrap_or(0) as c_uint;
opts.file_mode = self.file_perm.map(|p| p.bits()).unwrap_or(0) as c_uint;
if self.path_ptrs.len() > 0 {
opts.paths.strings = self.path_ptrs.as_ptr() as *mut _;
opts.paths.count = self.path_ptrs.len() as size_t;
}
match self.target_dir {
Some(ref c) => opts.target_directory = c.as_ptr(),
None => {}
}
match self.ancestor_label {
Some(ref c) => opts.ancestor_label = c.as_ptr(),
None => {}
}
match self.our_label {
Some(ref c) => opts.our_label = c.as_ptr(),
None => {}
}
match self.their_label {
Some(ref c) => opts.their_label = c.as_ptr(),
None => {}
}
if self.progress.is_some() {
let f: raw::git_checkout_progress_cb = progress_cb;
opts.progress_cb = Some(f);
opts.progress_payload = self as *mut _ as *mut _;
}
opts.checkout_strategy = self.checkout_opts as c_uint;
}
}
extern fn progress_cb(path: *const c_char,
completed: size_t,
total: size_t,
data: *mut c_void) {
unsafe {
let payload: &mut CheckoutBuilder = &mut *(data as *mut CheckoutBuilder);
let callback = match payload.progress {
Some(ref mut c) => c,
None => return,
};
let path = ffi::c_str_to_bytes(&path);
panic::wrap(|| {
callback(path, completed as usize, total as usize);
});
}
}
#[cfg(test)]
mod tests {
use std::io::{fs, TempDir};
use super::RepoBuilder;
use Repository;
#[test]
fn smoke() {
let r = RepoBuilder::new().clone("/path/to/nowhere", &Path::new("foo"));
assert!(r.is_err());
}
#[test]
fn smoke2() {
let td = TempDir::new("test").unwrap();
Repository::init_bare(&td.path().join("bare")).unwrap();
let url = if cfg!(unix) {
format!("file://{}/bare", td.path().display())
} else {
format!("file:///{}/bare", td.path().display().to_string()
.as_slice().replace("\\", "/"))
};
let dst = td.path().join("foo");
RepoBuilder::new().clone(url.as_slice(), &dst).unwrap();
fs::rmdir_recursive(&dst).unwrap();
RepoBuilder::new().local(false).clone(url.as_slice(), &dst).unwrap();
fs::rmdir_recursive(&dst).unwrap();
RepoBuilder::new().local(false).hardlinks(false).bare(true)
.clone(url.as_slice(), &dst).unwrap();
fs::rmdir_recursive(&dst).unwrap();
assert!(RepoBuilder::new().branch("foo")
.clone(url.as_slice(), &dst).is_err());
}
}