diff options
Diffstat (limited to 'vendor/git2/src/build.rs')
-rw-r--r-- | vendor/git2/src/build.rs | 861 |
1 files changed, 861 insertions, 0 deletions
diff --git a/vendor/git2/src/build.rs b/vendor/git2/src/build.rs new file mode 100644 index 0000000..d3c95f6 --- /dev/null +++ b/vendor/git2/src/build.rs @@ -0,0 +1,861 @@ +//! Builder-pattern objects for configuration various git operations. + +use libc::{c_char, c_int, c_uint, c_void, size_t}; +use std::ffi::{CStr, CString}; +use std::mem; +use std::path::Path; +use std::ptr; + +use crate::util::{self, Binding}; +use crate::{panic, raw, Error, FetchOptions, IntoCString, Oid, Repository, Tree}; +use crate::{CheckoutNotificationType, DiffFile, FileMode, Remote}; + +/// A builder struct which is used to build configuration for cloning a new git +/// repository. +/// +/// # Example +/// +/// Cloning using SSH: +/// +/// ```no_run +/// use git2::{Cred, Error, RemoteCallbacks}; +/// use std::env; +/// use std::path::Path; +/// +/// // Prepare callbacks. +/// let mut callbacks = RemoteCallbacks::new(); +/// callbacks.credentials(|_url, username_from_url, _allowed_types| { +/// Cred::ssh_key( +/// username_from_url.unwrap(), +/// None, +/// Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())), +/// None, +/// ) +/// }); +/// +/// // Prepare fetch options. +/// let mut fo = git2::FetchOptions::new(); +/// fo.remote_callbacks(callbacks); +/// +/// // Prepare builder. +/// let mut builder = git2::build::RepoBuilder::new(); +/// builder.fetch_options(fo); +/// +/// // Clone the project. +/// builder.clone( +/// "git@github.com:rust-lang/git2-rs.git", +/// Path::new("/tmp/git2-rs"), +/// ); +/// ``` +pub struct RepoBuilder<'cb> { + bare: bool, + branch: Option<CString>, + local: bool, + hardlinks: bool, + checkout: Option<CheckoutBuilder<'cb>>, + fetch_opts: Option<FetchOptions<'cb>>, + clone_local: Option<CloneLocal>, + remote_create: Option<Box<RemoteCreate<'cb>>>, +} + +/// Type of callback passed to `RepoBuilder::remote_create`. +/// +/// The second and third arguments are the remote's name and the remote's URL. +pub type RemoteCreate<'cb> = + dyn for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb; + +/// A builder struct for git tree updates. +/// +/// Paths passed to `remove` and `upsert` can be multi-component paths, i.e. they +/// may contain slashes. +/// +/// This is a higher-level tree update facility. There is also [`TreeBuilder`] +/// which is lower-level (and operates only on one level of the tree at a time). +/// +/// [`TreeBuilder`]: crate::TreeBuilder +pub struct TreeUpdateBuilder { + updates: Vec<raw::git_tree_update>, + paths: Vec<CString>, +} + +/// A builder struct for configuring checkouts of a repository. +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<i32>, + dir_perm: Option<i32>, + disable_filters: bool, + checkout_opts: u32, + progress: Option<Box<Progress<'cb>>>, + notify: Option<Box<Notify<'cb>>>, + notify_flags: CheckoutNotificationType, +} + +/// Checkout progress notification callback. +/// +/// The first argument is the path for the notification, the next is the number +/// of completed steps so far, and the final is the total number of steps. +pub type Progress<'a> = dyn FnMut(Option<&Path>, usize, usize) + 'a; + +/// Checkout notifications callback. +/// +/// The first argument is the notification type, the next is the path for the +/// the notification, followed by the baseline diff, target diff, and workdir diff. +/// +/// The callback must return a bool specifying whether the checkout should +/// continue. +pub type Notify<'a> = dyn FnMut( + CheckoutNotificationType, + Option<&Path>, + Option<DiffFile<'_>>, + Option<DiffFile<'_>>, + Option<DiffFile<'_>>, + ) -> bool + + 'a; + +impl<'cb> Default for RepoBuilder<'cb> { + fn default() -> Self { + Self::new() + } +} + +/// Options that can be passed to `RepoBuilder::clone_local`. +#[derive(Clone, Copy)] +pub enum CloneLocal { + /// Auto-detect (default) + /// + /// Here libgit2 will bypass the git-aware transport for local paths, but + /// use a normal fetch for `file://` URLs. + Auto = raw::GIT_CLONE_LOCAL_AUTO as isize, + + /// Bypass the git-aware transport even for `file://` URLs. + Local = raw::GIT_CLONE_LOCAL as isize, + + /// Never bypass the git-aware transport + None = raw::GIT_CLONE_NO_LOCAL as isize, + + /// Bypass the git-aware transport, but don't try to use hardlinks. + NoLinks = raw::GIT_CLONE_LOCAL_NO_LINKS as isize, + + #[doc(hidden)] + __Nonexhaustive = 0xff, +} + +impl<'cb> RepoBuilder<'cb> { + /// Creates a new repository builder with all of the default configuration. + /// + /// When ready, the `clone()` method can be used to clone a new repository + /// using this configuration. + pub fn new() -> RepoBuilder<'cb> { + crate::init(); + RepoBuilder { + bare: false, + branch: None, + local: true, + clone_local: None, + hardlinks: true, + checkout: None, + fetch_opts: None, + remote_create: None, + } + } + + /// Indicate whether the repository will be cloned as a bare repository or + /// not. + pub fn bare(&mut self, bare: bool) -> &mut RepoBuilder<'cb> { + self.bare = bare; + self + } + + /// Specify the name of the branch to check out after the clone. + /// + /// If not specified, the remote's default branch will be used. + pub fn branch(&mut self, branch: &str) -> &mut RepoBuilder<'cb> { + self.branch = Some(CString::new(branch).unwrap()); + self + } + + /// Configures options for bypassing the git-aware transport on clone. + /// + /// Bypassing it means that instead of a fetch libgit2 will copy the object + /// database directory instead of figuring out what it needs, which is + /// faster. If possible, it will hardlink the files to save space. + pub fn clone_local(&mut self, clone_local: CloneLocal) -> &mut RepoBuilder<'cb> { + self.clone_local = Some(clone_local); + self + } + + /// Set the flag for bypassing the git aware transport mechanism for local + /// paths. + /// + /// If `true`, the git-aware transport will be bypassed for local paths. If + /// `false`, the git-aware transport will not be bypassed. + #[deprecated(note = "use `clone_local` instead")] + #[doc(hidden)] + pub fn local(&mut self, local: bool) -> &mut RepoBuilder<'cb> { + self.local = local; + self + } + + /// Set the flag for whether hardlinks are used when using a local git-aware + /// transport mechanism. + #[deprecated(note = "use `clone_local` instead")] + #[doc(hidden)] + pub fn hardlinks(&mut self, links: bool) -> &mut RepoBuilder<'cb> { + self.hardlinks = links; + self + } + + /// Configure the checkout which will be performed by consuming a checkout + /// builder. + pub fn with_checkout(&mut self, checkout: CheckoutBuilder<'cb>) -> &mut RepoBuilder<'cb> { + self.checkout = Some(checkout); + self + } + + /// Options which control the fetch, including callbacks. + /// + /// The callbacks are used for reporting fetch progress, and for acquiring + /// credentials in the event they are needed. + pub fn fetch_options(&mut self, fetch_opts: FetchOptions<'cb>) -> &mut RepoBuilder<'cb> { + self.fetch_opts = Some(fetch_opts); + self + } + + /// Configures a callback used to create the git remote, prior to its being + /// used to perform the clone operation. + pub fn remote_create<F>(&mut self, f: F) -> &mut RepoBuilder<'cb> + where + F: for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb, + { + self.remote_create = Some(Box::new(f)); + self + } + + /// Clone a remote repository. + /// + /// This will use the options configured so far to clone the specified URL + /// into the specified local path. + 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(ptr::null()); + + if let Some(ref local) = self.clone_local { + opts.local = *local as raw::git_clone_local_t; + } else { + 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, + }; + } + + if let Some(ref mut cbs) = self.fetch_opts { + opts.fetch_opts = cbs.raw(); + } + + if let Some(ref mut c) = self.checkout { + unsafe { + c.configure(&mut opts.checkout_opts); + } + } + + if let Some(ref mut callback) = self.remote_create { + opts.remote_cb = Some(remote_create_cb); + opts.remote_cb_payload = callback as *mut _ as *mut _; + } + + let url = CString::new(url)?; + // Normal file path OK (does not need Windows conversion). + let into = into.into_c_string()?; + let mut raw = ptr::null_mut(); + unsafe { + try_call!(raw::git_clone(&mut raw, url, into, &opts)); + Ok(Binding::from_raw(raw)) + } + } +} + +extern "C" fn 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_int { + unsafe { + let repo = Repository::from_raw(repo); + let code = panic::wrap(|| { + let name = CStr::from_ptr(name).to_str().unwrap(); + let url = CStr::from_ptr(url).to_str().unwrap(); + let f = payload as *mut Box<RemoteCreate<'_>>; + match (*f)(&repo, name, url) { + Ok(remote) => { + *out = crate::remote::remote_into_raw(remote); + 0 + } + Err(e) => e.raw_code(), + } + }); + mem::forget(repo); + code.unwrap_or(-1) + } +} + +impl<'cb> Default for CheckoutBuilder<'cb> { + fn default() -> Self { + Self::new() + } +} + +impl<'cb> CheckoutBuilder<'cb> { + /// Creates a new builder for checkouts with all of its default + /// configuration. + pub fn new() -> CheckoutBuilder<'cb> { + crate::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 as u32, + progress: None, + notify: None, + notify_flags: CheckoutNotificationType::empty(), + } + } + + /// Indicate that this checkout should perform a dry run by checking for + /// conflicts but not make any actual changes. + 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 + } + + /// Take any action necessary to get the working directory to match the + /// target including potentially discarding modified files. + pub fn force(&mut self) -> &mut CheckoutBuilder<'cb> { + self.checkout_opts &= !((1 << 4) - 1); + self.checkout_opts |= raw::GIT_CHECKOUT_FORCE as u32; + self + } + + /// Indicate that the checkout should be performed safely, allowing new + /// files to be created but not overwriting existing files or changes. + /// + /// This is the default. + pub fn safe(&mut self) -> &mut CheckoutBuilder<'cb> { + self.checkout_opts &= !((1 << 4) - 1); + self.checkout_opts |= raw::GIT_CHECKOUT_SAFE 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 + } + + /// In safe mode, create files that don't exist. + /// + /// Defaults to false. + pub fn recreate_missing(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_RECREATE_MISSING, allow) + } + + /// In safe mode, apply safe file updates even when there are conflicts + /// instead of canceling the checkout. + /// + /// Defaults to false. + pub fn allow_conflicts(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_ALLOW_CONFLICTS, allow) + } + + /// Remove untracked files from the working dir. + /// + /// Defaults to false. + pub fn remove_untracked(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_REMOVE_UNTRACKED, remove) + } + + /// Remove ignored files from the working dir. + /// + /// Defaults to false. + pub fn remove_ignored(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_REMOVE_IGNORED, remove) + } + + /// Only update the contents of files that already exist. + /// + /// If set, files will not be created or deleted. + /// + /// Defaults to false. + pub fn update_only(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_UPDATE_ONLY, update) + } + + /// Prevents checkout from writing the updated files' information to the + /// index. + /// + /// Defaults to true. + pub fn update_index(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_DONT_UPDATE_INDEX, !update) + } + + /// Indicate whether the index and git attributes should be refreshed from + /// disk before any operations. + /// + /// Defaults to true, + pub fn refresh(&mut self, refresh: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_NO_REFRESH, !refresh) + } + + /// Skip files with unmerged index entries. + /// + /// Defaults to false. + pub fn skip_unmerged(&mut self, skip: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_SKIP_UNMERGED, skip) + } + + /// Indicate whether the checkout should proceed on conflicts by using the + /// stage 2 version of the file ("ours"). + /// + /// Defaults to false. + pub fn use_ours(&mut self, ours: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_USE_OURS, ours) + } + + /// Indicate whether the checkout should proceed on conflicts by using the + /// stage 3 version of the file ("theirs"). + /// + /// Defaults to false. + pub fn use_theirs(&mut self, theirs: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_USE_THEIRS, theirs) + } + + /// Indicate whether ignored files should be overwritten during the checkout. + /// + /// Defaults to true. + pub fn overwrite_ignored(&mut self, overwrite: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, !overwrite) + } + + /// Indicate whether a normal merge file should be written for conflicts. + /// + /// Defaults to false. + pub fn conflict_style_merge(&mut self, on: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_MERGE, on) + } + + /// Specify for which notification types to invoke the notification + /// callback. + /// + /// Defaults to none. + pub fn notify_on( + &mut self, + notification_types: CheckoutNotificationType, + ) -> &mut CheckoutBuilder<'cb> { + self.notify_flags = notification_types; + self + } + + /// Indicates whether to include common ancestor data in diff3 format files + /// for conflicts. + /// + /// Defaults to false. + pub fn conflict_style_diff3(&mut self, on: bool) -> &mut CheckoutBuilder<'cb> { + self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_DIFF3, on) + } + + /// Indicate whether to apply filters like CRLF conversion. + pub fn disable_filters(&mut self, disable: bool) -> &mut CheckoutBuilder<'cb> { + self.disable_filters = disable; + self + } + + /// Set the mode with which new directories are created. + /// + /// Default is 0755 + pub fn dir_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb> { + self.dir_perm = Some(perm); + self + } + + /// Set the mode with which new files are created. + /// + /// The default is 0644 or 0755 as dictated by the blob. + pub fn file_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb> { + self.file_perm = Some(perm); + self + } + + /// Add a path to be checked out. + /// + /// If no paths are specified, then all files are checked out. Otherwise + /// only these specified paths are checked out. + pub fn path<T: IntoCString>(&mut self, path: T) -> &mut CheckoutBuilder<'cb> { + let path = util::cstring_to_repo_path(path).unwrap(); + self.path_ptrs.push(path.as_ptr()); + self.paths.push(path); + self + } + + /// Set the directory to check out to + pub fn target_dir(&mut self, dst: &Path) -> &mut CheckoutBuilder<'cb> { + // Normal file path OK (does not need Windows conversion). + self.target_dir = Some(dst.into_c_string().unwrap()); + self + } + + /// The name of the common ancestor side of conflicts + pub fn ancestor_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> { + self.ancestor_label = Some(CString::new(label).unwrap()); + self + } + + /// The name of the common our side of conflicts + pub fn our_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> { + self.our_label = Some(CString::new(label).unwrap()); + self + } + + /// The name of the common their side of conflicts + pub fn their_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> { + self.their_label = Some(CString::new(label).unwrap()); + self + } + + /// Set a callback to receive notifications of checkout progress. + pub fn progress<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb> + where + F: FnMut(Option<&Path>, usize, usize) + 'cb, + { + self.progress = Some(Box::new(cb) as Box<Progress<'cb>>); + self + } + + /// Set a callback to receive checkout notifications. + /// + /// Callbacks are invoked prior to modifying any files on disk. + /// Returning `false` from the callback will cancel the checkout. + pub fn notify<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb> + where + F: FnMut( + CheckoutNotificationType, + Option<&Path>, + Option<DiffFile<'_>>, + Option<DiffFile<'_>>, + Option<DiffFile<'_>>, + ) -> bool + + 'cb, + { + self.notify = Some(Box::new(cb) as Box<Notify<'cb>>); + self + } + + /// Configure a raw checkout options based on this configuration. + /// + /// This method is unsafe as there is no guarantee that this structure will + /// outlive the provided checkout options. + 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.unwrap_or(0) as c_uint; + opts.file_mode = self.file_perm.unwrap_or(0) as c_uint; + + if !self.path_ptrs.is_empty() { + opts.paths.strings = self.path_ptrs.as_ptr() as *mut _; + opts.paths.count = self.path_ptrs.len() as size_t; + } + + if let Some(ref c) = self.target_dir { + opts.target_directory = c.as_ptr(); + } + if let Some(ref c) = self.ancestor_label { + opts.ancestor_label = c.as_ptr(); + } + if let Some(ref c) = self.our_label { + opts.our_label = c.as_ptr(); + } + if let Some(ref c) = self.their_label { + opts.their_label = c.as_ptr(); + } + if self.progress.is_some() { + opts.progress_cb = Some(progress_cb); + opts.progress_payload = self as *mut _ as *mut _; + } + if self.notify.is_some() { + opts.notify_cb = Some(notify_cb); + opts.notify_payload = self as *mut _ as *mut _; + opts.notify_flags = self.notify_flags.bits() as c_uint; + } + opts.checkout_strategy = self.checkout_opts as c_uint; + } +} + +extern "C" fn progress_cb( + path: *const c_char, + completed: size_t, + total: size_t, + data: *mut c_void, +) { + panic::wrap(|| unsafe { + let payload = &mut *(data as *mut CheckoutBuilder<'_>); + let callback = match payload.progress { + Some(ref mut c) => c, + None => return, + }; + let path = if path.is_null() { + None + } else { + Some(util::bytes2path(CStr::from_ptr(path).to_bytes())) + }; + callback(path, completed as usize, total as usize) + }); +} + +extern "C" fn 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_int { + // pack callback etc + panic::wrap(|| unsafe { + let payload = &mut *(data as *mut CheckoutBuilder<'_>); + let callback = match payload.notify { + Some(ref mut c) => c, + None => return 0, + }; + let path = if path.is_null() { + None + } else { + Some(util::bytes2path(CStr::from_ptr(path).to_bytes())) + }; + + let baseline = if baseline.is_null() { + None + } else { + Some(DiffFile::from_raw(baseline)) + }; + + let target = if target.is_null() { + None + } else { + Some(DiffFile::from_raw(target)) + }; + + let workdir = if workdir.is_null() { + None + } else { + Some(DiffFile::from_raw(workdir)) + }; + + let why = CheckoutNotificationType::from_bits_truncate(why as u32); + let keep_going = callback(why, path, baseline, target, workdir); + if keep_going { + 0 + } else { + 1 + } + }) + .unwrap_or(2) +} + +unsafe impl Send for TreeUpdateBuilder {} + +impl Default for TreeUpdateBuilder { + fn default() -> Self { + Self::new() + } +} + +impl TreeUpdateBuilder { + /// Create a new empty series of updates. + pub fn new() -> Self { + Self { + updates: Vec::new(), + paths: Vec::new(), + } + } + + /// Add an update removing the specified `path` from a tree. + pub fn remove<T: IntoCString>(&mut self, path: T) -> &mut Self { + let path = util::cstring_to_repo_path(path).unwrap(); + let path_ptr = path.as_ptr(); + self.paths.push(path); + self.updates.push(raw::git_tree_update { + action: raw::GIT_TREE_UPDATE_REMOVE, + id: raw::git_oid { + id: [0; raw::GIT_OID_RAWSZ], + }, + filemode: raw::GIT_FILEMODE_UNREADABLE, + path: path_ptr, + }); + self + } + + /// Add an update setting the specified `path` to a specific Oid, whether it currently exists + /// or not. + /// + /// Note that libgit2 does not support an upsert of a previously removed path, or an upsert + /// that changes the type of an object (such as from tree to blob or vice versa). + pub fn upsert<T: IntoCString>(&mut self, path: T, id: Oid, filemode: FileMode) -> &mut Self { + let path = util::cstring_to_repo_path(path).unwrap(); + let path_ptr = path.as_ptr(); + self.paths.push(path); + self.updates.push(raw::git_tree_update { + action: raw::GIT_TREE_UPDATE_UPSERT, + id: unsafe { *id.raw() }, + filemode: u32::from(filemode) as raw::git_filemode_t, + path: path_ptr, + }); + self + } + + /// Create a new tree from the specified baseline and this series of updates. + /// + /// The baseline tree must exist in the specified repository. + pub fn create_updated(&mut self, repo: &Repository, baseline: &Tree<'_>) -> Result<Oid, Error> { + let mut ret = raw::git_oid { + id: [0; raw::GIT_OID_RAWSZ], + }; + unsafe { + try_call!(raw::git_tree_create_updated( + &mut ret, + repo.raw(), + baseline.raw(), + self.updates.len(), + self.updates.as_ptr() + )); + Ok(Binding::from_raw(&ret as *const _)) + } + } +} + +#[cfg(test)] +mod tests { + use super::{CheckoutBuilder, RepoBuilder, TreeUpdateBuilder}; + use crate::{CheckoutNotificationType, FileMode, Repository}; + use std::fs; + use std::path::Path; + use tempfile::TempDir; + + #[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().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().replace("\\", "/") + ) + }; + + let dst = td.path().join("foo"); + RepoBuilder::new().clone(&url, &dst).unwrap(); + fs::remove_dir_all(&dst).unwrap(); + assert!(RepoBuilder::new().branch("foo").clone(&url, &dst).is_err()); + } + + #[test] + fn smoke_tree_create_updated() { + let (_tempdir, repo) = crate::test::repo_init(); + let (_, tree_id) = crate::test::commit(&repo); + let tree = t!(repo.find_tree(tree_id)); + assert!(tree.get_name("bar").is_none()); + let foo_id = tree.get_name("foo").unwrap().id(); + let tree2_id = t!(TreeUpdateBuilder::new() + .remove("foo") + .upsert("bar/baz", foo_id, FileMode::Blob) + .create_updated(&repo, &tree)); + let tree2 = t!(repo.find_tree(tree2_id)); + assert!(tree2.get_name("foo").is_none()); + let baz_id = tree2.get_path(Path::new("bar/baz")).unwrap().id(); + assert_eq!(foo_id, baz_id); + } + + /// Issue regression test #365 + #[test] + fn notify_callback() { + let td = TempDir::new().unwrap(); + let cd = TempDir::new().unwrap(); + + { + let mut opts = crate::RepositoryInitOptions::new(); + opts.initial_head("main"); + let repo = Repository::init_opts(&td.path(), &opts).unwrap(); + + let mut config = repo.config().unwrap(); + config.set_str("user.name", "name").unwrap(); + config.set_str("user.email", "email").unwrap(); + + let mut index = repo.index().unwrap(); + let p = Path::new(td.path()).join("file"); + println!("using path {:?}", p); + fs::File::create(&p).unwrap(); + index.add_path(&Path::new("file")).unwrap(); + let id = index.write_tree().unwrap(); + + let tree = repo.find_tree(id).unwrap(); + let sig = repo.signature().unwrap(); + repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, &[]) + .unwrap(); + } + + let repo = Repository::open_bare(&td.path().join(".git")).unwrap(); + let tree = repo + .revparse_single(&"main") + .unwrap() + .peel_to_tree() + .unwrap(); + let mut index = repo.index().unwrap(); + index.read_tree(&tree).unwrap(); + + let mut checkout_opts = CheckoutBuilder::new(); + checkout_opts.target_dir(&cd.path()); + checkout_opts.notify_on(CheckoutNotificationType::all()); + checkout_opts.notify(|_notif, _path, baseline, target, workdir| { + assert!(baseline.is_none()); + assert_eq!(target.unwrap().path(), Some(Path::new("file"))); + assert!(workdir.is_none()); + true + }); + repo.checkout_index(Some(&mut index), Some(&mut checkout_opts)) + .unwrap(); + } +} |