summaryrefslogtreecommitdiffstats
path: root/vendor/git2/src/repo.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:41:35 +0000
commit7e5d7eea9c580ef4b41a765bde624af431942b96 (patch)
tree2c0d9ca12878fc4525650aa4e54d77a81a07cc09 /vendor/git2/src/repo.rs
parentAdding debian version 1.70.0+dfsg1-9. (diff)
downloadrustc-7e5d7eea9c580ef4b41a765bde624af431942b96.tar.xz
rustc-7e5d7eea9c580ef4b41a765bde624af431942b96.zip
Merging upstream version 1.70.0+dfsg2.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/git2/src/repo.rs')
-rw-r--r--vendor/git2/src/repo.rs4207
1 files changed, 4207 insertions, 0 deletions
diff --git a/vendor/git2/src/repo.rs b/vendor/git2/src/repo.rs
new file mode 100644
index 000000000..a794939be
--- /dev/null
+++ b/vendor/git2/src/repo.rs
@@ -0,0 +1,4207 @@
+use libc::{c_char, c_int, c_uint, c_void, size_t};
+use std::env;
+use std::ffi::{CStr, CString, OsStr};
+use std::iter::IntoIterator;
+use std::mem;
+use std::path::{Path, PathBuf};
+use std::ptr;
+use std::str;
+
+use crate::build::{CheckoutBuilder, RepoBuilder};
+use crate::diff::{
+ binary_cb_c, file_cb_c, hunk_cb_c, line_cb_c, BinaryCb, DiffCallbacks, FileCb, HunkCb, LineCb,
+};
+use crate::oid_array::OidArray;
+use crate::stash::{stash_cb, StashApplyOptions, StashCbData};
+use crate::string_array::StringArray;
+use crate::tagforeach::{tag_foreach_cb, TagForeachCB, TagForeachData};
+use crate::util::{self, path_to_repo_path, Binding};
+use crate::worktree::{Worktree, WorktreeAddOptions};
+use crate::CherrypickOptions;
+use crate::RevertOptions;
+use crate::{mailmap::Mailmap, panic};
+use crate::{
+ raw, AttrCheckFlags, Buf, Error, Object, Remote, RepositoryOpenFlags, RepositoryState, Revspec,
+ StashFlags,
+};
+use crate::{
+ AnnotatedCommit, MergeAnalysis, MergeOptions, MergePreference, SubmoduleIgnore,
+ SubmoduleStatus, SubmoduleUpdate,
+};
+use crate::{ApplyLocation, ApplyOptions, Rebase, RebaseOptions};
+use crate::{Blame, BlameOptions, Reference, References, ResetType, Signature, Submodule};
+use crate::{Blob, BlobWriter, Branch, BranchType, Branches, Commit, Config, Index, Oid, Tree};
+use crate::{Describe, IntoCString, Reflog, RepositoryInitMode, RevparseMode};
+use crate::{DescribeOptions, Diff, DiffOptions, Odb, PackBuilder, TreeBuilder};
+use crate::{Note, Notes, ObjectType, Revwalk, Status, StatusOptions, Statuses, Tag, Transaction};
+
+type MergeheadForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a;
+type FetchheadForeachCb<'a> = dyn FnMut(&str, &[u8], &Oid, bool) -> bool + 'a;
+
+struct FetchheadForeachCbData<'a> {
+ callback: &'a mut FetchheadForeachCb<'a>,
+}
+
+struct MergeheadForeachCbData<'a> {
+ callback: &'a mut MergeheadForeachCb<'a>,
+}
+
+extern "C" fn mergehead_foreach_cb(oid: *const raw::git_oid, payload: *mut c_void) -> c_int {
+ panic::wrap(|| unsafe {
+ let data = &mut *(payload as *mut MergeheadForeachCbData<'_>);
+ let res = {
+ let callback = &mut data.callback;
+ callback(&Binding::from_raw(oid))
+ };
+
+ if res {
+ 0
+ } else {
+ 1
+ }
+ })
+ .unwrap_or(1)
+}
+
+extern "C" fn fetchhead_foreach_cb(
+ ref_name: *const c_char,
+ remote_url: *const c_char,
+ oid: *const raw::git_oid,
+ is_merge: c_uint,
+ payload: *mut c_void,
+) -> c_int {
+ panic::wrap(|| unsafe {
+ let data = &mut *(payload as *mut FetchheadForeachCbData<'_>);
+ let res = {
+ let callback = &mut data.callback;
+
+ assert!(!ref_name.is_null());
+ assert!(!remote_url.is_null());
+ assert!(!oid.is_null());
+
+ let ref_name = str::from_utf8(CStr::from_ptr(ref_name).to_bytes()).unwrap();
+ let remote_url = CStr::from_ptr(remote_url).to_bytes();
+ let oid = Binding::from_raw(oid);
+ let is_merge = is_merge == 1;
+
+ callback(&ref_name, remote_url, &oid, is_merge)
+ };
+
+ if res {
+ 0
+ } else {
+ 1
+ }
+ })
+ .unwrap_or(1)
+}
+
+/// An owned git repository, representing all state associated with the
+/// underlying filesystem.
+///
+/// This structure corresponds to a `git_repository` in libgit2. Many other
+/// types in git2-rs are derivative from this structure and are attached to its
+/// lifetime.
+///
+/// When a repository goes out of scope it is freed in memory but not deleted
+/// from the filesystem.
+pub struct Repository {
+ raw: *mut raw::git_repository,
+}
+
+// It is the current belief that a `Repository` can be sent among threads, or
+// even shared among threads in a mutex.
+unsafe impl Send for Repository {}
+
+/// Options which can be used to configure how a repository is initialized
+pub struct RepositoryInitOptions {
+ flags: u32,
+ mode: u32,
+ workdir_path: Option<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
+ /// [FROM_ENV](RepositoryOpenFlags::FROM_ENV) flag, but additionally respects `$GIT_DIR`.
+ /// With `$GIT_DIR` unset, this will search for a repository starting in
+ /// the current directory.
+ pub fn open_from_env() -> Result<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 [NO_SEARCH](RepositoryOpenFlags::NO_SEARCH), the path must point
+ /// directly to a repository; otherwise, this may point to a subdirectory
+ /// of a repository, and `open_ext` will search up through parent
+ /// directories.
+ ///
+ /// If flags contains [CROSS_FS](RepositoryOpenFlags::CROSS_FS), the search through parent
+ /// directories will not cross a filesystem boundary (detected when the
+ /// stat st_dev field changes).
+ ///
+ /// If flags contains [BARE](RepositoryOpenFlags::BARE), force opening the repository as
+ /// bare even if it isn't, ignoring any working directory, and defer
+ /// loading the repository configuration for performance.
+ ///
+ /// If flags contains [NO_DOTGIT](RepositoryOpenFlags::NO_DOTGIT), don't try appending
+ /// `/.git` to `path`.
+ ///
+ /// If flags contains [FROM_ENV](RepositoryOpenFlags::FROM_ENV), `open_ext` will ignore
+ /// other flags and `ceiling_dirs`, and respect the same environment
+ /// variables git does. Note, however, that `path` overrides `$GIT_DIR`; to
+ /// respect `$GIT_DIR` as well, use `open_from_env`.
+ ///
+ /// ceiling_dirs specifies a list of paths that the search through parent
+ /// directories will stop before entering. Use the functions in std::env
+ /// to construct or manipulate such a path list. (You can use `&[] as
+ /// &[&std::ffi::OsStr]` as an argument if there are no ceiling
+ /// directories.)
+ pub fn open_ext<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))
+ }
+
+ /// Attempt to find the path to a git repo for a given path
+ ///
+ /// This starts at `path` and looks up the filesystem hierarchy
+ /// until it finds a repository, stopping if it finds a member of ceiling_dirs
+ pub fn discover_path<P: AsRef<Path>, I, O>(path: P, ceiling_dirs: I) -> Result<PathBuf, Error>
+ where
+ O: AsRef<OsStr>,
+ I: IntoIterator<Item = O>,
+ {
+ crate::init();
+ let buf = Buf::new();
+ // Normal file path OK (does not need Windows conversion).
+ let path = path.as_ref().into_c_string()?;
+ let ceiling_dirs_os = env::join_paths(ceiling_dirs)?;
+ let ceiling_dirs = ceiling_dirs_os.into_c_string()?;
+ unsafe {
+ try_call!(raw::git_repository_discover(
+ buf.raw(),
+ path,
+ 1,
+ ceiling_dirs
+ ));
+ }
+
+ Ok(util::bytes2path(&*buf).to_path_buf())
+ }
+
+ /// Creates a new repository in the specified folder.
+ ///
+ /// This by default will create any necessary directories to create the
+ /// repository, and it will read any user-specified templates when creating
+ /// the repository. This behavior can be configured through `init_opts`.
+ pub fn init<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> {
+ self.set_head_bytes(refname.as_bytes())
+ }
+
+ /// Make the repository HEAD point to the specified reference as a byte array.
+ ///
+ /// If the provided reference points to a tree or a blob, the HEAD is
+ /// unaltered and an error is returned.
+ ///
+ /// If the provided reference points to a branch, the HEAD will point to
+ /// that branch, staying attached, or become attached if it isn't yet. If
+ /// the branch doesn't exist yet, no error will be returned. The HEAD will
+ /// then be attached to an unborn branch.
+ ///
+ /// Otherwise, the HEAD will be detached and will directly point to the
+ /// commit.
+ pub fn set_head_bytes(&self, refname: &[u8]) -> Result<(), Error> {
+ let refname = CString::new(refname)?;
+ unsafe {
+ try_call!(raw::git_repository_set_head(self.raw, refname));
+ }
+ Ok(())
+ }
+
+ /// Determines whether the repository HEAD is detached.
+ pub fn head_detached(&self) -> Result<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 commitish cannot be found in the repository, the HEAD
+ /// is unaltered and an error is returned.
+ ///
+ /// If the provided commitish cannot be peeled into a commit, the HEAD is
+ /// unaltered and an error is returned.
+ ///
+ /// Otherwise, the HEAD will eventually be detached and will directly point
+ /// to the peeled commit.
+ pub fn set_head_detached(&self, commitish: Oid) -> Result<(), Error> {
+ unsafe {
+ try_call!(raw::git_repository_set_head_detached(
+ self.raw,
+ commitish.raw()
+ ));
+ }
+ Ok(())
+ }
+
+ /// Make the repository HEAD directly point to the commit.
+ ///
+ /// If the provided commitish cannot be found in the repository, the HEAD
+ /// is unaltered and an error is returned.
+ /// If the provided commitish cannot be peeled into a commit, the HEAD is
+ /// unaltered and an error is returned.
+ /// Otherwise, the HEAD will eventually be detached and will directly point
+ /// to the peeled commit.
+ pub fn set_head_detached_from_annotated(
+ &self,
+ commitish: AnnotatedCommit<'_>,
+ ) -> Result<(), Error> {
+ unsafe {
+ try_call!(raw::git_repository_set_head_detached_from_annotated(
+ self.raw,
+ commitish.raw()
+ ));
+ }
+ Ok(())
+ }
+
+ /// Create an iterator for the repo's references
+ pub fn references(&self) -> Result<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).
+ ///
+ /// **Caution**: If the [`Repository`] of this index is dropped, then this
+ /// [`Index`] will become detached, and most methods on it will fail. See
+ /// [`Index::open`]. Be sure the repository has a binding such as a local
+ /// variable to keep it alive at least as long as the index.
+ pub fn index(&self) -> Result<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.
+ ///
+ /// A symbolic reference is a reference name that refers to another
+ /// reference name. If the other name moves, the symbolic name will move,
+ /// too. As a simple example, the "HEAD" reference might refer to
+ /// "refs/heads/master" while on the "master" branch of a repository.
+ ///
+ /// Valid reference names must follow one of two patterns:
+ ///
+ /// 1. Top-level names must contain only capital letters and underscores,
+ /// and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD").
+ /// 2. Names prefixed with "refs/" can be almost anything. You must avoid
+ /// the characters '~', '^', ':', '\\', '?', '[', and '*', and the
+ /// sequences ".." and "@{" which have special meaning to revparse.
+ ///
+ /// This function will return an error if a reference already exists with
+ /// the given name unless force is true, in which case it will be
+ /// overwritten.
+ pub fn reference_symbolic(
+ &self,
+ name: &str,
+ target: &str,
+ force: bool,
+ log_message: &str,
+ ) -> Result<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 tag in the repository from an object without creating a reference.
+ ///
+ /// The message will not be cleaned up.
+ ///
+ /// The tag name will be checked for validity. You must avoid the characters
+ /// '~', '^', ':', ' \ ', '?', '[', and '*', and the sequences ".." and " @
+ /// {" which have special meaning to revparse.
+ pub fn tag_annotation_create(
+ &self,
+ name: &str,
+ target: &Object<'_>,
+ tagger: &Signature<'_>,
+ message: &str,
+ ) -> Result<Oid, Error> {
+ let name = CString::new(name)?;
+ let message = CString::new(message)?;
+ let mut raw_oid = raw::git_oid {
+ id: [0; raw::GIT_OID_RAWSZ],
+ };
+ unsafe {
+ try_call!(raw::git_tag_annotation_create(
+ &mut raw_oid,
+ self.raw,
+ name,
+ target.raw(),
+ tagger.raw(),
+ message
+ ));
+ Ok(Binding::from_raw(&raw_oid as *const _))
+ }
+ }
+
+ /// Create a new lightweight tag pointing at a target object
+ ///
+ /// A new direct reference will be created pointing to this target object.
+ /// If force is true and a reference already exists with the given name,
+ /// it'll be replaced.
+ pub fn tag_lightweight(
+ &self,
+ name: &str,
+ target: &Object<'_>,
+ force: bool,
+ ) -> Result<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
+ ///
+ /// Note that a commit is not considered a descendant of itself, in contrast
+ /// to `git merge-base --is-ancestor`.
+ pub fn graph_descendant_of(&self, commit: Oid, ancestor: Oid) -> Result<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 evaluated relative to the repo
+ /// path. If this is not the "natural" working directory, a .git gitlink
+ /// file will be created here linking to the repo path.
+ pub fn workdir_path(&mut self, path: &Path) -> &mut RepositoryInitOptions {
+ // Normal file path OK (does not need Windows conversion).
+ self.workdir_path = Some(path.into_c_string().unwrap());
+ self
+ }
+
+ /// If set, this will be used to initialize the "description" file in the
+ /// repository instead of using the template content.
+ pub fn description(&mut self, desc: &str) -> &mut RepositoryInitOptions {
+ self.description = Some(CString::new(desc).unwrap());
+ self
+ }
+
+ /// When the `external_template` option is set, this is the first location
+ /// to check for the template directory.
+ ///
+ /// If this is not configured, then the default locations will be searched
+ /// instead.
+ pub fn template_path(&mut self, path: &Path) -> &mut RepositoryInitOptions {
+ // Normal file path OK (does not need Windows conversion).
+ self.template_path = Some(path.into_c_string().unwrap());
+ self
+ }
+
+ /// The name of the head to point HEAD at.
+ ///
+ /// If not configured, this will be taken from your git configuration.
+ /// If this begins with `refs/` it will be used verbatim;
+ /// otherwise `refs/heads/` will be prefixed
+ pub fn initial_head(&mut self, head: &str) -> &mut RepositoryInitOptions {
+ self.initial_head = Some(CString::new(head).unwrap());
+ self
+ }
+
+ /// If set, then after the rest of the repository initialization is
+ /// completed an `origin` remote will be added pointing to this URL.
+ pub fn origin_url(&mut self, url: &str) -> &mut RepositoryInitOptions {
+ self.origin_url = Some(CString::new(url).unwrap());
+ self
+ }
+
+ /// Creates a set of raw init options to be used with
+ /// `git_repository_init_ext`.
+ ///
+ /// This method is unsafe as the returned value may have pointers to the
+ /// interior of this structure.
+ pub unsafe fn raw(&self) -> raw::git_repository_init_options {
+ let mut opts = mem::zeroed();
+ assert_eq!(
+ raw::git_repository_init_init_options(
+ &mut opts,
+ raw::GIT_REPOSITORY_INIT_OPTIONS_VERSION
+ ),
+ 0
+ );
+ opts.flags = self.flags;
+ opts.mode = self.mode;
+ opts.workdir_path = crate::call::convert(&self.workdir_path);
+ opts.description = crate::call::convert(&self.description);
+ opts.template_path = crate::call::convert(&self.template_path);
+ opts.initial_head = crate::call::convert(&self.initial_head);
+ opts.origin_url = crate::call::convert(&self.origin_url);
+ opts
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::build::CheckoutBuilder;
+ use crate::CherrypickOptions;
+ use crate::{
+ ObjectType, Oid, Repository, ResetType, Signature, SubmoduleIgnore, SubmoduleUpdate,
+ };
+ use std::ffi::OsStr;
+ use std::fs;
+ use std::path::Path;
+ use tempfile::TempDir;
+
+ #[test]
+ fn smoke_init() {
+ let td = TempDir::new().unwrap();
+ let path = td.path();
+
+ let repo = Repository::init(path).unwrap();
+ assert!(!repo.is_bare());
+ }
+
+ #[test]
+ fn smoke_init_bare() {
+ let td = TempDir::new().unwrap();
+ let path = td.path();
+
+ let repo = Repository::init_bare(path).unwrap();
+ assert!(repo.is_bare());
+ assert!(repo.namespace().is_none());
+ }
+
+ #[test]
+ fn smoke_open() {
+ let td = TempDir::new().unwrap();
+ let path = td.path();
+ Repository::init(td.path()).unwrap();
+ let repo = Repository::open(path).unwrap();
+ assert!(!repo.is_bare());
+ assert!(!repo.is_shallow());
+ assert!(repo.is_empty().unwrap());
+ assert_eq!(
+ crate::test::realpath(&repo.path()).unwrap(),
+ crate::test::realpath(&td.path().join(".git/")).unwrap()
+ );
+ assert_eq!(repo.state(), crate::RepositoryState::Clean);
+ }
+
+ #[test]
+ fn smoke_open_bare() {
+ let td = TempDir::new().unwrap();
+ let path = td.path();
+ Repository::init_bare(td.path()).unwrap();
+
+ let repo = Repository::open(path).unwrap();
+ assert!(repo.is_bare());
+ assert_eq!(
+ crate::test::realpath(&repo.path()).unwrap(),
+ crate::test::realpath(&td.path().join("")).unwrap()
+ );
+ }
+
+ #[test]
+ fn smoke_checkout() {
+ let (_td, repo) = crate::test::repo_init();
+ repo.checkout_head(None).unwrap();
+ }
+
+ #[test]
+ fn smoke_revparse() {
+ let (_td, repo) = crate::test::repo_init();
+ let rev = repo.revparse("HEAD").unwrap();
+ assert!(rev.to().is_none());
+ let from = rev.from().unwrap();
+ assert!(rev.from().is_some());
+
+ assert_eq!(repo.revparse_single("HEAD").unwrap().id(), from.id());
+ let obj = repo.find_object(from.id(), None).unwrap().clone();
+ obj.peel(ObjectType::Any).unwrap();
+ obj.short_id().unwrap();
+ repo.reset(&obj, ResetType::Hard, None).unwrap();
+ let mut opts = CheckoutBuilder::new();
+ t!(repo.reset(&obj, ResetType::Soft, Some(&mut opts)));
+ }
+
+ #[test]
+ fn makes_dirs() {
+ let td = TempDir::new().unwrap();
+ Repository::init(&td.path().join("a/b/c/d")).unwrap();
+ }
+
+ #[test]
+ fn smoke_discover() {
+ let td = TempDir::new().unwrap();
+ let subdir = td.path().join("subdi");
+ fs::create_dir(&subdir).unwrap();
+ Repository::init_bare(td.path()).unwrap();
+ let repo = Repository::discover(&subdir).unwrap();
+ assert_eq!(
+ crate::test::realpath(&repo.path()).unwrap(),
+ crate::test::realpath(&td.path().join("")).unwrap()
+ );
+ }
+
+ #[test]
+ fn smoke_discover_path() {
+ let td = TempDir::new().unwrap();
+ let subdir = td.path().join("subdi");
+ fs::create_dir(&subdir).unwrap();
+ Repository::init_bare(td.path()).unwrap();
+ let path = Repository::discover_path(&subdir, &[] as &[&OsStr]).unwrap();
+ assert_eq!(
+ crate::test::realpath(&path).unwrap(),
+ crate::test::realpath(&td.path().join("")).unwrap()
+ );
+ }
+
+ #[test]
+ fn smoke_discover_path_ceiling_dir() {
+ let td = TempDir::new().unwrap();
+ let subdir = td.path().join("subdi");
+ fs::create_dir(&subdir).unwrap();
+ let ceilingdir = subdir.join("ceiling");
+ fs::create_dir(&ceilingdir).unwrap();
+ let testdir = ceilingdir.join("testdi");
+ fs::create_dir(&testdir).unwrap();
+ Repository::init_bare(td.path()).unwrap();
+ let path = Repository::discover_path(&testdir, &[ceilingdir.as_os_str()]);
+
+ assert!(path.is_err());
+ }
+
+ #[test]
+ fn smoke_open_ext() {
+ let td = TempDir::new().unwrap();
+ let subdir = td.path().join("subdir");
+ fs::create_dir(&subdir).unwrap();
+ Repository::init(td.path()).unwrap();
+
+ let repo = Repository::open_ext(
+ &subdir,
+ crate::RepositoryOpenFlags::empty(),
+ &[] as &[&OsStr],
+ )
+ .unwrap();
+ assert!(!repo.is_bare());
+ assert_eq!(
+ crate::test::realpath(&repo.path()).unwrap(),
+ crate::test::realpath(&td.path().join(".git")).unwrap()
+ );
+
+ let repo =
+ Repository::open_ext(&subdir, crate::RepositoryOpenFlags::BARE, &[] as &[&OsStr])
+ .unwrap();
+ assert!(repo.is_bare());
+ assert_eq!(
+ crate::test::realpath(&repo.path()).unwrap(),
+ crate::test::realpath(&td.path().join(".git")).unwrap()
+ );
+
+ let err = Repository::open_ext(
+ &subdir,
+ crate::RepositoryOpenFlags::NO_SEARCH,
+ &[] as &[&OsStr],
+ )
+ .err()
+ .unwrap();
+ assert_eq!(err.code(), crate::ErrorCode::NotFound);
+
+ assert!(
+ Repository::open_ext(&subdir, crate::RepositoryOpenFlags::empty(), &[&subdir]).is_ok()
+ );
+ }
+
+ fn graph_repo_init() -> (TempDir, Repository) {
+ let (_td, repo) = crate::test::repo_init();
+ {
+ let head = repo.head().unwrap().target().unwrap();
+ let head = repo.find_commit(head).unwrap();
+
+ let mut index = repo.index().unwrap();
+ let id = index.write_tree().unwrap();
+
+ let tree = repo.find_tree(id).unwrap();
+ let sig = repo.signature().unwrap();
+ repo.commit(Some("HEAD"), &sig, &sig, "second", &tree, &[&head])
+ .unwrap();
+ }
+ (_td, repo)
+ }
+
+ #[test]
+ fn smoke_graph_ahead_behind() {
+ let (_td, repo) = graph_repo_init();
+ let head = repo.head().unwrap().target().unwrap();
+ let head = repo.find_commit(head).unwrap();
+ let head_id = head.id();
+ let head_parent_id = head.parent(0).unwrap().id();
+ let (ahead, behind) = repo.graph_ahead_behind(head_id, head_parent_id).unwrap();
+ assert_eq!(ahead, 1);
+ assert_eq!(behind, 0);
+ let (ahead, behind) = repo.graph_ahead_behind(head_parent_id, head_id).unwrap();
+ assert_eq!(ahead, 0);
+ assert_eq!(behind, 1);
+ }
+
+ #[test]
+ fn smoke_graph_descendant_of() {
+ let (_td, repo) = graph_repo_init();
+ let head = repo.head().unwrap().target().unwrap();
+ let head = repo.find_commit(head).unwrap();
+ let head_id = head.id();
+ let head_parent_id = head.parent(0).unwrap().id();
+ assert!(repo.graph_descendant_of(head_id, head_parent_id).unwrap());
+ assert!(!repo.graph_descendant_of(head_parent_id, head_id).unwrap());
+ }
+
+ #[test]
+ fn smoke_reference_has_log_ensure_log() {
+ let (_td, repo) = crate::test::repo_init();
+
+ assert_eq!(repo.reference_has_log("HEAD").unwrap(), true);
+ assert_eq!(repo.reference_has_log("refs/heads/main").unwrap(), true);
+ assert_eq!(repo.reference_has_log("NOT_HEAD").unwrap(), false);
+ let main_oid = repo.revparse_single("main").unwrap().id();
+ assert!(repo
+ .reference("NOT_HEAD", main_oid, false, "creating a new branch")
+ .is_ok());
+ assert_eq!(repo.reference_has_log("NOT_HEAD").unwrap(), false);
+ assert!(repo.reference_ensure_log("NOT_HEAD").is_ok());
+ assert_eq!(repo.reference_has_log("NOT_HEAD").unwrap(), true);
+ }
+
+ #[test]
+ fn smoke_set_head() {
+ let (_td, repo) = crate::test::repo_init();
+
+ assert!(repo.set_head("refs/heads/does-not-exist").is_ok());
+ assert!(repo.head().is_err());
+
+ assert!(repo.set_head("refs/heads/main").is_ok());
+ assert!(repo.head().is_ok());
+
+ assert!(repo.set_head("*").is_err());
+ }
+
+ #[test]
+ fn smoke_set_head_bytes() {
+ let (_td, repo) = crate::test::repo_init();
+
+ assert!(repo.set_head_bytes(b"refs/heads/does-not-exist").is_ok());
+ assert!(repo.head().is_err());
+
+ assert!(repo.set_head_bytes(b"refs/heads/main").is_ok());
+ assert!(repo.head().is_ok());
+
+ assert!(repo.set_head_bytes(b"*").is_err());
+ }
+
+ #[test]
+ fn smoke_set_head_detached() {
+ let (_td, repo) = crate::test::repo_init();
+
+ let void_oid = Oid::from_bytes(b"00000000000000000000").unwrap();
+ assert!(repo.set_head_detached(void_oid).is_err());
+
+ let main_oid = repo.revparse_single("main").unwrap().id();
+ assert!(repo.set_head_detached(main_oid).is_ok());
+ assert_eq!(repo.head().unwrap().target().unwrap(), main_oid);
+ }
+
+ /// create the following:
+ /// /---o4
+ /// /---o3
+ /// o1---o2
+ #[test]
+ fn smoke_merge_base() {
+ let (_td, repo) = graph_repo_init();
+ let sig = repo.signature().unwrap();
+
+ // let oid1 = head
+ let oid1 = repo.head().unwrap().target().unwrap();
+ let commit1 = repo.find_commit(oid1).unwrap();
+ println!("created oid1 {:?}", oid1);
+
+ repo.branch("branch_a", &commit1, true).unwrap();
+ repo.branch("branch_b", &commit1, true).unwrap();
+ repo.branch("branch_c", &commit1, true).unwrap();
+
+ // create commit oid2 on branch_a
+ let mut index = repo.index().unwrap();
+ let p = Path::new(repo.workdir().unwrap()).join("file_a");
+ println!("using path {:?}", p);
+ fs::File::create(&p).unwrap();
+ index.add_path(Path::new("file_a")).unwrap();
+ let id_a = index.write_tree().unwrap();
+ let tree_a = repo.find_tree(id_a).unwrap();
+ let oid2 = repo
+ .commit(
+ Some("refs/heads/branch_a"),
+ &sig,
+ &sig,
+ "commit 2",
+ &tree_a,
+ &[&commit1],
+ )
+ .unwrap();
+ repo.find_commit(oid2).unwrap();
+ println!("created oid2 {:?}", oid2);
+
+ t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+
+ // create commit oid3 on branch_b
+ let mut index = repo.index().unwrap();
+ let p = Path::new(repo.workdir().unwrap()).join("file_b");
+ fs::File::create(&p).unwrap();
+ index.add_path(Path::new("file_b")).unwrap();
+ let id_b = index.write_tree().unwrap();
+ let tree_b = repo.find_tree(id_b).unwrap();
+ let oid3 = repo
+ .commit(
+ Some("refs/heads/branch_b"),
+ &sig,
+ &sig,
+ "commit 3",
+ &tree_b,
+ &[&commit1],
+ )
+ .unwrap();
+ repo.find_commit(oid3).unwrap();
+ println!("created oid3 {:?}", oid3);
+
+ t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+
+ // create commit oid4 on branch_c
+ let mut index = repo.index().unwrap();
+ let p = Path::new(repo.workdir().unwrap()).join("file_c");
+ fs::File::create(&p).unwrap();
+ index.add_path(Path::new("file_c")).unwrap();
+ let id_c = index.write_tree().unwrap();
+ let tree_c = repo.find_tree(id_c).unwrap();
+ let oid4 = repo
+ .commit(
+ Some("refs/heads/branch_c"),
+ &sig,
+ &sig,
+ "commit 3",
+ &tree_c,
+ &[&commit1],
+ )
+ .unwrap();
+ repo.find_commit(oid4).unwrap();
+ println!("created oid4 {:?}", oid4);
+
+ // the merge base of (oid2,oid3) should be oid1
+ let merge_base = repo.merge_base(oid2, oid3).unwrap();
+ assert_eq!(merge_base, oid1);
+
+ // the merge base of (oid2,oid3,oid4) should be oid1
+ let merge_base = repo.merge_base_many(&[oid2, oid3, oid4]).unwrap();
+ assert_eq!(merge_base, oid1);
+ }
+
+ /// create an octopus:
+ /// /---o2-o4
+ /// o1 X
+ /// \---o3-o5
+ /// and checks that the merge bases of (o4,o5) are (o2,o3)
+ #[test]
+ fn smoke_merge_bases() {
+ let (_td, repo) = graph_repo_init();
+ let sig = repo.signature().unwrap();
+
+ // let oid1 = head
+ let oid1 = repo.head().unwrap().target().unwrap();
+ let commit1 = repo.find_commit(oid1).unwrap();
+ println!("created oid1 {:?}", oid1);
+
+ repo.branch("branch_a", &commit1, true).unwrap();
+ repo.branch("branch_b", &commit1, true).unwrap();
+
+ // create commit oid2 on branchA
+ let mut index = repo.index().unwrap();
+ let p = Path::new(repo.workdir().unwrap()).join("file_a");
+ println!("using path {:?}", p);
+ fs::File::create(&p).unwrap();
+ index.add_path(Path::new("file_a")).unwrap();
+ let id_a = index.write_tree().unwrap();
+ let tree_a = repo.find_tree(id_a).unwrap();
+ let oid2 = repo
+ .commit(
+ Some("refs/heads/branch_a"),
+ &sig,
+ &sig,
+ "commit 2",
+ &tree_a,
+ &[&commit1],
+ )
+ .unwrap();
+ let commit2 = repo.find_commit(oid2).unwrap();
+ println!("created oid2 {:?}", oid2);
+
+ t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+
+ // create commit oid3 on branchB
+ let mut index = repo.index().unwrap();
+ let p = Path::new(repo.workdir().unwrap()).join("file_b");
+ fs::File::create(&p).unwrap();
+ index.add_path(Path::new("file_b")).unwrap();
+ let id_b = index.write_tree().unwrap();
+ let tree_b = repo.find_tree(id_b).unwrap();
+ let oid3 = repo
+ .commit(
+ Some("refs/heads/branch_b"),
+ &sig,
+ &sig,
+ "commit 3",
+ &tree_b,
+ &[&commit1],
+ )
+ .unwrap();
+ let commit3 = repo.find_commit(oid3).unwrap();
+ println!("created oid3 {:?}", oid3);
+
+ // create merge commit oid4 on branchA with parents oid2 and oid3
+ //let mut index4 = repo.merge_commits(&commit2, &commit3, None).unwrap();
+ repo.set_head("refs/heads/branch_a").unwrap();
+ repo.checkout_head(None).unwrap();
+ let oid4 = repo
+ .commit(
+ Some("refs/heads/branch_a"),
+ &sig,
+ &sig,
+ "commit 4",
+ &tree_a,
+ &[&commit2, &commit3],
+ )
+ .unwrap();
+ //index4.write_tree_to(&repo).unwrap();
+ println!("created oid4 {:?}", oid4);
+
+ // create merge commit oid5 on branchB with parents oid2 and oid3
+ //let mut index5 = repo.merge_commits(&commit3, &commit2, None).unwrap();
+ repo.set_head("refs/heads/branch_b").unwrap();
+ repo.checkout_head(None).unwrap();
+ let oid5 = repo
+ .commit(
+ Some("refs/heads/branch_b"),
+ &sig,
+ &sig,
+ "commit 5",
+ &tree_a,
+ &[&commit3, &commit2],
+ )
+ .unwrap();
+ //index5.write_tree_to(&repo).unwrap();
+ println!("created oid5 {:?}", oid5);
+
+ // merge bases of (oid4,oid5) should be (oid2,oid3)
+ let merge_bases = repo.merge_bases(oid4, oid5).unwrap();
+ let mut found_oid2 = false;
+ let mut found_oid3 = false;
+ for mg in merge_bases.iter() {
+ println!("found merge base {:?}", mg);
+ if mg == &oid2 {
+ found_oid2 = true;
+ } else if mg == &oid3 {
+ found_oid3 = true;
+ } else {
+ assert!(false);
+ }
+ }
+ assert!(found_oid2);
+ assert!(found_oid3);
+ assert_eq!(merge_bases.len(), 2);
+
+ // merge bases of (oid4,oid5) should be (oid2,oid3)
+ let merge_bases = repo.merge_bases_many(&[oid4, oid5]).unwrap();
+ let mut found_oid2 = false;
+ let mut found_oid3 = false;
+ for mg in merge_bases.iter() {
+ println!("found merge base {:?}", mg);
+ if mg == &oid2 {
+ found_oid2 = true;
+ } else if mg == &oid3 {
+ found_oid3 = true;
+ } else {
+ assert!(false);
+ }
+ }
+ assert!(found_oid2);
+ assert!(found_oid3);
+ assert_eq!(merge_bases.len(), 2);
+ }
+
+ #[test]
+ fn smoke_revparse_ext() {
+ let (_td, repo) = graph_repo_init();
+
+ {
+ let short_refname = "main";
+ let expected_refname = "refs/heads/main";
+ let (obj, reference) = repo.revparse_ext(short_refname).unwrap();
+ let expected_obj = repo.revparse_single(expected_refname).unwrap();
+ assert_eq!(obj.id(), expected_obj.id());
+ assert_eq!(reference.unwrap().name().unwrap(), expected_refname);
+ }
+ {
+ let missing_refname = "refs/heads/does-not-exist";
+ assert!(repo.revparse_ext(missing_refname).is_err());
+ }
+ {
+ let (_obj, reference) = repo.revparse_ext("HEAD^").unwrap();
+ assert!(reference.is_none());
+ }
+ }
+
+ #[test]
+ fn smoke_is_path_ignored() {
+ let (_td, repo) = graph_repo_init();
+
+ assert!(!repo.is_path_ignored(Path::new("foo")).unwrap());
+
+ let _ = repo.add_ignore_rule("/foo");
+ assert!(repo.is_path_ignored(Path::new("foo")).unwrap());
+ if cfg!(windows) {
+ assert!(repo.is_path_ignored(Path::new("foo\\thing")).unwrap());
+ }
+
+ let _ = repo.clear_ignore_rules();
+ assert!(!repo.is_path_ignored(Path::new("foo")).unwrap());
+ if cfg!(windows) {
+ assert!(!repo.is_path_ignored(Path::new("foo\\thing")).unwrap());
+ }
+ }
+
+ #[test]
+ fn smoke_cherrypick() {
+ let (_td, repo) = crate::test::repo_init();
+ let sig = repo.signature().unwrap();
+
+ let oid1 = repo.head().unwrap().target().unwrap();
+ let commit1 = repo.find_commit(oid1).unwrap();
+
+ repo.branch("branch_a", &commit1, true).unwrap();
+
+ // Add 2 commits on top of the initial one in branch_a
+ let mut index = repo.index().unwrap();
+ let p1 = Path::new(repo.workdir().unwrap()).join("file_c");
+ fs::File::create(&p1).unwrap();
+ index.add_path(Path::new("file_c")).unwrap();
+ let id = index.write_tree().unwrap();
+ let tree_c = repo.find_tree(id).unwrap();
+ let oid2 = repo
+ .commit(
+ Some("refs/heads/branch_a"),
+ &sig,
+ &sig,
+ "commit 2",
+ &tree_c,
+ &[&commit1],
+ )
+ .unwrap();
+ let commit2 = repo.find_commit(oid2).unwrap();
+ println!("created oid2 {:?}", oid2);
+ assert!(p1.exists());
+
+ let mut index = repo.index().unwrap();
+ let p2 = Path::new(repo.workdir().unwrap()).join("file_d");
+ fs::File::create(&p2).unwrap();
+ index.add_path(Path::new("file_d")).unwrap();
+ let id = index.write_tree().unwrap();
+ let tree_d = repo.find_tree(id).unwrap();
+ let oid3 = repo
+ .commit(
+ Some("refs/heads/branch_a"),
+ &sig,
+ &sig,
+ "commit 3",
+ &tree_d,
+ &[&commit2],
+ )
+ .unwrap();
+ let commit3 = repo.find_commit(oid3).unwrap();
+ println!("created oid3 {:?}", oid3);
+ assert!(p1.exists());
+ assert!(p2.exists());
+
+ // cherry-pick commit3 on top of commit1 in branch b
+ repo.reset(commit1.as_object(), ResetType::Hard, None)
+ .unwrap();
+ let mut cherrypick_opts = CherrypickOptions::new();
+ repo.cherrypick(&commit3, Some(&mut cherrypick_opts))
+ .unwrap();
+ let id = repo.index().unwrap().write_tree().unwrap();
+ let tree_d = repo.find_tree(id).unwrap();
+ let oid4 = repo
+ .commit(Some("HEAD"), &sig, &sig, "commit 4", &tree_d, &[&commit1])
+ .unwrap();
+ let commit4 = repo.find_commit(oid4).unwrap();
+ // should have file from commit3, but not the file from commit2
+ assert_eq!(commit4.parent(0).unwrap().id(), commit1.id());
+ assert!(!p1.exists());
+ assert!(p2.exists());
+ }
+
+ #[test]
+ fn smoke_revert() {
+ let (_td, repo) = crate::test::repo_init();
+ let foo_file = Path::new(repo.workdir().unwrap()).join("foo");
+ assert!(!foo_file.exists());
+
+ let (oid1, _id) = crate::test::commit(&repo);
+ let commit1 = repo.find_commit(oid1).unwrap();
+ t!(repo.reset(commit1.as_object(), ResetType::Hard, None));
+ assert!(foo_file.exists());
+
+ repo.revert(&commit1, None).unwrap();
+ let id = repo.index().unwrap().write_tree().unwrap();
+ let tree2 = repo.find_tree(id).unwrap();
+ let sig = repo.signature().unwrap();
+ repo.commit(Some("HEAD"), &sig, &sig, "commit 1", &tree2, &[&commit1])
+ .unwrap();
+ // reverting once removes `foo` file
+ assert!(!foo_file.exists());
+
+ let oid2 = repo.head().unwrap().target().unwrap();
+ let commit2 = repo.find_commit(oid2).unwrap();
+ repo.revert(&commit2, None).unwrap();
+ let id = repo.index().unwrap().write_tree().unwrap();
+ let tree3 = repo.find_tree(id).unwrap();
+ repo.commit(Some("HEAD"), &sig, &sig, "commit 2", &tree3, &[&commit2])
+ .unwrap();
+ // reverting twice restores `foo` file
+ assert!(foo_file.exists());
+ }
+
+ #[test]
+ fn smoke_config_write_and_read() {
+ let (td, repo) = crate::test::repo_init();
+
+ let mut config = repo.config().unwrap();
+
+ config.set_bool("commit.gpgsign", false).unwrap();
+
+ let c = fs::read_to_string(td.path().join(".git").join("config")).unwrap();
+
+ assert!(c.contains("[commit]"));
+ assert!(c.contains("gpgsign = false"));
+
+ let config = repo.config().unwrap();
+
+ assert!(!config.get_bool("commit.gpgsign").unwrap());
+ }
+
+ #[test]
+ fn smoke_merge_analysis_for_ref() -> Result<(), crate::Error> {
+ let (_td, repo) = graph_repo_init();
+
+ // Set up this repo state:
+ // * second (their-branch)
+ // * initial (HEAD -> main)
+ //
+ // We expect that their-branch can be fast-forward merged into main.
+
+ // git checkout --detach HEAD
+ let head_commit = repo.head()?.peel_to_commit()?;
+ repo.set_head_detached(head_commit.id())?;
+
+ // git branch their-branch HEAD
+ let their_branch = repo.branch("their-branch", &head_commit, false)?;
+
+ // git branch -f main HEAD~
+ let mut parents_iter = head_commit.parents();
+ let parent = parents_iter.next().unwrap();
+ assert!(parents_iter.next().is_none());
+
+ let main = repo.branch("main", &parent, true)?;
+
+ // git checkout main
+ repo.set_head(main.get().name().expect("should be utf-8"))?;
+
+ let (merge_analysis, _merge_preference) = repo.merge_analysis_for_ref(
+ main.get(),
+ &[&repo.reference_to_annotated_commit(their_branch.get())?],
+ )?;
+
+ assert!(merge_analysis.contains(crate::MergeAnalysis::ANALYSIS_FASTFORWARD));
+
+ Ok(())
+ }
+
+ #[test]
+ fn smoke_submodule_set() -> Result<(), crate::Error> {
+ let (td1, _repo) = crate::test::repo_init();
+ let (td2, mut repo2) = crate::test::repo_init();
+ let url = crate::test::path2url(td1.path());
+ let name = "bar";
+ {
+ let mut s = repo2.submodule(&url, Path::new(name), true)?;
+ fs::remove_dir_all(td2.path().join("bar")).unwrap();
+ Repository::clone(&url, td2.path().join("bar"))?;
+ s.add_to_index(false)?;
+ s.add_finalize()?;
+ }
+
+ // update strategy
+ repo2.submodule_set_update(name, SubmoduleUpdate::None)?;
+ assert!(matches!(
+ repo2.find_submodule(name)?.update_strategy(),
+ SubmoduleUpdate::None
+ ));
+ repo2.submodule_set_update(name, SubmoduleUpdate::Rebase)?;
+ assert!(matches!(
+ repo2.find_submodule(name)?.update_strategy(),
+ SubmoduleUpdate::Rebase
+ ));
+
+ // ignore rule
+ repo2.submodule_set_ignore(name, SubmoduleIgnore::Untracked)?;
+ assert!(matches!(
+ repo2.find_submodule(name)?.ignore_rule(),
+ SubmoduleIgnore::Untracked
+ ));
+ repo2.submodule_set_ignore(name, SubmoduleIgnore::Dirty)?;
+ assert!(matches!(
+ repo2.find_submodule(name)?.ignore_rule(),
+ SubmoduleIgnore::Dirty
+ ));
+
+ // url
+ repo2.submodule_set_url(name, "fake-url")?;
+ assert_eq!(repo2.find_submodule(name)?.url(), Some("fake-url"));
+
+ // branch
+ repo2.submodule_set_branch(name, "fake-branch")?;
+ assert_eq!(repo2.find_submodule(name)?.branch(), Some("fake-branch"));
+
+ Ok(())
+ }
+
+ #[test]
+ fn smoke_mailmap_from_repository() {
+ let (_td, repo) = crate::test::repo_init();
+
+ let commit = {
+ let head = t!(repo.head()).target().unwrap();
+ t!(repo.find_commit(head))
+ };
+
+ // This is our baseline for HEAD.
+ let author = commit.author();
+ let committer = commit.committer();
+ assert_eq!(author.name(), Some("name"));
+ assert_eq!(author.email(), Some("email"));
+ assert_eq!(committer.name(), Some("name"));
+ assert_eq!(committer.email(), Some("email"));
+
+ // There is no .mailmap file in the test repo so all signature identities are equal.
+ let mailmap = t!(repo.mailmap());
+ let mailmapped_author = t!(commit.author_with_mailmap(&mailmap));
+ let mailmapped_committer = t!(commit.committer_with_mailmap(&mailmap));
+ assert_eq!(mailmapped_author.name(), author.name());
+ assert_eq!(mailmapped_author.email(), author.email());
+ assert_eq!(mailmapped_committer.name(), committer.name());
+ assert_eq!(mailmapped_committer.email(), committer.email());
+
+ let commit = {
+ // - Add a .mailmap file to the repository.
+ // - Commit with a signature identity different from the author's.
+ // - Include entries for both author and committer to prove we call
+ // the right raw functions.
+ let mailmap_file = Path::new(".mailmap");
+ let p = Path::new(repo.workdir().unwrap()).join(&mailmap_file);
+ t!(fs::write(
+ p,
+ r#"
+Author Name <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());
+ }
+}