use libc::{c_char, c_int, c_uint, c_void, size_t}; use std::env; use std::ffi::{CStr, CString, OsStr}; use std::iter::IntoIterator; use std::mem; use std::path::{Path, PathBuf}; use std::ptr; use std::str; use crate::build::{CheckoutBuilder, RepoBuilder}; use crate::diff::{ binary_cb_c, file_cb_c, hunk_cb_c, line_cb_c, BinaryCb, DiffCallbacks, FileCb, HunkCb, LineCb, }; use crate::oid_array::OidArray; use crate::stash::{stash_cb, StashApplyOptions, StashCbData}; use crate::string_array::StringArray; use crate::tagforeach::{tag_foreach_cb, TagForeachCB, TagForeachData}; use crate::util::{self, path_to_repo_path, Binding}; use crate::worktree::{Worktree, WorktreeAddOptions}; use crate::CherrypickOptions; use crate::RevertOptions; use crate::{mailmap::Mailmap, panic}; use crate::{ raw, AttrCheckFlags, Buf, Error, Object, Remote, RepositoryOpenFlags, RepositoryState, Revspec, StashFlags, }; use crate::{ AnnotatedCommit, MergeAnalysis, MergeOptions, MergePreference, SubmoduleIgnore, SubmoduleStatus, SubmoduleUpdate, }; use crate::{ApplyLocation, ApplyOptions, Rebase, RebaseOptions}; use crate::{Blame, BlameOptions, Reference, References, ResetType, Signature, Submodule}; use crate::{Blob, BlobWriter, Branch, BranchType, Branches, Commit, Config, Index, Oid, Tree}; use crate::{Describe, IntoCString, Reflog, RepositoryInitMode, RevparseMode}; use crate::{DescribeOptions, Diff, DiffOptions, Odb, PackBuilder, TreeBuilder}; use crate::{Note, Notes, ObjectType, Revwalk, Status, StatusOptions, Statuses, Tag, Transaction}; type MergeheadForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a; type FetchheadForeachCb<'a> = dyn FnMut(&str, &[u8], &Oid, bool) -> bool + 'a; struct FetchheadForeachCbData<'a> { callback: &'a mut FetchheadForeachCb<'a>, } struct MergeheadForeachCbData<'a> { callback: &'a mut MergeheadForeachCb<'a>, } extern "C" fn mergehead_foreach_cb(oid: *const raw::git_oid, payload: *mut c_void) -> c_int { panic::wrap(|| unsafe { let data = &mut *(payload as *mut MergeheadForeachCbData<'_>); let res = { let callback = &mut data.callback; callback(&Binding::from_raw(oid)) }; if res { 0 } else { 1 } }) .unwrap_or(1) } extern "C" fn fetchhead_foreach_cb( ref_name: *const c_char, remote_url: *const c_char, oid: *const raw::git_oid, is_merge: c_uint, payload: *mut c_void, ) -> c_int { panic::wrap(|| unsafe { let data = &mut *(payload as *mut FetchheadForeachCbData<'_>); let res = { let callback = &mut data.callback; assert!(!ref_name.is_null()); assert!(!remote_url.is_null()); assert!(!oid.is_null()); let ref_name = str::from_utf8(CStr::from_ptr(ref_name).to_bytes()).unwrap(); let remote_url = CStr::from_ptr(remote_url).to_bytes(); let oid = Binding::from_raw(oid); let is_merge = is_merge == 1; callback(&ref_name, remote_url, &oid, is_merge) }; if res { 0 } else { 1 } }) .unwrap_or(1) } /// An owned git repository, representing all state associated with the /// underlying filesystem. /// /// This structure corresponds to a `git_repository` in libgit2. Many other /// types in git2-rs are derivative from this structure and are attached to its /// lifetime. /// /// When a repository goes out of scope it is freed in memory but not deleted /// from the filesystem. pub struct Repository { raw: *mut raw::git_repository, } // It is the current belief that a `Repository` can be sent among threads, or // even shared among threads in a mutex. unsafe impl Send for Repository {} /// Options which can be used to configure how a repository is initialized pub struct RepositoryInitOptions { flags: u32, mode: u32, workdir_path: Option, description: Option, template_path: Option, initial_head: Option, origin_url: Option, } impl Repository { /// Attempt to open an already-existing repository at `path`. /// /// The path can point to either a normal or bare repository. pub fn open>(path: P) -> Result { crate::init(); // Normal file path OK (does not need Windows conversion). let path = path.as_ref().into_c_string()?; let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_repository_open(&mut ret, path)); Ok(Binding::from_raw(ret)) } } /// Attempt to open an already-existing bare repository at `path`. /// /// The path can point to only a bare repository. pub fn open_bare>(path: P) -> Result { crate::init(); // Normal file path OK (does not need Windows conversion). let path = path.as_ref().into_c_string()?; let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_repository_open_bare(&mut ret, path)); Ok(Binding::from_raw(ret)) } } /// Find and open an existing repository, respecting git environment /// variables. This acts like `open_ext` with the /// [FROM_ENV](RepositoryOpenFlags::FROM_ENV) flag, but additionally respects `$GIT_DIR`. /// With `$GIT_DIR` unset, this will search for a repository starting in /// the current directory. pub fn open_from_env() -> Result { crate::init(); let mut ret = ptr::null_mut(); let flags = raw::GIT_REPOSITORY_OPEN_FROM_ENV; unsafe { try_call!(raw::git_repository_open_ext( &mut ret, ptr::null(), flags as c_uint, ptr::null() )); Ok(Binding::from_raw(ret)) } } /// Find and open an existing repository, with additional options. /// /// If flags contains [NO_SEARCH](RepositoryOpenFlags::NO_SEARCH), the path must point /// directly to a repository; otherwise, this may point to a subdirectory /// of a repository, and `open_ext` will search up through parent /// directories. /// /// If flags contains [CROSS_FS](RepositoryOpenFlags::CROSS_FS), the search through parent /// directories will not cross a filesystem boundary (detected when the /// stat st_dev field changes). /// /// If flags contains [BARE](RepositoryOpenFlags::BARE), force opening the repository as /// bare even if it isn't, ignoring any working directory, and defer /// loading the repository configuration for performance. /// /// If flags contains [NO_DOTGIT](RepositoryOpenFlags::NO_DOTGIT), don't try appending /// `/.git` to `path`. /// /// If flags contains [FROM_ENV](RepositoryOpenFlags::FROM_ENV), `open_ext` will ignore /// other flags and `ceiling_dirs`, and respect the same environment /// variables git does. Note, however, that `path` overrides `$GIT_DIR`; to /// respect `$GIT_DIR` as well, use `open_from_env`. /// /// ceiling_dirs specifies a list of paths that the search through parent /// directories will stop before entering. Use the functions in std::env /// to construct or manipulate such a path list. (You can use `&[] as /// &[&std::ffi::OsStr]` as an argument if there are no ceiling /// directories.) pub fn open_ext( path: P, flags: RepositoryOpenFlags, ceiling_dirs: I, ) -> Result where P: AsRef, O: AsRef, I: IntoIterator, { crate::init(); // Normal file path OK (does not need Windows conversion). let path = path.as_ref().into_c_string()?; let ceiling_dirs_os = env::join_paths(ceiling_dirs)?; let ceiling_dirs = ceiling_dirs_os.into_c_string()?; let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_repository_open_ext( &mut ret, path, flags.bits() as c_uint, ceiling_dirs )); Ok(Binding::from_raw(ret)) } } /// Attempt to open an already-existing repository from a worktree. pub fn open_from_worktree(worktree: &Worktree) -> Result { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_repository_open_from_worktree( &mut ret, worktree.raw() )); Ok(Binding::from_raw(ret)) } } /// Attempt to open an already-existing repository at or above `path` /// /// This starts at `path` and looks up the filesystem hierarchy /// until it finds a repository. pub fn discover>(path: P) -> Result { // TODO: this diverges significantly from the libgit2 API crate::init(); let buf = Buf::new(); // Normal file path OK (does not need Windows conversion). let path = path.as_ref().into_c_string()?; unsafe { try_call!(raw::git_repository_discover( buf.raw(), path, 1, ptr::null() )); } Repository::open(util::bytes2path(&*buf)) } /// Attempt to find the path to a git repo for a given path /// /// This starts at `path` and looks up the filesystem hierarchy /// until it finds a repository, stopping if it finds a member of ceiling_dirs pub fn discover_path, I, O>(path: P, ceiling_dirs: I) -> Result where O: AsRef, I: IntoIterator, { crate::init(); let buf = Buf::new(); // Normal file path OK (does not need Windows conversion). let path = path.as_ref().into_c_string()?; let ceiling_dirs_os = env::join_paths(ceiling_dirs)?; let ceiling_dirs = ceiling_dirs_os.into_c_string()?; unsafe { try_call!(raw::git_repository_discover( buf.raw(), path, 1, ceiling_dirs )); } Ok(util::bytes2path(&*buf).to_path_buf()) } /// Creates a new repository in the specified folder. /// /// This by default will create any necessary directories to create the /// repository, and it will read any user-specified templates when creating /// the repository. This behavior can be configured through `init_opts`. pub fn init>(path: P) -> Result { Repository::init_opts(path, &RepositoryInitOptions::new()) } /// Creates a new `--bare` repository in the specified folder. /// /// The folder must exist prior to invoking this function. pub fn init_bare>(path: P) -> Result { Repository::init_opts(path, RepositoryInitOptions::new().bare(true)) } /// Creates a new repository in the specified folder with the given options. /// /// See `RepositoryInitOptions` struct for more information. pub fn init_opts>( path: P, opts: &RepositoryInitOptions, ) -> Result { crate::init(); // Normal file path OK (does not need Windows conversion). let path = path.as_ref().into_c_string()?; let mut ret = ptr::null_mut(); unsafe { let mut opts = opts.raw(); try_call!(raw::git_repository_init_ext(&mut ret, path, &mut opts)); Ok(Binding::from_raw(ret)) } } /// Clone a remote repository. /// /// See the `RepoBuilder` struct for more information. This function will /// delegate to a fresh `RepoBuilder` pub fn clone>(url: &str, into: P) -> Result { crate::init(); RepoBuilder::new().clone(url, into.as_ref()) } /// Clone a remote repository, initialize and update its submodules /// recursively. /// /// This is similar to `git clone --recursive`. pub fn clone_recurse>(url: &str, into: P) -> Result { let repo = Repository::clone(url, into)?; repo.update_submodules()?; Ok(repo) } /// Attempt to wrap an object database as a repository. pub fn from_odb(odb: Odb<'_>) -> Result { crate::init(); let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_repository_wrap_odb(&mut ret, odb.raw())); Ok(Binding::from_raw(ret)) } } /// Update submodules recursively. /// /// Uninitialized submodules will be initialized. fn update_submodules(&self) -> Result<(), Error> { fn add_subrepos(repo: &Repository, list: &mut Vec) -> Result<(), Error> { for mut subm in repo.submodules()? { subm.update(true, None)?; list.push(subm.open()?); } Ok(()) } let mut repos = Vec::new(); add_subrepos(self, &mut repos)?; while let Some(repo) = repos.pop() { add_subrepos(&repo, &mut repos)?; } Ok(()) } /// Execute a rev-parse operation against the `spec` listed. /// /// The resulting revision specification is returned, or an error is /// returned if one occurs. pub fn revparse(&self, spec: &str) -> Result, Error> { let mut raw = raw::git_revspec { from: ptr::null_mut(), to: ptr::null_mut(), flags: 0, }; let spec = CString::new(spec)?; unsafe { try_call!(raw::git_revparse(&mut raw, self.raw, spec)); let to = Binding::from_raw_opt(raw.to); let from = Binding::from_raw_opt(raw.from); let mode = RevparseMode::from_bits_truncate(raw.flags as u32); Ok(Revspec::from_objects(from, to, mode)) } } /// Find a single object, as specified by a revision string. pub fn revparse_single(&self, spec: &str) -> Result, Error> { let spec = CString::new(spec)?; let mut obj = ptr::null_mut(); unsafe { try_call!(raw::git_revparse_single(&mut obj, self.raw, spec)); assert!(!obj.is_null()); Ok(Binding::from_raw(obj)) } } /// Find a single object and intermediate reference by a revision string. /// /// See `man gitrevisions`, or /// for /// information on the syntax accepted. /// /// In some cases (`@{<-n>}` or `@{upstream}`), the expression /// may point to an intermediate reference. When such expressions are being /// passed in, this intermediate reference is returned. pub fn revparse_ext(&self, spec: &str) -> Result<(Object<'_>, Option>), Error> { let spec = CString::new(spec)?; let mut git_obj = ptr::null_mut(); let mut git_ref = ptr::null_mut(); unsafe { try_call!(raw::git_revparse_ext( &mut git_obj, &mut git_ref, self.raw, spec )); assert!(!git_obj.is_null()); Ok((Binding::from_raw(git_obj), Binding::from_raw_opt(git_ref))) } } /// Tests whether this repository is a bare repository or not. pub fn is_bare(&self) -> bool { unsafe { raw::git_repository_is_bare(self.raw) == 1 } } /// Tests whether this repository is a shallow clone. pub fn is_shallow(&self) -> bool { unsafe { raw::git_repository_is_shallow(self.raw) == 1 } } /// Tests whether this repository is a worktree. pub fn is_worktree(&self) -> bool { unsafe { raw::git_repository_is_worktree(self.raw) == 1 } } /// Tests whether this repository is empty. pub fn is_empty(&self) -> Result { let empty = unsafe { try_call!(raw::git_repository_is_empty(self.raw)) }; Ok(empty == 1) } /// Returns the path to the `.git` folder for normal repositories or the /// repository itself for bare repositories. pub fn path(&self) -> &Path { unsafe { let ptr = raw::git_repository_path(self.raw); util::bytes2path(crate::opt_bytes(self, ptr).unwrap()) } } /// Returns the current state of this repository pub fn state(&self) -> RepositoryState { let state = unsafe { raw::git_repository_state(self.raw) }; macro_rules! check( ($($raw:ident => $real:ident),*) => ( $(if state == raw::$raw as c_int { super::RepositoryState::$real }) else * else { panic!("unknown repository state: {}", state) } ) ); check!( GIT_REPOSITORY_STATE_NONE => Clean, GIT_REPOSITORY_STATE_MERGE => Merge, GIT_REPOSITORY_STATE_REVERT => Revert, GIT_REPOSITORY_STATE_REVERT_SEQUENCE => RevertSequence, GIT_REPOSITORY_STATE_CHERRYPICK => CherryPick, GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE => CherryPickSequence, GIT_REPOSITORY_STATE_BISECT => Bisect, GIT_REPOSITORY_STATE_REBASE => Rebase, GIT_REPOSITORY_STATE_REBASE_INTERACTIVE => RebaseInteractive, GIT_REPOSITORY_STATE_REBASE_MERGE => RebaseMerge, GIT_REPOSITORY_STATE_APPLY_MAILBOX => ApplyMailbox, GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE => ApplyMailboxOrRebase ) } /// Get the path of the working directory for this repository. /// /// If this repository is bare, then `None` is returned. pub fn workdir(&self) -> Option<&Path> { unsafe { let ptr = raw::git_repository_workdir(self.raw); if ptr.is_null() { None } else { Some(util::bytes2path(CStr::from_ptr(ptr).to_bytes())) } } } /// Set the path to the working directory for this repository. /// /// If `update_link` is true, create/update the gitlink file in the workdir /// and set config "core.worktree" (if workdir is not the parent of the .git /// directory). pub fn set_workdir(&self, path: &Path, update_gitlink: bool) -> Result<(), Error> { // Normal file path OK (does not need Windows conversion). let path = path.into_c_string()?; unsafe { try_call!(raw::git_repository_set_workdir( self.raw(), path, update_gitlink )); } Ok(()) } /// Get the currently active namespace for this repository. /// /// If there is no namespace, or the namespace is not a valid utf8 string, /// `None` is returned. pub fn namespace(&self) -> Option<&str> { self.namespace_bytes().and_then(|s| str::from_utf8(s).ok()) } /// Get the currently active namespace for this repository as a byte array. /// /// If there is no namespace, `None` is returned. pub fn namespace_bytes(&self) -> Option<&[u8]> { unsafe { crate::opt_bytes(self, raw::git_repository_get_namespace(self.raw)) } } /// Set the active namespace for this repository. pub fn set_namespace(&self, namespace: &str) -> Result<(), Error> { self.set_namespace_bytes(namespace.as_bytes()) } /// Set the active namespace for this repository as a byte array. pub fn set_namespace_bytes(&self, namespace: &[u8]) -> Result<(), Error> { unsafe { let namespace = CString::new(namespace)?; try_call!(raw::git_repository_set_namespace(self.raw, namespace)); Ok(()) } } /// Remove the active namespace for this repository. pub fn remove_namespace(&self) -> Result<(), Error> { unsafe { try_call!(raw::git_repository_set_namespace(self.raw, ptr::null())); Ok(()) } } /// Retrieves the Git merge message. /// Remember to remove the message when finished. pub fn message(&self) -> Result { unsafe { let buf = Buf::new(); try_call!(raw::git_repository_message(buf.raw(), self.raw)); Ok(str::from_utf8(&buf).unwrap().to_string()) } } /// Remove the Git merge message. pub fn remove_message(&self) -> Result<(), Error> { unsafe { try_call!(raw::git_repository_message_remove(self.raw)); Ok(()) } } /// List all remotes for a given repository pub fn remotes(&self) -> Result { let mut arr = raw::git_strarray { strings: ptr::null_mut(), count: 0, }; unsafe { try_call!(raw::git_remote_list(&mut arr, self.raw)); Ok(Binding::from_raw(arr)) } } /// Get the information for a particular remote pub fn find_remote(&self, name: &str) -> Result, Error> { let mut ret = ptr::null_mut(); let name = CString::new(name)?; unsafe { try_call!(raw::git_remote_lookup(&mut ret, self.raw, name)); Ok(Binding::from_raw(ret)) } } /// Add a remote with the default fetch refspec to the repository's /// configuration. pub fn remote(&self, name: &str, url: &str) -> Result, Error> { let mut ret = ptr::null_mut(); let name = CString::new(name)?; let url = CString::new(url)?; unsafe { try_call!(raw::git_remote_create(&mut ret, self.raw, name, url)); Ok(Binding::from_raw(ret)) } } /// Add a remote with the provided fetch refspec to the repository's /// configuration. pub fn remote_with_fetch( &self, name: &str, url: &str, fetch: &str, ) -> Result, Error> { let mut ret = ptr::null_mut(); let name = CString::new(name)?; let url = CString::new(url)?; let fetch = CString::new(fetch)?; unsafe { try_call!(raw::git_remote_create_with_fetchspec( &mut ret, self.raw, name, url, fetch )); Ok(Binding::from_raw(ret)) } } /// Create an anonymous remote /// /// Create a remote with the given URL and refspec in memory. You can use /// this when you have a URL instead of a remote's name. Note that anonymous /// remotes cannot be converted to persisted remotes. pub fn remote_anonymous(&self, url: &str) -> Result, Error> { let mut ret = ptr::null_mut(); let url = CString::new(url)?; unsafe { try_call!(raw::git_remote_create_anonymous(&mut ret, self.raw, url)); Ok(Binding::from_raw(ret)) } } /// Give a remote a new name /// /// All remote-tracking branches and configuration settings for the remote /// are updated. /// /// A temporary in-memory remote cannot be given a name with this method. /// /// No loaded instances of the remote with the old name will change their /// name or their list of refspecs. /// /// The returned array of strings is a list of the non-default refspecs /// which cannot be renamed and are returned for further processing by the /// caller. pub fn remote_rename(&self, name: &str, new_name: &str) -> Result { let name = CString::new(name)?; let new_name = CString::new(new_name)?; let mut problems = raw::git_strarray { count: 0, strings: ptr::null_mut(), }; unsafe { try_call!(raw::git_remote_rename( &mut problems, self.raw, name, new_name )); Ok(Binding::from_raw(problems)) } } /// Delete an existing persisted remote. /// /// All remote-tracking branches and configuration settings for the remote /// will be removed. pub fn remote_delete(&self, name: &str) -> Result<(), Error> { let name = CString::new(name)?; unsafe { try_call!(raw::git_remote_delete(self.raw, name)); } Ok(()) } /// Add a fetch refspec to the remote's configuration /// /// Add the given refspec to the fetch list in the configuration. No loaded /// remote instances will be affected. pub fn remote_add_fetch(&self, name: &str, spec: &str) -> Result<(), Error> { let name = CString::new(name)?; let spec = CString::new(spec)?; unsafe { try_call!(raw::git_remote_add_fetch(self.raw, name, spec)); } Ok(()) } /// Add a push refspec to the remote's configuration. /// /// Add the given refspec to the push list in the configuration. No /// loaded remote instances will be affected. pub fn remote_add_push(&self, name: &str, spec: &str) -> Result<(), Error> { let name = CString::new(name)?; let spec = CString::new(spec)?; unsafe { try_call!(raw::git_remote_add_push(self.raw, name, spec)); } Ok(()) } /// Set the remote's URL in the configuration /// /// Remote objects already in memory will not be affected. This assumes /// the common case of a single-url remote and will otherwise return an /// error. pub fn remote_set_url(&self, name: &str, url: &str) -> Result<(), Error> { let name = CString::new(name)?; let url = CString::new(url)?; unsafe { try_call!(raw::git_remote_set_url(self.raw, name, url)); } Ok(()) } /// Set the remote's URL for pushing in the configuration. /// /// Remote objects already in memory will not be affected. This assumes /// the common case of a single-url remote and will otherwise return an /// error. /// /// `None` indicates that it should be cleared. pub fn remote_set_pushurl(&self, name: &str, pushurl: Option<&str>) -> Result<(), Error> { let name = CString::new(name)?; let pushurl = crate::opt_cstr(pushurl)?; unsafe { try_call!(raw::git_remote_set_pushurl(self.raw, name, pushurl)); } Ok(()) } /// Sets the current head to the specified object and optionally resets /// the index and working tree to match. /// /// A soft reset means the head will be moved to the commit. /// /// A mixed reset will trigger a soft reset, plus the index will be /// replaced with the content of the commit tree. /// /// A hard reset will trigger a mixed reset and the working directory will /// be replaced with the content of the index. (Untracked and ignored files /// will be left alone, however.) /// /// The `target` is a commit-ish to which the head should be moved to. The /// object can either be a commit or a tag, but tags must be dereferenceable /// to a commit. /// /// The `checkout` options will only be used for a hard reset. pub fn reset( &self, target: &Object<'_>, kind: ResetType, checkout: Option<&mut CheckoutBuilder<'_>>, ) -> Result<(), Error> { unsafe { let mut opts: raw::git_checkout_options = mem::zeroed(); try_call!(raw::git_checkout_init_options( &mut opts, raw::GIT_CHECKOUT_OPTIONS_VERSION )); let opts = checkout.map(|c| { c.configure(&mut opts); &mut opts }); try_call!(raw::git_reset(self.raw, target.raw(), kind, opts)); } Ok(()) } /// Updates some entries in the index from the target commit tree. /// /// The scope of the updated entries is determined by the paths being /// in the iterator provided. /// /// Passing a `None` target will result in removing entries in the index /// matching the provided pathspecs. pub fn reset_default(&self, target: Option<&Object<'_>>, paths: I) -> Result<(), Error> where T: IntoCString, I: IntoIterator, { let (_a, _b, mut arr) = crate::util::iter2cstrs_paths(paths)?; let target = target.map(|t| t.raw()); unsafe { try_call!(raw::git_reset_default(self.raw, target, &mut arr)); } Ok(()) } /// Retrieve and resolve the reference pointed at by HEAD. pub fn head(&self) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_repository_head(&mut ret, self.raw)); Ok(Binding::from_raw(ret)) } } /// Make the repository HEAD point to the specified reference. /// /// If the provided reference points to a tree or a blob, the HEAD is /// unaltered and an error is returned. /// /// If the provided reference points to a branch, the HEAD will point to /// that branch, staying attached, or become attached if it isn't yet. If /// the branch doesn't exist yet, no error will be returned. The HEAD will /// then be attached to an unborn branch. /// /// Otherwise, the HEAD will be detached and will directly point to the /// commit. pub fn set_head(&self, refname: &str) -> Result<(), Error> { self.set_head_bytes(refname.as_bytes()) } /// Make the repository HEAD point to the specified reference as a byte array. /// /// If the provided reference points to a tree or a blob, the HEAD is /// unaltered and an error is returned. /// /// If the provided reference points to a branch, the HEAD will point to /// that branch, staying attached, or become attached if it isn't yet. If /// the branch doesn't exist yet, no error will be returned. The HEAD will /// then be attached to an unborn branch. /// /// Otherwise, the HEAD will be detached and will directly point to the /// commit. pub fn set_head_bytes(&self, refname: &[u8]) -> Result<(), Error> { let refname = CString::new(refname)?; unsafe { try_call!(raw::git_repository_set_head(self.raw, refname)); } Ok(()) } /// Determines whether the repository HEAD is detached. pub fn head_detached(&self) -> Result { unsafe { let value = raw::git_repository_head_detached(self.raw); match value { 0 => Ok(false), 1 => Ok(true), _ => Err(Error::last_error(value).unwrap()), } } } /// Make the repository HEAD directly point to the commit. /// /// If the provided commitish cannot be found in the repository, the HEAD /// is unaltered and an error is returned. /// /// If the provided commitish cannot be peeled into a commit, the HEAD is /// unaltered and an error is returned. /// /// Otherwise, the HEAD will eventually be detached and will directly point /// to the peeled commit. pub fn set_head_detached(&self, commitish: Oid) -> Result<(), Error> { unsafe { try_call!(raw::git_repository_set_head_detached( self.raw, commitish.raw() )); } Ok(()) } /// Make the repository HEAD directly point to the commit. /// /// If the provided commitish cannot be found in the repository, the HEAD /// is unaltered and an error is returned. /// If the provided commitish cannot be peeled into a commit, the HEAD is /// unaltered and an error is returned. /// Otherwise, the HEAD will eventually be detached and will directly point /// to the peeled commit. pub fn set_head_detached_from_annotated( &self, commitish: AnnotatedCommit<'_>, ) -> Result<(), Error> { unsafe { try_call!(raw::git_repository_set_head_detached_from_annotated( self.raw, commitish.raw() )); } Ok(()) } /// Create an iterator for the repo's references pub fn references(&self) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_reference_iterator_new(&mut ret, self.raw)); Ok(Binding::from_raw(ret)) } } /// Create an iterator for the repo's references that match the specified /// glob pub fn references_glob(&self, glob: &str) -> Result, Error> { let mut ret = ptr::null_mut(); let glob = CString::new(glob)?; unsafe { try_call!(raw::git_reference_iterator_glob_new( &mut ret, self.raw, glob )); Ok(Binding::from_raw(ret)) } } /// Load all submodules for this repository and return them. pub fn submodules(&self) -> Result>, Error> { struct Data<'a, 'b> { repo: &'b Repository, ret: &'a mut Vec>, } let mut ret = Vec::new(); unsafe { let mut data = Data { repo: self, ret: &mut ret, }; let cb: raw::git_submodule_cb = Some(append); try_call!(raw::git_submodule_foreach( self.raw, cb, &mut data as *mut _ as *mut c_void )); } return Ok(ret); extern "C" fn append( _repo: *mut raw::git_submodule, name: *const c_char, data: *mut c_void, ) -> c_int { unsafe { let data = &mut *(data as *mut Data<'_, '_>); let mut raw = ptr::null_mut(); let rc = raw::git_submodule_lookup(&mut raw, data.repo.raw(), name); assert_eq!(rc, 0); data.ret.push(Binding::from_raw(raw)); } 0 } } /// Gather file status information and populate the returned structure. /// /// Note that if a pathspec is given in the options to filter the /// status, then the results from rename detection (if you enable it) may /// not be accurate. To do rename detection properly, this must be called /// with no pathspec so that all files can be considered. pub fn statuses(&self, options: Option<&mut StatusOptions>) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_status_list_new( &mut ret, self.raw, options.map(|s| s.raw()).unwrap_or(ptr::null()) )); Ok(Binding::from_raw(ret)) } } /// Test if the ignore rules apply to a given file. /// /// This function checks the ignore rules to see if they would apply to the /// given file. This indicates if the file would be ignored regardless of /// whether the file is already in the index or committed to the repository. /// /// One way to think of this is if you were to do "git add ." on the /// directory containing the file, would it be added or not? pub fn status_should_ignore(&self, path: &Path) -> Result { let mut ret = 0 as c_int; let path = util::cstring_to_repo_path(path)?; unsafe { try_call!(raw::git_status_should_ignore(&mut ret, self.raw, path)); } Ok(ret != 0) } /// Get file status for a single file. /// /// This tries to get status for the filename that you give. If no files /// match that name (in either the HEAD, index, or working directory), this /// returns NotFound. /// /// If the name matches multiple files (for example, if the path names a /// directory or if running on a case- insensitive filesystem and yet the /// HEAD has two entries that both match the path), then this returns /// Ambiguous because it cannot give correct results. /// /// This does not do any sort of rename detection. Renames require a set of /// targets and because of the path filtering, there is not enough /// information to check renames correctly. To check file status with rename /// detection, there is no choice but to do a full `statuses` and scan /// through looking for the path that you are interested in. pub fn status_file(&self, path: &Path) -> Result { let mut ret = 0 as c_uint; let path = path_to_repo_path(path)?; unsafe { try_call!(raw::git_status_file(&mut ret, self.raw, path)); } Ok(Status::from_bits_truncate(ret as u32)) } /// Create an iterator which loops over the requested branches. pub fn branches(&self, filter: Option) -> Result, Error> { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_branch_iterator_new(&mut raw, self.raw(), filter)); Ok(Branches::from_raw(raw)) } } /// Get the Index file for this repository. /// /// If a custom index has not been set, the default index for the repository /// will be returned (the one located in .git/index). /// /// **Caution**: If the [`Repository`] of this index is dropped, then this /// [`Index`] will become detached, and most methods on it will fail. See /// [`Index::open`]. Be sure the repository has a binding such as a local /// variable to keep it alive at least as long as the index. pub fn index(&self) -> Result { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_repository_index(&mut raw, self.raw())); Ok(Binding::from_raw(raw)) } } /// Set the Index file for this repository. pub fn set_index(&self, index: &mut Index) -> Result<(), Error> { unsafe { try_call!(raw::git_repository_set_index(self.raw(), index.raw())); } Ok(()) } /// Get the configuration file for this repository. /// /// If a configuration file has not been set, the default config set for the /// repository will be returned, including global and system configurations /// (if they are available). pub fn config(&self) -> Result { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_repository_config(&mut raw, self.raw())); Ok(Binding::from_raw(raw)) } } /// Get the value of a git attribute for a path as a string. /// /// This function will return a special string if the attribute is set to a special value. /// Interpreting the special string is discouraged. You should always use /// [`AttrValue::from_string`](crate::AttrValue::from_string) to interpret the return value /// and avoid the special string. /// /// As such, the return type of this function will probably be changed in the next major version /// to prevent interpreting the returned string without checking whether it's special. pub fn get_attr( &self, path: &Path, name: &str, flags: AttrCheckFlags, ) -> Result, Error> { Ok(self .get_attr_bytes(path, name, flags)? .and_then(|a| str::from_utf8(a).ok())) } /// Get the value of a git attribute for a path as a byte slice. /// /// This function will return a special byte slice if the attribute is set to a special value. /// Interpreting the special byte slice is discouraged. You should always use /// [`AttrValue::from_bytes`](crate::AttrValue::from_bytes) to interpret the return value and /// avoid the special string. /// /// As such, the return type of this function will probably be changed in the next major version /// to prevent interpreting the returned byte slice without checking whether it's special. pub fn get_attr_bytes( &self, path: &Path, name: &str, flags: AttrCheckFlags, ) -> Result, Error> { let mut ret = ptr::null(); let path = util::cstring_to_repo_path(path)?; let name = CString::new(name)?; unsafe { try_call!(raw::git_attr_get( &mut ret, self.raw(), flags.bits(), path, name )); Ok(crate::opt_bytes(self, ret)) } } /// Write an in-memory buffer to the ODB as a blob. /// /// The Oid returned can in turn be passed to `find_blob` to get a handle to /// the blob. pub fn blob(&self, data: &[u8]) -> Result { let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { let ptr = data.as_ptr() as *const c_void; let len = data.len() as size_t; try_call!(raw::git_blob_create_frombuffer( &mut raw, self.raw(), ptr, len )); Ok(Binding::from_raw(&raw as *const _)) } } /// Read a file from the filesystem and write its content to the Object /// Database as a loose blob /// /// The Oid returned can in turn be passed to `find_blob` to get a handle to /// the blob. pub fn blob_path(&self, path: &Path) -> Result { // Normal file path OK (does not need Windows conversion). let path = path.into_c_string()?; let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_blob_create_fromdisk(&mut raw, self.raw(), path)); Ok(Binding::from_raw(&raw as *const _)) } } /// Create a stream to write blob /// /// This function may need to buffer the data on disk and will in general /// not be the right choice if you know the size of the data to write. /// /// Use `BlobWriter::commit()` to commit the write to the object db /// and get the object id. /// /// If the `hintpath` parameter is filled, it will be used to determine /// what git filters should be applied to the object before it is written /// to the object database. pub fn blob_writer(&self, hintpath: Option<&Path>) -> Result, Error> { let path_str = match hintpath { Some(path) => Some(path.into_c_string()?), None => None, }; let path = match path_str { Some(ref path) => path.as_ptr(), None => ptr::null(), }; let mut out = ptr::null_mut(); unsafe { try_call!(raw::git_blob_create_fromstream(&mut out, self.raw(), path)); Ok(BlobWriter::from_raw(out)) } } /// Lookup a reference to one of the objects in a repository. pub fn find_blob(&self, oid: Oid) -> Result, Error> { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_blob_lookup(&mut raw, self.raw(), oid.raw())); Ok(Binding::from_raw(raw)) } } /// Get the object database for this repository pub fn odb(&self) -> Result, Error> { let mut odb = ptr::null_mut(); unsafe { try_call!(raw::git_repository_odb(&mut odb, self.raw())); Ok(Odb::from_raw(odb)) } } /// Override the object database for this repository pub fn set_odb(&self, odb: &Odb<'_>) -> Result<(), Error> { unsafe { try_call!(raw::git_repository_set_odb(self.raw(), odb.raw())); } Ok(()) } /// Create a new branch pointing at a target commit /// /// A new direct reference will be created pointing to this target commit. /// If `force` is true and a reference already exists with the given name, /// it'll be replaced. pub fn branch( &self, branch_name: &str, target: &Commit<'_>, force: bool, ) -> Result, Error> { let branch_name = CString::new(branch_name)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_branch_create( &mut raw, self.raw(), branch_name, target.raw(), force )); Ok(Branch::wrap(Binding::from_raw(raw))) } } /// Create a new branch pointing at a target commit /// /// This behaves like `Repository::branch()` but takes /// an annotated commit, which lets you specify which /// extended SHA syntax string was specified by a user, /// allowing for more exact reflog messages. /// /// See the documentation for `Repository::branch()` pub fn branch_from_annotated_commit( &self, branch_name: &str, target: &AnnotatedCommit<'_>, force: bool, ) -> Result, Error> { let branch_name = CString::new(branch_name)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_branch_create_from_annotated( &mut raw, self.raw(), branch_name, target.raw(), force )); Ok(Branch::wrap(Binding::from_raw(raw))) } } /// Lookup a branch by its name in a repository. pub fn find_branch(&self, name: &str, branch_type: BranchType) -> Result, Error> { let name = CString::new(name)?; let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_branch_lookup( &mut ret, self.raw(), name, branch_type )); Ok(Branch::wrap(Binding::from_raw(ret))) } } /// Create new commit in the repository /// /// If the `update_ref` is not `None`, name of the reference that will be /// updated to point to this commit. If the reference is not direct, it will /// be resolved to a direct reference. Use "HEAD" to update the HEAD of the /// current branch and make it point to this commit. If the reference /// doesn't exist yet, it will be created. If it does exist, the first /// parent must be the tip of this branch. pub fn commit( &self, update_ref: Option<&str>, author: &Signature<'_>, committer: &Signature<'_>, message: &str, tree: &Tree<'_>, parents: &[&Commit<'_>], ) -> Result { let update_ref = crate::opt_cstr(update_ref)?; let mut parent_ptrs = parents .iter() .map(|p| p.raw() as *const raw::git_commit) .collect::>(); let message = CString::new(message)?; let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_commit_create( &mut raw, self.raw(), update_ref, author.raw(), committer.raw(), ptr::null(), message, tree.raw(), parents.len() as size_t, parent_ptrs.as_mut_ptr() )); Ok(Binding::from_raw(&raw as *const _)) } } /// Create a commit object and return that as a Buf. /// /// That can be converted to a string like this `str::from_utf8(&buf).unwrap().to_string()`. /// And that string can be passed to the `commit_signed` function, /// the arguments behave the same as in the `commit` function. pub fn commit_create_buffer( &self, author: &Signature<'_>, committer: &Signature<'_>, message: &str, tree: &Tree<'_>, parents: &[&Commit<'_>], ) -> Result { let mut parent_ptrs = parents .iter() .map(|p| p.raw() as *const raw::git_commit) .collect::>(); let message = CString::new(message)?; let buf = Buf::new(); unsafe { try_call!(raw::git_commit_create_buffer( buf.raw(), self.raw(), author.raw(), committer.raw(), ptr::null(), message, tree.raw(), parents.len() as size_t, parent_ptrs.as_mut_ptr() )); Ok(buf) } } /// Create a commit object from the given buffer and signature /// /// Given the unsigned commit object's contents, its signature and the /// header field in which to store the signature, attach the signature to /// the commit and write it into the given repository. /// /// Use `None` in `signature_field` to use the default of `gpgsig`, which is /// almost certainly what you want. /// /// Returns the resulting (signed) commit id. pub fn commit_signed( &self, commit_content: &str, signature: &str, signature_field: Option<&str>, ) -> Result { let commit_content = CString::new(commit_content)?; let signature = CString::new(signature)?; let signature_field = crate::opt_cstr(signature_field)?; let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_commit_create_with_signature( &mut raw, self.raw(), commit_content, signature, signature_field )); Ok(Binding::from_raw(&raw as *const _)) } } /// Extract the signature from a commit /// /// Returns a tuple containing the signature in the first value and the /// signed data in the second. pub fn extract_signature( &self, commit_id: &Oid, signature_field: Option<&str>, ) -> Result<(Buf, Buf), Error> { let signature_field = crate::opt_cstr(signature_field)?; let signature = Buf::new(); let content = Buf::new(); unsafe { try_call!(raw::git_commit_extract_signature( signature.raw(), content.raw(), self.raw(), commit_id.raw() as *mut _, signature_field )); Ok((signature, content)) } } /// Lookup a reference to one of the commits in a repository. pub fn find_commit(&self, oid: Oid) -> Result, Error> { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_commit_lookup(&mut raw, self.raw(), oid.raw())); Ok(Binding::from_raw(raw)) } } /// Creates an `AnnotatedCommit` from the given commit id. pub fn find_annotated_commit(&self, id: Oid) -> Result, Error> { unsafe { let mut raw = ptr::null_mut(); try_call!(raw::git_annotated_commit_lookup( &mut raw, self.raw(), id.raw() )); Ok(Binding::from_raw(raw)) } } /// Lookup a reference to one of the objects in a repository. pub fn find_object(&self, oid: Oid, kind: Option) -> Result, Error> { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_object_lookup( &mut raw, self.raw(), oid.raw(), kind )); Ok(Binding::from_raw(raw)) } } /// Create a new direct reference. /// /// This function will return an error if a reference already exists with /// the given name unless force is true, in which case it will be /// overwritten. pub fn reference( &self, name: &str, id: Oid, force: bool, log_message: &str, ) -> Result, Error> { let name = CString::new(name)?; let log_message = CString::new(log_message)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_reference_create( &mut raw, self.raw(), name, id.raw(), force, log_message )); Ok(Binding::from_raw(raw)) } } /// Conditionally create new direct reference. /// /// A direct reference (also called an object id reference) refers directly /// to a specific object id (a.k.a. OID or SHA) in the repository. The id /// permanently refers to the object (although the reference itself can be /// moved). For example, in libgit2 the direct ref "refs/tags/v0.17.0" /// refers to OID 5b9fac39d8a76b9139667c26a63e6b3f204b3977. /// /// The direct reference will be created in the repository and written to /// the disk. /// /// Valid reference names must follow one of two patterns: /// /// 1. Top-level names must contain only capital letters and underscores, /// and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD"). /// 2. Names prefixed with "refs/" can be almost anything. You must avoid /// the characters `~`, `^`, `:`, `\\`, `?`, `[`, and `*`, and the /// sequences ".." and "@{" which have special meaning to revparse. /// /// This function will return an error if a reference already exists with /// the given name unless `force` is true, in which case it will be /// overwritten. /// /// The message for the reflog will be ignored if the reference does not /// belong in the standard set (HEAD, branches and remote-tracking /// branches) and it does not have a reflog. /// /// It will return GIT_EMODIFIED if the reference's value at the time of /// updating does not match the one passed through `current_id` (i.e. if the /// ref has changed since the user read it). pub fn reference_matching( &self, name: &str, id: Oid, force: bool, current_id: Oid, log_message: &str, ) -> Result, Error> { let name = CString::new(name)?; let log_message = CString::new(log_message)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_reference_create_matching( &mut raw, self.raw(), name, id.raw(), force, current_id.raw(), log_message )); Ok(Binding::from_raw(raw)) } } /// Create a new symbolic reference. /// /// A symbolic reference is a reference name that refers to another /// reference name. If the other name moves, the symbolic name will move, /// too. As a simple example, the "HEAD" reference might refer to /// "refs/heads/master" while on the "master" branch of a repository. /// /// Valid reference names must follow one of two patterns: /// /// 1. Top-level names must contain only capital letters and underscores, /// and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD"). /// 2. Names prefixed with "refs/" can be almost anything. You must avoid /// the characters '~', '^', ':', '\\', '?', '[', and '*', and the /// sequences ".." and "@{" which have special meaning to revparse. /// /// This function will return an error if a reference already exists with /// the given name unless force is true, in which case it will be /// overwritten. pub fn reference_symbolic( &self, name: &str, target: &str, force: bool, log_message: &str, ) -> Result, Error> { let name = CString::new(name)?; let target = CString::new(target)?; let log_message = CString::new(log_message)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_reference_symbolic_create( &mut raw, self.raw(), name, target, force, log_message )); Ok(Binding::from_raw(raw)) } } /// Create a new symbolic reference. /// /// This function will return an error if a reference already exists with /// the given name unless force is true, in which case it will be /// overwritten. /// /// It will return GIT_EMODIFIED if the reference's value at the time of /// updating does not match the one passed through current_value (i.e. if /// the ref has changed since the user read it). pub fn reference_symbolic_matching( &self, name: &str, target: &str, force: bool, current_value: &str, log_message: &str, ) -> Result, Error> { let name = CString::new(name)?; let target = CString::new(target)?; let current_value = CString::new(current_value)?; let log_message = CString::new(log_message)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_reference_symbolic_create_matching( &mut raw, self.raw(), name, target, force, current_value, log_message )); Ok(Binding::from_raw(raw)) } } /// Lookup a reference to one of the objects in a repository. pub fn find_reference(&self, name: &str) -> Result, Error> { let name = CString::new(name)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_reference_lookup(&mut raw, self.raw(), name)); Ok(Binding::from_raw(raw)) } } /// Lookup a reference to one of the objects in a repository. /// `Repository::find_reference` with teeth; give the method your reference in /// human-readable format e.g. 'main' instead of 'refs/heads/main', and it /// will do-what-you-mean, returning the `Reference`. pub fn resolve_reference_from_short_name(&self, refname: &str) -> Result, Error> { let refname = CString::new(refname)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_reference_dwim(&mut raw, self.raw(), refname)); Ok(Binding::from_raw(raw)) } } /// Lookup a reference by name and resolve immediately to OID. /// /// This function provides a quick way to resolve a reference name straight /// through to the object id that it refers to. This avoids having to /// allocate or free any `Reference` objects for simple situations. pub fn refname_to_id(&self, name: &str) -> Result { let name = CString::new(name)?; let mut ret = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_reference_name_to_id(&mut ret, self.raw(), name)); Ok(Binding::from_raw(&ret as *const _)) } } /// Creates a git_annotated_commit from the given reference. pub fn reference_to_annotated_commit( &self, reference: &Reference<'_>, ) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_annotated_commit_from_ref( &mut ret, self.raw(), reference.raw() )); Ok(AnnotatedCommit::from_raw(ret)) } } /// Creates a git_annotated_commit from FETCH_HEAD. pub fn annotated_commit_from_fetchhead( &self, branch_name: &str, remote_url: &str, id: &Oid, ) -> Result, Error> { let branch_name = CString::new(branch_name)?; let remote_url = CString::new(remote_url)?; let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_annotated_commit_from_fetchhead( &mut ret, self.raw(), branch_name, remote_url, id.raw() )); Ok(AnnotatedCommit::from_raw(ret)) } } /// Create a new action signature with default user and now timestamp. /// /// This looks up the user.name and user.email from the configuration and /// uses the current time as the timestamp, and creates a new signature /// based on that information. It will return `NotFound` if either the /// user.name or user.email are not set. pub fn signature(&self) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_signature_default(&mut ret, self.raw())); Ok(Binding::from_raw(ret)) } } /// Set up a new git submodule for checkout. /// /// This does "git submodule add" up to the fetch and checkout of the /// submodule contents. It preps a new submodule, creates an entry in /// `.gitmodules` and creates an empty initialized repository either at the /// given path in the working directory or in `.git/modules` with a gitlink /// from the working directory to the new repo. /// /// To fully emulate "git submodule add" call this function, then `open()` /// the submodule repo and perform the clone step as needed. Lastly, call /// `add_finalize()` to wrap up adding the new submodule and `.gitmodules` /// to the index to be ready to commit. pub fn submodule( &self, url: &str, path: &Path, use_gitlink: bool, ) -> Result, Error> { let url = CString::new(url)?; let path = path_to_repo_path(path)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_submodule_add_setup( &mut raw, self.raw(), url, path, use_gitlink )); Ok(Binding::from_raw(raw)) } } /// Lookup submodule information by name or path. /// /// Given either the submodule name or path (they are usually the same), /// this returns a structure describing the submodule. pub fn find_submodule(&self, name: &str) -> Result, Error> { let name = CString::new(name)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_submodule_lookup(&mut raw, self.raw(), name)); Ok(Binding::from_raw(raw)) } } /// Get the status for a submodule. /// /// This looks at a submodule and tries to determine the status. It /// will return a combination of the `SubmoduleStatus` values. pub fn submodule_status( &self, name: &str, ignore: SubmoduleIgnore, ) -> Result { let mut ret = 0; let name = CString::new(name)?; unsafe { try_call!(raw::git_submodule_status(&mut ret, self.raw, name, ignore)); } Ok(SubmoduleStatus::from_bits_truncate(ret as u32)) } /// Set the ignore rule for the submodule in the configuration /// /// This does not affect any currently-loaded instances. pub fn submodule_set_ignore( &mut self, name: &str, ignore: SubmoduleIgnore, ) -> Result<(), Error> { let name = CString::new(name)?; unsafe { try_call!(raw::git_submodule_set_ignore(self.raw(), name, ignore)); } Ok(()) } /// Set the update rule for the submodule in the configuration /// /// This setting won't affect any existing instances. pub fn submodule_set_update( &mut self, name: &str, update: SubmoduleUpdate, ) -> Result<(), Error> { let name = CString::new(name)?; unsafe { try_call!(raw::git_submodule_set_update(self.raw(), name, update)); } Ok(()) } /// Set the URL for the submodule in the configuration /// /// After calling this, you may wish to call [`Submodule::sync`] to write /// the changes to the checked out submodule repository. pub fn submodule_set_url(&mut self, name: &str, url: &str) -> Result<(), Error> { let name = CString::new(name)?; let url = CString::new(url)?; unsafe { try_call!(raw::git_submodule_set_url(self.raw(), name, url)); } Ok(()) } /// Set the branch for the submodule in the configuration /// /// After calling this, you may wish to call [`Submodule::sync`] to write /// the changes to the checked out submodule repository. pub fn submodule_set_branch(&mut self, name: &str, branch_name: &str) -> Result<(), Error> { let name = CString::new(name)?; let branch_name = CString::new(branch_name)?; unsafe { try_call!(raw::git_submodule_set_branch(self.raw(), name, branch_name)); } Ok(()) } /// Lookup a reference to one of the objects in a repository. pub fn find_tree(&self, oid: Oid) -> Result, Error> { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_tree_lookup(&mut raw, self.raw(), oid.raw())); Ok(Binding::from_raw(raw)) } } /// Create a new TreeBuilder, optionally initialized with the /// entries of the given Tree. /// /// The tree builder can be used to create or modify trees in memory and /// write them as tree objects to the database. pub fn treebuilder(&self, tree: Option<&Tree<'_>>) -> Result, Error> { unsafe { let mut ret = ptr::null_mut(); let tree = match tree { Some(tree) => tree.raw(), None => ptr::null_mut(), }; try_call!(raw::git_treebuilder_new(&mut ret, self.raw, tree)); Ok(Binding::from_raw(ret)) } } /// Create a new tag in the repository from an object /// /// A new reference will also be created pointing to this tag object. If /// `force` is true and a reference already exists with the given name, /// it'll be replaced. /// /// The message will not be cleaned up. /// /// The tag name will be checked for validity. You must avoid the characters /// '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences ".." and " @ /// {" which have special meaning to revparse. pub fn tag( &self, name: &str, target: &Object<'_>, tagger: &Signature<'_>, message: &str, force: bool, ) -> Result { let name = CString::new(name)?; let message = CString::new(message)?; let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_tag_create( &mut raw, self.raw, name, target.raw(), tagger.raw(), message, force )); Ok(Binding::from_raw(&raw as *const _)) } } /// Create a new tag in the repository from an object without creating a reference. /// /// The message will not be cleaned up. /// /// The tag name will be checked for validity. You must avoid the characters /// '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences ".." and " @ /// {" which have special meaning to revparse. pub fn tag_annotation_create( &self, name: &str, target: &Object<'_>, tagger: &Signature<'_>, message: &str, ) -> Result { let name = CString::new(name)?; let message = CString::new(message)?; let mut raw_oid = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_tag_annotation_create( &mut raw_oid, self.raw, name, target.raw(), tagger.raw(), message )); Ok(Binding::from_raw(&raw_oid as *const _)) } } /// Create a new lightweight tag pointing at a target object /// /// A new direct reference will be created pointing to this target object. /// If force is true and a reference already exists with the given name, /// it'll be replaced. pub fn tag_lightweight( &self, name: &str, target: &Object<'_>, force: bool, ) -> Result { let name = CString::new(name)?; let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_tag_create_lightweight( &mut raw, self.raw, name, target.raw(), force )); Ok(Binding::from_raw(&raw as *const _)) } } /// Lookup a tag object from the repository. pub fn find_tag(&self, id: Oid) -> Result, Error> { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_tag_lookup(&mut raw, self.raw, id.raw())); Ok(Binding::from_raw(raw)) } } /// Delete an existing tag reference. /// /// The tag name will be checked for validity, see `tag` for some rules /// about valid names. pub fn tag_delete(&self, name: &str) -> Result<(), Error> { let name = CString::new(name)?; unsafe { try_call!(raw::git_tag_delete(self.raw, name)); Ok(()) } } /// Get a list with all the tags in the repository. /// /// An optional fnmatch pattern can also be specified. pub fn tag_names(&self, pattern: Option<&str>) -> Result { let mut arr = raw::git_strarray { strings: ptr::null_mut(), count: 0, }; unsafe { match pattern { Some(s) => { let s = CString::new(s)?; try_call!(raw::git_tag_list_match(&mut arr, s, self.raw)); } None => { try_call!(raw::git_tag_list(&mut arr, self.raw)); } } Ok(Binding::from_raw(arr)) } } /// iterate over all tags calling `cb` on each. /// the callback is provided the tag id and name pub fn tag_foreach(&self, cb: T) -> Result<(), Error> where T: FnMut(Oid, &[u8]) -> bool, { let mut data = TagForeachData { cb: Box::new(cb) as TagForeachCB<'_>, }; unsafe { raw::git_tag_foreach( self.raw, Some(tag_foreach_cb), (&mut data) as *mut _ as *mut _, ); } Ok(()) } /// Updates files in the index and the working tree to match the content of /// the commit pointed at by HEAD. pub fn checkout_head(&self, opts: Option<&mut CheckoutBuilder<'_>>) -> Result<(), Error> { unsafe { let mut raw_opts = mem::zeroed(); try_call!(raw::git_checkout_init_options( &mut raw_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION )); if let Some(c) = opts { c.configure(&mut raw_opts); } try_call!(raw::git_checkout_head(self.raw, &raw_opts)); } Ok(()) } /// Updates files in the working tree to match the content of the index. /// /// If the index is `None`, the repository's index will be used. pub fn checkout_index( &self, index: Option<&mut Index>, opts: Option<&mut CheckoutBuilder<'_>>, ) -> Result<(), Error> { unsafe { let mut raw_opts = mem::zeroed(); try_call!(raw::git_checkout_init_options( &mut raw_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION )); if let Some(c) = opts { c.configure(&mut raw_opts); } try_call!(raw::git_checkout_index( self.raw, index.map(|i| &mut *i.raw()), &raw_opts )); } Ok(()) } /// Updates files in the index and working tree to match the content of the /// tree pointed at by the treeish. pub fn checkout_tree( &self, treeish: &Object<'_>, opts: Option<&mut CheckoutBuilder<'_>>, ) -> Result<(), Error> { unsafe { let mut raw_opts = mem::zeroed(); try_call!(raw::git_checkout_init_options( &mut raw_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION )); if let Some(c) = opts { c.configure(&mut raw_opts); } try_call!(raw::git_checkout_tree(self.raw, &*treeish.raw(), &raw_opts)); } Ok(()) } /// Merges the given commit(s) into HEAD, writing the results into the /// working directory. Any changes are staged for commit and any conflicts /// are written to the index. Callers should inspect the repository's index /// after this completes, resolve any conflicts and prepare a commit. /// /// For compatibility with git, the repository is put into a merging state. /// Once the commit is done (or if the user wishes to abort), you should /// clear this state by calling cleanup_state(). pub fn merge( &self, annotated_commits: &[&AnnotatedCommit<'_>], merge_opts: Option<&mut MergeOptions>, checkout_opts: Option<&mut CheckoutBuilder<'_>>, ) -> Result<(), Error> { unsafe { let mut raw_checkout_opts = mem::zeroed(); try_call!(raw::git_checkout_init_options( &mut raw_checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION )); if let Some(c) = checkout_opts { c.configure(&mut raw_checkout_opts); } let mut commit_ptrs = annotated_commits .iter() .map(|c| c.raw() as *const raw::git_annotated_commit) .collect::>(); try_call!(raw::git_merge( self.raw, commit_ptrs.as_mut_ptr(), annotated_commits.len() as size_t, merge_opts.map(|o| o.raw()).unwrap_or(ptr::null()), &raw_checkout_opts )); } Ok(()) } /// Merge two commits, producing an index that reflects the result of /// the merge. The index may be written as-is to the working directory or /// checked out. If the index is to be converted to a tree, the caller /// should resolve any conflicts that arose as part of the merge. pub fn merge_commits( &self, our_commit: &Commit<'_>, their_commit: &Commit<'_>, opts: Option<&MergeOptions>, ) -> Result { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_merge_commits( &mut raw, self.raw, our_commit.raw(), their_commit.raw(), opts.map(|o| o.raw()) )); Ok(Binding::from_raw(raw)) } } /// Merge two trees, producing an index that reflects the result of /// the merge. The index may be written as-is to the working directory or /// checked out. If the index is to be converted to a tree, the caller /// should resolve any conflicts that arose as part of the merge. pub fn merge_trees( &self, ancestor_tree: &Tree<'_>, our_tree: &Tree<'_>, their_tree: &Tree<'_>, opts: Option<&MergeOptions>, ) -> Result { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_merge_trees( &mut raw, self.raw, ancestor_tree.raw(), our_tree.raw(), their_tree.raw(), opts.map(|o| o.raw()) )); Ok(Binding::from_raw(raw)) } } /// Remove all the metadata associated with an ongoing command like merge, /// revert, cherry-pick, etc. For example: MERGE_HEAD, MERGE_MSG, etc. pub fn cleanup_state(&self) -> Result<(), Error> { unsafe { try_call!(raw::git_repository_state_cleanup(self.raw)); } Ok(()) } /// Analyzes the given branch(es) and determines the opportunities for /// merging them into the HEAD of the repository. pub fn merge_analysis( &self, their_heads: &[&AnnotatedCommit<'_>], ) -> Result<(MergeAnalysis, MergePreference), Error> { unsafe { let mut raw_merge_analysis = 0 as raw::git_merge_analysis_t; let mut raw_merge_preference = 0 as raw::git_merge_preference_t; let mut their_heads = their_heads .iter() .map(|v| v.raw() as *const _) .collect::>(); try_call!(raw::git_merge_analysis( &mut raw_merge_analysis, &mut raw_merge_preference, self.raw, their_heads.as_mut_ptr() as *mut _, their_heads.len() )); Ok(( MergeAnalysis::from_bits_truncate(raw_merge_analysis as u32), MergePreference::from_bits_truncate(raw_merge_preference as u32), )) } } /// Analyzes the given branch(es) and determines the opportunities for /// merging them into a reference. pub fn merge_analysis_for_ref( &self, our_ref: &Reference<'_>, their_heads: &[&AnnotatedCommit<'_>], ) -> Result<(MergeAnalysis, MergePreference), Error> { unsafe { let mut raw_merge_analysis = 0 as raw::git_merge_analysis_t; let mut raw_merge_preference = 0 as raw::git_merge_preference_t; let mut their_heads = their_heads .iter() .map(|v| v.raw() as *const _) .collect::>(); try_call!(raw::git_merge_analysis_for_ref( &mut raw_merge_analysis, &mut raw_merge_preference, self.raw, our_ref.raw(), their_heads.as_mut_ptr() as *mut _, their_heads.len() )); Ok(( MergeAnalysis::from_bits_truncate(raw_merge_analysis as u32), MergePreference::from_bits_truncate(raw_merge_preference as u32), )) } } /// Initializes a rebase operation to rebase the changes in `branch` /// relative to `upstream` onto another branch. To begin the rebase process, /// call `next()`. pub fn rebase( &self, branch: Option<&AnnotatedCommit<'_>>, upstream: Option<&AnnotatedCommit<'_>>, onto: Option<&AnnotatedCommit<'_>>, opts: Option<&mut RebaseOptions<'_>>, ) -> Result, Error> { let mut rebase: *mut raw::git_rebase = ptr::null_mut(); unsafe { try_call!(raw::git_rebase_init( &mut rebase, self.raw(), branch.map(|c| c.raw()), upstream.map(|c| c.raw()), onto.map(|c| c.raw()), opts.map(|o| o.raw()).unwrap_or(ptr::null()) )); Ok(Rebase::from_raw(rebase)) } } /// Opens an existing rebase that was previously started by either an /// invocation of `rebase()` or by another client. pub fn open_rebase(&self, opts: Option<&mut RebaseOptions<'_>>) -> Result, Error> { let mut rebase: *mut raw::git_rebase = ptr::null_mut(); unsafe { try_call!(raw::git_rebase_open( &mut rebase, self.raw(), opts.map(|o| o.raw()).unwrap_or(ptr::null()) )); Ok(Rebase::from_raw(rebase)) } } /// Add a note for an object /// /// The `notes_ref` argument is the canonical name of the reference to use, /// defaulting to "refs/notes/commits". If `force` is specified then /// previous notes are overwritten. pub fn note( &self, author: &Signature<'_>, committer: &Signature<'_>, notes_ref: Option<&str>, oid: Oid, note: &str, force: bool, ) -> Result { let notes_ref = crate::opt_cstr(notes_ref)?; let note = CString::new(note)?; let mut ret = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_note_create( &mut ret, self.raw, notes_ref, author.raw(), committer.raw(), oid.raw(), note, force )); Ok(Binding::from_raw(&ret as *const _)) } } /// Get the default notes reference for this repository pub fn note_default_ref(&self) -> Result { let ret = Buf::new(); unsafe { try_call!(raw::git_note_default_ref(ret.raw(), self.raw)); } Ok(str::from_utf8(&ret).unwrap().to_string()) } /// Creates a new iterator for notes in this repository. /// /// The `notes_ref` argument is the canonical name of the reference to use, /// defaulting to "refs/notes/commits". /// /// The iterator returned yields pairs of (Oid, Oid) where the first element /// is the id of the note and the second id is the id the note is /// annotating. pub fn notes(&self, notes_ref: Option<&str>) -> Result, Error> { let notes_ref = crate::opt_cstr(notes_ref)?; let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_note_iterator_new(&mut ret, self.raw, notes_ref)); Ok(Binding::from_raw(ret)) } } /// Read the note for an object. /// /// The `notes_ref` argument is the canonical name of the reference to use, /// defaulting to "refs/notes/commits". /// /// The id specified is the Oid of the git object to read the note from. pub fn find_note(&self, notes_ref: Option<&str>, id: Oid) -> Result, Error> { let notes_ref = crate::opt_cstr(notes_ref)?; let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_note_read(&mut ret, self.raw, notes_ref, id.raw())); Ok(Binding::from_raw(ret)) } } /// Remove the note for an object. /// /// The `notes_ref` argument is the canonical name of the reference to use, /// defaulting to "refs/notes/commits". /// /// The id specified is the Oid of the git object to remove the note from. pub fn note_delete( &self, id: Oid, notes_ref: Option<&str>, author: &Signature<'_>, committer: &Signature<'_>, ) -> Result<(), Error> { let notes_ref = crate::opt_cstr(notes_ref)?; unsafe { try_call!(raw::git_note_remove( self.raw, notes_ref, author.raw(), committer.raw(), id.raw() )); Ok(()) } } /// Create a revwalk that can be used to traverse the commit graph. pub fn revwalk(&self) -> Result, Error> { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_revwalk_new(&mut raw, self.raw())); Ok(Binding::from_raw(raw)) } } /// Get the blame for a single file. pub fn blame_file( &self, path: &Path, opts: Option<&mut BlameOptions>, ) -> Result, Error> { let path = path_to_repo_path(path)?; let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_blame_file( &mut raw, self.raw(), path, opts.map(|s| s.raw()) )); Ok(Binding::from_raw(raw)) } } /// Find a merge base between two commits pub fn merge_base(&self, one: Oid, two: Oid) -> Result { let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_merge_base( &mut raw, self.raw, one.raw(), two.raw() )); Ok(Binding::from_raw(&raw as *const _)) } } /// Find a merge base given a list of commits pub fn merge_base_many(&self, oids: &[Oid]) -> Result { let mut raw = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; unsafe { try_call!(raw::git_merge_base_many( &mut raw, self.raw, oids.len() as size_t, oids.as_ptr() as *const raw::git_oid )); Ok(Binding::from_raw(&raw as *const _)) } } /// Find all merge bases between two commits pub fn merge_bases(&self, one: Oid, two: Oid) -> Result { let mut arr = raw::git_oidarray { ids: ptr::null_mut(), count: 0, }; unsafe { try_call!(raw::git_merge_bases( &mut arr, self.raw, one.raw(), two.raw() )); Ok(Binding::from_raw(arr)) } } /// Find all merge bases given a list of commits pub fn merge_bases_many(&self, oids: &[Oid]) -> Result { let mut arr = raw::git_oidarray { ids: ptr::null_mut(), count: 0, }; unsafe { try_call!(raw::git_merge_bases_many( &mut arr, self.raw, oids.len() as size_t, oids.as_ptr() as *const raw::git_oid )); Ok(Binding::from_raw(arr)) } } /// Count the number of unique commits between two commit objects /// /// There is no need for branches containing the commits to have any /// upstream relationship, but it helps to think of one as a branch and the /// other as its upstream, the ahead and behind values will be what git /// would report for the branches. pub fn graph_ahead_behind(&self, local: Oid, upstream: Oid) -> Result<(usize, usize), Error> { unsafe { let mut ahead: size_t = 0; let mut behind: size_t = 0; try_call!(raw::git_graph_ahead_behind( &mut ahead, &mut behind, self.raw(), local.raw(), upstream.raw() )); Ok((ahead as usize, behind as usize)) } } /// Determine if a commit is the descendant of another commit /// /// Note that a commit is not considered a descendant of itself, in contrast /// to `git merge-base --is-ancestor`. pub fn graph_descendant_of(&self, commit: Oid, ancestor: Oid) -> Result { unsafe { let rv = try_call!(raw::git_graph_descendant_of( self.raw(), commit.raw(), ancestor.raw() )); Ok(rv != 0) } } /// Read the reflog for the given reference /// /// If there is no reflog file for the given reference yet, an empty reflog /// object will be returned. pub fn reflog(&self, name: &str) -> Result { let name = CString::new(name)?; let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_reflog_read(&mut ret, self.raw, name)); Ok(Binding::from_raw(ret)) } } /// Delete the reflog for the given reference pub fn reflog_delete(&self, name: &str) -> Result<(), Error> { let name = CString::new(name)?; unsafe { try_call!(raw::git_reflog_delete(self.raw, name)); } Ok(()) } /// Rename a reflog /// /// The reflog to be renamed is expected to already exist. pub fn reflog_rename(&self, old_name: &str, new_name: &str) -> Result<(), Error> { let old_name = CString::new(old_name)?; let new_name = CString::new(new_name)?; unsafe { try_call!(raw::git_reflog_rename(self.raw, old_name, new_name)); } Ok(()) } /// Check if the given reference has a reflog. pub fn reference_has_log(&self, name: &str) -> Result { let name = CString::new(name)?; let ret = unsafe { try_call!(raw::git_reference_has_log(self.raw, name)) }; Ok(ret != 0) } /// Ensure that the given reference has a reflog. pub fn reference_ensure_log(&self, name: &str) -> Result<(), Error> { let name = CString::new(name)?; unsafe { try_call!(raw::git_reference_ensure_log(self.raw, name)); } Ok(()) } /// Describes a commit /// /// Performs a describe operation on the current commit and the worktree. /// After performing a describe on HEAD, a status is run and description is /// considered to be dirty if there are. pub fn describe(&self, opts: &DescribeOptions) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_describe_workdir(&mut ret, self.raw, opts.raw())); Ok(Binding::from_raw(ret)) } } /// Directly run a diff on two blobs. /// /// Compared to a file, a blob lacks some contextual information. As such, the /// `DiffFile` given to the callback will have some fake data; i.e. mode will be /// 0 and path will be `None`. /// /// `None` is allowed for either `old_blob` or `new_blob` and will be treated /// as an empty blob, with the oid set to zero in the `DiffFile`. Passing `None` /// for both blobs is a noop; no callbacks will be made at all. /// /// We do run a binary content check on the blob content and if either blob looks /// like binary data, the `DiffFile` binary attribute will be set to 1 and no call to /// the `hunk_cb` nor `line_cb` will be made (unless you set the `force_text` /// option). pub fn diff_blobs( &self, old_blob: Option<&Blob<'_>>, old_as_path: Option<&str>, new_blob: Option<&Blob<'_>>, new_as_path: Option<&str>, opts: Option<&mut DiffOptions>, file_cb: Option<&mut FileCb<'_>>, binary_cb: Option<&mut BinaryCb<'_>>, hunk_cb: Option<&mut HunkCb<'_>>, line_cb: Option<&mut LineCb<'_>>, ) -> Result<(), Error> { let old_as_path = crate::opt_cstr(old_as_path)?; let new_as_path = crate::opt_cstr(new_as_path)?; let mut cbs = DiffCallbacks { file: file_cb, binary: binary_cb, hunk: hunk_cb, line: line_cb, }; let ptr = &mut cbs as *mut _; unsafe { let file_cb_c: raw::git_diff_file_cb = if cbs.file.is_some() { Some(file_cb_c) } else { None }; let binary_cb_c: raw::git_diff_binary_cb = if cbs.binary.is_some() { Some(binary_cb_c) } else { None }; let hunk_cb_c: raw::git_diff_hunk_cb = if cbs.hunk.is_some() { Some(hunk_cb_c) } else { None }; let line_cb_c: raw::git_diff_line_cb = if cbs.line.is_some() { Some(line_cb_c) } else { None }; try_call!(raw::git_diff_blobs( old_blob.map(|s| s.raw()), old_as_path, new_blob.map(|s| s.raw()), new_as_path, opts.map(|s| s.raw()), file_cb_c, binary_cb_c, hunk_cb_c, line_cb_c, ptr as *mut _ )); Ok(()) } } /// Create a diff with the difference between two tree objects. /// /// This is equivalent to `git diff ` /// /// The first tree will be used for the "old_file" side of the delta and the /// second tree will be used for the "new_file" side of the delta. You can /// pass `None` to indicate an empty tree, although it is an error to pass /// `None` for both the `old_tree` and `new_tree`. pub fn diff_tree_to_tree( &self, old_tree: Option<&Tree<'_>>, new_tree: Option<&Tree<'_>>, opts: Option<&mut DiffOptions>, ) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_diff_tree_to_tree( &mut ret, self.raw(), old_tree.map(|s| s.raw()), new_tree.map(|s| s.raw()), opts.map(|s| s.raw()) )); Ok(Binding::from_raw(ret)) } } /// Create a diff between a tree and repository index. /// /// This is equivalent to `git diff --cached ` or if you pass /// the HEAD tree, then like `git diff --cached`. /// /// The tree you pass will be used for the "old_file" side of the delta, and /// the index will be used for the "new_file" side of the delta. /// /// If you pass `None` for the index, then the existing index of the `repo` /// will be used. In this case, the index will be refreshed from disk /// (if it has changed) before the diff is generated. /// /// If the tree is `None`, then it is considered an empty tree. pub fn diff_tree_to_index( &self, old_tree: Option<&Tree<'_>>, index: Option<&Index>, opts: Option<&mut DiffOptions>, ) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_diff_tree_to_index( &mut ret, self.raw(), old_tree.map(|s| s.raw()), index.map(|s| s.raw()), opts.map(|s| s.raw()) )); Ok(Binding::from_raw(ret)) } } /// Create a diff between two index objects. /// /// The first index will be used for the "old_file" side of the delta, and /// the second index will be used for the "new_file" side of the delta. pub fn diff_index_to_index( &self, old_index: &Index, new_index: &Index, opts: Option<&mut DiffOptions>, ) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_diff_index_to_index( &mut ret, self.raw(), old_index.raw(), new_index.raw(), opts.map(|s| s.raw()) )); Ok(Binding::from_raw(ret)) } } /// Create a diff between the repository index and the workdir directory. /// /// This matches the `git diff` command. See the note below on /// `tree_to_workdir` for a discussion of the difference between /// `git diff` and `git diff HEAD` and how to emulate a `git diff ` /// using libgit2. /// /// The index will be used for the "old_file" side of the delta, and the /// working directory will be used for the "new_file" side of the delta. /// /// If you pass `None` for the index, then the existing index of the `repo` /// will be used. In this case, the index will be refreshed from disk /// (if it has changed) before the diff is generated. pub fn diff_index_to_workdir( &self, index: Option<&Index>, opts: Option<&mut DiffOptions>, ) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_diff_index_to_workdir( &mut ret, self.raw(), index.map(|s| s.raw()), opts.map(|s| s.raw()) )); Ok(Binding::from_raw(ret)) } } /// Create a diff between a tree and the working directory. /// /// The tree you provide will be used for the "old_file" side of the delta, /// and the working directory will be used for the "new_file" side. /// /// This is not the same as `git diff ` or `git diff-index /// `. Those commands use information from the index, whereas this /// function strictly returns the differences between the tree and the files /// in the working directory, regardless of the state of the index. Use /// `tree_to_workdir_with_index` to emulate those commands. /// /// To see difference between this and `tree_to_workdir_with_index`, /// consider the example of a staged file deletion where the file has then /// been put back into the working dir and further modified. The /// tree-to-workdir diff for that file is 'modified', but `git diff` would /// show status 'deleted' since there is a staged delete. /// /// If `None` is passed for `tree`, then an empty tree is used. pub fn diff_tree_to_workdir( &self, old_tree: Option<&Tree<'_>>, opts: Option<&mut DiffOptions>, ) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_diff_tree_to_workdir( &mut ret, self.raw(), old_tree.map(|s| s.raw()), opts.map(|s| s.raw()) )); Ok(Binding::from_raw(ret)) } } /// Create a diff between a tree and the working directory using index data /// to account for staged deletes, tracked files, etc. /// /// This emulates `git diff ` by diffing the tree to the index and /// the index to the working directory and blending the results into a /// single diff that includes staged deleted, etc. pub fn diff_tree_to_workdir_with_index( &self, old_tree: Option<&Tree<'_>>, opts: Option<&mut DiffOptions>, ) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_diff_tree_to_workdir_with_index( &mut ret, self.raw(), old_tree.map(|s| s.raw()), opts.map(|s| s.raw()) )); Ok(Binding::from_raw(ret)) } } /// Create a PackBuilder pub fn packbuilder(&self) -> Result, Error> { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_packbuilder_new(&mut ret, self.raw())); Ok(Binding::from_raw(ret)) } } /// Save the local modifications to a new stash. pub fn stash_save( &mut self, stasher: &Signature<'_>, message: &str, flags: Option, ) -> Result { self.stash_save2(stasher, Some(message), flags) } /// Save the local modifications to a new stash. /// unlike `stash_save` it allows to pass a null `message` pub fn stash_save2( &mut self, stasher: &Signature<'_>, message: Option<&str>, flags: Option, ) -> Result { unsafe { let mut raw_oid = raw::git_oid { id: [0; raw::GIT_OID_RAWSZ], }; let message = crate::opt_cstr(message)?; let flags = flags.unwrap_or_else(StashFlags::empty); try_call!(raw::git_stash_save( &mut raw_oid, self.raw(), stasher.raw(), message, flags.bits() as c_uint )); Ok(Binding::from_raw(&raw_oid as *const _)) } } /// Apply a single stashed state from the stash list. pub fn stash_apply( &mut self, index: usize, opts: Option<&mut StashApplyOptions<'_>>, ) -> Result<(), Error> { unsafe { let opts = opts.map(|opts| opts.raw()); try_call!(raw::git_stash_apply(self.raw(), index, opts)); Ok(()) } } /// Loop over all the stashed states and issue a callback for each one. /// /// Return `true` to continue iterating or `false` to stop. pub fn stash_foreach(&mut self, mut callback: C) -> Result<(), Error> where C: FnMut(usize, &str, &Oid) -> bool, { unsafe { let mut data = StashCbData { callback: &mut callback, }; let cb: raw::git_stash_cb = Some(stash_cb); try_call!(raw::git_stash_foreach( self.raw(), cb, &mut data as *mut _ as *mut _ )); Ok(()) } } /// Remove a single stashed state from the stash list. pub fn stash_drop(&mut self, index: usize) -> Result<(), Error> { unsafe { try_call!(raw::git_stash_drop(self.raw(), index)); Ok(()) } } /// Apply a single stashed state from the stash list and remove it from the list if successful. pub fn stash_pop( &mut self, index: usize, opts: Option<&mut StashApplyOptions<'_>>, ) -> Result<(), Error> { unsafe { let opts = opts.map(|opts| opts.raw()); try_call!(raw::git_stash_pop(self.raw(), index, opts)); Ok(()) } } /// Add ignore rules for a repository. /// /// The format of the rules is the same one of the .gitignore file. pub fn add_ignore_rule(&self, rules: &str) -> Result<(), Error> { let rules = CString::new(rules)?; unsafe { try_call!(raw::git_ignore_add_rule(self.raw, rules)); } Ok(()) } /// Clear ignore rules that were explicitly added. pub fn clear_ignore_rules(&self) -> Result<(), Error> { unsafe { try_call!(raw::git_ignore_clear_internal_rules(self.raw)); } Ok(()) } /// Test if the ignore rules apply to a given path. pub fn is_path_ignored>(&self, path: P) -> Result { let path = util::cstring_to_repo_path(path.as_ref())?; let mut ignored: c_int = 0; unsafe { try_call!(raw::git_ignore_path_is_ignored( &mut ignored, self.raw, path )); } Ok(ignored == 1) } /// Perform a cherrypick pub fn cherrypick( &self, commit: &Commit<'_>, options: Option<&mut CherrypickOptions<'_>>, ) -> Result<(), Error> { let raw_opts = options.map(|o| o.raw()); let ptr_raw_opts = match raw_opts.as_ref() { Some(v) => v, None => std::ptr::null(), }; unsafe { try_call!(raw::git_cherrypick(self.raw(), commit.raw(), ptr_raw_opts)); Ok(()) } } /// Create an index of uncommitted changes, representing the result of /// cherry-picking. pub fn cherrypick_commit( &self, cherrypick_commit: &Commit<'_>, our_commit: &Commit<'_>, mainline: u32, options: Option<&MergeOptions>, ) -> Result { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_cherrypick_commit( &mut ret, self.raw(), cherrypick_commit.raw(), our_commit.raw(), mainline, options.map(|o| o.raw()) )); Ok(Binding::from_raw(ret)) } } /// Find the remote name of a remote-tracking branch pub fn branch_remote_name(&self, refname: &str) -> Result { let refname = CString::new(refname)?; unsafe { let buf = Buf::new(); try_call!(raw::git_branch_remote_name(buf.raw(), self.raw, refname)); Ok(buf) } } /// Retrieves the name of the reference supporting the remote tracking branch, /// given the name of a local branch reference. pub fn branch_upstream_name(&self, refname: &str) -> Result { let refname = CString::new(refname)?; unsafe { let buf = Buf::new(); try_call!(raw::git_branch_upstream_name(buf.raw(), self.raw, refname)); Ok(buf) } } /// Retrieve the name of the upstream remote of a local branch. pub fn branch_upstream_remote(&self, refname: &str) -> Result { let refname = CString::new(refname)?; unsafe { let buf = Buf::new(); try_call!(raw::git_branch_upstream_remote( buf.raw(), self.raw, refname )); Ok(buf) } } /// Apply a Diff to the given repo, making changes directly in the working directory, the index, or both. pub fn apply( &self, diff: &Diff<'_>, location: ApplyLocation, options: Option<&mut ApplyOptions<'_>>, ) -> Result<(), Error> { unsafe { try_call!(raw::git_apply( self.raw, diff.raw(), location.raw(), options.map(|s| s.raw()).unwrap_or(ptr::null()) )); Ok(()) } } /// Apply a Diff to the provided tree, and return the resulting Index. pub fn apply_to_tree( &self, tree: &Tree<'_>, diff: &Diff<'_>, options: Option<&mut ApplyOptions<'_>>, ) -> Result { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_apply_to_tree( &mut ret, self.raw, tree.raw(), diff.raw(), options.map(|s| s.raw()).unwrap_or(ptr::null()) )); Ok(Binding::from_raw(ret)) } } /// Reverts the given commit, producing changes in the index and working directory. pub fn revert( &self, commit: &Commit<'_>, options: Option<&mut RevertOptions<'_>>, ) -> Result<(), Error> { let raw_opts = options.map(|o| o.raw()); let ptr_raw_opts = match raw_opts.as_ref() { Some(v) => v, None => 0 as *const _, }; unsafe { try_call!(raw::git_revert(self.raw(), commit.raw(), ptr_raw_opts)); Ok(()) } } /// Reverts the given commit against the given "our" commit, /// producing an index that reflects the result of the revert. pub fn revert_commit( &self, revert_commit: &Commit<'_>, our_commit: &Commit<'_>, mainline: u32, options: Option<&MergeOptions>, ) -> Result { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_revert_commit( &mut ret, self.raw(), revert_commit.raw(), our_commit.raw(), mainline, options.map(|o| o.raw()) )); Ok(Binding::from_raw(ret)) } } /// Lists all the worktrees for the repository pub fn worktrees(&self) -> Result { let mut arr = raw::git_strarray { strings: ptr::null_mut(), count: 0, }; unsafe { try_call!(raw::git_worktree_list(&mut arr, self.raw)); Ok(Binding::from_raw(arr)) } } /// Opens a worktree by name for the given repository /// /// This can open any worktree that the worktrees method returns. pub fn find_worktree(&self, name: &str) -> Result { let mut raw = ptr::null_mut(); let raw_name = CString::new(name)?; unsafe { try_call!(raw::git_worktree_lookup(&mut raw, self.raw, raw_name)); Ok(Binding::from_raw(raw)) } } /// Creates a new worktree for the repository pub fn worktree<'a>( &'a self, name: &str, path: &Path, opts: Option<&WorktreeAddOptions<'a>>, ) -> Result { let mut raw = ptr::null_mut(); let raw_name = CString::new(name)?; let raw_path = path.into_c_string()?; unsafe { try_call!(raw::git_worktree_add( &mut raw, self.raw, raw_name, raw_path, opts.map(|o| o.raw()) )); Ok(Binding::from_raw(raw)) } } /// Create a new transaction pub fn transaction<'a>(&'a self) -> Result, Error> { let mut raw = ptr::null_mut(); unsafe { try_call!(raw::git_transaction_new(&mut raw, self.raw)); Ok(Binding::from_raw(raw)) } } /// Gets this repository's mailmap. pub fn mailmap(&self) -> Result { let mut ret = ptr::null_mut(); unsafe { try_call!(raw::git_mailmap_from_repository(&mut ret, self.raw)); Ok(Binding::from_raw(ret)) } } /// If a merge is in progress, invoke 'callback' for each commit ID in the /// MERGE_HEAD file. pub fn mergehead_foreach(&mut self, mut callback: C) -> Result<(), Error> where C: FnMut(&Oid) -> bool, { unsafe { let mut data = MergeheadForeachCbData { callback: &mut callback, }; let cb: raw::git_repository_mergehead_foreach_cb = Some(mergehead_foreach_cb); try_call!(raw::git_repository_mergehead_foreach( self.raw(), cb, &mut data as *mut _ as *mut _ )); Ok(()) } } /// Invoke 'callback' for each entry in the given FETCH_HEAD file. /// /// `callback` will be called with with following arguments: /// /// - `&str`: the reference name /// - `&[u8]`: the remote URL /// - `&Oid`: the reference target OID /// - `bool`: was the reference the result of a merge pub fn fetchhead_foreach(&self, mut callback: C) -> Result<(), Error> where C: FnMut(&str, &[u8], &Oid, bool) -> bool, { unsafe { let mut data = FetchheadForeachCbData { callback: &mut callback, }; let cb: raw::git_repository_fetchhead_foreach_cb = Some(fetchhead_foreach_cb); try_call!(raw::git_repository_fetchhead_foreach( self.raw(), cb, &mut data as *mut _ as *mut _ )); Ok(()) } } } impl Binding for Repository { type Raw = *mut raw::git_repository; unsafe fn from_raw(ptr: *mut raw::git_repository) -> Repository { Repository { raw: ptr } } fn raw(&self) -> *mut raw::git_repository { self.raw } } impl Drop for Repository { fn drop(&mut self) { unsafe { raw::git_repository_free(self.raw) } } } impl RepositoryInitOptions { /// Creates a default set of initialization options. /// /// By default this will set flags for creating all necessary directories /// and initializing a directory from the user-configured templates path. pub fn new() -> RepositoryInitOptions { RepositoryInitOptions { flags: raw::GIT_REPOSITORY_INIT_MKDIR as u32 | raw::GIT_REPOSITORY_INIT_MKPATH as u32 | raw::GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE as u32, mode: 0, workdir_path: None, description: None, template_path: None, initial_head: None, origin_url: None, } } /// Create a bare repository with no working directory. /// /// Defaults to false. pub fn bare(&mut self, bare: bool) -> &mut RepositoryInitOptions { self.flag(raw::GIT_REPOSITORY_INIT_BARE, bare) } /// Return an error if the repository path appears to already be a git /// repository. /// /// Defaults to false. pub fn no_reinit(&mut self, enabled: bool) -> &mut RepositoryInitOptions { self.flag(raw::GIT_REPOSITORY_INIT_NO_REINIT, enabled) } /// Normally a '/.git/' will be appended to the repo path for non-bare repos /// (if it is not already there), but passing this flag prevents that /// behavior. /// /// Defaults to false. pub fn no_dotgit_dir(&mut self, enabled: bool) -> &mut RepositoryInitOptions { self.flag(raw::GIT_REPOSITORY_INIT_NO_DOTGIT_DIR, enabled) } /// Make the repo path (and workdir path) as needed. The ".git" directory /// will always be created regardless of this flag. /// /// Defaults to true. pub fn mkdir(&mut self, enabled: bool) -> &mut RepositoryInitOptions { self.flag(raw::GIT_REPOSITORY_INIT_MKDIR, enabled) } /// Recursively make all components of the repo and workdir path as /// necessary. /// /// Defaults to true. pub fn mkpath(&mut self, enabled: bool) -> &mut RepositoryInitOptions { self.flag(raw::GIT_REPOSITORY_INIT_MKPATH, enabled) } /// Set to one of the `RepositoryInit` constants, or a custom value. pub fn mode(&mut self, mode: RepositoryInitMode) -> &mut RepositoryInitOptions { self.mode = mode.bits(); self } /// Enable or disable using external templates. /// /// If enabled, then the `template_path` option will be queried first, then /// `init.templatedir` from the global config, and finally /// `/usr/share/git-core-templates` will be used (if it exists). /// /// Defaults to true. pub fn external_template(&mut self, enabled: bool) -> &mut RepositoryInitOptions { self.flag(raw::GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE, enabled) } fn flag( &mut self, flag: raw::git_repository_init_flag_t, on: bool, ) -> &mut RepositoryInitOptions { if on { self.flags |= flag as u32; } else { self.flags &= !(flag as u32); } self } /// The path to the working directory. /// /// If this is a relative path it will be evaluated relative to the repo /// path. If this is not the "natural" working directory, a .git gitlink /// file will be created here linking to the repo path. pub fn workdir_path(&mut self, path: &Path) -> &mut RepositoryInitOptions { // Normal file path OK (does not need Windows conversion). self.workdir_path = Some(path.into_c_string().unwrap()); self } /// If set, this will be used to initialize the "description" file in the /// repository instead of using the template content. pub fn description(&mut self, desc: &str) -> &mut RepositoryInitOptions { self.description = Some(CString::new(desc).unwrap()); self } /// When the `external_template` option is set, this is the first location /// to check for the template directory. /// /// If this is not configured, then the default locations will be searched /// instead. pub fn template_path(&mut self, path: &Path) -> &mut RepositoryInitOptions { // Normal file path OK (does not need Windows conversion). self.template_path = Some(path.into_c_string().unwrap()); self } /// The name of the head to point HEAD at. /// /// If not configured, this will be taken from your git configuration. /// If this begins with `refs/` it will be used verbatim; /// otherwise `refs/heads/` will be prefixed pub fn initial_head(&mut self, head: &str) -> &mut RepositoryInitOptions { self.initial_head = Some(CString::new(head).unwrap()); self } /// If set, then after the rest of the repository initialization is /// completed an `origin` remote will be added pointing to this URL. pub fn origin_url(&mut self, url: &str) -> &mut RepositoryInitOptions { self.origin_url = Some(CString::new(url).unwrap()); self } /// Creates a set of raw init options to be used with /// `git_repository_init_ext`. /// /// This method is unsafe as the returned value may have pointers to the /// interior of this structure. pub unsafe fn raw(&self) -> raw::git_repository_init_options { let mut opts = mem::zeroed(); assert_eq!( raw::git_repository_init_init_options( &mut opts, raw::GIT_REPOSITORY_INIT_OPTIONS_VERSION ), 0 ); opts.flags = self.flags; opts.mode = self.mode; opts.workdir_path = crate::call::convert(&self.workdir_path); opts.description = crate::call::convert(&self.description); opts.template_path = crate::call::convert(&self.template_path); opts.initial_head = crate::call::convert(&self.initial_head); opts.origin_url = crate::call::convert(&self.origin_url); opts } } #[cfg(test)] mod tests { use crate::build::CheckoutBuilder; use crate::CherrypickOptions; use crate::{ ObjectType, Oid, Repository, ResetType, Signature, SubmoduleIgnore, SubmoduleUpdate, }; use std::ffi::OsStr; use std::fs; use std::path::Path; use tempfile::TempDir; #[test] fn smoke_init() { let td = TempDir::new().unwrap(); let path = td.path(); let repo = Repository::init(path).unwrap(); assert!(!repo.is_bare()); } #[test] fn smoke_init_bare() { let td = TempDir::new().unwrap(); let path = td.path(); let repo = Repository::init_bare(path).unwrap(); assert!(repo.is_bare()); assert!(repo.namespace().is_none()); } #[test] fn smoke_open() { let td = TempDir::new().unwrap(); let path = td.path(); Repository::init(td.path()).unwrap(); let repo = Repository::open(path).unwrap(); assert!(!repo.is_bare()); assert!(!repo.is_shallow()); assert!(repo.is_empty().unwrap()); assert_eq!( crate::test::realpath(&repo.path()).unwrap(), crate::test::realpath(&td.path().join(".git/")).unwrap() ); assert_eq!(repo.state(), crate::RepositoryState::Clean); } #[test] fn smoke_open_bare() { let td = TempDir::new().unwrap(); let path = td.path(); Repository::init_bare(td.path()).unwrap(); let repo = Repository::open(path).unwrap(); assert!(repo.is_bare()); assert_eq!( crate::test::realpath(&repo.path()).unwrap(), crate::test::realpath(&td.path().join("")).unwrap() ); } #[test] fn smoke_checkout() { let (_td, repo) = crate::test::repo_init(); repo.checkout_head(None).unwrap(); } #[test] fn smoke_revparse() { let (_td, repo) = crate::test::repo_init(); let rev = repo.revparse("HEAD").unwrap(); assert!(rev.to().is_none()); let from = rev.from().unwrap(); assert!(rev.from().is_some()); assert_eq!(repo.revparse_single("HEAD").unwrap().id(), from.id()); let obj = repo.find_object(from.id(), None).unwrap().clone(); obj.peel(ObjectType::Any).unwrap(); obj.short_id().unwrap(); repo.reset(&obj, ResetType::Hard, None).unwrap(); let mut opts = CheckoutBuilder::new(); t!(repo.reset(&obj, ResetType::Soft, Some(&mut opts))); } #[test] fn makes_dirs() { let td = TempDir::new().unwrap(); Repository::init(&td.path().join("a/b/c/d")).unwrap(); } #[test] fn smoke_discover() { let td = TempDir::new().unwrap(); let subdir = td.path().join("subdi"); fs::create_dir(&subdir).unwrap(); Repository::init_bare(td.path()).unwrap(); let repo = Repository::discover(&subdir).unwrap(); assert_eq!( crate::test::realpath(&repo.path()).unwrap(), crate::test::realpath(&td.path().join("")).unwrap() ); } #[test] fn smoke_discover_path() { let td = TempDir::new().unwrap(); let subdir = td.path().join("subdi"); fs::create_dir(&subdir).unwrap(); Repository::init_bare(td.path()).unwrap(); let path = Repository::discover_path(&subdir, &[] as &[&OsStr]).unwrap(); assert_eq!( crate::test::realpath(&path).unwrap(), crate::test::realpath(&td.path().join("")).unwrap() ); } #[test] fn smoke_discover_path_ceiling_dir() { let td = TempDir::new().unwrap(); let subdir = td.path().join("subdi"); fs::create_dir(&subdir).unwrap(); let ceilingdir = subdir.join("ceiling"); fs::create_dir(&ceilingdir).unwrap(); let testdir = ceilingdir.join("testdi"); fs::create_dir(&testdir).unwrap(); Repository::init_bare(td.path()).unwrap(); let path = Repository::discover_path(&testdir, &[ceilingdir.as_os_str()]); assert!(path.is_err()); } #[test] fn smoke_open_ext() { let td = TempDir::new().unwrap(); let subdir = td.path().join("subdir"); fs::create_dir(&subdir).unwrap(); Repository::init(td.path()).unwrap(); let repo = Repository::open_ext( &subdir, crate::RepositoryOpenFlags::empty(), &[] as &[&OsStr], ) .unwrap(); assert!(!repo.is_bare()); assert_eq!( crate::test::realpath(&repo.path()).unwrap(), crate::test::realpath(&td.path().join(".git")).unwrap() ); let repo = Repository::open_ext(&subdir, crate::RepositoryOpenFlags::BARE, &[] as &[&OsStr]) .unwrap(); assert!(repo.is_bare()); assert_eq!( crate::test::realpath(&repo.path()).unwrap(), crate::test::realpath(&td.path().join(".git")).unwrap() ); let err = Repository::open_ext( &subdir, crate::RepositoryOpenFlags::NO_SEARCH, &[] as &[&OsStr], ) .err() .unwrap(); assert_eq!(err.code(), crate::ErrorCode::NotFound); assert!( Repository::open_ext(&subdir, crate::RepositoryOpenFlags::empty(), &[&subdir]).is_ok() ); } fn graph_repo_init() -> (TempDir, Repository) { let (_td, repo) = crate::test::repo_init(); { let head = repo.head().unwrap().target().unwrap(); let head = repo.find_commit(head).unwrap(); let mut index = repo.index().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, "second", &tree, &[&head]) .unwrap(); } (_td, repo) } #[test] fn smoke_graph_ahead_behind() { let (_td, repo) = graph_repo_init(); let head = repo.head().unwrap().target().unwrap(); let head = repo.find_commit(head).unwrap(); let head_id = head.id(); let head_parent_id = head.parent(0).unwrap().id(); let (ahead, behind) = repo.graph_ahead_behind(head_id, head_parent_id).unwrap(); assert_eq!(ahead, 1); assert_eq!(behind, 0); let (ahead, behind) = repo.graph_ahead_behind(head_parent_id, head_id).unwrap(); assert_eq!(ahead, 0); assert_eq!(behind, 1); } #[test] fn smoke_graph_descendant_of() { let (_td, repo) = graph_repo_init(); let head = repo.head().unwrap().target().unwrap(); let head = repo.find_commit(head).unwrap(); let head_id = head.id(); let head_parent_id = head.parent(0).unwrap().id(); assert!(repo.graph_descendant_of(head_id, head_parent_id).unwrap()); assert!(!repo.graph_descendant_of(head_parent_id, head_id).unwrap()); } #[test] fn smoke_reference_has_log_ensure_log() { let (_td, repo) = crate::test::repo_init(); assert_eq!(repo.reference_has_log("HEAD").unwrap(), true); assert_eq!(repo.reference_has_log("refs/heads/main").unwrap(), true); assert_eq!(repo.reference_has_log("NOT_HEAD").unwrap(), false); let main_oid = repo.revparse_single("main").unwrap().id(); assert!(repo .reference("NOT_HEAD", main_oid, false, "creating a new branch") .is_ok()); assert_eq!(repo.reference_has_log("NOT_HEAD").unwrap(), false); assert!(repo.reference_ensure_log("NOT_HEAD").is_ok()); assert_eq!(repo.reference_has_log("NOT_HEAD").unwrap(), true); } #[test] fn smoke_set_head() { let (_td, repo) = crate::test::repo_init(); assert!(repo.set_head("refs/heads/does-not-exist").is_ok()); assert!(repo.head().is_err()); assert!(repo.set_head("refs/heads/main").is_ok()); assert!(repo.head().is_ok()); assert!(repo.set_head("*").is_err()); } #[test] fn smoke_set_head_bytes() { let (_td, repo) = crate::test::repo_init(); assert!(repo.set_head_bytes(b"refs/heads/does-not-exist").is_ok()); assert!(repo.head().is_err()); assert!(repo.set_head_bytes(b"refs/heads/main").is_ok()); assert!(repo.head().is_ok()); assert!(repo.set_head_bytes(b"*").is_err()); } #[test] fn smoke_set_head_detached() { let (_td, repo) = crate::test::repo_init(); let void_oid = Oid::from_bytes(b"00000000000000000000").unwrap(); assert!(repo.set_head_detached(void_oid).is_err()); let main_oid = repo.revparse_single("main").unwrap().id(); assert!(repo.set_head_detached(main_oid).is_ok()); assert_eq!(repo.head().unwrap().target().unwrap(), main_oid); } /// create the following: /// /---o4 /// /---o3 /// o1---o2 #[test] fn smoke_merge_base() { let (_td, repo) = graph_repo_init(); let sig = repo.signature().unwrap(); // let oid1 = head let oid1 = repo.head().unwrap().target().unwrap(); let commit1 = repo.find_commit(oid1).unwrap(); println!("created oid1 {:?}", oid1); repo.branch("branch_a", &commit1, true).unwrap(); repo.branch("branch_b", &commit1, true).unwrap(); repo.branch("branch_c", &commit1, true).unwrap(); // create commit oid2 on branch_a let mut index = repo.index().unwrap(); let p = Path::new(repo.workdir().unwrap()).join("file_a"); println!("using path {:?}", p); fs::File::create(&p).unwrap(); index.add_path(Path::new("file_a")).unwrap(); let id_a = index.write_tree().unwrap(); let tree_a = repo.find_tree(id_a).unwrap(); let oid2 = repo .commit( Some("refs/heads/branch_a"), &sig, &sig, "commit 2", &tree_a, &[&commit1], ) .unwrap(); repo.find_commit(oid2).unwrap(); println!("created oid2 {:?}", oid2); t!(repo.reset(commit1.as_object(), ResetType::Hard, None)); // create commit oid3 on branch_b let mut index = repo.index().unwrap(); let p = Path::new(repo.workdir().unwrap()).join("file_b"); fs::File::create(&p).unwrap(); index.add_path(Path::new("file_b")).unwrap(); let id_b = index.write_tree().unwrap(); let tree_b = repo.find_tree(id_b).unwrap(); let oid3 = repo .commit( Some("refs/heads/branch_b"), &sig, &sig, "commit 3", &tree_b, &[&commit1], ) .unwrap(); repo.find_commit(oid3).unwrap(); println!("created oid3 {:?}", oid3); t!(repo.reset(commit1.as_object(), ResetType::Hard, None)); // create commit oid4 on branch_c let mut index = repo.index().unwrap(); let p = Path::new(repo.workdir().unwrap()).join("file_c"); fs::File::create(&p).unwrap(); index.add_path(Path::new("file_c")).unwrap(); let id_c = index.write_tree().unwrap(); let tree_c = repo.find_tree(id_c).unwrap(); let oid4 = repo .commit( Some("refs/heads/branch_c"), &sig, &sig, "commit 3", &tree_c, &[&commit1], ) .unwrap(); repo.find_commit(oid4).unwrap(); println!("created oid4 {:?}", oid4); // the merge base of (oid2,oid3) should be oid1 let merge_base = repo.merge_base(oid2, oid3).unwrap(); assert_eq!(merge_base, oid1); // the merge base of (oid2,oid3,oid4) should be oid1 let merge_base = repo.merge_base_many(&[oid2, oid3, oid4]).unwrap(); assert_eq!(merge_base, oid1); } /// create an octopus: /// /---o2-o4 /// o1 X /// \---o3-o5 /// and checks that the merge bases of (o4,o5) are (o2,o3) #[test] fn smoke_merge_bases() { let (_td, repo) = graph_repo_init(); let sig = repo.signature().unwrap(); // let oid1 = head let oid1 = repo.head().unwrap().target().unwrap(); let commit1 = repo.find_commit(oid1).unwrap(); println!("created oid1 {:?}", oid1); repo.branch("branch_a", &commit1, true).unwrap(); repo.branch("branch_b", &commit1, true).unwrap(); // create commit oid2 on branchA let mut index = repo.index().unwrap(); let p = Path::new(repo.workdir().unwrap()).join("file_a"); println!("using path {:?}", p); fs::File::create(&p).unwrap(); index.add_path(Path::new("file_a")).unwrap(); let id_a = index.write_tree().unwrap(); let tree_a = repo.find_tree(id_a).unwrap(); let oid2 = repo .commit( Some("refs/heads/branch_a"), &sig, &sig, "commit 2", &tree_a, &[&commit1], ) .unwrap(); let commit2 = repo.find_commit(oid2).unwrap(); println!("created oid2 {:?}", oid2); t!(repo.reset(commit1.as_object(), ResetType::Hard, None)); // create commit oid3 on branchB let mut index = repo.index().unwrap(); let p = Path::new(repo.workdir().unwrap()).join("file_b"); fs::File::create(&p).unwrap(); index.add_path(Path::new("file_b")).unwrap(); let id_b = index.write_tree().unwrap(); let tree_b = repo.find_tree(id_b).unwrap(); let oid3 = repo .commit( Some("refs/heads/branch_b"), &sig, &sig, "commit 3", &tree_b, &[&commit1], ) .unwrap(); let commit3 = repo.find_commit(oid3).unwrap(); println!("created oid3 {:?}", oid3); // create merge commit oid4 on branchA with parents oid2 and oid3 //let mut index4 = repo.merge_commits(&commit2, &commit3, None).unwrap(); repo.set_head("refs/heads/branch_a").unwrap(); repo.checkout_head(None).unwrap(); let oid4 = repo .commit( Some("refs/heads/branch_a"), &sig, &sig, "commit 4", &tree_a, &[&commit2, &commit3], ) .unwrap(); //index4.write_tree_to(&repo).unwrap(); println!("created oid4 {:?}", oid4); // create merge commit oid5 on branchB with parents oid2 and oid3 //let mut index5 = repo.merge_commits(&commit3, &commit2, None).unwrap(); repo.set_head("refs/heads/branch_b").unwrap(); repo.checkout_head(None).unwrap(); let oid5 = repo .commit( Some("refs/heads/branch_b"), &sig, &sig, "commit 5", &tree_a, &[&commit3, &commit2], ) .unwrap(); //index5.write_tree_to(&repo).unwrap(); println!("created oid5 {:?}", oid5); // merge bases of (oid4,oid5) should be (oid2,oid3) let merge_bases = repo.merge_bases(oid4, oid5).unwrap(); let mut found_oid2 = false; let mut found_oid3 = false; for mg in merge_bases.iter() { println!("found merge base {:?}", mg); if mg == &oid2 { found_oid2 = true; } else if mg == &oid3 { found_oid3 = true; } else { assert!(false); } } assert!(found_oid2); assert!(found_oid3); assert_eq!(merge_bases.len(), 2); // merge bases of (oid4,oid5) should be (oid2,oid3) let merge_bases = repo.merge_bases_many(&[oid4, oid5]).unwrap(); let mut found_oid2 = false; let mut found_oid3 = false; for mg in merge_bases.iter() { println!("found merge base {:?}", mg); if mg == &oid2 { found_oid2 = true; } else if mg == &oid3 { found_oid3 = true; } else { assert!(false); } } assert!(found_oid2); assert!(found_oid3); assert_eq!(merge_bases.len(), 2); } #[test] fn smoke_revparse_ext() { let (_td, repo) = graph_repo_init(); { let short_refname = "main"; let expected_refname = "refs/heads/main"; let (obj, reference) = repo.revparse_ext(short_refname).unwrap(); let expected_obj = repo.revparse_single(expected_refname).unwrap(); assert_eq!(obj.id(), expected_obj.id()); assert_eq!(reference.unwrap().name().unwrap(), expected_refname); } { let missing_refname = "refs/heads/does-not-exist"; assert!(repo.revparse_ext(missing_refname).is_err()); } { let (_obj, reference) = repo.revparse_ext("HEAD^").unwrap(); assert!(reference.is_none()); } } #[test] fn smoke_is_path_ignored() { let (_td, repo) = graph_repo_init(); assert!(!repo.is_path_ignored(Path::new("foo")).unwrap()); let _ = repo.add_ignore_rule("/foo"); assert!(repo.is_path_ignored(Path::new("foo")).unwrap()); if cfg!(windows) { assert!(repo.is_path_ignored(Path::new("foo\\thing")).unwrap()); } let _ = repo.clear_ignore_rules(); assert!(!repo.is_path_ignored(Path::new("foo")).unwrap()); if cfg!(windows) { assert!(!repo.is_path_ignored(Path::new("foo\\thing")).unwrap()); } } #[test] fn smoke_cherrypick() { let (_td, repo) = crate::test::repo_init(); let sig = repo.signature().unwrap(); let oid1 = repo.head().unwrap().target().unwrap(); let commit1 = repo.find_commit(oid1).unwrap(); repo.branch("branch_a", &commit1, true).unwrap(); // Add 2 commits on top of the initial one in branch_a let mut index = repo.index().unwrap(); let p1 = Path::new(repo.workdir().unwrap()).join("file_c"); fs::File::create(&p1).unwrap(); index.add_path(Path::new("file_c")).unwrap(); let id = index.write_tree().unwrap(); let tree_c = repo.find_tree(id).unwrap(); let oid2 = repo .commit( Some("refs/heads/branch_a"), &sig, &sig, "commit 2", &tree_c, &[&commit1], ) .unwrap(); let commit2 = repo.find_commit(oid2).unwrap(); println!("created oid2 {:?}", oid2); assert!(p1.exists()); let mut index = repo.index().unwrap(); let p2 = Path::new(repo.workdir().unwrap()).join("file_d"); fs::File::create(&p2).unwrap(); index.add_path(Path::new("file_d")).unwrap(); let id = index.write_tree().unwrap(); let tree_d = repo.find_tree(id).unwrap(); let oid3 = repo .commit( Some("refs/heads/branch_a"), &sig, &sig, "commit 3", &tree_d, &[&commit2], ) .unwrap(); let commit3 = repo.find_commit(oid3).unwrap(); println!("created oid3 {:?}", oid3); assert!(p1.exists()); assert!(p2.exists()); // cherry-pick commit3 on top of commit1 in branch b repo.reset(commit1.as_object(), ResetType::Hard, None) .unwrap(); let mut cherrypick_opts = CherrypickOptions::new(); repo.cherrypick(&commit3, Some(&mut cherrypick_opts)) .unwrap(); let id = repo.index().unwrap().write_tree().unwrap(); let tree_d = repo.find_tree(id).unwrap(); let oid4 = repo .commit(Some("HEAD"), &sig, &sig, "commit 4", &tree_d, &[&commit1]) .unwrap(); let commit4 = repo.find_commit(oid4).unwrap(); // should have file from commit3, but not the file from commit2 assert_eq!(commit4.parent(0).unwrap().id(), commit1.id()); assert!(!p1.exists()); assert!(p2.exists()); } #[test] fn smoke_revert() { let (_td, repo) = crate::test::repo_init(); let foo_file = Path::new(repo.workdir().unwrap()).join("foo"); assert!(!foo_file.exists()); let (oid1, _id) = crate::test::commit(&repo); let commit1 = repo.find_commit(oid1).unwrap(); t!(repo.reset(commit1.as_object(), ResetType::Hard, None)); assert!(foo_file.exists()); repo.revert(&commit1, None).unwrap(); let id = repo.index().unwrap().write_tree().unwrap(); let tree2 = repo.find_tree(id).unwrap(); let sig = repo.signature().unwrap(); repo.commit(Some("HEAD"), &sig, &sig, "commit 1", &tree2, &[&commit1]) .unwrap(); // reverting once removes `foo` file assert!(!foo_file.exists()); let oid2 = repo.head().unwrap().target().unwrap(); let commit2 = repo.find_commit(oid2).unwrap(); repo.revert(&commit2, None).unwrap(); let id = repo.index().unwrap().write_tree().unwrap(); let tree3 = repo.find_tree(id).unwrap(); repo.commit(Some("HEAD"), &sig, &sig, "commit 2", &tree3, &[&commit2]) .unwrap(); // reverting twice restores `foo` file assert!(foo_file.exists()); } #[test] fn smoke_config_write_and_read() { let (td, repo) = crate::test::repo_init(); let mut config = repo.config().unwrap(); config.set_bool("commit.gpgsign", false).unwrap(); let c = fs::read_to_string(td.path().join(".git").join("config")).unwrap(); assert!(c.contains("[commit]")); assert!(c.contains("gpgsign = false")); let config = repo.config().unwrap(); assert!(!config.get_bool("commit.gpgsign").unwrap()); } #[test] fn smoke_merge_analysis_for_ref() -> Result<(), crate::Error> { let (_td, repo) = graph_repo_init(); // Set up this repo state: // * second (their-branch) // * initial (HEAD -> main) // // We expect that their-branch can be fast-forward merged into main. // git checkout --detach HEAD let head_commit = repo.head()?.peel_to_commit()?; repo.set_head_detached(head_commit.id())?; // git branch their-branch HEAD let their_branch = repo.branch("their-branch", &head_commit, false)?; // git branch -f main HEAD~ let mut parents_iter = head_commit.parents(); let parent = parents_iter.next().unwrap(); assert!(parents_iter.next().is_none()); let main = repo.branch("main", &parent, true)?; // git checkout main repo.set_head(main.get().name().expect("should be utf-8"))?; let (merge_analysis, _merge_preference) = repo.merge_analysis_for_ref( main.get(), &[&repo.reference_to_annotated_commit(their_branch.get())?], )?; assert!(merge_analysis.contains(crate::MergeAnalysis::ANALYSIS_FASTFORWARD)); Ok(()) } #[test] fn smoke_submodule_set() -> Result<(), crate::Error> { let (td1, _repo) = crate::test::repo_init(); let (td2, mut repo2) = crate::test::repo_init(); let url = crate::test::path2url(td1.path()); let name = "bar"; { let mut s = repo2.submodule(&url, Path::new(name), true)?; fs::remove_dir_all(td2.path().join("bar")).unwrap(); Repository::clone(&url, td2.path().join("bar"))?; s.add_to_index(false)?; s.add_finalize()?; } // update strategy repo2.submodule_set_update(name, SubmoduleUpdate::None)?; assert!(matches!( repo2.find_submodule(name)?.update_strategy(), SubmoduleUpdate::None )); repo2.submodule_set_update(name, SubmoduleUpdate::Rebase)?; assert!(matches!( repo2.find_submodule(name)?.update_strategy(), SubmoduleUpdate::Rebase )); // ignore rule repo2.submodule_set_ignore(name, SubmoduleIgnore::Untracked)?; assert!(matches!( repo2.find_submodule(name)?.ignore_rule(), SubmoduleIgnore::Untracked )); repo2.submodule_set_ignore(name, SubmoduleIgnore::Dirty)?; assert!(matches!( repo2.find_submodule(name)?.ignore_rule(), SubmoduleIgnore::Dirty )); // url repo2.submodule_set_url(name, "fake-url")?; assert_eq!(repo2.find_submodule(name)?.url(), Some("fake-url")); // branch repo2.submodule_set_branch(name, "fake-branch")?; assert_eq!(repo2.find_submodule(name)?.branch(), Some("fake-branch")); Ok(()) } #[test] fn smoke_mailmap_from_repository() { let (_td, repo) = crate::test::repo_init(); let commit = { let head = t!(repo.head()).target().unwrap(); t!(repo.find_commit(head)) }; // This is our baseline for HEAD. let author = commit.author(); let committer = commit.committer(); assert_eq!(author.name(), Some("name")); assert_eq!(author.email(), Some("email")); assert_eq!(committer.name(), Some("name")); assert_eq!(committer.email(), Some("email")); // There is no .mailmap file in the test repo so all signature identities are equal. let mailmap = t!(repo.mailmap()); let mailmapped_author = t!(commit.author_with_mailmap(&mailmap)); let mailmapped_committer = t!(commit.committer_with_mailmap(&mailmap)); assert_eq!(mailmapped_author.name(), author.name()); assert_eq!(mailmapped_author.email(), author.email()); assert_eq!(mailmapped_committer.name(), committer.name()); assert_eq!(mailmapped_committer.email(), committer.email()); let commit = { // - Add a .mailmap file to the repository. // - Commit with a signature identity different from the author's. // - Include entries for both author and committer to prove we call // the right raw functions. let mailmap_file = Path::new(".mailmap"); let p = Path::new(repo.workdir().unwrap()).join(&mailmap_file); t!(fs::write( p, r#" Author Name name Committer Name "#, )); let mut index = t!(repo.index()); t!(index.add_path(&mailmap_file)); let id_mailmap = t!(index.write_tree()); let tree_mailmap = t!(repo.find_tree(id_mailmap)); let head = t!(repo.commit( Some("HEAD"), &author, t!(&Signature::now("committer", "committer@email")), "Add mailmap", &tree_mailmap, &[&commit], )); t!(repo.find_commit(head)) }; // Sanity check that we're working with the right commit and that its // author and committer identities differ. let author = commit.author(); let committer = commit.committer(); assert_ne!(author.name(), committer.name()); assert_ne!(author.email(), committer.email()); assert_eq!(author.name(), Some("name")); assert_eq!(author.email(), Some("email")); assert_eq!(committer.name(), Some("committer")); assert_eq!(committer.email(), Some("committer@email")); // Fetch the newly added .mailmap from the repository. let mailmap = t!(repo.mailmap()); let mailmapped_author = t!(commit.author_with_mailmap(&mailmap)); let mailmapped_committer = t!(commit.committer_with_mailmap(&mailmap)); let mm_resolve_author = t!(mailmap.resolve_signature(&author)); let mm_resolve_committer = t!(mailmap.resolve_signature(&committer)); // Mailmap Signature lifetime is independent of Commit lifetime. drop(author); drop(committer); drop(commit); // author_with_mailmap() + committer_with_mailmap() work assert_eq!(mailmapped_author.name(), Some("Author Name")); assert_eq!(mailmapped_author.email(), Some("author.proper@email")); assert_eq!(mailmapped_committer.name(), Some("Committer Name")); assert_eq!(mailmapped_committer.email(), Some("committer.proper@email")); // resolve_signature() works assert_eq!(mm_resolve_author.email(), mailmapped_author.email()); assert_eq!(mm_resolve_committer.email(), mailmapped_committer.email()); } }