| 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; |
| 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<CString>, |
| description: Option<CString>, |
| template_path: Option<CString>, |
| initial_head: Option<CString>, |
| origin_url: Option<CString>, |
| } |
| |
| 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<P: AsRef<Path>>(path: P) -> Result<Repository, Error> { |
| 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<P: AsRef<Path>>(path: P) -> Result<Repository, Error> { |
| 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 |
| /// `REPOSITORY_OPEN_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<Repository, Error> { |
| 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 REPOSITORY_OPEN_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 REPOSITORY_OPEN_CROSS_FS, the search through parent |
| /// directories will not cross a filesystem boundary (detected when the |
| /// stat st_dev field changes). |
| /// |
| /// If flags contains REPOSITORY_OPEN_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 REPOSITORY_OPEN_NO_DOTGIT, don't try appending |
| /// `/.git` to `path`. |
| /// |
| /// If flags contains REPOSITORY_OPEN_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. |
| pub fn open_ext<P, O, I>( |
| path: P, |
| flags: RepositoryOpenFlags, |
| ceiling_dirs: I, |
| ) -> Result<Repository, Error> |
| where |
| P: AsRef<Path>, |
| O: AsRef<OsStr>, |
| I: IntoIterator<Item = O>, |
| { |
| 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<Repository, Error> { |
| 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<P: AsRef<Path>>(path: P) -> Result<Repository, Error> { |
| // 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)) |
| } |
| |
| /// 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<P: AsRef<Path>>(path: P) -> Result<Repository, Error> { |
| 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<P: AsRef<Path>>(path: P) -> Result<Repository, Error> { |
| 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<P: AsRef<Path>>( |
| path: P, |
| opts: &RepositoryInitOptions, |
| ) -> Result<Repository, Error> { |
| 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<P: AsRef<Path>>(url: &str, into: P) -> Result<Repository, Error> { |
| 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<P: AsRef<Path>>(url: &str, into: P) -> Result<Repository, Error> { |
| 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<Repository, Error> { |
| 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<Repository>) -> 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<Revspec<'_>, 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<Object<'_>, 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 |
| /// <http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions> for |
| /// information on the syntax accepted. |
| /// |
| /// In some cases (`@{<-n>}` or `<branchname>@{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<Reference<'_>>), 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<bool, Error> { |
| 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<String, Error> { |
| 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<StringArray, Error> { |
| 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<Remote<'_>, 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<Remote<'_>, 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<Remote<'_>, 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<Remote<'_>, 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<StringArray, Error> { |
| 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<T, I>(&self, target: Option<&Object<'_>>, paths: I) -> Result<(), Error> |
| where |
| T: IntoCString, |
| I: IntoIterator<Item = T>, |
| { |
| 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<Reference<'_>, 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> { |
| 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<bool, Error> { |
| 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 committish 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 committish 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<References<'_>, 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<References<'_>, 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<Vec<Submodule<'_>>, Error> { |
| struct Data<'a, 'b> { |
| repo: &'b Repository, |
| ret: &'a mut Vec<Submodule<'b>>, |
| } |
| 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<Statuses<'_>, 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<bool, Error> { |
| 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<Status, Error> { |
| 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<BranchType>) -> Result<Branches<'_>, 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). |
| pub fn index(&self) -> Result<Index, Error> { |
| 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<Config, Error> { |
| 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<Option<&str>, 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<Option<&[u8]>, 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<Oid, Error> { |
| 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<Oid, Error> { |
| // 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<BlobWriter<'_>, 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<Blob<'_>, 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<Odb<'_>, 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<Branch<'_>, 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<Branch<'_>, 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<Branch<'_>, 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<Oid, Error> { |
| let update_ref = crate::opt_cstr(update_ref)?; |
| let mut parent_ptrs = parents |
| .iter() |
| .map(|p| p.raw() as *const raw::git_commit) |
| .collect::<Vec<_>>(); |
| 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<Buf, Error> { |
| let mut parent_ptrs = parents |
| .iter() |
| .map(|p| p.raw() as *const raw::git_commit) |
| .collect::<Vec<_>>(); |
| 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<Oid, Error> { |
| 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<Commit<'_>, 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<AnnotatedCommit<'_>, 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<ObjectType>) -> Result<Object<'_>, 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<Reference<'_>, 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<Reference<'_>, 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. |
| /// |
| /// 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<Reference<'_>, 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<Reference<'_>, 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<Reference<'_>, 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<Reference<'_>, 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<Oid, Error> { |
| 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<AnnotatedCommit<'_>, 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<AnnotatedCommit<'_>, 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<Signature<'static>, 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<Submodule<'_>, 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<Submodule<'_>, 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<SubmoduleStatus, Error> { |
| 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<Tree<'_>, 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<TreeBuilder<'_>, 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<Oid, Error> { |
| 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 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<Oid, Error> { |
| 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<Tag<'_>, 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<StringArray, Error> { |
| 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<T>(&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::<Vec<_>>(); |
| |
| 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<Index, Error> { |
| 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<Index, Error> { |
| 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::<Vec<_>>(); |
| 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::<Vec<_>>(); |
| 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<Rebase<'_>, 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<Rebase<'_>, 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<Oid, Error> { |
| 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<String, Error> { |
| 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<Notes<'_>, 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<Note<'_>, 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<Revwalk<'_>, 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<Blame<'_>, 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<Oid, Error> { |
| 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<Oid, Error> { |
| 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<OidArray, Error> { |
| 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<OidArray, Error> { |
| 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 |
| pub fn graph_descendant_of(&self, commit: Oid, ancestor: Oid) -> Result<bool, Error> { |
| 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<Reflog, Error> { |
| 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<bool, Error> { |
| 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<Describe<'_>, 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 <old-tree> <new-tree>` |
| /// |
| /// 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<Diff<'_>, 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 <treeish>` 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<Diff<'_>, 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<Diff<'_>, 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 <treeish>` |
| /// 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<Diff<'_>, 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 <treeish>` or `git diff-index |
| /// <treeish>`. 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<Diff<'_>, 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 <tree>` 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<Diff<'_>, 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<PackBuilder<'_>, 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<StashFlags>, |
| ) -> Result<Oid, Error> { |
| 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<StashFlags>, |
| ) -> Result<Oid, Error> { |
| 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<C>(&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<P: AsRef<Path>>(&self, path: P) -> Result<bool, Error> { |
| 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<Index, Error> { |
| 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<Buf, Error> { |
| 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<Buf, Error> { |
| 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<Buf, Error> { |
| 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<Index, Error> { |
| 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<Index, Error> { |
| 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<StringArray, Error> { |
| 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<Worktree, Error> { |
| 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<Worktree, Error> { |
| 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<Transaction<'a>, 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<Mailmap, Error> { |
| 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<C>(&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<C>(&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 evaulated 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_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_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 <author.proper@email> name <email> |
| Committer Name <committer.proper@email> <committer@email>"#, |
| )); |
| 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()); |
| } |
| } |