diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:41:35 +0000 |
commit | 7e5d7eea9c580ef4b41a765bde624af431942b96 (patch) | |
tree | 2c0d9ca12878fc4525650aa4e54d77a81a07cc09 /vendor/git2/src/repo.rs | |
parent | Adding debian version 1.70.0+dfsg1-9. (diff) | |
download | rustc-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.rs | 4207 |
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()); + } +} |