diff options
Diffstat (limited to 'extra/git2/src')
63 files changed, 0 insertions, 25700 deletions
diff --git a/extra/git2/src/apply.rs b/extra/git2/src/apply.rs deleted file mode 100644 index 34dc811a0..000000000 --- a/extra/git2/src/apply.rs +++ /dev/null @@ -1,208 +0,0 @@ -//! git_apply support -//! see original: <https://github.com/libgit2/libgit2/blob/master/include/git2/apply.h> - -use crate::{panic, raw, util::Binding, DiffDelta, DiffHunk}; -use libc::c_int; -use std::{ffi::c_void, mem}; - -/// Possible application locations for git_apply -/// see <https://libgit2.org/libgit2/#HEAD/type/git_apply_options> -#[derive(Copy, Clone, Debug)] -pub enum ApplyLocation { - /// Apply the patch to the workdir - WorkDir, - /// Apply the patch to the index - Index, - /// Apply the patch to both the working directory and the index - Both, -} - -impl Binding for ApplyLocation { - type Raw = raw::git_apply_location_t; - unsafe fn from_raw(raw: raw::git_apply_location_t) -> Self { - match raw { - raw::GIT_APPLY_LOCATION_WORKDIR => Self::WorkDir, - raw::GIT_APPLY_LOCATION_INDEX => Self::Index, - raw::GIT_APPLY_LOCATION_BOTH => Self::Both, - _ => panic!("Unknown git diff binary kind"), - } - } - fn raw(&self) -> raw::git_apply_location_t { - match *self { - Self::WorkDir => raw::GIT_APPLY_LOCATION_WORKDIR, - Self::Index => raw::GIT_APPLY_LOCATION_INDEX, - Self::Both => raw::GIT_APPLY_LOCATION_BOTH, - } - } -} - -/// Options to specify when applying a diff -pub struct ApplyOptions<'cb> { - raw: raw::git_apply_options, - hunk_cb: Option<Box<HunkCB<'cb>>>, - delta_cb: Option<Box<DeltaCB<'cb>>>, -} - -type HunkCB<'a> = dyn FnMut(Option<DiffHunk<'_>>) -> bool + 'a; -type DeltaCB<'a> = dyn FnMut(Option<DiffDelta<'_>>) -> bool + 'a; - -extern "C" fn delta_cb_c(delta: *const raw::git_diff_delta, data: *mut c_void) -> c_int { - panic::wrap(|| unsafe { - let delta = Binding::from_raw_opt(delta as *mut _); - - let payload = &mut *(data as *mut ApplyOptions<'_>); - let callback = match payload.delta_cb { - Some(ref mut c) => c, - None => return -1, - }; - - let apply = callback(delta); - if apply { - 0 - } else { - 1 - } - }) - .unwrap_or(-1) -} - -extern "C" fn hunk_cb_c(hunk: *const raw::git_diff_hunk, data: *mut c_void) -> c_int { - panic::wrap(|| unsafe { - let hunk = Binding::from_raw_opt(hunk); - - let payload = &mut *(data as *mut ApplyOptions<'_>); - let callback = match payload.hunk_cb { - Some(ref mut c) => c, - None => return -1, - }; - - let apply = callback(hunk); - if apply { - 0 - } else { - 1 - } - }) - .unwrap_or(-1) -} - -impl<'cb> ApplyOptions<'cb> { - /// Creates a new set of empty options (zeroed). - pub fn new() -> Self { - let mut opts = Self { - raw: unsafe { mem::zeroed() }, - hunk_cb: None, - delta_cb: None, - }; - assert_eq!( - unsafe { raw::git_apply_options_init(&mut opts.raw, raw::GIT_APPLY_OPTIONS_VERSION) }, - 0 - ); - opts - } - - fn flag(&mut self, opt: raw::git_apply_flags_t, val: bool) -> &mut Self { - let opt = opt as u32; - if val { - self.raw.flags |= opt; - } else { - self.raw.flags &= !opt; - } - self - } - - /// Don't actually make changes, just test that the patch applies. - pub fn check(&mut self, check: bool) -> &mut Self { - self.flag(raw::GIT_APPLY_CHECK, check) - } - - /// When applying a patch, callback that will be made per hunk. - pub fn hunk_callback<F>(&mut self, cb: F) -> &mut Self - where - F: FnMut(Option<DiffHunk<'_>>) -> bool + 'cb, - { - self.hunk_cb = Some(Box::new(cb) as Box<HunkCB<'cb>>); - - self.raw.hunk_cb = Some(hunk_cb_c); - self.raw.payload = self as *mut _ as *mut _; - - self - } - - /// When applying a patch, callback that will be made per delta (file). - pub fn delta_callback<F>(&mut self, cb: F) -> &mut Self - where - F: FnMut(Option<DiffDelta<'_>>) -> bool + 'cb, - { - self.delta_cb = Some(Box::new(cb) as Box<DeltaCB<'cb>>); - - self.raw.delta_cb = Some(delta_cb_c); - self.raw.payload = self as *mut _ as *mut _; - - self - } - - /// Pointer to a raw git_stash_apply_options - pub unsafe fn raw(&mut self) -> *const raw::git_apply_options { - &self.raw as *const _ - } -} - -#[cfg(test)] -mod tests { - use super::*; - use std::{fs::File, io::Write, path::Path}; - - #[test] - fn smoke_test() { - let (_td, repo) = crate::test::repo_init(); - let diff = t!(repo.diff_tree_to_workdir(None, None)); - let mut count_hunks = 0; - let mut count_delta = 0; - { - let mut opts = ApplyOptions::new(); - opts.hunk_callback(|_hunk| { - count_hunks += 1; - true - }); - opts.delta_callback(|_delta| { - count_delta += 1; - true - }); - t!(repo.apply(&diff, ApplyLocation::Both, Some(&mut opts))); - } - assert_eq!(count_hunks, 0); - assert_eq!(count_delta, 0); - } - - #[test] - fn apply_hunks_and_delta() { - let file_path = Path::new("foo.txt"); - let (td, repo) = crate::test::repo_init(); - // create new file - t!(t!(File::create(&td.path().join(file_path))).write_all(b"bar")); - // stage the new file - t!(t!(repo.index()).add_path(file_path)); - // now change workdir version - t!(t!(File::create(&td.path().join(file_path))).write_all(b"foo\nbar")); - - let diff = t!(repo.diff_index_to_workdir(None, None)); - assert_eq!(diff.deltas().len(), 1); - let mut count_hunks = 0; - let mut count_delta = 0; - { - let mut opts = ApplyOptions::new(); - opts.hunk_callback(|_hunk| { - count_hunks += 1; - true - }); - opts.delta_callback(|_delta| { - count_delta += 1; - true - }); - t!(repo.apply(&diff, ApplyLocation::Index, Some(&mut opts))); - } - assert_eq!(count_delta, 1); - assert_eq!(count_hunks, 1); - } -} diff --git a/extra/git2/src/attr.rs b/extra/git2/src/attr.rs deleted file mode 100644 index 33b1d2d4a..000000000 --- a/extra/git2/src/attr.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crate::raw; -use std::ptr; -use std::str; - -/// All possible states of an attribute. -/// -/// This enum is used to interpret the value returned by -/// [`Repository::get_attr`](crate::Repository::get_attr) and -/// [`Repository::get_attr_bytes`](crate::Repository::get_attr_bytes). -#[derive(Debug, Clone, Copy, Eq)] -pub enum AttrValue<'string> { - /// The attribute is set to true. - True, - /// The attribute is unset (set to false). - False, - /// The attribute is set to a [valid UTF-8 string](prim@str). - String(&'string str), - /// The attribute is set to a string that might not be [valid UTF-8](prim@str). - Bytes(&'string [u8]), - /// The attribute is not specified. - Unspecified, -} - -macro_rules! from_value { - ($value:expr => $string:expr) => { - match unsafe { raw::git_attr_value($value.map_or(ptr::null(), |v| v.as_ptr().cast())) } { - raw::GIT_ATTR_VALUE_TRUE => Self::True, - raw::GIT_ATTR_VALUE_FALSE => Self::False, - raw::GIT_ATTR_VALUE_STRING => $string, - raw::GIT_ATTR_VALUE_UNSPECIFIED => Self::Unspecified, - _ => unreachable!(), - } - }; -} - -impl<'string> AttrValue<'string> { - /// Returns the state of an attribute by inspecting its [value](crate::Repository::get_attr) - /// by a [string](prim@str). - /// - /// This function always returns [`AttrValue::String`] and never returns [`AttrValue::Bytes`] - /// when the attribute is set to a string. - pub fn from_string(value: Option<&'string str>) -> Self { - from_value!(value => Self::String(value.unwrap())) - } - - /// Returns the state of an attribute by inspecting its [value](crate::Repository::get_attr_bytes) - /// by a [byte](u8) [slice]. - /// - /// This function will perform UTF-8 validation when the attribute is set to a string, returns - /// [`AttrValue::String`] if it's valid UTF-8 and [`AttrValue::Bytes`] otherwise. - pub fn from_bytes(value: Option<&'string [u8]>) -> Self { - let mut value = Self::always_bytes(value); - if let Self::Bytes(bytes) = value { - if let Ok(string) = str::from_utf8(bytes) { - value = Self::String(string); - } - } - value - } - - /// Returns the state of an attribute just like [`AttrValue::from_bytes`], but skips UTF-8 - /// validation and always returns [`AttrValue::Bytes`] when it's set to a string. - pub fn always_bytes(value: Option<&'string [u8]>) -> Self { - from_value!(value => Self::Bytes(value.unwrap())) - } -} - -/// Compare two [`AttrValue`]s. -/// -/// Note that this implementation does not differentiate between [`AttrValue::String`] and -/// [`AttrValue::Bytes`]. -impl PartialEq for AttrValue<'_> { - fn eq(&self, other: &AttrValue<'_>) -> bool { - match (self, other) { - (Self::True, AttrValue::True) - | (Self::False, AttrValue::False) - | (Self::Unspecified, AttrValue::Unspecified) => true, - (AttrValue::String(string), AttrValue::Bytes(bytes)) - | (AttrValue::Bytes(bytes), AttrValue::String(string)) => string.as_bytes() == *bytes, - (AttrValue::String(left), AttrValue::String(right)) => left == right, - (AttrValue::Bytes(left), AttrValue::Bytes(right)) => left == right, - _ => false, - } - } -} - -#[cfg(test)] -mod tests { - use super::AttrValue; - - macro_rules! test_attr_value { - ($function:ident, $variant:ident) => { - const ATTR_TRUE: &str = "[internal]__TRUE__"; - const ATTR_FALSE: &str = "[internal]__FALSE__"; - const ATTR_UNSET: &str = "[internal]__UNSET__"; - let as_bytes = AsRef::<[u8]>::as_ref; - // Use `matches!` here since the `PartialEq` implementation does not differentiate - // between `String` and `Bytes`. - assert!(matches!( - AttrValue::$function(Some(ATTR_TRUE.as_ref())), - AttrValue::$variant(s) if as_bytes(s) == ATTR_TRUE.as_bytes() - )); - assert!(matches!( - AttrValue::$function(Some(ATTR_FALSE.as_ref())), - AttrValue::$variant(s) if as_bytes(s) == ATTR_FALSE.as_bytes() - )); - assert!(matches!( - AttrValue::$function(Some(ATTR_UNSET.as_ref())), - AttrValue::$variant(s) if as_bytes(s) == ATTR_UNSET.as_bytes() - )); - assert!(matches!( - AttrValue::$function(Some("foo".as_ref())), - AttrValue::$variant(s) if as_bytes(s) == b"foo" - )); - assert!(matches!( - AttrValue::$function(Some("bar".as_ref())), - AttrValue::$variant(s) if as_bytes(s) == b"bar" - )); - assert_eq!(AttrValue::$function(None), AttrValue::Unspecified); - }; - } - - #[test] - fn attr_value_from_string() { - test_attr_value!(from_string, String); - } - - #[test] - fn attr_value_from_bytes() { - test_attr_value!(from_bytes, String); - assert!(matches!( - AttrValue::from_bytes(Some(&[0xff])), - AttrValue::Bytes(&[0xff]) - )); - assert!(matches!( - AttrValue::from_bytes(Some(b"\xffoobar")), - AttrValue::Bytes(b"\xffoobar") - )); - } - - #[test] - fn attr_value_always_bytes() { - test_attr_value!(always_bytes, Bytes); - assert!(matches!( - AttrValue::always_bytes(Some(&[0xff; 2])), - AttrValue::Bytes(&[0xff, 0xff]) - )); - assert!(matches!( - AttrValue::always_bytes(Some(b"\xffoo")), - AttrValue::Bytes(b"\xffoo") - )); - } - - #[test] - fn attr_value_partial_eq() { - assert_eq!(AttrValue::True, AttrValue::True); - assert_eq!(AttrValue::False, AttrValue::False); - assert_eq!(AttrValue::String("foo"), AttrValue::String("foo")); - assert_eq!(AttrValue::Bytes(b"foo"), AttrValue::Bytes(b"foo")); - assert_eq!(AttrValue::String("bar"), AttrValue::Bytes(b"bar")); - assert_eq!(AttrValue::Bytes(b"bar"), AttrValue::String("bar")); - assert_eq!(AttrValue::Unspecified, AttrValue::Unspecified); - assert_ne!(AttrValue::True, AttrValue::False); - assert_ne!(AttrValue::False, AttrValue::Unspecified); - assert_ne!(AttrValue::Unspecified, AttrValue::True); - assert_ne!(AttrValue::True, AttrValue::String("true")); - assert_ne!(AttrValue::Unspecified, AttrValue::Bytes(b"unspecified")); - assert_ne!(AttrValue::Bytes(b"false"), AttrValue::False); - assert_ne!(AttrValue::String("unspecified"), AttrValue::Unspecified); - assert_ne!(AttrValue::String("foo"), AttrValue::String("bar")); - assert_ne!(AttrValue::Bytes(b"foo"), AttrValue::Bytes(b"bar")); - assert_ne!(AttrValue::String("foo"), AttrValue::Bytes(b"bar")); - assert_ne!(AttrValue::Bytes(b"foo"), AttrValue::String("bar")); - } -} diff --git a/extra/git2/src/blame.rs b/extra/git2/src/blame.rs deleted file mode 100644 index 4bf41fed1..000000000 --- a/extra/git2/src/blame.rs +++ /dev/null @@ -1,379 +0,0 @@ -use crate::util::{self, Binding}; -use crate::{raw, signature, Error, Oid, Repository, Signature}; -use libc::c_char; -use std::iter::FusedIterator; -use std::mem; -use std::ops::Range; -use std::path::Path; -use std::{marker, ptr}; - -/// Opaque structure to hold blame results. -pub struct Blame<'repo> { - raw: *mut raw::git_blame, - _marker: marker::PhantomData<&'repo Repository>, -} - -/// Structure that represents a blame hunk. -pub struct BlameHunk<'blame> { - raw: *mut raw::git_blame_hunk, - _marker: marker::PhantomData<&'blame raw::git_blame>, -} - -/// Blame options -pub struct BlameOptions { - raw: raw::git_blame_options, -} - -/// An iterator over the hunks in a blame. -pub struct BlameIter<'blame> { - range: Range<usize>, - blame: &'blame Blame<'blame>, -} - -impl<'repo> Blame<'repo> { - /// Get blame data for a file that has been modified in memory. - /// - /// Lines that differ between the buffer and the committed version are - /// marked as having a zero OID for their final_commit_id. - pub fn blame_buffer(&self, buffer: &[u8]) -> Result<Blame<'_>, Error> { - let mut raw = ptr::null_mut(); - - unsafe { - try_call!(raw::git_blame_buffer( - &mut raw, - self.raw, - buffer.as_ptr() as *const c_char, - buffer.len() - )); - Ok(Binding::from_raw(raw)) - } - } - - /// Gets the number of hunks that exist in the blame structure. - pub fn len(&self) -> usize { - unsafe { raw::git_blame_get_hunk_count(self.raw) as usize } - } - - /// Return `true` is there is no hunk in the blame structure. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Gets the blame hunk at the given index. - pub fn get_index(&self, index: usize) -> Option<BlameHunk<'_>> { - unsafe { - let ptr = raw::git_blame_get_hunk_byindex(self.raw(), index as u32); - if ptr.is_null() { - None - } else { - Some(BlameHunk::from_raw_const(ptr)) - } - } - } - - /// Gets the hunk that relates to the given line number in the newest - /// commit. - pub fn get_line(&self, lineno: usize) -> Option<BlameHunk<'_>> { - unsafe { - let ptr = raw::git_blame_get_hunk_byline(self.raw(), lineno); - if ptr.is_null() { - None - } else { - Some(BlameHunk::from_raw_const(ptr)) - } - } - } - - /// Returns an iterator over the hunks in this blame. - pub fn iter(&self) -> BlameIter<'_> { - BlameIter { - range: 0..self.len(), - blame: self, - } - } -} - -impl<'blame> BlameHunk<'blame> { - unsafe fn from_raw_const(raw: *const raw::git_blame_hunk) -> BlameHunk<'blame> { - BlameHunk { - raw: raw as *mut raw::git_blame_hunk, - _marker: marker::PhantomData, - } - } - - /// Returns OID of the commit where this line was last changed - pub fn final_commit_id(&self) -> Oid { - unsafe { Oid::from_raw(&(*self.raw).final_commit_id) } - } - - /// Returns signature of the commit. - pub fn final_signature(&self) -> Signature<'_> { - unsafe { signature::from_raw_const(self, (*self.raw).final_signature) } - } - - /// Returns line number where this hunk begins. - /// - /// Note that the start line is counting from 1. - pub fn final_start_line(&self) -> usize { - unsafe { (*self.raw).final_start_line_number } - } - - /// Returns the OID of the commit where this hunk was found. - /// - /// This will usually be the same as `final_commit_id`, - /// except when `BlameOptions::track_copies_any_commit_copies` has been - /// turned on - pub fn orig_commit_id(&self) -> Oid { - unsafe { Oid::from_raw(&(*self.raw).orig_commit_id) } - } - - /// Returns signature of the commit. - pub fn orig_signature(&self) -> Signature<'_> { - unsafe { signature::from_raw_const(self, (*self.raw).orig_signature) } - } - - /// Returns line number where this hunk begins. - /// - /// Note that the start line is counting from 1. - pub fn orig_start_line(&self) -> usize { - unsafe { (*self.raw).orig_start_line_number } - } - - /// Returns path to the file where this hunk originated. - /// - /// Note: `None` could be returned for non-unicode paths on Windows. - pub fn path(&self) -> Option<&Path> { - unsafe { - if let Some(bytes) = crate::opt_bytes(self, (*self.raw).orig_path) { - Some(util::bytes2path(bytes)) - } else { - None - } - } - } - - /// Tests whether this hunk has been tracked to a boundary commit - /// (the root, or the commit specified in git_blame_options.oldest_commit). - pub fn is_boundary(&self) -> bool { - unsafe { (*self.raw).boundary == 1 } - } - - /// Returns number of lines in this hunk. - pub fn lines_in_hunk(&self) -> usize { - unsafe { (*self.raw).lines_in_hunk as usize } - } -} - -impl Default for BlameOptions { - fn default() -> Self { - Self::new() - } -} - -impl BlameOptions { - /// Initialize options - pub fn new() -> BlameOptions { - unsafe { - let mut raw: raw::git_blame_options = mem::zeroed(); - assert_eq!( - raw::git_blame_init_options(&mut raw, raw::GIT_BLAME_OPTIONS_VERSION), - 0 - ); - - Binding::from_raw(&raw as *const _ as *mut _) - } - } - - fn flag(&mut self, opt: u32, val: bool) -> &mut BlameOptions { - if val { - self.raw.flags |= opt; - } else { - self.raw.flags &= !opt; - } - self - } - - /// Track lines that have moved within a file. - pub fn track_copies_same_file(&mut self, opt: bool) -> &mut BlameOptions { - self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_FILE, opt) - } - - /// Track lines that have moved across files in the same commit. - pub fn track_copies_same_commit_moves(&mut self, opt: bool) -> &mut BlameOptions { - self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES, opt) - } - - /// Track lines that have been copied from another file that exists - /// in the same commit. - pub fn track_copies_same_commit_copies(&mut self, opt: bool) -> &mut BlameOptions { - self.flag(raw::GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES, opt) - } - - /// Track lines that have been copied from another file that exists - /// in any commit. - pub fn track_copies_any_commit_copies(&mut self, opt: bool) -> &mut BlameOptions { - self.flag(raw::GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES, opt) - } - - /// Restrict the search of commits to those reachable following only - /// the first parents. - pub fn first_parent(&mut self, opt: bool) -> &mut BlameOptions { - self.flag(raw::GIT_BLAME_FIRST_PARENT, opt) - } - - /// Use mailmap file to map author and committer names and email addresses - /// to canonical real names and email addresses. The mailmap will be read - /// from the working directory, or HEAD in a bare repository. - pub fn use_mailmap(&mut self, opt: bool) -> &mut BlameOptions { - self.flag(raw::GIT_BLAME_USE_MAILMAP, opt) - } - - /// Ignore whitespace differences. - pub fn ignore_whitespace(&mut self, opt: bool) -> &mut BlameOptions { - self.flag(raw::GIT_BLAME_IGNORE_WHITESPACE, opt) - } - - /// Setter for the id of the newest commit to consider. - pub fn newest_commit(&mut self, id: Oid) -> &mut BlameOptions { - unsafe { - self.raw.newest_commit = *id.raw(); - } - self - } - - /// Setter for the id of the oldest commit to consider. - pub fn oldest_commit(&mut self, id: Oid) -> &mut BlameOptions { - unsafe { - self.raw.oldest_commit = *id.raw(); - } - self - } - - /// The first line in the file to blame. - pub fn min_line(&mut self, lineno: usize) -> &mut BlameOptions { - self.raw.min_line = lineno; - self - } - - /// The last line in the file to blame. - pub fn max_line(&mut self, lineno: usize) -> &mut BlameOptions { - self.raw.max_line = lineno; - self - } -} - -impl<'repo> Binding for Blame<'repo> { - type Raw = *mut raw::git_blame; - - unsafe fn from_raw(raw: *mut raw::git_blame) -> Blame<'repo> { - Blame { - raw, - _marker: marker::PhantomData, - } - } - - fn raw(&self) -> *mut raw::git_blame { - self.raw - } -} - -impl<'repo> Drop for Blame<'repo> { - fn drop(&mut self) { - unsafe { raw::git_blame_free(self.raw) } - } -} - -impl<'blame> Binding for BlameHunk<'blame> { - type Raw = *mut raw::git_blame_hunk; - - unsafe fn from_raw(raw: *mut raw::git_blame_hunk) -> BlameHunk<'blame> { - BlameHunk { - raw, - _marker: marker::PhantomData, - } - } - - fn raw(&self) -> *mut raw::git_blame_hunk { - self.raw - } -} - -impl Binding for BlameOptions { - type Raw = *mut raw::git_blame_options; - - unsafe fn from_raw(opts: *mut raw::git_blame_options) -> BlameOptions { - BlameOptions { raw: *opts } - } - - fn raw(&self) -> *mut raw::git_blame_options { - &self.raw as *const _ as *mut _ - } -} - -impl<'blame> Iterator for BlameIter<'blame> { - type Item = BlameHunk<'blame>; - fn next(&mut self) -> Option<BlameHunk<'blame>> { - self.range.next().and_then(|i| self.blame.get_index(i)) - } - - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} - -impl<'blame> DoubleEndedIterator for BlameIter<'blame> { - fn next_back(&mut self) -> Option<BlameHunk<'blame>> { - self.range.next_back().and_then(|i| self.blame.get_index(i)) - } -} - -impl<'blame> FusedIterator for BlameIter<'blame> {} - -impl<'blame> ExactSizeIterator for BlameIter<'blame> {} - -#[cfg(test)] -mod tests { - use std::fs::{self, File}; - use std::path::Path; - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let mut index = repo.index().unwrap(); - - let root = repo.workdir().unwrap(); - fs::create_dir(&root.join("foo")).unwrap(); - File::create(&root.join("foo/bar")).unwrap(); - index.add_path(Path::new("foo/bar")).unwrap(); - - let id = index.write_tree().unwrap(); - let tree = repo.find_tree(id).unwrap(); - let sig = repo.signature().unwrap(); - let id = repo.refname_to_id("HEAD").unwrap(); - let parent = repo.find_commit(id).unwrap(); - let commit = repo - .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent]) - .unwrap(); - - let blame = repo.blame_file(Path::new("foo/bar"), None).unwrap(); - - assert_eq!(blame.len(), 1); - assert_eq!(blame.iter().count(), 1); - - let hunk = blame.get_index(0).unwrap(); - assert_eq!(hunk.final_commit_id(), commit); - assert_eq!(hunk.final_signature().name(), sig.name()); - assert_eq!(hunk.final_signature().email(), sig.email()); - assert_eq!(hunk.final_start_line(), 1); - assert_eq!(hunk.path(), Some(Path::new("foo/bar"))); - assert_eq!(hunk.lines_in_hunk(), 0); - assert!(!hunk.is_boundary()); - - let blame_buffer = blame.blame_buffer("\n".as_bytes()).unwrap(); - let line = blame_buffer.get_line(1).unwrap(); - - assert_eq!(blame_buffer.len(), 2); - assert_eq!(blame_buffer.iter().count(), 2); - assert!(line.final_commit_id().is_zero()); - } -} diff --git a/extra/git2/src/blob.rs b/extra/git2/src/blob.rs deleted file mode 100644 index 5c4a6ce6b..000000000 --- a/extra/git2/src/blob.rs +++ /dev/null @@ -1,208 +0,0 @@ -use std::io; -use std::marker; -use std::mem; -use std::slice; - -use crate::util::Binding; -use crate::{raw, Error, Object, Oid}; - -/// A structure to represent a git [blob][1] -/// -/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects -pub struct Blob<'repo> { - raw: *mut raw::git_blob, - _marker: marker::PhantomData<Object<'repo>>, -} - -impl<'repo> Blob<'repo> { - /// Get the id (SHA1) of a repository blob - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_blob_id(&*self.raw)) } - } - - /// Determine if the blob content is most certainly binary or not. - pub fn is_binary(&self) -> bool { - unsafe { raw::git_blob_is_binary(&*self.raw) == 1 } - } - - /// Get the content of this blob. - pub fn content(&self) -> &[u8] { - unsafe { - let data = raw::git_blob_rawcontent(&*self.raw) as *const u8; - let len = raw::git_blob_rawsize(&*self.raw) as usize; - slice::from_raw_parts(data, len) - } - } - - /// Get the size in bytes of the contents of this blob. - pub fn size(&self) -> usize { - unsafe { raw::git_blob_rawsize(&*self.raw) as usize } - } - - /// Casts this Blob to be usable as an `Object` - pub fn as_object(&self) -> &Object<'repo> { - unsafe { &*(self as *const _ as *const Object<'repo>) } - } - - /// Consumes Blob to be returned as an `Object` - pub fn into_object(self) -> Object<'repo> { - assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>()); - unsafe { mem::transmute(self) } - } -} - -impl<'repo> Binding for Blob<'repo> { - type Raw = *mut raw::git_blob; - - unsafe fn from_raw(raw: *mut raw::git_blob) -> Blob<'repo> { - Blob { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_blob { - self.raw - } -} - -impl<'repo> std::fmt::Debug for Blob<'repo> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_struct("Blob").field("id", &self.id()).finish() - } -} - -impl<'repo> Clone for Blob<'repo> { - fn clone(&self) -> Self { - self.as_object().clone().into_blob().ok().unwrap() - } -} - -impl<'repo> Drop for Blob<'repo> { - fn drop(&mut self) { - unsafe { raw::git_blob_free(self.raw) } - } -} - -/// A structure to represent a git writestream for blobs -pub struct BlobWriter<'repo> { - raw: *mut raw::git_writestream, - need_cleanup: bool, - _marker: marker::PhantomData<Object<'repo>>, -} - -impl<'repo> BlobWriter<'repo> { - /// Finalize blob writing stream and write the blob to the object db - pub fn commit(mut self) -> Result<Oid, Error> { - // After commit we already doesn't need cleanup on drop - self.need_cleanup = false; - let mut raw = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_blob_create_fromstream_commit(&mut raw, self.raw)); - Ok(Binding::from_raw(&raw as *const _)) - } - } -} - -impl<'repo> Binding for BlobWriter<'repo> { - type Raw = *mut raw::git_writestream; - - unsafe fn from_raw(raw: *mut raw::git_writestream) -> BlobWriter<'repo> { - BlobWriter { - raw, - need_cleanup: true, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_writestream { - self.raw - } -} - -impl<'repo> Drop for BlobWriter<'repo> { - fn drop(&mut self) { - // We need cleanup in case the stream has not been committed - if self.need_cleanup { - unsafe { - if let Some(f) = (*self.raw).free { - f(self.raw) - } - } - } - } -} - -impl<'repo> io::Write for BlobWriter<'repo> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - unsafe { - if let Some(f) = (*self.raw).write { - let res = f(self.raw, buf.as_ptr() as *const _, buf.len()); - if res < 0 { - Err(io::Error::new(io::ErrorKind::Other, "Write error")) - } else { - Ok(buf.len()) - } - } else { - Err(io::Error::new(io::ErrorKind::Other, "no write callback")) - } - } - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::Repository; - use std::fs::File; - use std::io::prelude::*; - use std::path::Path; - use tempfile::TempDir; - - #[test] - fn buffer() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let id = repo.blob(&[5, 4, 6]).unwrap(); - let blob = repo.find_blob(id).unwrap(); - - assert_eq!(blob.id(), id); - assert_eq!(blob.size(), 3); - assert_eq!(blob.content(), [5, 4, 6]); - assert!(blob.is_binary()); - - repo.find_object(id, None).unwrap().as_blob().unwrap(); - repo.find_object(id, None) - .unwrap() - .into_blob() - .ok() - .unwrap(); - } - - #[test] - fn path() { - let td = TempDir::new().unwrap(); - let path = td.path().join("foo"); - File::create(&path).unwrap().write_all(&[7, 8, 9]).unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let id = repo.blob_path(&path).unwrap(); - let blob = repo.find_blob(id).unwrap(); - assert_eq!(blob.content(), [7, 8, 9]); - blob.into_object(); - } - - #[test] - fn stream() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let mut ws = repo.blob_writer(Some(Path::new("foo"))).unwrap(); - let wl = ws.write(&[10, 11, 12]).unwrap(); - assert_eq!(wl, 3); - let id = ws.commit().unwrap(); - let blob = repo.find_blob(id).unwrap(); - assert_eq!(blob.content(), [10, 11, 12]); - blob.into_object(); - } -} diff --git a/extra/git2/src/branch.rs b/extra/git2/src/branch.rs deleted file mode 100644 index e1eba99c2..000000000 --- a/extra/git2/src/branch.rs +++ /dev/null @@ -1,197 +0,0 @@ -use std::ffi::CString; -use std::marker; -use std::ptr; -use std::str; - -use crate::util::Binding; -use crate::{raw, BranchType, Error, Reference, References}; - -/// A structure to represent a git [branch][1] -/// -/// A branch is currently just a wrapper to an underlying `Reference`. The -/// reference can be accessed through the `get` and `into_reference` methods. -/// -/// [1]: http://git-scm.com/book/en/Git-Branching-What-a-Branch-Is -pub struct Branch<'repo> { - inner: Reference<'repo>, -} - -/// An iterator over the branches inside of a repository. -pub struct Branches<'repo> { - raw: *mut raw::git_branch_iterator, - _marker: marker::PhantomData<References<'repo>>, -} - -impl<'repo> Branch<'repo> { - /// Creates Branch type from a Reference - pub fn wrap(reference: Reference<'_>) -> Branch<'_> { - Branch { inner: reference } - } - - /// Ensure the branch name is well-formed. - pub fn name_is_valid(name: &str) -> Result<bool, Error> { - crate::init(); - let name = CString::new(name)?; - let mut valid: libc::c_int = 0; - unsafe { - try_call!(raw::git_branch_name_is_valid(&mut valid, name.as_ptr())); - } - Ok(valid == 1) - } - - /// Gain access to the reference that is this branch - pub fn get(&self) -> &Reference<'repo> { - &self.inner - } - - /// Gain mutable access to the reference that is this branch - pub fn get_mut(&mut self) -> &mut Reference<'repo> { - &mut self.inner - } - - /// Take ownership of the underlying reference. - pub fn into_reference(self) -> Reference<'repo> { - self.inner - } - - /// Delete an existing branch reference. - pub fn delete(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_branch_delete(self.get().raw())); - } - Ok(()) - } - - /// Determine if the current local branch is pointed at by HEAD. - pub fn is_head(&self) -> bool { - unsafe { raw::git_branch_is_head(&*self.get().raw()) == 1 } - } - - /// Move/rename an existing local branch reference. - pub fn rename(&mut self, new_branch_name: &str, force: bool) -> Result<Branch<'repo>, Error> { - let mut ret = ptr::null_mut(); - let new_branch_name = CString::new(new_branch_name)?; - unsafe { - try_call!(raw::git_branch_move( - &mut ret, - self.get().raw(), - new_branch_name, - force - )); - Ok(Branch::wrap(Binding::from_raw(ret))) - } - } - - /// Return the name of the given local or remote branch. - /// - /// May return `Ok(None)` if the name is not valid utf-8. - pub fn name(&self) -> Result<Option<&str>, Error> { - self.name_bytes().map(|s| str::from_utf8(s).ok()) - } - - /// Return the name of the given local or remote branch. - pub fn name_bytes(&self) -> Result<&[u8], Error> { - let mut ret = ptr::null(); - unsafe { - try_call!(raw::git_branch_name(&mut ret, &*self.get().raw())); - Ok(crate::opt_bytes(self, ret).unwrap()) - } - } - - /// Return the reference supporting the remote tracking branch, given a - /// local branch reference. - pub fn upstream(&self) -> Result<Branch<'repo>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_branch_upstream(&mut ret, &*self.get().raw())); - Ok(Branch::wrap(Binding::from_raw(ret))) - } - } - - /// Set the upstream configuration for a given local branch. - /// - /// If `None` is specified, then the upstream branch is unset. The name - /// provided is the name of the branch to set as upstream. - pub fn set_upstream(&mut self, upstream_name: Option<&str>) -> Result<(), Error> { - let upstream_name = crate::opt_cstr(upstream_name)?; - unsafe { - try_call!(raw::git_branch_set_upstream( - self.get().raw(), - upstream_name - )); - Ok(()) - } - } -} - -impl<'repo> Branches<'repo> { - /// Creates a new iterator from the raw pointer given. - /// - /// This function is unsafe as it is not guaranteed that `raw` is a valid - /// pointer. - pub unsafe fn from_raw(raw: *mut raw::git_branch_iterator) -> Branches<'repo> { - Branches { - raw, - _marker: marker::PhantomData, - } - } -} - -impl<'repo> Iterator for Branches<'repo> { - type Item = Result<(Branch<'repo>, BranchType), Error>; - fn next(&mut self) -> Option<Result<(Branch<'repo>, BranchType), Error>> { - let mut ret = ptr::null_mut(); - let mut typ = raw::GIT_BRANCH_LOCAL; - unsafe { - try_call_iter!(raw::git_branch_next(&mut ret, &mut typ, self.raw)); - let typ = match typ { - raw::GIT_BRANCH_LOCAL => BranchType::Local, - raw::GIT_BRANCH_REMOTE => BranchType::Remote, - n => panic!("unexected branch type: {}", n), - }; - Some(Ok((Branch::wrap(Binding::from_raw(ret)), typ))) - } - } -} - -impl<'repo> Drop for Branches<'repo> { - fn drop(&mut self) { - unsafe { raw::git_branch_iterator_free(self.raw) } - } -} - -#[cfg(test)] -mod tests { - use crate::{Branch, BranchType}; - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let head = repo.head().unwrap(); - let target = head.target().unwrap(); - let commit = repo.find_commit(target).unwrap(); - - let mut b1 = repo.branch("foo", &commit, false).unwrap(); - assert!(!b1.is_head()); - repo.branch("foo2", &commit, false).unwrap(); - - assert_eq!(repo.branches(None).unwrap().count(), 3); - repo.find_branch("foo", BranchType::Local).unwrap(); - let mut b1 = b1.rename("bar", false).unwrap(); - assert_eq!(b1.name().unwrap(), Some("bar")); - assert!(b1.upstream().is_err()); - b1.set_upstream(Some("main")).unwrap(); - b1.upstream().unwrap(); - b1.set_upstream(None).unwrap(); - - b1.delete().unwrap(); - } - - #[test] - fn name_is_valid() { - assert!(Branch::name_is_valid("foo").unwrap()); - assert!(!Branch::name_is_valid("").unwrap()); - assert!(!Branch::name_is_valid("with spaces").unwrap()); - assert!(!Branch::name_is_valid("~tilde").unwrap()); - } -} diff --git a/extra/git2/src/buf.rs b/extra/git2/src/buf.rs deleted file mode 100644 index fd2bcbf96..000000000 --- a/extra/git2/src/buf.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::ops::{Deref, DerefMut}; -use std::ptr; -use std::slice; -use std::str; - -use crate::raw; -use crate::util::Binding; - -/// A structure to wrap an intermediate buffer used by libgit2. -/// -/// A buffer can be thought of a `Vec<u8>`, but the `Vec` type is not used to -/// avoid copying data back and forth. -pub struct Buf { - raw: raw::git_buf, -} - -impl Default for Buf { - fn default() -> Self { - Self::new() - } -} - -impl Buf { - /// Creates a new empty buffer. - pub fn new() -> Buf { - crate::init(); - unsafe { - Binding::from_raw(&mut raw::git_buf { - ptr: ptr::null_mut(), - size: 0, - reserved: 0, - } as *mut _) - } - } - - /// Attempt to view this buffer as a string slice. - /// - /// Returns `None` if the buffer is not valid utf-8. - pub fn as_str(&self) -> Option<&str> { - str::from_utf8(&**self).ok() - } -} - -impl Deref for Buf { - type Target = [u8]; - fn deref(&self) -> &[u8] { - unsafe { slice::from_raw_parts(self.raw.ptr as *const u8, self.raw.size as usize) } - } -} - -impl DerefMut for Buf { - fn deref_mut(&mut self) -> &mut [u8] { - unsafe { slice::from_raw_parts_mut(self.raw.ptr as *mut u8, self.raw.size as usize) } - } -} - -impl Binding for Buf { - type Raw = *mut raw::git_buf; - unsafe fn from_raw(raw: *mut raw::git_buf) -> Buf { - Buf { raw: *raw } - } - fn raw(&self) -> *mut raw::git_buf { - &self.raw as *const _ as *mut _ - } -} - -impl Drop for Buf { - fn drop(&mut self) { - unsafe { raw::git_buf_dispose(&mut self.raw) } - } -} diff --git a/extra/git2/src/build.rs b/extra/git2/src/build.rs deleted file mode 100644 index d3c95f655..000000000 --- a/extra/git2/src/build.rs +++ /dev/null @@ -1,861 +0,0 @@ -//! Builder-pattern objects for configuration various git operations. - -use libc::{c_char, c_int, c_uint, c_void, size_t}; -use std::ffi::{CStr, CString}; -use std::mem; -use std::path::Path; -use std::ptr; - -use crate::util::{self, Binding}; -use crate::{panic, raw, Error, FetchOptions, IntoCString, Oid, Repository, Tree}; -use crate::{CheckoutNotificationType, DiffFile, FileMode, Remote}; - -/// A builder struct which is used to build configuration for cloning a new git -/// repository. -/// -/// # Example -/// -/// Cloning using SSH: -/// -/// ```no_run -/// use git2::{Cred, Error, RemoteCallbacks}; -/// use std::env; -/// use std::path::Path; -/// -/// // Prepare callbacks. -/// let mut callbacks = RemoteCallbacks::new(); -/// callbacks.credentials(|_url, username_from_url, _allowed_types| { -/// Cred::ssh_key( -/// username_from_url.unwrap(), -/// None, -/// Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())), -/// None, -/// ) -/// }); -/// -/// // Prepare fetch options. -/// let mut fo = git2::FetchOptions::new(); -/// fo.remote_callbacks(callbacks); -/// -/// // Prepare builder. -/// let mut builder = git2::build::RepoBuilder::new(); -/// builder.fetch_options(fo); -/// -/// // Clone the project. -/// builder.clone( -/// "git@github.com:rust-lang/git2-rs.git", -/// Path::new("/tmp/git2-rs"), -/// ); -/// ``` -pub struct RepoBuilder<'cb> { - bare: bool, - branch: Option<CString>, - local: bool, - hardlinks: bool, - checkout: Option<CheckoutBuilder<'cb>>, - fetch_opts: Option<FetchOptions<'cb>>, - clone_local: Option<CloneLocal>, - remote_create: Option<Box<RemoteCreate<'cb>>>, -} - -/// Type of callback passed to `RepoBuilder::remote_create`. -/// -/// The second and third arguments are the remote's name and the remote's URL. -pub type RemoteCreate<'cb> = - dyn for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb; - -/// A builder struct for git tree updates. -/// -/// Paths passed to `remove` and `upsert` can be multi-component paths, i.e. they -/// may contain slashes. -/// -/// This is a higher-level tree update facility. There is also [`TreeBuilder`] -/// which is lower-level (and operates only on one level of the tree at a time). -/// -/// [`TreeBuilder`]: crate::TreeBuilder -pub struct TreeUpdateBuilder { - updates: Vec<raw::git_tree_update>, - paths: Vec<CString>, -} - -/// A builder struct for configuring checkouts of a repository. -pub struct CheckoutBuilder<'cb> { - their_label: Option<CString>, - our_label: Option<CString>, - ancestor_label: Option<CString>, - target_dir: Option<CString>, - paths: Vec<CString>, - path_ptrs: Vec<*const c_char>, - file_perm: Option<i32>, - dir_perm: Option<i32>, - disable_filters: bool, - checkout_opts: u32, - progress: Option<Box<Progress<'cb>>>, - notify: Option<Box<Notify<'cb>>>, - notify_flags: CheckoutNotificationType, -} - -/// Checkout progress notification callback. -/// -/// The first argument is the path for the notification, the next is the number -/// of completed steps so far, and the final is the total number of steps. -pub type Progress<'a> = dyn FnMut(Option<&Path>, usize, usize) + 'a; - -/// Checkout notifications callback. -/// -/// The first argument is the notification type, the next is the path for the -/// the notification, followed by the baseline diff, target diff, and workdir diff. -/// -/// The callback must return a bool specifying whether the checkout should -/// continue. -pub type Notify<'a> = dyn FnMut( - CheckoutNotificationType, - Option<&Path>, - Option<DiffFile<'_>>, - Option<DiffFile<'_>>, - Option<DiffFile<'_>>, - ) -> bool - + 'a; - -impl<'cb> Default for RepoBuilder<'cb> { - fn default() -> Self { - Self::new() - } -} - -/// Options that can be passed to `RepoBuilder::clone_local`. -#[derive(Clone, Copy)] -pub enum CloneLocal { - /// Auto-detect (default) - /// - /// Here libgit2 will bypass the git-aware transport for local paths, but - /// use a normal fetch for `file://` URLs. - Auto = raw::GIT_CLONE_LOCAL_AUTO as isize, - - /// Bypass the git-aware transport even for `file://` URLs. - Local = raw::GIT_CLONE_LOCAL as isize, - - /// Never bypass the git-aware transport - None = raw::GIT_CLONE_NO_LOCAL as isize, - - /// Bypass the git-aware transport, but don't try to use hardlinks. - NoLinks = raw::GIT_CLONE_LOCAL_NO_LINKS as isize, - - #[doc(hidden)] - __Nonexhaustive = 0xff, -} - -impl<'cb> RepoBuilder<'cb> { - /// Creates a new repository builder with all of the default configuration. - /// - /// When ready, the `clone()` method can be used to clone a new repository - /// using this configuration. - pub fn new() -> RepoBuilder<'cb> { - crate::init(); - RepoBuilder { - bare: false, - branch: None, - local: true, - clone_local: None, - hardlinks: true, - checkout: None, - fetch_opts: None, - remote_create: None, - } - } - - /// Indicate whether the repository will be cloned as a bare repository or - /// not. - pub fn bare(&mut self, bare: bool) -> &mut RepoBuilder<'cb> { - self.bare = bare; - self - } - - /// Specify the name of the branch to check out after the clone. - /// - /// If not specified, the remote's default branch will be used. - pub fn branch(&mut self, branch: &str) -> &mut RepoBuilder<'cb> { - self.branch = Some(CString::new(branch).unwrap()); - self - } - - /// Configures options for bypassing the git-aware transport on clone. - /// - /// Bypassing it means that instead of a fetch libgit2 will copy the object - /// database directory instead of figuring out what it needs, which is - /// faster. If possible, it will hardlink the files to save space. - pub fn clone_local(&mut self, clone_local: CloneLocal) -> &mut RepoBuilder<'cb> { - self.clone_local = Some(clone_local); - self - } - - /// Set the flag for bypassing the git aware transport mechanism for local - /// paths. - /// - /// If `true`, the git-aware transport will be bypassed for local paths. If - /// `false`, the git-aware transport will not be bypassed. - #[deprecated(note = "use `clone_local` instead")] - #[doc(hidden)] - pub fn local(&mut self, local: bool) -> &mut RepoBuilder<'cb> { - self.local = local; - self - } - - /// Set the flag for whether hardlinks are used when using a local git-aware - /// transport mechanism. - #[deprecated(note = "use `clone_local` instead")] - #[doc(hidden)] - pub fn hardlinks(&mut self, links: bool) -> &mut RepoBuilder<'cb> { - self.hardlinks = links; - self - } - - /// Configure the checkout which will be performed by consuming a checkout - /// builder. - pub fn with_checkout(&mut self, checkout: CheckoutBuilder<'cb>) -> &mut RepoBuilder<'cb> { - self.checkout = Some(checkout); - self - } - - /// Options which control the fetch, including callbacks. - /// - /// The callbacks are used for reporting fetch progress, and for acquiring - /// credentials in the event they are needed. - pub fn fetch_options(&mut self, fetch_opts: FetchOptions<'cb>) -> &mut RepoBuilder<'cb> { - self.fetch_opts = Some(fetch_opts); - self - } - - /// Configures a callback used to create the git remote, prior to its being - /// used to perform the clone operation. - pub fn remote_create<F>(&mut self, f: F) -> &mut RepoBuilder<'cb> - where - F: for<'a> FnMut(&'a Repository, &str, &str) -> Result<Remote<'a>, Error> + 'cb, - { - self.remote_create = Some(Box::new(f)); - self - } - - /// Clone a remote repository. - /// - /// This will use the options configured so far to clone the specified URL - /// into the specified local path. - pub fn clone(&mut self, url: &str, into: &Path) -> Result<Repository, Error> { - let mut opts: raw::git_clone_options = unsafe { mem::zeroed() }; - unsafe { - try_call!(raw::git_clone_init_options( - &mut opts, - raw::GIT_CLONE_OPTIONS_VERSION - )); - } - opts.bare = self.bare as c_int; - opts.checkout_branch = self - .branch - .as_ref() - .map(|s| s.as_ptr()) - .unwrap_or(ptr::null()); - - if let Some(ref local) = self.clone_local { - opts.local = *local as raw::git_clone_local_t; - } else { - opts.local = match (self.local, self.hardlinks) { - (true, false) => raw::GIT_CLONE_LOCAL_NO_LINKS, - (false, _) => raw::GIT_CLONE_NO_LOCAL, - (true, _) => raw::GIT_CLONE_LOCAL_AUTO, - }; - } - - if let Some(ref mut cbs) = self.fetch_opts { - opts.fetch_opts = cbs.raw(); - } - - if let Some(ref mut c) = self.checkout { - unsafe { - c.configure(&mut opts.checkout_opts); - } - } - - if let Some(ref mut callback) = self.remote_create { - opts.remote_cb = Some(remote_create_cb); - opts.remote_cb_payload = callback as *mut _ as *mut _; - } - - let url = CString::new(url)?; - // Normal file path OK (does not need Windows conversion). - let into = into.into_c_string()?; - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_clone(&mut raw, url, into, &opts)); - Ok(Binding::from_raw(raw)) - } - } -} - -extern "C" fn remote_create_cb( - out: *mut *mut raw::git_remote, - repo: *mut raw::git_repository, - name: *const c_char, - url: *const c_char, - payload: *mut c_void, -) -> c_int { - unsafe { - let repo = Repository::from_raw(repo); - let code = panic::wrap(|| { - let name = CStr::from_ptr(name).to_str().unwrap(); - let url = CStr::from_ptr(url).to_str().unwrap(); - let f = payload as *mut Box<RemoteCreate<'_>>; - match (*f)(&repo, name, url) { - Ok(remote) => { - *out = crate::remote::remote_into_raw(remote); - 0 - } - Err(e) => e.raw_code(), - } - }); - mem::forget(repo); - code.unwrap_or(-1) - } -} - -impl<'cb> Default for CheckoutBuilder<'cb> { - fn default() -> Self { - Self::new() - } -} - -impl<'cb> CheckoutBuilder<'cb> { - /// Creates a new builder for checkouts with all of its default - /// configuration. - pub fn new() -> CheckoutBuilder<'cb> { - crate::init(); - CheckoutBuilder { - disable_filters: false, - dir_perm: None, - file_perm: None, - path_ptrs: Vec::new(), - paths: Vec::new(), - target_dir: None, - ancestor_label: None, - our_label: None, - their_label: None, - checkout_opts: raw::GIT_CHECKOUT_SAFE as u32, - progress: None, - notify: None, - notify_flags: CheckoutNotificationType::empty(), - } - } - - /// Indicate that this checkout should perform a dry run by checking for - /// conflicts but not make any actual changes. - pub fn dry_run(&mut self) -> &mut CheckoutBuilder<'cb> { - self.checkout_opts &= !((1 << 4) - 1); - self.checkout_opts |= raw::GIT_CHECKOUT_NONE as u32; - self - } - - /// Take any action necessary to get the working directory to match the - /// target including potentially discarding modified files. - pub fn force(&mut self) -> &mut CheckoutBuilder<'cb> { - self.checkout_opts &= !((1 << 4) - 1); - self.checkout_opts |= raw::GIT_CHECKOUT_FORCE as u32; - self - } - - /// Indicate that the checkout should be performed safely, allowing new - /// files to be created but not overwriting existing files or changes. - /// - /// This is the default. - pub fn safe(&mut self) -> &mut CheckoutBuilder<'cb> { - self.checkout_opts &= !((1 << 4) - 1); - self.checkout_opts |= raw::GIT_CHECKOUT_SAFE as u32; - self - } - - fn flag(&mut self, bit: raw::git_checkout_strategy_t, on: bool) -> &mut CheckoutBuilder<'cb> { - if on { - self.checkout_opts |= bit as u32; - } else { - self.checkout_opts &= !(bit as u32); - } - self - } - - /// In safe mode, create files that don't exist. - /// - /// Defaults to false. - pub fn recreate_missing(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_RECREATE_MISSING, allow) - } - - /// In safe mode, apply safe file updates even when there are conflicts - /// instead of canceling the checkout. - /// - /// Defaults to false. - pub fn allow_conflicts(&mut self, allow: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_ALLOW_CONFLICTS, allow) - } - - /// Remove untracked files from the working dir. - /// - /// Defaults to false. - pub fn remove_untracked(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_REMOVE_UNTRACKED, remove) - } - - /// Remove ignored files from the working dir. - /// - /// Defaults to false. - pub fn remove_ignored(&mut self, remove: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_REMOVE_IGNORED, remove) - } - - /// Only update the contents of files that already exist. - /// - /// If set, files will not be created or deleted. - /// - /// Defaults to false. - pub fn update_only(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_UPDATE_ONLY, update) - } - - /// Prevents checkout from writing the updated files' information to the - /// index. - /// - /// Defaults to true. - pub fn update_index(&mut self, update: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_DONT_UPDATE_INDEX, !update) - } - - /// Indicate whether the index and git attributes should be refreshed from - /// disk before any operations. - /// - /// Defaults to true, - pub fn refresh(&mut self, refresh: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_NO_REFRESH, !refresh) - } - - /// Skip files with unmerged index entries. - /// - /// Defaults to false. - pub fn skip_unmerged(&mut self, skip: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_SKIP_UNMERGED, skip) - } - - /// Indicate whether the checkout should proceed on conflicts by using the - /// stage 2 version of the file ("ours"). - /// - /// Defaults to false. - pub fn use_ours(&mut self, ours: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_USE_OURS, ours) - } - - /// Indicate whether the checkout should proceed on conflicts by using the - /// stage 3 version of the file ("theirs"). - /// - /// Defaults to false. - pub fn use_theirs(&mut self, theirs: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_USE_THEIRS, theirs) - } - - /// Indicate whether ignored files should be overwritten during the checkout. - /// - /// Defaults to true. - pub fn overwrite_ignored(&mut self, overwrite: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, !overwrite) - } - - /// Indicate whether a normal merge file should be written for conflicts. - /// - /// Defaults to false. - pub fn conflict_style_merge(&mut self, on: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_MERGE, on) - } - - /// Specify for which notification types to invoke the notification - /// callback. - /// - /// Defaults to none. - pub fn notify_on( - &mut self, - notification_types: CheckoutNotificationType, - ) -> &mut CheckoutBuilder<'cb> { - self.notify_flags = notification_types; - self - } - - /// Indicates whether to include common ancestor data in diff3 format files - /// for conflicts. - /// - /// Defaults to false. - pub fn conflict_style_diff3(&mut self, on: bool) -> &mut CheckoutBuilder<'cb> { - self.flag(raw::GIT_CHECKOUT_CONFLICT_STYLE_DIFF3, on) - } - - /// Indicate whether to apply filters like CRLF conversion. - pub fn disable_filters(&mut self, disable: bool) -> &mut CheckoutBuilder<'cb> { - self.disable_filters = disable; - self - } - - /// Set the mode with which new directories are created. - /// - /// Default is 0755 - pub fn dir_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb> { - self.dir_perm = Some(perm); - self - } - - /// Set the mode with which new files are created. - /// - /// The default is 0644 or 0755 as dictated by the blob. - pub fn file_perm(&mut self, perm: i32) -> &mut CheckoutBuilder<'cb> { - self.file_perm = Some(perm); - self - } - - /// Add a path to be checked out. - /// - /// If no paths are specified, then all files are checked out. Otherwise - /// only these specified paths are checked out. - pub fn path<T: IntoCString>(&mut self, path: T) -> &mut CheckoutBuilder<'cb> { - let path = util::cstring_to_repo_path(path).unwrap(); - self.path_ptrs.push(path.as_ptr()); - self.paths.push(path); - self - } - - /// Set the directory to check out to - pub fn target_dir(&mut self, dst: &Path) -> &mut CheckoutBuilder<'cb> { - // Normal file path OK (does not need Windows conversion). - self.target_dir = Some(dst.into_c_string().unwrap()); - self - } - - /// The name of the common ancestor side of conflicts - pub fn ancestor_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> { - self.ancestor_label = Some(CString::new(label).unwrap()); - self - } - - /// The name of the common our side of conflicts - pub fn our_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> { - self.our_label = Some(CString::new(label).unwrap()); - self - } - - /// The name of the common their side of conflicts - pub fn their_label(&mut self, label: &str) -> &mut CheckoutBuilder<'cb> { - self.their_label = Some(CString::new(label).unwrap()); - self - } - - /// Set a callback to receive notifications of checkout progress. - pub fn progress<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb> - where - F: FnMut(Option<&Path>, usize, usize) + 'cb, - { - self.progress = Some(Box::new(cb) as Box<Progress<'cb>>); - self - } - - /// Set a callback to receive checkout notifications. - /// - /// Callbacks are invoked prior to modifying any files on disk. - /// Returning `false` from the callback will cancel the checkout. - pub fn notify<F>(&mut self, cb: F) -> &mut CheckoutBuilder<'cb> - where - F: FnMut( - CheckoutNotificationType, - Option<&Path>, - Option<DiffFile<'_>>, - Option<DiffFile<'_>>, - Option<DiffFile<'_>>, - ) -> bool - + 'cb, - { - self.notify = Some(Box::new(cb) as Box<Notify<'cb>>); - self - } - - /// Configure a raw checkout options based on this configuration. - /// - /// This method is unsafe as there is no guarantee that this structure will - /// outlive the provided checkout options. - pub unsafe fn configure(&mut self, opts: &mut raw::git_checkout_options) { - opts.version = raw::GIT_CHECKOUT_OPTIONS_VERSION; - opts.disable_filters = self.disable_filters as c_int; - opts.dir_mode = self.dir_perm.unwrap_or(0) as c_uint; - opts.file_mode = self.file_perm.unwrap_or(0) as c_uint; - - if !self.path_ptrs.is_empty() { - opts.paths.strings = self.path_ptrs.as_ptr() as *mut _; - opts.paths.count = self.path_ptrs.len() as size_t; - } - - if let Some(ref c) = self.target_dir { - opts.target_directory = c.as_ptr(); - } - if let Some(ref c) = self.ancestor_label { - opts.ancestor_label = c.as_ptr(); - } - if let Some(ref c) = self.our_label { - opts.our_label = c.as_ptr(); - } - if let Some(ref c) = self.their_label { - opts.their_label = c.as_ptr(); - } - if self.progress.is_some() { - opts.progress_cb = Some(progress_cb); - opts.progress_payload = self as *mut _ as *mut _; - } - if self.notify.is_some() { - opts.notify_cb = Some(notify_cb); - opts.notify_payload = self as *mut _ as *mut _; - opts.notify_flags = self.notify_flags.bits() as c_uint; - } - opts.checkout_strategy = self.checkout_opts as c_uint; - } -} - -extern "C" fn progress_cb( - path: *const c_char, - completed: size_t, - total: size_t, - data: *mut c_void, -) { - panic::wrap(|| unsafe { - let payload = &mut *(data as *mut CheckoutBuilder<'_>); - let callback = match payload.progress { - Some(ref mut c) => c, - None => return, - }; - let path = if path.is_null() { - None - } else { - Some(util::bytes2path(CStr::from_ptr(path).to_bytes())) - }; - callback(path, completed as usize, total as usize) - }); -} - -extern "C" fn notify_cb( - why: raw::git_checkout_notify_t, - path: *const c_char, - baseline: *const raw::git_diff_file, - target: *const raw::git_diff_file, - workdir: *const raw::git_diff_file, - data: *mut c_void, -) -> c_int { - // pack callback etc - panic::wrap(|| unsafe { - let payload = &mut *(data as *mut CheckoutBuilder<'_>); - let callback = match payload.notify { - Some(ref mut c) => c, - None => return 0, - }; - let path = if path.is_null() { - None - } else { - Some(util::bytes2path(CStr::from_ptr(path).to_bytes())) - }; - - let baseline = if baseline.is_null() { - None - } else { - Some(DiffFile::from_raw(baseline)) - }; - - let target = if target.is_null() { - None - } else { - Some(DiffFile::from_raw(target)) - }; - - let workdir = if workdir.is_null() { - None - } else { - Some(DiffFile::from_raw(workdir)) - }; - - let why = CheckoutNotificationType::from_bits_truncate(why as u32); - let keep_going = callback(why, path, baseline, target, workdir); - if keep_going { - 0 - } else { - 1 - } - }) - .unwrap_or(2) -} - -unsafe impl Send for TreeUpdateBuilder {} - -impl Default for TreeUpdateBuilder { - fn default() -> Self { - Self::new() - } -} - -impl TreeUpdateBuilder { - /// Create a new empty series of updates. - pub fn new() -> Self { - Self { - updates: Vec::new(), - paths: Vec::new(), - } - } - - /// Add an update removing the specified `path` from a tree. - pub fn remove<T: IntoCString>(&mut self, path: T) -> &mut Self { - let path = util::cstring_to_repo_path(path).unwrap(); - let path_ptr = path.as_ptr(); - self.paths.push(path); - self.updates.push(raw::git_tree_update { - action: raw::GIT_TREE_UPDATE_REMOVE, - id: raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }, - filemode: raw::GIT_FILEMODE_UNREADABLE, - path: path_ptr, - }); - self - } - - /// Add an update setting the specified `path` to a specific Oid, whether it currently exists - /// or not. - /// - /// Note that libgit2 does not support an upsert of a previously removed path, or an upsert - /// that changes the type of an object (such as from tree to blob or vice versa). - pub fn upsert<T: IntoCString>(&mut self, path: T, id: Oid, filemode: FileMode) -> &mut Self { - let path = util::cstring_to_repo_path(path).unwrap(); - let path_ptr = path.as_ptr(); - self.paths.push(path); - self.updates.push(raw::git_tree_update { - action: raw::GIT_TREE_UPDATE_UPSERT, - id: unsafe { *id.raw() }, - filemode: u32::from(filemode) as raw::git_filemode_t, - path: path_ptr, - }); - self - } - - /// Create a new tree from the specified baseline and this series of updates. - /// - /// The baseline tree must exist in the specified repository. - pub fn create_updated(&mut self, repo: &Repository, baseline: &Tree<'_>) -> Result<Oid, Error> { - let mut ret = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_tree_create_updated( - &mut ret, - repo.raw(), - baseline.raw(), - self.updates.len(), - self.updates.as_ptr() - )); - Ok(Binding::from_raw(&ret as *const _)) - } - } -} - -#[cfg(test)] -mod tests { - use super::{CheckoutBuilder, RepoBuilder, TreeUpdateBuilder}; - use crate::{CheckoutNotificationType, FileMode, Repository}; - use std::fs; - use std::path::Path; - use tempfile::TempDir; - - #[test] - fn smoke() { - let r = RepoBuilder::new().clone("/path/to/nowhere", Path::new("foo")); - assert!(r.is_err()); - } - - #[test] - fn smoke2() { - let td = TempDir::new().unwrap(); - Repository::init_bare(&td.path().join("bare")).unwrap(); - let url = if cfg!(unix) { - format!("file://{}/bare", td.path().display()) - } else { - format!( - "file:///{}/bare", - td.path().display().to_string().replace("\\", "/") - ) - }; - - let dst = td.path().join("foo"); - RepoBuilder::new().clone(&url, &dst).unwrap(); - fs::remove_dir_all(&dst).unwrap(); - assert!(RepoBuilder::new().branch("foo").clone(&url, &dst).is_err()); - } - - #[test] - fn smoke_tree_create_updated() { - let (_tempdir, repo) = crate::test::repo_init(); - let (_, tree_id) = crate::test::commit(&repo); - let tree = t!(repo.find_tree(tree_id)); - assert!(tree.get_name("bar").is_none()); - let foo_id = tree.get_name("foo").unwrap().id(); - let tree2_id = t!(TreeUpdateBuilder::new() - .remove("foo") - .upsert("bar/baz", foo_id, FileMode::Blob) - .create_updated(&repo, &tree)); - let tree2 = t!(repo.find_tree(tree2_id)); - assert!(tree2.get_name("foo").is_none()); - let baz_id = tree2.get_path(Path::new("bar/baz")).unwrap().id(); - assert_eq!(foo_id, baz_id); - } - - /// Issue regression test #365 - #[test] - fn notify_callback() { - let td = TempDir::new().unwrap(); - let cd = TempDir::new().unwrap(); - - { - let mut opts = crate::RepositoryInitOptions::new(); - opts.initial_head("main"); - let repo = Repository::init_opts(&td.path(), &opts).unwrap(); - - let mut config = repo.config().unwrap(); - config.set_str("user.name", "name").unwrap(); - config.set_str("user.email", "email").unwrap(); - - let mut index = repo.index().unwrap(); - let p = Path::new(td.path()).join("file"); - println!("using path {:?}", p); - fs::File::create(&p).unwrap(); - index.add_path(&Path::new("file")).unwrap(); - let id = index.write_tree().unwrap(); - - let tree = repo.find_tree(id).unwrap(); - let sig = repo.signature().unwrap(); - repo.commit(Some("HEAD"), &sig, &sig, "initial", &tree, &[]) - .unwrap(); - } - - let repo = Repository::open_bare(&td.path().join(".git")).unwrap(); - let tree = repo - .revparse_single(&"main") - .unwrap() - .peel_to_tree() - .unwrap(); - let mut index = repo.index().unwrap(); - index.read_tree(&tree).unwrap(); - - let mut checkout_opts = CheckoutBuilder::new(); - checkout_opts.target_dir(&cd.path()); - checkout_opts.notify_on(CheckoutNotificationType::all()); - checkout_opts.notify(|_notif, _path, baseline, target, workdir| { - assert!(baseline.is_none()); - assert_eq!(target.unwrap().path(), Some(Path::new("file"))); - assert!(workdir.is_none()); - true - }); - repo.checkout_index(Some(&mut index), Some(&mut checkout_opts)) - .unwrap(); - } -} diff --git a/extra/git2/src/call.rs b/extra/git2/src/call.rs deleted file mode 100644 index d9fd23468..000000000 --- a/extra/git2/src/call.rs +++ /dev/null @@ -1,246 +0,0 @@ -#![macro_use] -use libc; - -use crate::Error; - -macro_rules! call { - (raw::$p:ident ($($e:expr),*)) => ( - raw::$p($(crate::call::convert(&$e)),*) - ) -} - -macro_rules! try_call { - (raw::$p:ident ($($e:expr),*)) => ({ - match crate::call::c_try(raw::$p($(crate::call::convert(&$e)),*)) { - Ok(o) => o, - Err(e) => { crate::panic::check(); return Err(e) } - } - }) -} - -macro_rules! try_call_iter { - ($($f:tt)*) => { - match call!($($f)*) { - 0 => {} - raw::GIT_ITEROVER => return None, - e => return Some(Err(crate::call::last_error(e))) - } - } -} - -#[doc(hidden)] -pub trait Convert<T> { - fn convert(&self) -> T; -} - -pub fn convert<T, U: Convert<T>>(u: &U) -> T { - u.convert() -} - -pub fn c_try(ret: libc::c_int) -> Result<libc::c_int, Error> { - match ret { - n if n < 0 => Err(last_error(n)), - n => Ok(n), - } -} - -pub fn last_error(code: libc::c_int) -> Error { - // nowadays this unwrap is safe as `Error::last_error` always returns - // `Some`. - Error::last_error(code).unwrap() -} - -mod impls { - use std::ffi::CString; - use std::ptr; - - use libc; - - use crate::call::Convert; - use crate::{raw, BranchType, ConfigLevel, Direction, ObjectType, ResetType}; - use crate::{ - AutotagOption, DiffFormat, FetchPrune, FileFavor, SubmoduleIgnore, SubmoduleUpdate, - }; - - impl<T: Copy> Convert<T> for T { - fn convert(&self) -> T { - *self - } - } - - impl Convert<libc::c_int> for bool { - fn convert(&self) -> libc::c_int { - *self as libc::c_int - } - } - impl<'a, T> Convert<*const T> for &'a T { - fn convert(&self) -> *const T { - *self as *const T - } - } - impl<'a, T> Convert<*mut T> for &'a mut T { - fn convert(&self) -> *mut T { - &**self as *const T as *mut T - } - } - impl<T> Convert<*const T> for *mut T { - fn convert(&self) -> *const T { - *self as *const T - } - } - - impl Convert<*const libc::c_char> for CString { - fn convert(&self) -> *const libc::c_char { - self.as_ptr() - } - } - - impl<T, U: Convert<*const T>> Convert<*const T> for Option<U> { - fn convert(&self) -> *const T { - self.as_ref().map(|s| s.convert()).unwrap_or(ptr::null()) - } - } - - impl<T, U: Convert<*mut T>> Convert<*mut T> for Option<U> { - fn convert(&self) -> *mut T { - self.as_ref() - .map(|s| s.convert()) - .unwrap_or(ptr::null_mut()) - } - } - - impl Convert<raw::git_reset_t> for ResetType { - fn convert(&self) -> raw::git_reset_t { - match *self { - ResetType::Soft => raw::GIT_RESET_SOFT, - ResetType::Hard => raw::GIT_RESET_HARD, - ResetType::Mixed => raw::GIT_RESET_MIXED, - } - } - } - - impl Convert<raw::git_direction> for Direction { - fn convert(&self) -> raw::git_direction { - match *self { - Direction::Push => raw::GIT_DIRECTION_PUSH, - Direction::Fetch => raw::GIT_DIRECTION_FETCH, - } - } - } - - impl Convert<raw::git_object_t> for ObjectType { - fn convert(&self) -> raw::git_object_t { - match *self { - ObjectType::Any => raw::GIT_OBJECT_ANY, - ObjectType::Commit => raw::GIT_OBJECT_COMMIT, - ObjectType::Tree => raw::GIT_OBJECT_TREE, - ObjectType::Blob => raw::GIT_OBJECT_BLOB, - ObjectType::Tag => raw::GIT_OBJECT_TAG, - } - } - } - - impl Convert<raw::git_object_t> for Option<ObjectType> { - fn convert(&self) -> raw::git_object_t { - self.unwrap_or(ObjectType::Any).convert() - } - } - - impl Convert<raw::git_branch_t> for BranchType { - fn convert(&self) -> raw::git_branch_t { - match *self { - BranchType::Remote => raw::GIT_BRANCH_REMOTE, - BranchType::Local => raw::GIT_BRANCH_LOCAL, - } - } - } - - impl Convert<raw::git_branch_t> for Option<BranchType> { - fn convert(&self) -> raw::git_branch_t { - self.map(|s| s.convert()).unwrap_or(raw::GIT_BRANCH_ALL) - } - } - - impl Convert<raw::git_config_level_t> for ConfigLevel { - fn convert(&self) -> raw::git_config_level_t { - match *self { - ConfigLevel::ProgramData => raw::GIT_CONFIG_LEVEL_PROGRAMDATA, - ConfigLevel::System => raw::GIT_CONFIG_LEVEL_SYSTEM, - ConfigLevel::XDG => raw::GIT_CONFIG_LEVEL_XDG, - ConfigLevel::Global => raw::GIT_CONFIG_LEVEL_GLOBAL, - ConfigLevel::Local => raw::GIT_CONFIG_LEVEL_LOCAL, - ConfigLevel::App => raw::GIT_CONFIG_LEVEL_APP, - ConfigLevel::Highest => raw::GIT_CONFIG_HIGHEST_LEVEL, - } - } - } - - impl Convert<raw::git_diff_format_t> for DiffFormat { - fn convert(&self) -> raw::git_diff_format_t { - match *self { - DiffFormat::Patch => raw::GIT_DIFF_FORMAT_PATCH, - DiffFormat::PatchHeader => raw::GIT_DIFF_FORMAT_PATCH_HEADER, - DiffFormat::Raw => raw::GIT_DIFF_FORMAT_RAW, - DiffFormat::NameOnly => raw::GIT_DIFF_FORMAT_NAME_ONLY, - DiffFormat::NameStatus => raw::GIT_DIFF_FORMAT_NAME_STATUS, - DiffFormat::PatchId => raw::GIT_DIFF_FORMAT_PATCH_ID, - } - } - } - - impl Convert<raw::git_merge_file_favor_t> for FileFavor { - fn convert(&self) -> raw::git_merge_file_favor_t { - match *self { - FileFavor::Normal => raw::GIT_MERGE_FILE_FAVOR_NORMAL, - FileFavor::Ours => raw::GIT_MERGE_FILE_FAVOR_OURS, - FileFavor::Theirs => raw::GIT_MERGE_FILE_FAVOR_THEIRS, - FileFavor::Union => raw::GIT_MERGE_FILE_FAVOR_UNION, - } - } - } - - impl Convert<raw::git_submodule_ignore_t> for SubmoduleIgnore { - fn convert(&self) -> raw::git_submodule_ignore_t { - match *self { - SubmoduleIgnore::Unspecified => raw::GIT_SUBMODULE_IGNORE_UNSPECIFIED, - SubmoduleIgnore::None => raw::GIT_SUBMODULE_IGNORE_NONE, - SubmoduleIgnore::Untracked => raw::GIT_SUBMODULE_IGNORE_UNTRACKED, - SubmoduleIgnore::Dirty => raw::GIT_SUBMODULE_IGNORE_DIRTY, - SubmoduleIgnore::All => raw::GIT_SUBMODULE_IGNORE_ALL, - } - } - } - - impl Convert<raw::git_submodule_update_t> for SubmoduleUpdate { - fn convert(&self) -> raw::git_submodule_update_t { - match *self { - SubmoduleUpdate::Checkout => raw::GIT_SUBMODULE_UPDATE_CHECKOUT, - SubmoduleUpdate::Rebase => raw::GIT_SUBMODULE_UPDATE_REBASE, - SubmoduleUpdate::Merge => raw::GIT_SUBMODULE_UPDATE_MERGE, - SubmoduleUpdate::None => raw::GIT_SUBMODULE_UPDATE_NONE, - SubmoduleUpdate::Default => raw::GIT_SUBMODULE_UPDATE_DEFAULT, - } - } - } - - impl Convert<raw::git_remote_autotag_option_t> for AutotagOption { - fn convert(&self) -> raw::git_remote_autotag_option_t { - match *self { - AutotagOption::Unspecified => raw::GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED, - AutotagOption::None => raw::GIT_REMOTE_DOWNLOAD_TAGS_NONE, - AutotagOption::Auto => raw::GIT_REMOTE_DOWNLOAD_TAGS_AUTO, - AutotagOption::All => raw::GIT_REMOTE_DOWNLOAD_TAGS_ALL, - } - } - } - - impl Convert<raw::git_fetch_prune_t> for FetchPrune { - fn convert(&self) -> raw::git_fetch_prune_t { - match *self { - FetchPrune::Unspecified => raw::GIT_FETCH_PRUNE_UNSPECIFIED, - FetchPrune::On => raw::GIT_FETCH_PRUNE, - FetchPrune::Off => raw::GIT_FETCH_NO_PRUNE, - } - } - } -} diff --git a/extra/git2/src/cert.rs b/extra/git2/src/cert.rs deleted file mode 100644 index b232cc3ce..000000000 --- a/extra/git2/src/cert.rs +++ /dev/null @@ -1,191 +0,0 @@ -//! Certificate types which are passed to `CertificateCheck` in -//! `RemoteCallbacks`. - -use std::marker; -use std::mem; -use std::slice; - -use crate::raw; -use crate::util::Binding; - -/// A certificate for a remote connection, viewable as one of `CertHostkey` or -/// `CertX509` currently. -pub struct Cert<'a> { - raw: *mut raw::git_cert, - _marker: marker::PhantomData<&'a raw::git_cert>, -} - -/// Hostkey information taken from libssh2 -pub struct CertHostkey<'a> { - raw: *mut raw::git_cert_hostkey, - _marker: marker::PhantomData<&'a raw::git_cert>, -} - -/// X.509 certificate information -pub struct CertX509<'a> { - raw: *mut raw::git_cert_x509, - _marker: marker::PhantomData<&'a raw::git_cert>, -} - -/// The SSH host key type. -#[derive(Copy, Clone, Debug)] -#[non_exhaustive] -pub enum SshHostKeyType { - /// Unknown key type - Unknown = raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN as isize, - /// RSA key type - Rsa = raw::GIT_CERT_SSH_RAW_TYPE_RSA as isize, - /// DSS key type - Dss = raw::GIT_CERT_SSH_RAW_TYPE_DSS as isize, - /// ECDSA 256 key type - Ecdsa256 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 as isize, - /// ECDSA 384 key type - Ecdsa384 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 as isize, - /// ECDSA 521 key type - Ecdsa521 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 as isize, - /// ED25519 key type - Ed255219 = raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 as isize, -} - -impl SshHostKeyType { - /// The name of the key type as encoded in the known_hosts file. - pub fn name(&self) -> &'static str { - match self { - SshHostKeyType::Unknown => "unknown", - SshHostKeyType::Rsa => "ssh-rsa", - SshHostKeyType::Dss => "ssh-dss", - SshHostKeyType::Ecdsa256 => "ecdsa-sha2-nistp256", - SshHostKeyType::Ecdsa384 => "ecdsa-sha2-nistp384", - SshHostKeyType::Ecdsa521 => "ecdsa-sha2-nistp521", - SshHostKeyType::Ed255219 => "ssh-ed25519", - } - } - - /// A short name of the key type, the colloquial form used as a human-readable description. - pub fn short_name(&self) -> &'static str { - match self { - SshHostKeyType::Unknown => "Unknown", - SshHostKeyType::Rsa => "RSA", - SshHostKeyType::Dss => "DSA", - SshHostKeyType::Ecdsa256 => "ECDSA", - SshHostKeyType::Ecdsa384 => "ECDSA", - SshHostKeyType::Ecdsa521 => "ECDSA", - SshHostKeyType::Ed255219 => "ED25519", - } - } -} - -impl<'a> Cert<'a> { - /// Attempt to view this certificate as an SSH hostkey. - /// - /// Returns `None` if this is not actually an SSH hostkey. - pub fn as_hostkey(&self) -> Option<&CertHostkey<'a>> { - self.cast(raw::GIT_CERT_HOSTKEY_LIBSSH2) - } - - /// Attempt to view this certificate as an X.509 certificate. - /// - /// Returns `None` if this is not actually an X.509 certificate. - pub fn as_x509(&self) -> Option<&CertX509<'a>> { - self.cast(raw::GIT_CERT_X509) - } - - fn cast<T>(&self, kind: raw::git_cert_t) -> Option<&T> { - assert_eq!(mem::size_of::<Cert<'a>>(), mem::size_of::<T>()); - unsafe { - if kind == (*self.raw).cert_type { - Some(&*(self as *const Cert<'a> as *const T)) - } else { - None - } - } - } -} - -impl<'a> CertHostkey<'a> { - /// Returns the md5 hash of the hostkey, if available. - pub fn hash_md5(&self) -> Option<&[u8; 16]> { - unsafe { - if (*self.raw).kind as u32 & raw::GIT_CERT_SSH_MD5 as u32 == 0 { - None - } else { - Some(&(*self.raw).hash_md5) - } - } - } - - /// Returns the SHA-1 hash of the hostkey, if available. - pub fn hash_sha1(&self) -> Option<&[u8; 20]> { - unsafe { - if (*self.raw).kind as u32 & raw::GIT_CERT_SSH_SHA1 as u32 == 0 { - None - } else { - Some(&(*self.raw).hash_sha1) - } - } - } - - /// Returns the SHA-256 hash of the hostkey, if available. - pub fn hash_sha256(&self) -> Option<&[u8; 32]> { - unsafe { - if (*self.raw).kind as u32 & raw::GIT_CERT_SSH_SHA256 as u32 == 0 { - None - } else { - Some(&(*self.raw).hash_sha256) - } - } - } - - /// Returns the raw host key. - pub fn hostkey(&self) -> Option<&[u8]> { - unsafe { - if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 { - return None; - } - Some(slice::from_raw_parts( - (*self.raw).hostkey as *const u8, - (*self.raw).hostkey_len as usize, - )) - } - } - - /// Returns the type of the host key. - pub fn hostkey_type(&self) -> Option<SshHostKeyType> { - unsafe { - if (*self.raw).kind & raw::GIT_CERT_SSH_RAW == 0 { - return None; - } - let t = match (*self.raw).raw_type { - raw::GIT_CERT_SSH_RAW_TYPE_UNKNOWN => SshHostKeyType::Unknown, - raw::GIT_CERT_SSH_RAW_TYPE_RSA => SshHostKeyType::Rsa, - raw::GIT_CERT_SSH_RAW_TYPE_DSS => SshHostKeyType::Dss, - raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_256 => SshHostKeyType::Ecdsa256, - raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_384 => SshHostKeyType::Ecdsa384, - raw::GIT_CERT_SSH_RAW_TYPE_KEY_ECDSA_521 => SshHostKeyType::Ecdsa521, - raw::GIT_CERT_SSH_RAW_TYPE_KEY_ED25519 => SshHostKeyType::Ed255219, - t => panic!("unexpected host key type {:?}", t), - }; - Some(t) - } - } -} - -impl<'a> CertX509<'a> { - /// Return the X.509 certificate data as a byte slice - pub fn data(&self) -> &[u8] { - unsafe { slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).len as usize) } - } -} - -impl<'a> Binding for Cert<'a> { - type Raw = *mut raw::git_cert; - unsafe fn from_raw(raw: *mut raw::git_cert) -> Cert<'a> { - Cert { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_cert { - self.raw - } -} diff --git a/extra/git2/src/cherrypick.rs b/extra/git2/src/cherrypick.rs deleted file mode 100644 index 659b73089..000000000 --- a/extra/git2/src/cherrypick.rs +++ /dev/null @@ -1,72 +0,0 @@ -use std::mem; - -use crate::build::CheckoutBuilder; -use crate::merge::MergeOptions; -use crate::raw; -use std::ptr; - -/// Options to specify when cherry picking -pub struct CherrypickOptions<'cb> { - mainline: u32, - checkout_builder: Option<CheckoutBuilder<'cb>>, - merge_opts: Option<MergeOptions>, -} - -impl<'cb> CherrypickOptions<'cb> { - /// Creates a default set of cherrypick options - pub fn new() -> CherrypickOptions<'cb> { - CherrypickOptions { - mainline: 0, - checkout_builder: None, - merge_opts: None, - } - } - - /// Set the mainline value - /// - /// For merge commits, the "mainline" is treated as the parent. - pub fn mainline(&mut self, mainline: u32) -> &mut Self { - self.mainline = mainline; - self - } - - /// Set the checkout builder - pub fn checkout_builder(&mut self, cb: CheckoutBuilder<'cb>) -> &mut Self { - self.checkout_builder = Some(cb); - self - } - - /// Set the merge options - pub fn merge_opts(&mut self, merge_opts: MergeOptions) -> &mut Self { - self.merge_opts = Some(merge_opts); - self - } - - /// Obtain the raw struct - pub fn raw(&mut self) -> raw::git_cherrypick_options { - unsafe { - let mut checkout_opts: raw::git_checkout_options = mem::zeroed(); - raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION); - if let Some(ref mut cb) = self.checkout_builder { - cb.configure(&mut checkout_opts); - } - - let mut merge_opts: raw::git_merge_options = mem::zeroed(); - raw::git_merge_init_options(&mut merge_opts, raw::GIT_MERGE_OPTIONS_VERSION); - if let Some(ref opts) = self.merge_opts { - ptr::copy(opts.raw(), &mut merge_opts, 1); - } - - let mut cherrypick_opts: raw::git_cherrypick_options = mem::zeroed(); - raw::git_cherrypick_init_options( - &mut cherrypick_opts, - raw::GIT_CHERRYPICK_OPTIONS_VERSION, - ); - cherrypick_opts.mainline = self.mainline; - cherrypick_opts.checkout_opts = checkout_opts; - cherrypick_opts.merge_opts = merge_opts; - - cherrypick_opts - } - } -} diff --git a/extra/git2/src/commit.rs b/extra/git2/src/commit.rs deleted file mode 100644 index 4887e927e..000000000 --- a/extra/git2/src/commit.rs +++ /dev/null @@ -1,473 +0,0 @@ -use libc; -use std::iter::FusedIterator; -use std::marker; -use std::mem; -use std::ops::Range; -use std::ptr; -use std::str; - -use crate::util::Binding; -use crate::{raw, signature, Buf, Error, IntoCString, Mailmap, Object, Oid, Signature, Time, Tree}; - -/// A structure to represent a git [commit][1] -/// -/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects -pub struct Commit<'repo> { - raw: *mut raw::git_commit, - _marker: marker::PhantomData<Object<'repo>>, -} - -/// An iterator over the parent commits of a commit. -/// -/// Aborts iteration when a commit cannot be found -pub struct Parents<'commit, 'repo> { - range: Range<usize>, - commit: &'commit Commit<'repo>, -} - -/// An iterator over the parent commits' ids of a commit. -/// -/// Aborts iteration when a commit cannot be found -pub struct ParentIds<'commit> { - range: Range<usize>, - commit: &'commit Commit<'commit>, -} - -impl<'repo> Commit<'repo> { - /// Get the id (SHA1) of a repository commit - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_commit_id(&*self.raw)) } - } - - /// Get the id of the tree pointed to by this commit. - /// - /// No attempts are made to fetch an object from the ODB. - pub fn tree_id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_commit_tree_id(&*self.raw)) } - } - - /// Get the tree pointed to by a commit. - pub fn tree(&self) -> Result<Tree<'repo>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_commit_tree(&mut ret, &*self.raw)); - Ok(Binding::from_raw(ret)) - } - } - - /// Get access to the underlying raw pointer. - pub fn raw(&self) -> *mut raw::git_commit { - self.raw - } - - /// Get the full message of a commit. - /// - /// The returned message will be slightly prettified by removing any - /// potential leading newlines. - /// - /// `None` will be returned if the message is not valid utf-8 - pub fn message(&self) -> Option<&str> { - str::from_utf8(self.message_bytes()).ok() - } - - /// Get the full message of a commit as a byte slice. - /// - /// The returned message will be slightly prettified by removing any - /// potential leading newlines. - pub fn message_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_commit_message(&*self.raw)).unwrap() } - } - - /// Get the encoding for the message of a commit, as a string representing a - /// standard encoding name. - /// - /// `None` will be returned if the encoding is not known - pub fn message_encoding(&self) -> Option<&str> { - let bytes = unsafe { crate::opt_bytes(self, raw::git_commit_message_encoding(&*self.raw)) }; - bytes.and_then(|b| str::from_utf8(b).ok()) - } - - /// Get the full raw message of a commit. - /// - /// `None` will be returned if the message is not valid utf-8 - pub fn message_raw(&self) -> Option<&str> { - str::from_utf8(self.message_raw_bytes()).ok() - } - - /// Get the full raw message of a commit. - pub fn message_raw_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_commit_message_raw(&*self.raw)).unwrap() } - } - - /// Get the full raw text of the commit header. - /// - /// `None` will be returned if the message is not valid utf-8 - pub fn raw_header(&self) -> Option<&str> { - str::from_utf8(self.raw_header_bytes()).ok() - } - - /// Get an arbitrary header field. - pub fn header_field_bytes<T: IntoCString>(&self, field: T) -> Result<Buf, Error> { - let buf = Buf::new(); - let raw_field = field.into_c_string()?; - unsafe { - try_call!(raw::git_commit_header_field( - buf.raw(), - &*self.raw, - raw_field - )); - } - Ok(buf) - } - - /// Get the full raw text of the commit header. - pub fn raw_header_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_commit_raw_header(&*self.raw)).unwrap() } - } - - /// Get the short "summary" of the git commit message. - /// - /// The returned message is the summary of the commit, comprising the first - /// paragraph of the message with whitespace trimmed and squashed. - /// - /// `None` may be returned if an error occurs or if the summary is not valid - /// utf-8. - pub fn summary(&self) -> Option<&str> { - self.summary_bytes().and_then(|s| str::from_utf8(s).ok()) - } - - /// Get the short "summary" of the git commit message. - /// - /// The returned message is the summary of the commit, comprising the first - /// paragraph of the message with whitespace trimmed and squashed. - /// - /// `None` may be returned if an error occurs - pub fn summary_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_commit_summary(self.raw)) } - } - - /// Get the long "body" of the git commit message. - /// - /// The returned message is the body of the commit, comprising everything - /// but the first paragraph of the message. Leading and trailing whitespaces - /// are trimmed. - /// - /// `None` may be returned if an error occurs or if the summary is not valid - /// utf-8. - pub fn body(&self) -> Option<&str> { - self.body_bytes().and_then(|s| str::from_utf8(s).ok()) - } - - /// Get the long "body" of the git commit message. - /// - /// The returned message is the body of the commit, comprising everything - /// but the first paragraph of the message. Leading and trailing whitespaces - /// are trimmed. - /// - /// `None` may be returned if an error occurs. - pub fn body_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_commit_body(self.raw)) } - } - - /// Get the commit time (i.e. committer time) of a commit. - /// - /// The first element of the tuple is the time, in seconds, since the epoch. - /// The second element is the offset, in minutes, of the time zone of the - /// committer's preferred time zone. - pub fn time(&self) -> Time { - unsafe { - Time::new( - raw::git_commit_time(&*self.raw) as i64, - raw::git_commit_time_offset(&*self.raw) as i32, - ) - } - } - - /// Creates a new iterator over the parents of this commit. - pub fn parents<'a>(&'a self) -> Parents<'a, 'repo> { - Parents { - range: 0..self.parent_count(), - commit: self, - } - } - - /// Creates a new iterator over the parents of this commit. - pub fn parent_ids(&self) -> ParentIds<'_> { - ParentIds { - range: 0..self.parent_count(), - commit: self, - } - } - - /// Get the author of this commit. - pub fn author(&self) -> Signature<'_> { - unsafe { - let ptr = raw::git_commit_author(&*self.raw); - signature::from_raw_const(self, ptr) - } - } - - /// Get the author of this commit, using the mailmap to map names and email - /// addresses to canonical real names and email addresses. - pub fn author_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_commit_author_with_mailmap( - &mut ret, - &*self.raw, - &*mailmap.raw() - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Get the committer of this commit. - pub fn committer(&self) -> Signature<'_> { - unsafe { - let ptr = raw::git_commit_committer(&*self.raw); - signature::from_raw_const(self, ptr) - } - } - - /// Get the committer of this commit, using the mailmap to map names and email - /// addresses to canonical real names and email addresses. - pub fn committer_with_mailmap(&self, mailmap: &Mailmap) -> Result<Signature<'static>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_commit_committer_with_mailmap( - &mut ret, - &*self.raw, - &*mailmap.raw() - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Amend this existing commit with all non-`None` values - /// - /// This creates a new commit that is exactly the same as the old commit, - /// except that any non-`None` values will be updated. The new commit has - /// the same parents as the old commit. - /// - /// For information about `update_ref`, see [`Repository::commit`]. - /// - /// [`Repository::commit`]: struct.Repository.html#method.commit - pub fn amend( - &self, - update_ref: Option<&str>, - author: Option<&Signature<'_>>, - committer: Option<&Signature<'_>>, - message_encoding: Option<&str>, - message: Option<&str>, - tree: Option<&Tree<'repo>>, - ) -> Result<Oid, Error> { - let mut raw = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - let update_ref = crate::opt_cstr(update_ref)?; - let encoding = crate::opt_cstr(message_encoding)?; - let message = crate::opt_cstr(message)?; - unsafe { - try_call!(raw::git_commit_amend( - &mut raw, - self.raw(), - update_ref, - author.map(|s| s.raw()), - committer.map(|s| s.raw()), - encoding, - message, - tree.map(|t| t.raw()) - )); - Ok(Binding::from_raw(&raw as *const _)) - } - } - - /// Get the number of parents of this commit. - /// - /// Use the `parents` iterator to return an iterator over all parents. - pub fn parent_count(&self) -> usize { - unsafe { raw::git_commit_parentcount(&*self.raw) as usize } - } - - /// Get the specified parent of the commit. - /// - /// Use the `parents` iterator to return an iterator over all parents. - pub fn parent(&self, i: usize) -> Result<Commit<'repo>, Error> { - unsafe { - let mut raw = ptr::null_mut(); - try_call!(raw::git_commit_parent( - &mut raw, - &*self.raw, - i as libc::c_uint - )); - Ok(Binding::from_raw(raw)) - } - } - - /// Get the specified parent id of the commit. - /// - /// This is different from `parent`, which will attempt to load the - /// parent commit from the ODB. - /// - /// Use the `parent_ids` iterator to return an iterator over all parents. - pub fn parent_id(&self, i: usize) -> Result<Oid, Error> { - unsafe { - let id = raw::git_commit_parent_id(self.raw, i as libc::c_uint); - if id.is_null() { - Err(Error::from_str("parent index out of bounds")) - } else { - Ok(Binding::from_raw(id)) - } - } - } - - /// Casts this Commit to be usable as an `Object` - pub fn as_object(&self) -> &Object<'repo> { - unsafe { &*(self as *const _ as *const Object<'repo>) } - } - - /// Consumes Commit to be returned as an `Object` - pub fn into_object(self) -> Object<'repo> { - assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>()); - unsafe { mem::transmute(self) } - } -} - -impl<'repo> Binding for Commit<'repo> { - type Raw = *mut raw::git_commit; - unsafe fn from_raw(raw: *mut raw::git_commit) -> Commit<'repo> { - Commit { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_commit { - self.raw - } -} - -impl<'repo> std::fmt::Debug for Commit<'repo> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - let mut ds = f.debug_struct("Commit"); - ds.field("id", &self.id()); - if let Some(summary) = self.summary() { - ds.field("summary", &summary); - } - ds.finish() - } -} - -/// Aborts iteration when a commit cannot be found -impl<'repo, 'commit> Iterator for Parents<'commit, 'repo> { - type Item = Commit<'repo>; - fn next(&mut self) -> Option<Commit<'repo>> { - self.range.next().and_then(|i| self.commit.parent(i).ok()) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} - -/// Aborts iteration when a commit cannot be found -impl<'repo, 'commit> DoubleEndedIterator for Parents<'commit, 'repo> { - fn next_back(&mut self) -> Option<Commit<'repo>> { - self.range - .next_back() - .and_then(|i| self.commit.parent(i).ok()) - } -} - -impl<'repo, 'commit> FusedIterator for Parents<'commit, 'repo> {} - -impl<'repo, 'commit> ExactSizeIterator for Parents<'commit, 'repo> {} - -/// Aborts iteration when a commit cannot be found -impl<'commit> Iterator for ParentIds<'commit> { - type Item = Oid; - fn next(&mut self) -> Option<Oid> { - self.range - .next() - .and_then(|i| self.commit.parent_id(i).ok()) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} - -/// Aborts iteration when a commit cannot be found -impl<'commit> DoubleEndedIterator for ParentIds<'commit> { - fn next_back(&mut self) -> Option<Oid> { - self.range - .next_back() - .and_then(|i| self.commit.parent_id(i).ok()) - } -} - -impl<'commit> FusedIterator for ParentIds<'commit> {} - -impl<'commit> ExactSizeIterator for ParentIds<'commit> {} - -impl<'repo> Clone for Commit<'repo> { - fn clone(&self) -> Self { - self.as_object().clone().into_commit().ok().unwrap() - } -} - -impl<'repo> Drop for Commit<'repo> { - fn drop(&mut self) { - unsafe { raw::git_commit_free(self.raw) } - } -} - -#[cfg(test)] -mod tests { - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let head = repo.head().unwrap(); - let target = head.target().unwrap(); - let commit = repo.find_commit(target).unwrap(); - assert_eq!(commit.message(), Some("initial\n\nbody")); - assert_eq!(commit.body(), Some("body")); - assert_eq!(commit.id(), target); - commit.message_raw().unwrap(); - commit.raw_header().unwrap(); - commit.message_encoding(); - commit.summary().unwrap(); - commit.body().unwrap(); - commit.tree_id(); - commit.tree().unwrap(); - assert_eq!(commit.parents().count(), 0); - - let tree_header_bytes = commit.header_field_bytes("tree").unwrap(); - assert_eq!( - crate::Oid::from_str(tree_header_bytes.as_str().unwrap()).unwrap(), - commit.tree_id() - ); - assert_eq!(commit.author().name(), Some("name")); - assert_eq!(commit.author().email(), Some("email")); - assert_eq!(commit.committer().name(), Some("name")); - assert_eq!(commit.committer().email(), Some("email")); - - let sig = repo.signature().unwrap(); - let tree = repo.find_tree(commit.tree_id()).unwrap(); - let id = repo - .commit(Some("HEAD"), &sig, &sig, "bar", &tree, &[&commit]) - .unwrap(); - let head = repo.find_commit(id).unwrap(); - - let new_head = head - .amend(Some("HEAD"), None, None, None, Some("new message"), None) - .unwrap(); - let new_head = repo.find_commit(new_head).unwrap(); - assert_eq!(new_head.message(), Some("new message")); - new_head.into_object(); - - repo.find_object(target, None).unwrap().as_commit().unwrap(); - repo.find_object(target, None) - .unwrap() - .into_commit() - .ok() - .unwrap(); - } -} diff --git a/extra/git2/src/config.rs b/extra/git2/src/config.rs deleted file mode 100644 index ae5c4ff63..000000000 --- a/extra/git2/src/config.rs +++ /dev/null @@ -1,777 +0,0 @@ -use libc; -use std::ffi::CString; -use std::marker; -use std::path::{Path, PathBuf}; -use std::ptr; -use std::str; - -use crate::util::{self, Binding}; -use crate::{raw, Buf, ConfigLevel, Error, IntoCString}; - -/// A structure representing a git configuration key/value store -pub struct Config { - raw: *mut raw::git_config, -} - -/// A struct representing a certain entry owned by a `Config` instance. -/// -/// An entry has a name, a value, and a level it applies to. -pub struct ConfigEntry<'cfg> { - raw: *mut raw::git_config_entry, - _marker: marker::PhantomData<&'cfg Config>, - owned: bool, -} - -/// An iterator over the `ConfigEntry` values of a `Config` structure. -/// -/// Due to lifetime restrictions, `ConfigEntries` does not implement the -/// standard [`Iterator`] trait. It provides a [`next`] function which only -/// allows access to one entry at a time. [`for_each`] is available as a -/// convenience function. -/// -/// [`next`]: ConfigEntries::next -/// [`for_each`]: ConfigEntries::for_each -/// -/// # Example -/// -/// ``` -/// // Example of how to collect all entries. -/// use git2::Config; -/// -/// let config = Config::new()?; -/// let iter = config.entries(None)?; -/// let mut entries = Vec::new(); -/// iter -/// .for_each(|entry| { -/// let name = entry.name().unwrap().to_string(); -/// let value = entry.value().unwrap_or("").to_string(); -/// entries.push((name, value)) -/// })?; -/// for entry in &entries { -/// println!("{} = {}", entry.0, entry.1); -/// } -/// # Ok::<(), git2::Error>(()) -/// -/// ``` -pub struct ConfigEntries<'cfg> { - raw: *mut raw::git_config_iterator, - current: Option<ConfigEntry<'cfg>>, - _marker: marker::PhantomData<&'cfg Config>, -} - -impl Config { - /// Allocate a new configuration object - /// - /// This object is empty, so you have to add a file to it before you can do - /// anything with it. - pub fn new() -> Result<Config, Error> { - crate::init(); - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_config_new(&mut raw)); - Ok(Binding::from_raw(raw)) - } - } - - /// Create a new config instance containing a single on-disk file - pub fn open(path: &Path) -> Result<Config, Error> { - crate::init(); - let mut raw = ptr::null_mut(); - // Normal file path OK (does not need Windows conversion). - let path = path.into_c_string()?; - unsafe { - try_call!(raw::git_config_open_ondisk(&mut raw, path)); - Ok(Binding::from_raw(raw)) - } - } - - /// Open the global, XDG and system configuration files - /// - /// Utility wrapper that finds the global, XDG and system configuration - /// files and opens them into a single prioritized config object that can - /// be used when accessing default config data outside a repository. - pub fn open_default() -> Result<Config, Error> { - crate::init(); - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_config_open_default(&mut raw)); - Ok(Binding::from_raw(raw)) - } - } - - /// Locate the path to the global configuration file - /// - /// The user or global configuration file is usually located in - /// `$HOME/.gitconfig`. - /// - /// This method will try to guess the full path to that file, if the file - /// exists. The returned path may be used on any method call to load - /// the global configuration file. - /// - /// This method will not guess the path to the XDG compatible config file - /// (`.config/git/config`). - pub fn find_global() -> Result<PathBuf, Error> { - crate::init(); - let buf = Buf::new(); - unsafe { - try_call!(raw::git_config_find_global(buf.raw())); - } - Ok(util::bytes2path(&buf).to_path_buf()) - } - - /// Locate the path to the system configuration file - /// - /// If /etc/gitconfig doesn't exist, it will look for `%PROGRAMFILES%` - pub fn find_system() -> Result<PathBuf, Error> { - crate::init(); - let buf = Buf::new(); - unsafe { - try_call!(raw::git_config_find_system(buf.raw())); - } - Ok(util::bytes2path(&buf).to_path_buf()) - } - - /// Locate the path to the global XDG compatible configuration file - /// - /// The XDG compatible configuration file is usually located in - /// `$HOME/.config/git/config`. - pub fn find_xdg() -> Result<PathBuf, Error> { - crate::init(); - let buf = Buf::new(); - unsafe { - try_call!(raw::git_config_find_xdg(buf.raw())); - } - Ok(util::bytes2path(&buf).to_path_buf()) - } - - /// Add an on-disk config file instance to an existing config - /// - /// The on-disk file pointed at by path will be opened and parsed; it's - /// expected to be a native Git config file following the default Git config - /// syntax (see man git-config). - /// - /// Further queries on this config object will access each of the config - /// file instances in order (instances with a higher priority level will be - /// accessed first). - pub fn add_file(&mut self, path: &Path, level: ConfigLevel, force: bool) -> Result<(), Error> { - // Normal file path OK (does not need Windows conversion). - let path = path.into_c_string()?; - unsafe { - try_call!(raw::git_config_add_file_ondisk( - self.raw, - path, - level, - ptr::null(), - force - )); - Ok(()) - } - } - - /// Delete a config variable from the config file with the highest level - /// (usually the local one). - pub fn remove(&mut self, name: &str) -> Result<(), Error> { - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_delete_entry(self.raw, name)); - Ok(()) - } - } - - /// Remove multivar config variables in the config file with the highest level (usually the - /// local one). - /// - /// The regular expression is applied case-sensitively on the value. - pub fn remove_multivar(&mut self, name: &str, regexp: &str) -> Result<(), Error> { - let name = CString::new(name)?; - let regexp = CString::new(regexp)?; - unsafe { - try_call!(raw::git_config_delete_multivar(self.raw, name, regexp)); - } - Ok(()) - } - - /// Get the value of a boolean config variable. - /// - /// All config files will be looked into, in the order of their defined - /// level. A higher level means a higher priority. The first occurrence of - /// the variable will be returned here. - pub fn get_bool(&self, name: &str) -> Result<bool, Error> { - let mut out = 0 as libc::c_int; - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_get_bool(&mut out, &*self.raw, name)); - } - Ok(out != 0) - } - - /// Get the value of an integer config variable. - /// - /// All config files will be looked into, in the order of their defined - /// level. A higher level means a higher priority. The first occurrence of - /// the variable will be returned here. - pub fn get_i32(&self, name: &str) -> Result<i32, Error> { - let mut out = 0i32; - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_get_int32(&mut out, &*self.raw, name)); - } - Ok(out) - } - - /// Get the value of an integer config variable. - /// - /// All config files will be looked into, in the order of their defined - /// level. A higher level means a higher priority. The first occurrence of - /// the variable will be returned here. - pub fn get_i64(&self, name: &str) -> Result<i64, Error> { - let mut out = 0i64; - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_get_int64(&mut out, &*self.raw, name)); - } - Ok(out) - } - - /// Get the value of a string config variable. - /// - /// This is the same as `get_bytes` except that it may return `Err` if - /// the bytes are not valid utf-8. - /// - /// This method will return an error if this `Config` is not a snapshot. - pub fn get_str(&self, name: &str) -> Result<&str, Error> { - str::from_utf8(self.get_bytes(name)?) - .map_err(|_| Error::from_str("configuration value is not valid utf8")) - } - - /// Get the value of a string config variable as a byte slice. - /// - /// This method will return an error if this `Config` is not a snapshot. - pub fn get_bytes(&self, name: &str) -> Result<&[u8], Error> { - let mut ret = ptr::null(); - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_get_string(&mut ret, &*self.raw, name)); - Ok(crate::opt_bytes(self, ret).unwrap()) - } - } - - /// Get the value of a string config variable as an owned string. - /// - /// All config files will be looked into, in the order of their - /// defined level. A higher level means a higher priority. The - /// first occurrence of the variable will be returned here. - /// - /// An error will be returned if the config value is not valid utf-8. - pub fn get_string(&self, name: &str) -> Result<String, Error> { - let ret = Buf::new(); - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_get_string_buf(ret.raw(), self.raw, name)); - } - str::from_utf8(&ret) - .map(|s| s.to_string()) - .map_err(|_| Error::from_str("configuration value is not valid utf8")) - } - - /// Get the value of a path config variable as an owned `PathBuf`. - /// - /// A leading '~' will be expanded to the global search path (which - /// defaults to the user's home directory but can be overridden via - /// [`raw::git_libgit2_opts`]. - /// - /// All config files will be looked into, in the order of their - /// defined level. A higher level means a higher priority. The - /// first occurrence of the variable will be returned here. - pub fn get_path(&self, name: &str) -> Result<PathBuf, Error> { - let ret = Buf::new(); - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_get_path(ret.raw(), self.raw, name)); - } - Ok(crate::util::bytes2path(&ret).to_path_buf()) - } - - /// Get the ConfigEntry for a config variable. - pub fn get_entry(&self, name: &str) -> Result<ConfigEntry<'_>, Error> { - let mut ret = ptr::null_mut(); - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_get_entry(&mut ret, self.raw, name)); - Ok(Binding::from_raw(ret)) - } - } - - /// Iterate over all the config variables - /// - /// If `glob` is `Some`, then the iterator will only iterate over all - /// variables whose name matches the pattern. - /// - /// The regular expression is applied case-sensitively on the normalized form of - /// the variable name: the section and variable parts are lower-cased. The - /// subsection is left unchanged. - /// - /// Due to lifetime restrictions, the returned value does not implement - /// the standard [`Iterator`] trait. See [`ConfigEntries`] for more. - /// - /// # Example - /// - /// ``` - /// use git2::Config; - /// - /// let cfg = Config::new().unwrap(); - /// - /// let mut entries = cfg.entries(None).unwrap(); - /// while let Some(entry) = entries.next() { - /// let entry = entry.unwrap(); - /// println!("{} => {}", entry.name().unwrap(), entry.value().unwrap()); - /// } - /// ``` - pub fn entries(&self, glob: Option<&str>) -> Result<ConfigEntries<'_>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - match glob { - Some(s) => { - let s = CString::new(s)?; - try_call!(raw::git_config_iterator_glob_new(&mut ret, &*self.raw, s)); - } - None => { - try_call!(raw::git_config_iterator_new(&mut ret, &*self.raw)); - } - } - Ok(Binding::from_raw(ret)) - } - } - - /// Iterate over the values of a multivar - /// - /// If `regexp` is `Some`, then the iterator will only iterate over all - /// values which match the pattern. - /// - /// The regular expression is applied case-sensitively on the normalized form of - /// the variable name: the section and variable parts are lower-cased. The - /// subsection is left unchanged. - /// - /// Due to lifetime restrictions, the returned value does not implement - /// the standard [`Iterator`] trait. See [`ConfigEntries`] for more. - pub fn multivar(&self, name: &str, regexp: Option<&str>) -> Result<ConfigEntries<'_>, Error> { - let mut ret = ptr::null_mut(); - let name = CString::new(name)?; - let regexp = regexp.map(CString::new).transpose()?; - unsafe { - try_call!(raw::git_config_multivar_iterator_new( - &mut ret, &*self.raw, name, regexp - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Open the global/XDG configuration file according to git's rules - /// - /// Git allows you to store your global configuration at `$HOME/.config` or - /// `$XDG_CONFIG_HOME/git/config`. For backwards compatibility, the XDG file - /// shouldn't be used unless the use has created it explicitly. With this - /// function you'll open the correct one to write to. - pub fn open_global(&mut self) -> Result<Config, Error> { - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_config_open_global(&mut raw, self.raw)); - Ok(Binding::from_raw(raw)) - } - } - - /// Build a single-level focused config object from a multi-level one. - /// - /// The returned config object can be used to perform get/set/delete - /// operations on a single specific level. - pub fn open_level(&self, level: ConfigLevel) -> Result<Config, Error> { - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_config_open_level(&mut raw, &*self.raw, level)); - Ok(Binding::from_raw(raw)) - } - } - - /// Set the value of a boolean config variable in the config file with the - /// highest level (usually the local one). - pub fn set_bool(&mut self, name: &str, value: bool) -> Result<(), Error> { - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_set_bool(self.raw, name, value)); - } - Ok(()) - } - - /// Set the value of an integer config variable in the config file with the - /// highest level (usually the local one). - pub fn set_i32(&mut self, name: &str, value: i32) -> Result<(), Error> { - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_set_int32(self.raw, name, value)); - } - Ok(()) - } - - /// Set the value of an integer config variable in the config file with the - /// highest level (usually the local one). - pub fn set_i64(&mut self, name: &str, value: i64) -> Result<(), Error> { - let name = CString::new(name)?; - unsafe { - try_call!(raw::git_config_set_int64(self.raw, name, value)); - } - Ok(()) - } - - /// Set the value of an multivar config variable in the config file with the - /// highest level (usually the local one). - /// - /// The regular expression is applied case-sensitively on the value. - pub fn set_multivar(&mut self, name: &str, regexp: &str, value: &str) -> Result<(), Error> { - let name = CString::new(name)?; - let regexp = CString::new(regexp)?; - let value = CString::new(value)?; - unsafe { - try_call!(raw::git_config_set_multivar(self.raw, name, regexp, value)); - } - Ok(()) - } - - /// Set the value of a string config variable in the config file with the - /// highest level (usually the local one). - pub fn set_str(&mut self, name: &str, value: &str) -> Result<(), Error> { - let name = CString::new(name)?; - let value = CString::new(value)?; - unsafe { - try_call!(raw::git_config_set_string(self.raw, name, value)); - } - Ok(()) - } - - /// Create a snapshot of the configuration - /// - /// Create a snapshot of the current state of a configuration, which allows - /// you to look into a consistent view of the configuration for looking up - /// complex values (e.g. a remote, submodule). - pub fn snapshot(&mut self) -> Result<Config, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_config_snapshot(&mut ret, self.raw)); - Ok(Binding::from_raw(ret)) - } - } - - /// Parse a string as a bool. - /// - /// Interprets "true", "yes", "on", 1, or any non-zero number as true. - /// Interprets "false", "no", "off", 0, or an empty string as false. - pub fn parse_bool<S: IntoCString>(s: S) -> Result<bool, Error> { - let s = s.into_c_string()?; - let mut out = 0; - crate::init(); - unsafe { - try_call!(raw::git_config_parse_bool(&mut out, s)); - } - Ok(out != 0) - } - - /// Parse a string as an i32; handles suffixes like k, M, or G, and - /// multiplies by the appropriate power of 1024. - pub fn parse_i32<S: IntoCString>(s: S) -> Result<i32, Error> { - let s = s.into_c_string()?; - let mut out = 0; - crate::init(); - unsafe { - try_call!(raw::git_config_parse_int32(&mut out, s)); - } - Ok(out) - } - - /// Parse a string as an i64; handles suffixes like k, M, or G, and - /// multiplies by the appropriate power of 1024. - pub fn parse_i64<S: IntoCString>(s: S) -> Result<i64, Error> { - let s = s.into_c_string()?; - let mut out = 0; - crate::init(); - unsafe { - try_call!(raw::git_config_parse_int64(&mut out, s)); - } - Ok(out) - } -} - -impl Binding for Config { - type Raw = *mut raw::git_config; - unsafe fn from_raw(raw: *mut raw::git_config) -> Config { - Config { raw } - } - fn raw(&self) -> *mut raw::git_config { - self.raw - } -} - -impl Drop for Config { - fn drop(&mut self) { - unsafe { raw::git_config_free(self.raw) } - } -} - -impl<'cfg> ConfigEntry<'cfg> { - /// Gets the name of this entry. - /// - /// May return `None` if the name is not valid utf-8 - pub fn name(&self) -> Option<&str> { - str::from_utf8(self.name_bytes()).ok() - } - - /// Gets the name of this entry as a byte slice. - pub fn name_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() } - } - - /// Gets the value of this entry. - /// - /// May return `None` if the value is not valid utf-8 - /// - /// # Panics - /// - /// Panics when no value is defined. - pub fn value(&self) -> Option<&str> { - str::from_utf8(self.value_bytes()).ok() - } - - /// Gets the value of this entry as a byte slice. - /// - /// # Panics - /// - /// Panics when no value is defined. - pub fn value_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, (*self.raw).value).unwrap() } - } - - /// Returns `true` when a value is defined otherwise `false`. - /// - /// No value defined is a short-hand to represent a Boolean `true`. - pub fn has_value(&self) -> bool { - unsafe { !(*self.raw).value.is_null() } - } - - /// Gets the configuration level of this entry. - pub fn level(&self) -> ConfigLevel { - unsafe { ConfigLevel::from_raw((*self.raw).level) } - } - - /// Depth of includes where this variable was found - pub fn include_depth(&self) -> u32 { - unsafe { (*self.raw).include_depth as u32 } - } -} - -impl<'cfg> Binding for ConfigEntry<'cfg> { - type Raw = *mut raw::git_config_entry; - - unsafe fn from_raw(raw: *mut raw::git_config_entry) -> ConfigEntry<'cfg> { - ConfigEntry { - raw, - _marker: marker::PhantomData, - owned: true, - } - } - fn raw(&self) -> *mut raw::git_config_entry { - self.raw - } -} - -impl<'cfg> Binding for ConfigEntries<'cfg> { - type Raw = *mut raw::git_config_iterator; - - unsafe fn from_raw(raw: *mut raw::git_config_iterator) -> ConfigEntries<'cfg> { - ConfigEntries { - raw, - current: None, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_config_iterator { - self.raw - } -} - -impl<'cfg> ConfigEntries<'cfg> { - /// Advances the iterator and returns the next value. - /// - /// Returns `None` when iteration is finished. - pub fn next(&mut self) -> Option<Result<&ConfigEntry<'cfg>, Error>> { - let mut raw = ptr::null_mut(); - drop(self.current.take()); - unsafe { - try_call_iter!(raw::git_config_next(&mut raw, self.raw)); - let entry = ConfigEntry { - owned: false, - raw, - _marker: marker::PhantomData, - }; - self.current = Some(entry); - Some(Ok(self.current.as_ref().unwrap())) - } - } - - /// Calls the given closure for each remaining entry in the iterator. - pub fn for_each<F: FnMut(&ConfigEntry<'cfg>)>(mut self, mut f: F) -> Result<(), Error> { - while let Some(entry) = self.next() { - let entry = entry?; - f(entry); - } - Ok(()) - } -} - -impl<'cfg> Drop for ConfigEntries<'cfg> { - fn drop(&mut self) { - unsafe { raw::git_config_iterator_free(self.raw) } - } -} - -impl<'cfg> Drop for ConfigEntry<'cfg> { - fn drop(&mut self) { - if self.owned { - unsafe { raw::git_config_entry_free(self.raw) } - } - } -} - -#[cfg(test)] -mod tests { - use std::fs::File; - use tempfile::TempDir; - - use crate::Config; - - #[test] - fn smoke() { - let _cfg = Config::new().unwrap(); - let _ = Config::find_global(); - let _ = Config::find_system(); - let _ = Config::find_xdg(); - } - - #[test] - fn persisted() { - let td = TempDir::new().unwrap(); - let path = td.path().join("foo"); - File::create(&path).unwrap(); - - let mut cfg = Config::open(&path).unwrap(); - assert!(cfg.get_bool("foo.bar").is_err()); - cfg.set_bool("foo.k1", true).unwrap(); - cfg.set_i32("foo.k2", 1).unwrap(); - cfg.set_i64("foo.k3", 2).unwrap(); - cfg.set_str("foo.k4", "bar").unwrap(); - cfg.snapshot().unwrap(); - drop(cfg); - - let cfg = Config::open(&path).unwrap().snapshot().unwrap(); - assert_eq!(cfg.get_bool("foo.k1").unwrap(), true); - assert_eq!(cfg.get_i32("foo.k2").unwrap(), 1); - assert_eq!(cfg.get_i64("foo.k3").unwrap(), 2); - assert_eq!(cfg.get_str("foo.k4").unwrap(), "bar"); - - let mut entries = cfg.entries(None).unwrap(); - while let Some(entry) = entries.next() { - let entry = entry.unwrap(); - entry.name(); - entry.value(); - entry.level(); - } - } - - #[test] - fn multivar() { - let td = TempDir::new().unwrap(); - let path = td.path().join("foo"); - File::create(&path).unwrap(); - - let mut cfg = Config::open(&path).unwrap(); - cfg.set_multivar("foo.bar", "^$", "baz").unwrap(); - cfg.set_multivar("foo.bar", "^$", "qux").unwrap(); - cfg.set_multivar("foo.bar", "^$", "quux").unwrap(); - cfg.set_multivar("foo.baz", "^$", "oki").unwrap(); - - // `entries` filters by name - let mut entries: Vec<String> = Vec::new(); - cfg.entries(Some("foo.bar")) - .unwrap() - .for_each(|entry| entries.push(entry.value().unwrap().to_string())) - .unwrap(); - entries.sort(); - assert_eq!(entries, ["baz", "quux", "qux"]); - - // which is the same as `multivar` without a regex - let mut multivals = Vec::new(); - cfg.multivar("foo.bar", None) - .unwrap() - .for_each(|entry| multivals.push(entry.value().unwrap().to_string())) - .unwrap(); - multivals.sort(); - assert_eq!(multivals, entries); - - // yet _with_ a regex, `multivar` filters by value - let mut quxish = Vec::new(); - cfg.multivar("foo.bar", Some("qu.*x")) - .unwrap() - .for_each(|entry| quxish.push(entry.value().unwrap().to_string())) - .unwrap(); - quxish.sort(); - assert_eq!(quxish, ["quux", "qux"]); - - cfg.remove_multivar("foo.bar", ".*").unwrap(); - - let count = |entries: super::ConfigEntries<'_>| -> usize { - let mut c = 0; - entries.for_each(|_| c += 1).unwrap(); - c - }; - - assert_eq!(count(cfg.entries(Some("foo.bar")).unwrap()), 0); - assert_eq!(count(cfg.multivar("foo.bar", None).unwrap()), 0); - } - - #[test] - fn parse() { - assert_eq!(Config::parse_bool("").unwrap(), false); - assert_eq!(Config::parse_bool("false").unwrap(), false); - assert_eq!(Config::parse_bool("no").unwrap(), false); - assert_eq!(Config::parse_bool("off").unwrap(), false); - assert_eq!(Config::parse_bool("0").unwrap(), false); - - assert_eq!(Config::parse_bool("true").unwrap(), true); - assert_eq!(Config::parse_bool("yes").unwrap(), true); - assert_eq!(Config::parse_bool("on").unwrap(), true); - assert_eq!(Config::parse_bool("1").unwrap(), true); - assert_eq!(Config::parse_bool("42").unwrap(), true); - - assert!(Config::parse_bool(" ").is_err()); - assert!(Config::parse_bool("some-string").is_err()); - assert!(Config::parse_bool("-").is_err()); - - assert_eq!(Config::parse_i32("0").unwrap(), 0); - assert_eq!(Config::parse_i32("1").unwrap(), 1); - assert_eq!(Config::parse_i32("100").unwrap(), 100); - assert_eq!(Config::parse_i32("-1").unwrap(), -1); - assert_eq!(Config::parse_i32("-100").unwrap(), -100); - assert_eq!(Config::parse_i32("1k").unwrap(), 1024); - assert_eq!(Config::parse_i32("4k").unwrap(), 4096); - assert_eq!(Config::parse_i32("1M").unwrap(), 1048576); - assert_eq!(Config::parse_i32("1G").unwrap(), 1024 * 1024 * 1024); - - assert_eq!(Config::parse_i64("0").unwrap(), 0); - assert_eq!(Config::parse_i64("1").unwrap(), 1); - assert_eq!(Config::parse_i64("100").unwrap(), 100); - assert_eq!(Config::parse_i64("-1").unwrap(), -1); - assert_eq!(Config::parse_i64("-100").unwrap(), -100); - assert_eq!(Config::parse_i64("1k").unwrap(), 1024); - assert_eq!(Config::parse_i64("4k").unwrap(), 4096); - assert_eq!(Config::parse_i64("1M").unwrap(), 1048576); - assert_eq!(Config::parse_i64("1G").unwrap(), 1024 * 1024 * 1024); - assert_eq!(Config::parse_i64("100G").unwrap(), 100 * 1024 * 1024 * 1024); - } -} diff --git a/extra/git2/src/cred.rs b/extra/git2/src/cred.rs deleted file mode 100644 index 49afb4239..000000000 --- a/extra/git2/src/cred.rs +++ /dev/null @@ -1,717 +0,0 @@ -use log::{debug, trace}; -use std::ffi::CString; -use std::io::Write; -use std::mem; -use std::path::Path; -use std::process::{Command, Stdio}; -use std::ptr; -use url; - -use crate::util::Binding; -use crate::{raw, Config, Error, IntoCString}; - -/// A structure to represent git credentials in libgit2. -pub struct Cred { - raw: *mut raw::git_cred, -} - -/// Management of the gitcredentials(7) interface. -pub struct CredentialHelper { - /// A public field representing the currently discovered username from - /// configuration. - pub username: Option<String>, - protocol: Option<String>, - host: Option<String>, - port: Option<u16>, - path: Option<String>, - url: String, - commands: Vec<String>, -} - -impl Cred { - /// Create a "default" credential usable for Negotiate mechanisms like NTLM - /// or Kerberos authentication. - pub fn default() -> Result<Cred, Error> { - crate::init(); - let mut out = ptr::null_mut(); - unsafe { - try_call!(raw::git_cred_default_new(&mut out)); - Ok(Binding::from_raw(out)) - } - } - - /// Create a new ssh key credential object used for querying an ssh-agent. - /// - /// The username specified is the username to authenticate. - pub fn ssh_key_from_agent(username: &str) -> Result<Cred, Error> { - crate::init(); - let mut out = ptr::null_mut(); - let username = CString::new(username)?; - unsafe { - try_call!(raw::git_cred_ssh_key_from_agent(&mut out, username)); - Ok(Binding::from_raw(out)) - } - } - - /// Create a new passphrase-protected ssh key credential object. - pub fn ssh_key( - username: &str, - publickey: Option<&Path>, - privatekey: &Path, - passphrase: Option<&str>, - ) -> Result<Cred, Error> { - crate::init(); - let username = CString::new(username)?; - let publickey = crate::opt_cstr(publickey)?; - let privatekey = privatekey.into_c_string()?; - let passphrase = crate::opt_cstr(passphrase)?; - let mut out = ptr::null_mut(); - unsafe { - try_call!(raw::git_cred_ssh_key_new( - &mut out, username, publickey, privatekey, passphrase - )); - Ok(Binding::from_raw(out)) - } - } - - /// Create a new ssh key credential object reading the keys from memory. - pub fn ssh_key_from_memory( - username: &str, - publickey: Option<&str>, - privatekey: &str, - passphrase: Option<&str>, - ) -> Result<Cred, Error> { - crate::init(); - let username = CString::new(username)?; - let publickey = crate::opt_cstr(publickey)?; - let privatekey = CString::new(privatekey)?; - let passphrase = crate::opt_cstr(passphrase)?; - let mut out = ptr::null_mut(); - unsafe { - try_call!(raw::git_cred_ssh_key_memory_new( - &mut out, username, publickey, privatekey, passphrase - )); - Ok(Binding::from_raw(out)) - } - } - - /// Create a new plain-text username and password credential object. - pub fn userpass_plaintext(username: &str, password: &str) -> Result<Cred, Error> { - crate::init(); - let username = CString::new(username)?; - let password = CString::new(password)?; - let mut out = ptr::null_mut(); - unsafe { - try_call!(raw::git_cred_userpass_plaintext_new( - &mut out, username, password - )); - Ok(Binding::from_raw(out)) - } - } - - /// Attempt to read `credential.helper` according to gitcredentials(7) [1] - /// - /// This function will attempt to parse the user's `credential.helper` - /// configuration, invoke the necessary processes, and read off what the - /// username/password should be for a particular URL. - /// - /// The returned credential type will be a username/password credential if - /// successful. - /// - /// [1]: https://www.kernel.org/pub/software/scm/git/docs/gitcredentials.html - pub fn credential_helper( - config: &Config, - url: &str, - username: Option<&str>, - ) -> Result<Cred, Error> { - match CredentialHelper::new(url) - .config(config) - .username(username) - .execute() - { - Some((username, password)) => Cred::userpass_plaintext(&username, &password), - None => Err(Error::from_str( - "failed to acquire username/password \ - from local configuration", - )), - } - } - - /// Create a credential to specify a username. - /// - /// This is used with ssh authentication to query for the username if none is - /// specified in the URL. - pub fn username(username: &str) -> Result<Cred, Error> { - crate::init(); - let username = CString::new(username)?; - let mut out = ptr::null_mut(); - unsafe { - try_call!(raw::git_cred_username_new(&mut out, username)); - Ok(Binding::from_raw(out)) - } - } - - /// Check whether a credential object contains username information. - pub fn has_username(&self) -> bool { - unsafe { raw::git_cred_has_username(self.raw) == 1 } - } - - /// Return the type of credentials that this object represents. - pub fn credtype(&self) -> raw::git_credtype_t { - unsafe { (*self.raw).credtype } - } - - /// Unwrap access to the underlying raw pointer, canceling the destructor - pub unsafe fn unwrap(mut self) -> *mut raw::git_cred { - mem::replace(&mut self.raw, ptr::null_mut()) - } -} - -impl Binding for Cred { - type Raw = *mut raw::git_cred; - - unsafe fn from_raw(raw: *mut raw::git_cred) -> Cred { - Cred { raw } - } - fn raw(&self) -> *mut raw::git_cred { - self.raw - } -} - -impl Drop for Cred { - fn drop(&mut self) { - if !self.raw.is_null() { - unsafe { - if let Some(f) = (*self.raw).free { - f(self.raw) - } - } - } - } -} - -impl CredentialHelper { - /// Create a new credential helper object which will be used to probe git's - /// local credential configuration. - /// - /// The URL specified is the namespace on which this will query credentials. - /// Invalid URLs are currently ignored. - pub fn new(url: &str) -> CredentialHelper { - let mut ret = CredentialHelper { - protocol: None, - host: None, - port: None, - path: None, - username: None, - url: url.to_string(), - commands: Vec::new(), - }; - - // Parse out the (protocol, host) if one is available - if let Ok(url) = url::Url::parse(url) { - if let Some(url::Host::Domain(s)) = url.host() { - ret.host = Some(s.to_string()); - } - ret.port = url.port(); - ret.protocol = Some(url.scheme().to_string()); - } - ret - } - - /// Set the username that this credential helper will query with. - /// - /// By default the username is `None`. - pub fn username(&mut self, username: Option<&str>) -> &mut CredentialHelper { - self.username = username.map(|s| s.to_string()); - self - } - - /// Query the specified configuration object to discover commands to - /// execute, usernames to query, etc. - pub fn config(&mut self, config: &Config) -> &mut CredentialHelper { - // Figure out the configured username/helper program. - // - // see http://git-scm.com/docs/gitcredentials.html#_configuration_options - if self.username.is_none() { - self.config_username(config); - } - self.config_helper(config); - self.config_use_http_path(config); - self - } - - // Configure the queried username from `config` - fn config_username(&mut self, config: &Config) { - let key = self.exact_key("username"); - self.username = config - .get_string(&key) - .ok() - .or_else(|| { - self.url_key("username") - .and_then(|s| config.get_string(&s).ok()) - }) - .or_else(|| config.get_string("credential.username").ok()) - } - - // Discover all `helper` directives from `config` - fn config_helper(&mut self, config: &Config) { - let exact = config.get_string(&self.exact_key("helper")); - self.add_command(exact.as_ref().ok().map(|s| &s[..])); - if let Some(key) = self.url_key("helper") { - let url = config.get_string(&key); - self.add_command(url.as_ref().ok().map(|s| &s[..])); - } - let global = config.get_string("credential.helper"); - self.add_command(global.as_ref().ok().map(|s| &s[..])); - } - - // Discover `useHttpPath` from `config` - fn config_use_http_path(&mut self, config: &Config) { - let mut use_http_path = false; - if let Some(value) = config.get_bool(&self.exact_key("useHttpPath")).ok() { - use_http_path = value; - } else if let Some(value) = self - .url_key("useHttpPath") - .and_then(|key| config.get_bool(&key).ok()) - { - use_http_path = value; - } else if let Some(value) = config.get_bool("credential.useHttpPath").ok() { - use_http_path = value; - } - - if use_http_path { - if let Ok(url) = url::Url::parse(&self.url) { - let path = url.path(); - // Url::parse always includes a leading slash for rooted URLs, while git does not. - self.path = Some(path.strip_prefix('/').unwrap_or(path).to_string()); - } - } - } - - // Add a `helper` configured command to the list of commands to execute. - // - // see https://www.kernel.org/pub/software/scm/git/docs/technical - // /api-credentials.html#_credential_helpers - fn add_command(&mut self, cmd: Option<&str>) { - let cmd = match cmd { - Some("") | None => return, - Some(s) => s, - }; - - if cmd.starts_with('!') { - self.commands.push(cmd[1..].to_string()); - } else if cmd.contains("/") || cmd.contains("\\") { - self.commands.push(cmd.to_string()); - } else { - self.commands.push(format!("git credential-{}", cmd)); - } - } - - fn exact_key(&self, name: &str) -> String { - format!("credential.{}.{}", self.url, name) - } - - fn url_key(&self, name: &str) -> Option<String> { - match (&self.host, &self.protocol) { - (&Some(ref host), &Some(ref protocol)) => { - Some(format!("credential.{}://{}.{}", protocol, host, name)) - } - _ => None, - } - } - - /// Execute this helper, attempting to discover a username/password pair. - /// - /// All I/O errors are ignored, (to match git behavior), and this function - /// only succeeds if both a username and a password were found - pub fn execute(&self) -> Option<(String, String)> { - let mut username = self.username.clone(); - let mut password = None; - for cmd in &self.commands { - let (u, p) = self.execute_cmd(cmd, &username); - if u.is_some() && username.is_none() { - username = u; - } - if p.is_some() && password.is_none() { - password = p; - } - if username.is_some() && password.is_some() { - break; - } - } - - match (username, password) { - (Some(u), Some(p)) => Some((u, p)), - _ => None, - } - } - - // Execute the given `cmd`, providing the appropriate variables on stdin and - // then afterwards parsing the output into the username/password on stdout. - fn execute_cmd( - &self, - cmd: &str, - username: &Option<String>, - ) -> (Option<String>, Option<String>) { - macro_rules! my_try( ($e:expr) => ( - match $e { - Ok(e) => e, - Err(e) => { - debug!("{} failed with {}", stringify!($e), e); - return (None, None) - } - } - ) ); - - // It looks like the `cmd` specification is typically bourne-shell-like - // syntax, so try that first. If that fails, though, we may be on a - // Windows machine for example where `sh` isn't actually available by - // default. Most credential helper configurations though are pretty - // simple (aka one or two space-separated strings) so also try to invoke - // the process directly. - // - // If that fails then it's up to the user to put `sh` in path and make - // sure it works. - let mut c = Command::new("sh"); - c.arg("-c") - .arg(&format!("{} get", cmd)) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - debug!("executing credential helper {:?}", c); - let mut p = match c.spawn() { - Ok(p) => p, - Err(e) => { - debug!("`sh` failed to spawn: {}", e); - let mut parts = cmd.split_whitespace(); - let mut c = Command::new(parts.next().unwrap()); - for arg in parts { - c.arg(arg); - } - c.arg("get") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); - debug!("executing credential helper {:?}", c); - match c.spawn() { - Ok(p) => p, - Err(e) => { - debug!("fallback of {:?} failed with {}", cmd, e); - return (None, None); - } - } - } - }; - - // Ignore write errors as the command may not actually be listening for - // stdin - { - let stdin = p.stdin.as_mut().unwrap(); - if let Some(ref p) = self.protocol { - let _ = writeln!(stdin, "protocol={}", p); - } - if let Some(ref p) = self.host { - if let Some(ref p2) = self.port { - let _ = writeln!(stdin, "host={}:{}", p, p2); - } else { - let _ = writeln!(stdin, "host={}", p); - } - } - if let Some(ref p) = self.path { - let _ = writeln!(stdin, "path={}", p); - } - if let Some(ref p) = *username { - let _ = writeln!(stdin, "username={}", p); - } - } - let output = my_try!(p.wait_with_output()); - if !output.status.success() { - debug!( - "credential helper failed: {}\nstdout ---\n{}\nstderr ---\n{}", - output.status, - String::from_utf8_lossy(&output.stdout), - String::from_utf8_lossy(&output.stderr) - ); - return (None, None); - } - trace!( - "credential helper stderr ---\n{}", - String::from_utf8_lossy(&output.stderr) - ); - self.parse_output(output.stdout) - } - - // Parse the output of a command into the username/password found - fn parse_output(&self, output: Vec<u8>) -> (Option<String>, Option<String>) { - // Parse the output of the command, looking for username/password - let mut username = None; - let mut password = None; - for line in output.split(|t| *t == b'\n') { - let mut parts = line.splitn(2, |t| *t == b'='); - let key = parts.next().unwrap(); - let value = match parts.next() { - Some(s) => s, - None => { - trace!("ignoring output line: {}", String::from_utf8_lossy(line)); - continue; - } - }; - let value = match String::from_utf8(value.to_vec()) { - Ok(s) => s, - Err(..) => continue, - }; - match key { - b"username" => username = Some(value), - b"password" => password = Some(value), - _ => {} - } - } - (username, password) - } -} - -#[cfg(test)] -mod test { - use std::env; - use std::fs::File; - use std::io::prelude::*; - use std::path::Path; - use tempfile::TempDir; - - use crate::{Config, ConfigLevel, Cred, CredentialHelper}; - - macro_rules! test_cfg( ($($k:expr => $v:expr),*) => ({ - let td = TempDir::new().unwrap(); - let mut cfg = Config::new().unwrap(); - cfg.add_file(&td.path().join("cfg"), ConfigLevel::Highest, false).unwrap(); - $(cfg.set_str($k, $v).unwrap();)* - cfg - }) ); - - #[test] - fn smoke() { - Cred::default().unwrap(); - } - - #[test] - fn credential_helper1() { - let cfg = test_cfg! { - "credential.helper" => "!f() { echo username=a; echo password=b; }; f" - }; - let (u, p) = CredentialHelper::new("https://example.com/foo/bar") - .config(&cfg) - .execute() - .unwrap(); - assert_eq!(u, "a"); - assert_eq!(p, "b"); - } - - #[test] - fn credential_helper2() { - let cfg = test_cfg! {}; - assert!(CredentialHelper::new("https://example.com/foo/bar") - .config(&cfg) - .execute() - .is_none()); - } - - #[test] - fn credential_helper3() { - let cfg = test_cfg! { - "credential.https://example.com.helper" => - "!f() { echo username=c; }; f", - "credential.helper" => "!f() { echo username=a; echo password=b; }; f" - }; - let (u, p) = CredentialHelper::new("https://example.com/foo/bar") - .config(&cfg) - .execute() - .unwrap(); - assert_eq!(u, "c"); - assert_eq!(p, "b"); - } - - #[test] - fn credential_helper4() { - if cfg!(windows) { - return; - } // shell scripts don't work on Windows - - let td = TempDir::new().unwrap(); - let path = td.path().join("script"); - File::create(&path) - .unwrap() - .write( - br"\ -#!/bin/sh -echo username=c -", - ) - .unwrap(); - chmod(&path); - let cfg = test_cfg! { - "credential.https://example.com.helper" => - &path.display().to_string()[..], - "credential.helper" => "!f() { echo username=a; echo password=b; }; f" - }; - let (u, p) = CredentialHelper::new("https://example.com/foo/bar") - .config(&cfg) - .execute() - .unwrap(); - assert_eq!(u, "c"); - assert_eq!(p, "b"); - } - - #[test] - fn credential_helper5() { - if !Path::new("/usr/bin/git").exists() { - return; - } //this test does not work if git is not installed - if cfg!(windows) { - return; - } // shell scripts don't work on Windows - let td = TempDir::new().unwrap(); - let path = td.path().join("git-credential-script"); - File::create(&path) - .unwrap() - .write( - br"\ -#!/bin/sh -echo username=c -", - ) - .unwrap(); - chmod(&path); - - let paths = env::var("PATH").unwrap(); - let paths = - env::split_paths(&paths).chain(path.parent().map(|p| p.to_path_buf()).into_iter()); - env::set_var("PATH", &env::join_paths(paths).unwrap()); - - let cfg = test_cfg! { - "credential.https://example.com.helper" => "script", - "credential.helper" => "!f() { echo username=a; echo password=b; }; f" - }; - let (u, p) = CredentialHelper::new("https://example.com/foo/bar") - .config(&cfg) - .execute() - .unwrap(); - assert_eq!(u, "c"); - assert_eq!(p, "b"); - } - - #[test] - fn credential_helper6() { - let cfg = test_cfg! { - "credential.helper" => "" - }; - assert!(CredentialHelper::new("https://example.com/foo/bar") - .config(&cfg) - .execute() - .is_none()); - } - - #[test] - fn credential_helper7() { - if cfg!(windows) { - return; - } // shell scripts don't work on Windows - let td = TempDir::new().unwrap(); - let path = td.path().join("script"); - File::create(&path) - .unwrap() - .write( - br"\ -#!/bin/sh -echo username=$1 -echo password=$2 -", - ) - .unwrap(); - chmod(&path); - let cfg = test_cfg! { - "credential.helper" => &format!("{} a b", path.display()) - }; - let (u, p) = CredentialHelper::new("https://example.com/foo/bar") - .config(&cfg) - .execute() - .unwrap(); - assert_eq!(u, "a"); - assert_eq!(p, "b"); - } - - #[test] - fn credential_helper8() { - let cfg = test_cfg! { - "credential.useHttpPath" => "true" - }; - let mut helper = CredentialHelper::new("https://example.com/foo/bar"); - helper.config(&cfg); - assert_eq!(helper.path.as_deref(), Some("foo/bar")); - } - - #[test] - fn credential_helper9() { - let cfg = test_cfg! { - "credential.helper" => "!f() { while read line; do eval $line; done; if [ \"$host\" = example.com:3000 ]; then echo username=a; echo password=b; fi; }; f" - }; - let (u, p) = CredentialHelper::new("https://example.com:3000/foo/bar") - .config(&cfg) - .execute() - .unwrap(); - assert_eq!(u, "a"); - assert_eq!(p, "b"); - } - - #[test] - #[cfg(feature = "ssh")] - fn ssh_key_from_memory() { - let cred = Cred::ssh_key_from_memory( - "test", - Some("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDByAO8uj+kXicj6C2ODMspgmUoVyl5eaw8vR6a1yEnFuJFzevabNlN6Ut+CPT3TRnYk5BW73pyXBtnSL2X95BOnbjMDXc4YIkgs3YYHWnxbqsD4Pj/RoGqhf+gwhOBtL0poh8tT8WqXZYxdJQKLQC7oBqf3ykCEYulE4oeRUmNh4IzEE+skD/zDkaJ+S1HRD8D8YCiTO01qQnSmoDFdmIZTi8MS8Cw+O/Qhym1271ThMlhD6PubSYJXfE6rVbE7A9RzH73A6MmKBlzK8VTb4SlNSrr/DOk+L0uq+wPkv+pm+D9WtxoqQ9yl6FaK1cPawa3+7yRNle3m+72KCtyMkQv"), - r#" - -----BEGIN RSA PRIVATE KEY----- - Proc-Type: 4,ENCRYPTED - DEK-Info: AES-128-CBC,818C7722D3B01F2161C2ACF6A5BBAAE8 - - 3Cht4QB3PcoQ0I55j1B3m2ZzIC/mrh+K5nQeA1Vy2GBTMyM7yqGHqTOv7qLhJscd - H+cB0Pm6yCr3lYuNrcKWOCUto+91P7ikyARruHVwyIxKdNx15uNulOzQJHQWNbA4 - RQHlhjON4atVo2FyJ6n+ujK6QiBg2PR5Vbbw/AtV6zBCFW3PhzDn+qqmHjpBFqj2 - vZUUe+MkDQcaF5J45XMHahhSdo/uKCDhfbylExp/+ACWkvxdPpsvcARM6X434ucD - aPY+4i0/JyLkdbm0GFN9/q3i53qf4kCBhojFl4AYJdGI0AzAgbdTXZ7EJHbAGZHS - os5K0oTwDVXMI0sSE2I/qHxaZZsDP1dOKq6di6SFPUp8liYimm7rNintRX88Gl2L - g1ko9abp/NlgD0YY/3mad+NNAISDL/YfXq2fklH3En3/7ZrOVZFKfZXwQwas5g+p - VQPKi3+ae74iOjLyuPDSc1ePmhUNYeP+9rLSc0wiaiHqls+2blPPDxAGMEo63kbz - YPVjdmuVX4VWnyEsfTxxJdFDYGSNh6rlrrO1RFrex7kJvpg5gTX4M/FT8TfCd7Hn - M6adXsLMqwu5tz8FuDmAtVdq8zdSrgZeAbpJ9D3EDOmZ70xz4XBL19ImxDp+Qqs2 - kQX7kobRzeeP2URfRoGr7XZikQWyQ2UASfPcQULY8R58QoZWWsQ4w51GZHg7TDnw - 1DRo/0OgkK7Gqf215nFmMpB4uyi58cq3WFwWQa1IqslkObpVgBQZcNZb/hKUYPGk - g4zehfIgAfCdnQHwZvQ6Fdzhcs3SZeO+zVyuiZN3Gsi9HU0/1vpAKiuuOzcG02vF - b6Y6hwsAA9yphF3atI+ARD4ZwXdDfzuGb3yJglMT3Fr/xuLwAvdchRo1spANKA0E - tT5okLrK0H4wnHvf2SniVVWRhmJis0lQo9LjGGwRIdsPpVnJSDvaISIVF+fHT90r - HvxN8zXI93x9jcPtwp7puQ1C7ehKJK10sZ71OLIZeuUgwt+5DRunqg6evPco9Go7 - UOGwcVhLY200KT+1k7zWzCS0yVQp2HRm6cxsZXAp4ClBSwIx15eIoLIrjZdJRjCq - COp6pZx1fnvJ9ERIvl5hon+Ty+renMcFKz2HmchC7egpcqIxW9Dsv6zjhHle6pxb - 37GaEKHF2KA3RN+dSV/K8n+C9Yent5tx5Y9a/pMcgRGtgu+G+nyFmkPKn5Zt39yX - qDpyM0LtbRVZPs+MgiqoGIwYc/ujoCq7GL38gezsBQoHaTt79yYBqCp6UR0LMuZ5 - f/7CtWqffgySfJ/0wjGidDAumDv8CK45AURpL/Z+tbFG3M9ar/LZz/Y6EyBcLtGY - Wwb4zs8zXIA0qHrjNTnPqHDvezziArYfgPjxCIHMZzms9Yn8+N02p39uIytqg434 - BAlCqZ7GYdDFfTpWIwX+segTK9ux0KdBqcQv+9Fwwjkq9KySnRKqNl7ZJcefFZJq - c6PA1iinZWBjuaO1HKx3PFulrl0bcpR9Kud1ZIyfnh5rwYN8UQkkcR/wZPla04TY - 8l5dq/LI/3G5sZXwUHKOcuQWTj7Saq7Q6gkKoMfqt0wC5bpZ1m17GHPoMz6GtX9O - -----END RSA PRIVATE KEY----- - "#, - Some("test123")); - assert!(cred.is_ok()); - } - - #[cfg(unix)] - fn chmod(path: &Path) { - use std::fs; - use std::os::unix::prelude::*; - let mut perms = fs::metadata(path).unwrap().permissions(); - perms.set_mode(0o755); - fs::set_permissions(path, perms).unwrap(); - } - #[cfg(windows)] - fn chmod(_path: &Path) {} -} diff --git a/extra/git2/src/describe.rs b/extra/git2/src/describe.rs deleted file mode 100644 index cbaa1893b..000000000 --- a/extra/git2/src/describe.rs +++ /dev/null @@ -1,201 +0,0 @@ -use std::ffi::CString; -use std::marker; -use std::mem; -use std::ptr; - -use libc::{c_int, c_uint}; - -use crate::util::Binding; -use crate::{raw, Buf, Error, Repository}; - -/// The result of a `describe` operation on either an `Describe` or a -/// `Repository`. -pub struct Describe<'repo> { - raw: *mut raw::git_describe_result, - _marker: marker::PhantomData<&'repo Repository>, -} - -/// Options which indicate how a `Describe` is created. -pub struct DescribeOptions { - raw: raw::git_describe_options, - pattern: CString, -} - -/// Options which can be used to customize how a description is formatted. -pub struct DescribeFormatOptions { - raw: raw::git_describe_format_options, - dirty_suffix: CString, -} - -impl<'repo> Describe<'repo> { - /// Prints this describe result, returning the result as a string. - pub fn format(&self, opts: Option<&DescribeFormatOptions>) -> Result<String, Error> { - let buf = Buf::new(); - let opts = opts.map(|o| &o.raw as *const _).unwrap_or(ptr::null()); - unsafe { - try_call!(raw::git_describe_format(buf.raw(), self.raw, opts)); - } - Ok(String::from_utf8(buf.to_vec()).unwrap()) - } -} - -impl<'repo> Binding for Describe<'repo> { - type Raw = *mut raw::git_describe_result; - - unsafe fn from_raw(raw: *mut raw::git_describe_result) -> Describe<'repo> { - Describe { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_describe_result { - self.raw - } -} - -impl<'repo> Drop for Describe<'repo> { - fn drop(&mut self) { - unsafe { raw::git_describe_result_free(self.raw) } - } -} - -impl Default for DescribeFormatOptions { - fn default() -> Self { - Self::new() - } -} - -impl DescribeFormatOptions { - /// Creates a new blank set of formatting options for a description. - pub fn new() -> DescribeFormatOptions { - let mut opts = DescribeFormatOptions { - raw: unsafe { mem::zeroed() }, - dirty_suffix: CString::new(Vec::new()).unwrap(), - }; - opts.raw.version = 1; - opts.raw.abbreviated_size = 7; - opts - } - - /// Sets the size of the abbreviated commit id to use. - /// - /// The value is the lower bound for the length of the abbreviated string, - /// and the default is 7. - pub fn abbreviated_size(&mut self, size: u32) -> &mut Self { - self.raw.abbreviated_size = size as c_uint; - self - } - - /// Sets whether or not the long format is used even when a shorter name - /// could be used. - pub fn always_use_long_format(&mut self, long: bool) -> &mut Self { - self.raw.always_use_long_format = long as c_int; - self - } - - /// If the workdir is dirty and this is set, this string will be appended to - /// the description string. - pub fn dirty_suffix(&mut self, suffix: &str) -> &mut Self { - self.dirty_suffix = CString::new(suffix).unwrap(); - self.raw.dirty_suffix = self.dirty_suffix.as_ptr(); - self - } -} - -impl Default for DescribeOptions { - fn default() -> Self { - Self::new() - } -} - -impl DescribeOptions { - /// Creates a new blank set of formatting options for a description. - pub fn new() -> DescribeOptions { - let mut opts = DescribeOptions { - raw: unsafe { mem::zeroed() }, - pattern: CString::new(Vec::new()).unwrap(), - }; - opts.raw.version = 1; - opts.raw.max_candidates_tags = 10; - opts - } - - #[allow(missing_docs)] - pub fn max_candidates_tags(&mut self, max: u32) -> &mut Self { - self.raw.max_candidates_tags = max as c_uint; - self - } - - /// Sets the reference lookup strategy - /// - /// This behaves like the `--tags` option to git-describe. - pub fn describe_tags(&mut self) -> &mut Self { - self.raw.describe_strategy = raw::GIT_DESCRIBE_TAGS as c_uint; - self - } - - /// Sets the reference lookup strategy - /// - /// This behaves like the `--all` option to git-describe. - pub fn describe_all(&mut self) -> &mut Self { - self.raw.describe_strategy = raw::GIT_DESCRIBE_ALL as c_uint; - self - } - - /// Indicates when calculating the distance from the matching tag or - /// reference whether to only walk down the first-parent ancestry. - pub fn only_follow_first_parent(&mut self, follow: bool) -> &mut Self { - self.raw.only_follow_first_parent = follow as c_int; - self - } - - /// If no matching tag or reference is found whether a describe option would - /// normally fail. This option indicates, however, that it will instead fall - /// back to showing the full id of the commit. - pub fn show_commit_oid_as_fallback(&mut self, show: bool) -> &mut Self { - self.raw.show_commit_oid_as_fallback = show as c_int; - self - } - - #[allow(missing_docs)] - pub fn pattern(&mut self, pattern: &str) -> &mut Self { - self.pattern = CString::new(pattern).unwrap(); - self.raw.pattern = self.pattern.as_ptr(); - self - } -} - -impl Binding for DescribeOptions { - type Raw = *mut raw::git_describe_options; - - unsafe fn from_raw(_raw: *mut raw::git_describe_options) -> DescribeOptions { - panic!("unimplemened") - } - fn raw(&self) -> *mut raw::git_describe_options { - &self.raw as *const _ as *mut _ - } -} - -#[cfg(test)] -mod tests { - use crate::DescribeOptions; - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let head = t!(repo.head()).target().unwrap(); - - let d = t!(repo.describe(DescribeOptions::new().show_commit_oid_as_fallback(true))); - let id = head.to_string(); - assert_eq!(t!(d.format(None)), &id[..7]); - - let obj = t!(repo.find_object(head, None)); - let sig = t!(repo.signature()); - t!(repo.tag("foo", &obj, &sig, "message", true)); - let d = t!(repo.describe(&DescribeOptions::new())); - assert_eq!(t!(d.format(None)), "foo"); - - let d = t!(obj.describe(&DescribeOptions::new())); - assert_eq!(t!(d.format(None)), "foo"); - } -} diff --git a/extra/git2/src/diff.rs b/extra/git2/src/diff.rs deleted file mode 100644 index 16595509d..000000000 --- a/extra/git2/src/diff.rs +++ /dev/null @@ -1,1863 +0,0 @@ -use libc::{c_char, c_int, c_void, size_t}; -use std::ffi::CString; -use std::iter::FusedIterator; -use std::marker; -use std::mem; -use std::ops::Range; -use std::path::Path; -use std::ptr; -use std::slice; - -use crate::util::{self, Binding}; -use crate::{panic, raw, Buf, Delta, DiffFormat, Error, FileMode, Oid, Repository}; -use crate::{DiffFlags, DiffStatsFormat, IntoCString}; - -/// The diff object that contains all individual file deltas. -/// -/// This is an opaque structure which will be allocated by one of the diff -/// generator functions on the `Repository` structure (e.g. `diff_tree_to_tree` -/// or other `diff_*` functions). -pub struct Diff<'repo> { - raw: *mut raw::git_diff, - _marker: marker::PhantomData<&'repo Repository>, -} - -unsafe impl<'repo> Send for Diff<'repo> {} - -/// Description of changes to one entry. -pub struct DiffDelta<'a> { - raw: *mut raw::git_diff_delta, - _marker: marker::PhantomData<&'a raw::git_diff_delta>, -} - -/// Description of one side of a delta. -/// -/// Although this is called a "file" it could represent a file, a symbolic -/// link, a submodule commit id, or even a tree (although that only happens if -/// you are tracking type changes or ignored/untracked directories). -pub struct DiffFile<'a> { - raw: *const raw::git_diff_file, - _marker: marker::PhantomData<&'a raw::git_diff_file>, -} - -/// Structure describing options about how the diff should be executed. -pub struct DiffOptions { - pathspec: Vec<CString>, - pathspec_ptrs: Vec<*const c_char>, - old_prefix: Option<CString>, - new_prefix: Option<CString>, - raw: raw::git_diff_options, -} - -/// Control behavior of rename and copy detection -pub struct DiffFindOptions { - raw: raw::git_diff_find_options, -} - -/// Control behavior of formatting emails -pub struct DiffFormatEmailOptions { - raw: raw::git_diff_format_email_options, -} - -/// Control behavior of formatting emails -pub struct DiffPatchidOptions { - raw: raw::git_diff_patchid_options, -} - -/// An iterator over the diffs in a delta -pub struct Deltas<'diff> { - range: Range<usize>, - diff: &'diff Diff<'diff>, -} - -/// Structure describing a line (or data span) of a diff. -pub struct DiffLine<'a> { - raw: *const raw::git_diff_line, - _marker: marker::PhantomData<&'a raw::git_diff_line>, -} - -/// Structure describing a hunk of a diff. -pub struct DiffHunk<'a> { - raw: *const raw::git_diff_hunk, - _marker: marker::PhantomData<&'a raw::git_diff_hunk>, -} - -/// Structure describing a hunk of a diff. -pub struct DiffStats { - raw: *mut raw::git_diff_stats, -} - -/// Structure describing the binary contents of a diff. -pub struct DiffBinary<'a> { - raw: *const raw::git_diff_binary, - _marker: marker::PhantomData<&'a raw::git_diff_binary>, -} - -/// The contents of one of the files in a binary diff. -pub struct DiffBinaryFile<'a> { - raw: *const raw::git_diff_binary_file, - _marker: marker::PhantomData<&'a raw::git_diff_binary_file>, -} - -/// When producing a binary diff, the binary data returned will be -/// either the deflated full ("literal") contents of the file, or -/// the deflated binary delta between the two sides (whichever is -/// smaller). -#[derive(Copy, Clone, Debug)] -pub enum DiffBinaryKind { - /// There is no binary delta - None, - /// The binary data is the literal contents of the file - Literal, - /// The binary data is the delta from one side to the other - Delta, -} - -type PrintCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a; - -pub type FileCb<'a> = dyn FnMut(DiffDelta<'_>, f32) -> bool + 'a; -pub type BinaryCb<'a> = dyn FnMut(DiffDelta<'_>, DiffBinary<'_>) -> bool + 'a; -pub type HunkCb<'a> = dyn FnMut(DiffDelta<'_>, DiffHunk<'_>) -> bool + 'a; -pub type LineCb<'a> = dyn FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool + 'a; - -pub struct DiffCallbacks<'a, 'b, 'c, 'd, 'e, 'f, 'g, 'h> { - pub file: Option<&'a mut FileCb<'b>>, - pub binary: Option<&'c mut BinaryCb<'d>>, - pub hunk: Option<&'e mut HunkCb<'f>>, - pub line: Option<&'g mut LineCb<'h>>, -} - -impl<'repo> Diff<'repo> { - /// Merge one diff into another. - /// - /// This merges items from the "from" list into the "self" list. The - /// resulting diff will have all items that appear in either list. - /// If an item appears in both lists, then it will be "merged" to appear - /// as if the old version was from the "onto" list and the new version - /// is from the "from" list (with the exception that if the item has a - /// pending DELETE in the middle, then it will show as deleted). - pub fn merge(&mut self, from: &Diff<'repo>) -> Result<(), Error> { - unsafe { - try_call!(raw::git_diff_merge(self.raw, &*from.raw)); - } - Ok(()) - } - - /// Returns an iterator over the deltas in this diff. - pub fn deltas(&self) -> Deltas<'_> { - let num_deltas = unsafe { raw::git_diff_num_deltas(&*self.raw) }; - Deltas { - range: 0..(num_deltas as usize), - diff: self, - } - } - - /// Return the diff delta for an entry in the diff list. - pub fn get_delta(&self, i: usize) -> Option<DiffDelta<'_>> { - unsafe { - let ptr = raw::git_diff_get_delta(&*self.raw, i as size_t); - Binding::from_raw_opt(ptr as *mut _) - } - } - - /// Check if deltas are sorted case sensitively or insensitively. - pub fn is_sorted_icase(&self) -> bool { - unsafe { raw::git_diff_is_sorted_icase(&*self.raw) == 1 } - } - - /// Iterate over a diff generating formatted text output. - /// - /// Returning `false` from the callback will terminate the iteration and - /// return an error from this function. - pub fn print<F>(&self, format: DiffFormat, mut cb: F) -> Result<(), Error> - where - F: FnMut(DiffDelta<'_>, Option<DiffHunk<'_>>, DiffLine<'_>) -> bool, - { - let mut cb: &mut PrintCb<'_> = &mut cb; - let ptr = &mut cb as *mut _; - let print: raw::git_diff_line_cb = Some(print_cb); - unsafe { - try_call!(raw::git_diff_print(self.raw, format, print, ptr as *mut _)); - Ok(()) - } - } - - /// Loop over all deltas in a diff issuing callbacks. - /// - /// Returning `false` from any callback will terminate the iteration and - /// return an error from this function. - pub fn foreach( - &self, - file_cb: &mut FileCb<'_>, - binary_cb: Option<&mut BinaryCb<'_>>, - hunk_cb: Option<&mut HunkCb<'_>>, - line_cb: Option<&mut LineCb<'_>>, - ) -> Result<(), Error> { - let mut cbs = DiffCallbacks { - file: Some(file_cb), - binary: binary_cb, - hunk: hunk_cb, - line: line_cb, - }; - let ptr = &mut cbs as *mut _; - unsafe { - 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 - }; - let file_cb: raw::git_diff_file_cb = Some(file_cb_c); - try_call!(raw::git_diff_foreach( - self.raw, - file_cb, - binary_cb_c, - hunk_cb_c, - line_cb_c, - ptr as *mut _ - )); - Ok(()) - } - } - - /// Accumulate diff statistics for all patches. - pub fn stats(&self) -> Result<DiffStats, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_diff_get_stats(&mut ret, self.raw)); - Ok(Binding::from_raw(ret)) - } - } - - /// Transform a diff marking file renames, copies, etc. - /// - /// This modifies a diff in place, replacing old entries that look like - /// renames or copies with new entries reflecting those changes. This also - /// will, if requested, break modified files into add/remove pairs if the - /// amount of change is above a threshold. - pub fn find_similar(&mut self, opts: Option<&mut DiffFindOptions>) -> Result<(), Error> { - let opts = opts.map(|opts| &opts.raw); - unsafe { - try_call!(raw::git_diff_find_similar(self.raw, opts)); - } - Ok(()) - } - - /// Create an e-mail ready patch from a diff. - /// - /// Matches the format created by `git format-patch` - #[doc(hidden)] - #[deprecated(note = "refactored to `Email::from_diff` to match upstream")] - pub fn format_email( - &mut self, - patch_no: usize, - total_patches: usize, - commit: &crate::Commit<'repo>, - opts: Option<&mut DiffFormatEmailOptions>, - ) -> Result<Buf, Error> { - assert!(patch_no > 0); - assert!(patch_no <= total_patches); - let mut default = DiffFormatEmailOptions::default(); - let raw_opts = opts.map_or(&mut default.raw, |opts| &mut opts.raw); - let summary = commit.summary_bytes().unwrap(); - let mut message = commit.message_bytes(); - assert!(message.starts_with(summary)); - message = &message[summary.len()..]; - raw_opts.patch_no = patch_no; - raw_opts.total_patches = total_patches; - let id = commit.id(); - raw_opts.id = id.raw(); - raw_opts.summary = summary.as_ptr() as *const _; - raw_opts.body = message.as_ptr() as *const _; - raw_opts.author = commit.author().raw(); - let buf = Buf::new(); - #[allow(deprecated)] - unsafe { - try_call!(raw::git_diff_format_email(buf.raw(), self.raw, &*raw_opts)); - } - Ok(buf) - } - - /// Create an patch ID from a diff. - pub fn patchid(&self, opts: Option<&mut DiffPatchidOptions>) -> Result<Oid, Error> { - let mut raw = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_diff_patchid( - &mut raw, - self.raw, - opts.map(|o| &mut o.raw) - )); - Ok(Binding::from_raw(&raw as *const _)) - } - } - - // TODO: num_deltas_of_type, find_similar -} -impl Diff<'static> { - /// Read the contents of a git patch file into a `git_diff` object. - /// - /// The diff object produced is similar to the one that would be - /// produced if you actually produced it computationally by comparing - /// two trees, however there may be subtle differences. For example, - /// a patch file likely contains abbreviated object IDs, so the - /// object IDs parsed by this function will also be abbreviated. - pub fn from_buffer(buffer: &[u8]) -> Result<Diff<'static>, Error> { - crate::init(); - let mut diff: *mut raw::git_diff = std::ptr::null_mut(); - unsafe { - // NOTE: Doesn't depend on repo, so lifetime can be 'static - try_call!(raw::git_diff_from_buffer( - &mut diff, - buffer.as_ptr() as *const c_char, - buffer.len() - )); - Ok(Diff::from_raw(diff)) - } - } -} - -pub extern "C" fn print_cb( - delta: *const raw::git_diff_delta, - hunk: *const raw::git_diff_hunk, - line: *const raw::git_diff_line, - data: *mut c_void, -) -> c_int { - unsafe { - let delta = Binding::from_raw(delta as *mut _); - let hunk = Binding::from_raw_opt(hunk); - let line = Binding::from_raw(line); - - let r = panic::wrap(|| { - let data = data as *mut &mut PrintCb<'_>; - (*data)(delta, hunk, line) - }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } - } -} - -pub extern "C" fn file_cb_c( - delta: *const raw::git_diff_delta, - progress: f32, - data: *mut c_void, -) -> c_int { - unsafe { - let delta = Binding::from_raw(delta as *mut _); - - let r = panic::wrap(|| { - let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>; - match (*cbs).file { - Some(ref mut cb) => cb(delta, progress), - None => false, - } - }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } - } -} - -pub extern "C" fn binary_cb_c( - delta: *const raw::git_diff_delta, - binary: *const raw::git_diff_binary, - data: *mut c_void, -) -> c_int { - unsafe { - let delta = Binding::from_raw(delta as *mut _); - let binary = Binding::from_raw(binary); - - let r = panic::wrap(|| { - let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>; - match (*cbs).binary { - Some(ref mut cb) => cb(delta, binary), - None => false, - } - }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } - } -} - -pub extern "C" fn hunk_cb_c( - delta: *const raw::git_diff_delta, - hunk: *const raw::git_diff_hunk, - data: *mut c_void, -) -> c_int { - unsafe { - let delta = Binding::from_raw(delta as *mut _); - let hunk = Binding::from_raw(hunk); - - let r = panic::wrap(|| { - let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>; - match (*cbs).hunk { - Some(ref mut cb) => cb(delta, hunk), - None => false, - } - }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } - } -} - -pub extern "C" fn line_cb_c( - delta: *const raw::git_diff_delta, - hunk: *const raw::git_diff_hunk, - line: *const raw::git_diff_line, - data: *mut c_void, -) -> c_int { - unsafe { - let delta = Binding::from_raw(delta as *mut _); - let hunk = Binding::from_raw_opt(hunk); - let line = Binding::from_raw(line); - - let r = panic::wrap(|| { - let cbs = data as *mut DiffCallbacks<'_, '_, '_, '_, '_, '_, '_, '_>; - match (*cbs).line { - Some(ref mut cb) => cb(delta, hunk, line), - None => false, - } - }); - if r == Some(true) { - raw::GIT_OK - } else { - raw::GIT_EUSER - } - } -} - -impl<'repo> Binding for Diff<'repo> { - type Raw = *mut raw::git_diff; - unsafe fn from_raw(raw: *mut raw::git_diff) -> Diff<'repo> { - Diff { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_diff { - self.raw - } -} - -impl<'repo> Drop for Diff<'repo> { - fn drop(&mut self) { - unsafe { raw::git_diff_free(self.raw) } - } -} - -impl<'a> DiffDelta<'a> { - /// Returns the flags on the delta. - /// - /// For more information, see `DiffFlags`'s documentation. - pub fn flags(&self) -> DiffFlags { - let flags = unsafe { (*self.raw).flags }; - let mut result = DiffFlags::empty(); - - #[cfg(target_env = "msvc")] - fn as_u32(flag: i32) -> u32 { - flag as u32 - } - #[cfg(not(target_env = "msvc"))] - fn as_u32(flag: u32) -> u32 { - flag - } - - if (flags & as_u32(raw::GIT_DIFF_FLAG_BINARY)) != 0 { - result |= DiffFlags::BINARY; - } - if (flags & as_u32(raw::GIT_DIFF_FLAG_NOT_BINARY)) != 0 { - result |= DiffFlags::NOT_BINARY; - } - if (flags & as_u32(raw::GIT_DIFF_FLAG_VALID_ID)) != 0 { - result |= DiffFlags::VALID_ID; - } - if (flags & as_u32(raw::GIT_DIFF_FLAG_EXISTS)) != 0 { - result |= DiffFlags::EXISTS; - } - result - } - - // TODO: expose when diffs are more exposed - // pub fn similarity(&self) -> u16 { - // unsafe { (*self.raw).similarity } - // } - - /// Returns the number of files in this delta. - pub fn nfiles(&self) -> u16 { - unsafe { (*self.raw).nfiles } - } - - /// Returns the status of this entry - /// - /// For more information, see `Delta`'s documentation - pub fn status(&self) -> Delta { - match unsafe { (*self.raw).status } { - raw::GIT_DELTA_UNMODIFIED => Delta::Unmodified, - raw::GIT_DELTA_ADDED => Delta::Added, - raw::GIT_DELTA_DELETED => Delta::Deleted, - raw::GIT_DELTA_MODIFIED => Delta::Modified, - raw::GIT_DELTA_RENAMED => Delta::Renamed, - raw::GIT_DELTA_COPIED => Delta::Copied, - raw::GIT_DELTA_IGNORED => Delta::Ignored, - raw::GIT_DELTA_UNTRACKED => Delta::Untracked, - raw::GIT_DELTA_TYPECHANGE => Delta::Typechange, - raw::GIT_DELTA_UNREADABLE => Delta::Unreadable, - raw::GIT_DELTA_CONFLICTED => Delta::Conflicted, - n => panic!("unknown diff status: {}", n), - } - } - - /// Return the file which represents the "from" side of the diff. - /// - /// What side this means depends on the function that was used to generate - /// the diff and will be documented on the function itself. - pub fn old_file(&self) -> DiffFile<'a> { - unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) } - } - - /// Return the file which represents the "to" side of the diff. - /// - /// What side this means depends on the function that was used to generate - /// the diff and will be documented on the function itself. - pub fn new_file(&self) -> DiffFile<'a> { - unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) } - } -} - -impl<'a> Binding for DiffDelta<'a> { - type Raw = *mut raw::git_diff_delta; - unsafe fn from_raw(raw: *mut raw::git_diff_delta) -> DiffDelta<'a> { - DiffDelta { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_diff_delta { - self.raw - } -} - -impl<'a> std::fmt::Debug for DiffDelta<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_struct("DiffDelta") - .field("nfiles", &self.nfiles()) - .field("status", &self.status()) - .field("old_file", &self.old_file()) - .field("new_file", &self.new_file()) - .finish() - } -} - -impl<'a> DiffFile<'a> { - /// Returns the Oid of this item. - /// - /// If this entry represents an absent side of a diff (e.g. the `old_file` - /// of a `Added` delta), then the oid returned will be zeroes. - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(&(*self.raw).id as *const _) } - } - - /// Returns the path, in bytes, of the entry relative to the working - /// directory of the repository. - pub fn path_bytes(&self) -> Option<&'a [u8]> { - static FOO: () = (); - unsafe { crate::opt_bytes(&FOO, (*self.raw).path) } - } - - /// Returns the path of the entry relative to the working directory of the - /// repository. - pub fn path(&self) -> Option<&'a Path> { - self.path_bytes().map(util::bytes2path) - } - - /// Returns the size of this entry, in bytes - pub fn size(&self) -> u64 { - unsafe { (*self.raw).size as u64 } - } - - /// Returns `true` if file(s) are treated as binary data. - pub fn is_binary(&self) -> bool { - unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_BINARY as u32 != 0 } - } - - /// Returns `true` if file(s) are treated as text data. - pub fn is_not_binary(&self) -> bool { - unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_NOT_BINARY as u32 != 0 } - } - - /// Returns `true` if `id` value is known correct. - pub fn is_valid_id(&self) -> bool { - unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_VALID_ID as u32 != 0 } - } - - /// Returns `true` if file exists at this side of the delta. - pub fn exists(&self) -> bool { - unsafe { (*self.raw).flags & raw::GIT_DIFF_FLAG_EXISTS as u32 != 0 } - } - - /// Returns file mode. - pub fn mode(&self) -> FileMode { - match unsafe { (*self.raw).mode.into() } { - raw::GIT_FILEMODE_UNREADABLE => FileMode::Unreadable, - raw::GIT_FILEMODE_TREE => FileMode::Tree, - raw::GIT_FILEMODE_BLOB => FileMode::Blob, - raw::GIT_FILEMODE_BLOB_GROUP_WRITABLE => FileMode::BlobGroupWritable, - raw::GIT_FILEMODE_BLOB_EXECUTABLE => FileMode::BlobExecutable, - raw::GIT_FILEMODE_LINK => FileMode::Link, - raw::GIT_FILEMODE_COMMIT => FileMode::Commit, - mode => panic!("unknown mode: {}", mode), - } - } -} - -impl<'a> Binding for DiffFile<'a> { - type Raw = *const raw::git_diff_file; - unsafe fn from_raw(raw: *const raw::git_diff_file) -> DiffFile<'a> { - DiffFile { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *const raw::git_diff_file { - self.raw - } -} - -impl<'a> std::fmt::Debug for DiffFile<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - let mut ds = f.debug_struct("DiffFile"); - ds.field("id", &self.id()); - if let Some(path_bytes) = &self.path_bytes() { - ds.field("path_bytes", path_bytes); - } - if let Some(path) = &self.path() { - ds.field("path", path); - } - ds.field("size", &self.size()).finish() - } -} - -impl Default for DiffOptions { - fn default() -> Self { - Self::new() - } -} - -impl DiffOptions { - /// Creates a new set of empty diff options. - /// - /// All flags and other options are defaulted to false or their otherwise - /// zero equivalents. - pub fn new() -> DiffOptions { - let mut opts = DiffOptions { - pathspec: Vec::new(), - pathspec_ptrs: Vec::new(), - raw: unsafe { mem::zeroed() }, - old_prefix: None, - new_prefix: None, - }; - assert_eq!(unsafe { raw::git_diff_init_options(&mut opts.raw, 1) }, 0); - opts - } - - fn flag(&mut self, opt: raw::git_diff_option_t, val: bool) -> &mut DiffOptions { - let opt = opt as u32; - if val { - self.raw.flags |= opt; - } else { - self.raw.flags &= !opt; - } - self - } - - /// Flag indicating whether the sides of the diff will be reversed. - pub fn reverse(&mut self, reverse: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_REVERSE, reverse) - } - - /// Flag indicating whether ignored files are included. - pub fn include_ignored(&mut self, include: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_INCLUDE_IGNORED, include) - } - - /// Flag indicating whether ignored directories are traversed deeply or not. - pub fn recurse_ignored_dirs(&mut self, recurse: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_RECURSE_IGNORED_DIRS, recurse) - } - - /// Flag indicating whether untracked files are in the diff - pub fn include_untracked(&mut self, include: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_INCLUDE_UNTRACKED, include) - } - - /// Flag indicating whether untracked directories are traversed deeply or - /// not. - pub fn recurse_untracked_dirs(&mut self, recurse: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_RECURSE_UNTRACKED_DIRS, recurse) - } - - /// Flag indicating whether unmodified files are in the diff. - pub fn include_unmodified(&mut self, include: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_INCLUDE_UNMODIFIED, include) - } - - /// If enabled, then Typechange delta records are generated. - pub fn include_typechange(&mut self, include: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE, include) - } - - /// Event with `include_typechange`, the tree returned generally shows a - /// deleted blob. This flag correctly labels the tree transitions as a - /// typechange record with the `new_file`'s mode set to tree. - /// - /// Note that the tree SHA will not be available. - pub fn include_typechange_trees(&mut self, include: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_INCLUDE_TYPECHANGE_TREES, include) - } - - /// Flag indicating whether file mode changes are ignored. - pub fn ignore_filemode(&mut self, ignore: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_IGNORE_FILEMODE, ignore) - } - - /// Flag indicating whether all submodules should be treated as unmodified. - pub fn ignore_submodules(&mut self, ignore: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_IGNORE_SUBMODULES, ignore) - } - - /// Flag indicating whether case insensitive filenames should be used. - pub fn ignore_case(&mut self, ignore: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_IGNORE_CASE, ignore) - } - - /// If pathspecs are specified, this flag means that they should be applied - /// as an exact match instead of a fnmatch pattern. - pub fn disable_pathspec_match(&mut self, disable: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_DISABLE_PATHSPEC_MATCH, disable) - } - - /// Disable updating the `binary` flag in delta records. This is useful when - /// iterating over a diff if you don't need hunk and data callbacks and want - /// to avoid having to load a file completely. - pub fn skip_binary_check(&mut self, skip: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_SKIP_BINARY_CHECK, skip) - } - - /// When diff finds an untracked directory, to match the behavior of core - /// Git, it scans the contents for ignored and untracked files. If all - /// contents are ignored, then the directory is ignored; if any contents are - /// not ignored, then the directory is untracked. This is extra work that - /// may not matter in many cases. - /// - /// This flag turns off that scan and immediately labels an untracked - /// directory as untracked (changing the behavior to not match core git). - pub fn enable_fast_untracked_dirs(&mut self, enable: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS, enable) - } - - /// When diff finds a file in the working directory with stat information - /// different from the index, but the OID ends up being the same, write the - /// correct stat information into the index. Note: without this flag, diff - /// will always leave the index untouched. - pub fn update_index(&mut self, update: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_UPDATE_INDEX, update) - } - - /// Include unreadable files in the diff - pub fn include_unreadable(&mut self, include: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE, include) - } - - /// Include unreadable files in the diff as untracked files - pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED, include) - } - - /// Treat all files as text, disabling binary attributes and detection. - pub fn force_text(&mut self, force: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_FORCE_TEXT, force) - } - - /// Treat all files as binary, disabling text diffs - pub fn force_binary(&mut self, force: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_FORCE_BINARY, force) - } - - /// Ignore all whitespace - pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE, ignore) - } - - /// Ignore changes in the amount of whitespace - pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_CHANGE, ignore) - } - - /// Ignore whitespace at the end of line - pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_IGNORE_WHITESPACE_EOL, ignore) - } - - /// Ignore blank lines - pub fn ignore_blank_lines(&mut self, ignore: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_IGNORE_BLANK_LINES, ignore) - } - - /// When generating patch text, include the content of untracked files. - /// - /// This automatically turns on `include_untracked` but it does not turn on - /// `recurse_untracked_dirs`. Add that flag if you want the content of every - /// single untracked file. - pub fn show_untracked_content(&mut self, show: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_SHOW_UNTRACKED_CONTENT, show) - } - - /// When generating output, include the names of unmodified files if they - /// are included in the `Diff`. Normally these are skipped in the formats - /// that list files (e.g. name-only, name-status, raw). Even with this these - /// will not be included in the patch format. - pub fn show_unmodified(&mut self, show: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_SHOW_UNMODIFIED, show) - } - - /// Use the "patience diff" algorithm - pub fn patience(&mut self, patience: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_PATIENCE, patience) - } - - /// Take extra time to find the minimal diff - pub fn minimal(&mut self, minimal: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_MINIMAL, minimal) - } - - /// Include the necessary deflate/delta information so that `git-apply` can - /// apply given diff information to binary files. - pub fn show_binary(&mut self, show: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_SHOW_BINARY, show) - } - - /// Use a heuristic that takes indentation and whitespace into account - /// which generally can produce better diffs when dealing with ambiguous - /// diff hunks. - pub fn indent_heuristic(&mut self, heuristic: bool) -> &mut DiffOptions { - self.flag(raw::GIT_DIFF_INDENT_HEURISTIC, heuristic) - } - - /// Set the number of unchanged lines that define the boundary of a hunk - /// (and to display before and after). - /// - /// The default value for this is 3. - pub fn context_lines(&mut self, lines: u32) -> &mut DiffOptions { - self.raw.context_lines = lines; - self - } - - /// Set the maximum number of unchanged lines between hunk boundaries before - /// the hunks will be merged into one. - /// - /// The default value for this is 0. - pub fn interhunk_lines(&mut self, lines: u32) -> &mut DiffOptions { - self.raw.interhunk_lines = lines; - self - } - - /// The default value for this is `core.abbrev` or 7 if unset. - pub fn id_abbrev(&mut self, abbrev: u16) -> &mut DiffOptions { - self.raw.id_abbrev = abbrev; - self - } - - /// Maximum size (in bytes) above which a blob will be marked as binary - /// automatically. - /// - /// A negative value will disable this entirely. - /// - /// The default value for this is 512MB. - pub fn max_size(&mut self, size: i64) -> &mut DiffOptions { - self.raw.max_size = size as raw::git_off_t; - self - } - - /// The virtual "directory" to prefix old file names with in hunk headers. - /// - /// The default value for this is "a". - pub fn old_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions { - self.old_prefix = Some(t.into_c_string().unwrap()); - self - } - - /// The virtual "directory" to prefix new file names with in hunk headers. - /// - /// The default value for this is "b". - pub fn new_prefix<T: IntoCString>(&mut self, t: T) -> &mut DiffOptions { - self.new_prefix = Some(t.into_c_string().unwrap()); - self - } - - /// Add to the array of paths/fnmatch patterns to constrain the diff. - pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut DiffOptions { - let s = util::cstring_to_repo_path(pathspec).unwrap(); - self.pathspec_ptrs.push(s.as_ptr()); - self.pathspec.push(s); - self - } - - /// Acquire a pointer to the underlying raw options. - /// - /// This function is unsafe as the pointer is only valid so long as this - /// structure is not moved, modified, or used elsewhere. - pub unsafe fn raw(&mut self) -> *const raw::git_diff_options { - self.raw.old_prefix = self - .old_prefix - .as_ref() - .map(|s| s.as_ptr()) - .unwrap_or(ptr::null()); - self.raw.new_prefix = self - .new_prefix - .as_ref() - .map(|s| s.as_ptr()) - .unwrap_or(ptr::null()); - self.raw.pathspec.count = self.pathspec_ptrs.len() as size_t; - self.raw.pathspec.strings = self.pathspec_ptrs.as_ptr() as *mut _; - &self.raw as *const _ - } - - // TODO: expose ignore_submodules, notify_cb/notify_payload -} - -impl<'diff> Iterator for Deltas<'diff> { - type Item = DiffDelta<'diff>; - fn next(&mut self) -> Option<DiffDelta<'diff>> { - self.range.next().and_then(|i| self.diff.get_delta(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'diff> DoubleEndedIterator for Deltas<'diff> { - fn next_back(&mut self) -> Option<DiffDelta<'diff>> { - self.range.next_back().and_then(|i| self.diff.get_delta(i)) - } -} -impl<'diff> FusedIterator for Deltas<'diff> {} - -impl<'diff> ExactSizeIterator for Deltas<'diff> {} - -/// Line origin constants. -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum DiffLineType { - /// These values will be sent to `git_diff_line_cb` along with the line - Context, - /// - Addition, - /// - Deletion, - /// Both files have no LF at end - ContextEOFNL, - /// Old has no LF at end, new does - AddEOFNL, - /// Old has LF at end, new does not - DeleteEOFNL, - /// The following values will only be sent to a `git_diff_line_cb` when - /// the content of a diff is being formatted through `git_diff_print`. - FileHeader, - /// - HunkHeader, - /// For "Binary files x and y differ" - Binary, -} - -impl Binding for DiffLineType { - type Raw = raw::git_diff_line_t; - unsafe fn from_raw(raw: raw::git_diff_line_t) -> Self { - match raw { - raw::GIT_DIFF_LINE_CONTEXT => DiffLineType::Context, - raw::GIT_DIFF_LINE_ADDITION => DiffLineType::Addition, - raw::GIT_DIFF_LINE_DELETION => DiffLineType::Deletion, - raw::GIT_DIFF_LINE_CONTEXT_EOFNL => DiffLineType::ContextEOFNL, - raw::GIT_DIFF_LINE_ADD_EOFNL => DiffLineType::AddEOFNL, - raw::GIT_DIFF_LINE_DEL_EOFNL => DiffLineType::DeleteEOFNL, - raw::GIT_DIFF_LINE_FILE_HDR => DiffLineType::FileHeader, - raw::GIT_DIFF_LINE_HUNK_HDR => DiffLineType::HunkHeader, - raw::GIT_DIFF_LINE_BINARY => DiffLineType::Binary, - _ => panic!("Unknown git diff line type"), - } - } - fn raw(&self) -> raw::git_diff_line_t { - match *self { - DiffLineType::Context => raw::GIT_DIFF_LINE_CONTEXT, - DiffLineType::Addition => raw::GIT_DIFF_LINE_ADDITION, - DiffLineType::Deletion => raw::GIT_DIFF_LINE_DELETION, - DiffLineType::ContextEOFNL => raw::GIT_DIFF_LINE_CONTEXT_EOFNL, - DiffLineType::AddEOFNL => raw::GIT_DIFF_LINE_ADD_EOFNL, - DiffLineType::DeleteEOFNL => raw::GIT_DIFF_LINE_DEL_EOFNL, - DiffLineType::FileHeader => raw::GIT_DIFF_LINE_FILE_HDR, - DiffLineType::HunkHeader => raw::GIT_DIFF_LINE_HUNK_HDR, - DiffLineType::Binary => raw::GIT_DIFF_LINE_BINARY, - } - } -} - -impl<'a> DiffLine<'a> { - /// Line number in old file or `None` for added line - pub fn old_lineno(&self) -> Option<u32> { - match unsafe { (*self.raw).old_lineno } { - n if n < 0 => None, - n => Some(n as u32), - } - } - - /// Line number in new file or `None` for deleted line - pub fn new_lineno(&self) -> Option<u32> { - match unsafe { (*self.raw).new_lineno } { - n if n < 0 => None, - n => Some(n as u32), - } - } - - /// Number of newline characters in content - pub fn num_lines(&self) -> u32 { - unsafe { (*self.raw).num_lines as u32 } - } - - /// Offset in the original file to the content - pub fn content_offset(&self) -> i64 { - unsafe { (*self.raw).content_offset as i64 } - } - - /// Content of this line as bytes. - pub fn content(&self) -> &'a [u8] { - unsafe { - slice::from_raw_parts( - (*self.raw).content as *const u8, - (*self.raw).content_len as usize, - ) - } - } - - /// origin of this `DiffLine`. - /// - pub fn origin_value(&self) -> DiffLineType { - unsafe { Binding::from_raw((*self.raw).origin as raw::git_diff_line_t) } - } - - /// Sigil showing the origin of this `DiffLine`. - /// - /// * ` ` - Line context - /// * `+` - Line addition - /// * `-` - Line deletion - /// * `=` - Context (End of file) - /// * `>` - Add (End of file) - /// * `<` - Remove (End of file) - /// * `F` - File header - /// * `H` - Hunk header - /// * `B` - Line binary - pub fn origin(&self) -> char { - match unsafe { (*self.raw).origin as raw::git_diff_line_t } { - raw::GIT_DIFF_LINE_CONTEXT => ' ', - raw::GIT_DIFF_LINE_ADDITION => '+', - raw::GIT_DIFF_LINE_DELETION => '-', - raw::GIT_DIFF_LINE_CONTEXT_EOFNL => '=', - raw::GIT_DIFF_LINE_ADD_EOFNL => '>', - raw::GIT_DIFF_LINE_DEL_EOFNL => '<', - raw::GIT_DIFF_LINE_FILE_HDR => 'F', - raw::GIT_DIFF_LINE_HUNK_HDR => 'H', - raw::GIT_DIFF_LINE_BINARY => 'B', - _ => ' ', - } - } -} - -impl<'a> Binding for DiffLine<'a> { - type Raw = *const raw::git_diff_line; - unsafe fn from_raw(raw: *const raw::git_diff_line) -> DiffLine<'a> { - DiffLine { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *const raw::git_diff_line { - self.raw - } -} - -impl<'a> std::fmt::Debug for DiffLine<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - let mut ds = f.debug_struct("DiffLine"); - if let Some(old_lineno) = &self.old_lineno() { - ds.field("old_lineno", old_lineno); - } - if let Some(new_lineno) = &self.new_lineno() { - ds.field("new_lineno", new_lineno); - } - ds.field("num_lines", &self.num_lines()) - .field("content_offset", &self.content_offset()) - .field("content", &self.content()) - .field("origin", &self.origin()) - .finish() - } -} - -impl<'a> DiffHunk<'a> { - /// Starting line number in old_file - pub fn old_start(&self) -> u32 { - unsafe { (*self.raw).old_start as u32 } - } - - /// Number of lines in old_file - pub fn old_lines(&self) -> u32 { - unsafe { (*self.raw).old_lines as u32 } - } - - /// Starting line number in new_file - pub fn new_start(&self) -> u32 { - unsafe { (*self.raw).new_start as u32 } - } - - /// Number of lines in new_file - pub fn new_lines(&self) -> u32 { - unsafe { (*self.raw).new_lines as u32 } - } - - /// Header text - pub fn header(&self) -> &'a [u8] { - unsafe { - slice::from_raw_parts( - (*self.raw).header.as_ptr() as *const u8, - (*self.raw).header_len as usize, - ) - } - } -} - -impl<'a> Binding for DiffHunk<'a> { - type Raw = *const raw::git_diff_hunk; - unsafe fn from_raw(raw: *const raw::git_diff_hunk) -> DiffHunk<'a> { - DiffHunk { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *const raw::git_diff_hunk { - self.raw - } -} - -impl<'a> std::fmt::Debug for DiffHunk<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_struct("DiffHunk") - .field("old_start", &self.old_start()) - .field("old_lines", &self.old_lines()) - .field("new_start", &self.new_start()) - .field("new_lines", &self.new_lines()) - .field("header", &self.header()) - .finish() - } -} - -impl DiffStats { - /// Get the total number of files changed in a diff. - pub fn files_changed(&self) -> usize { - unsafe { raw::git_diff_stats_files_changed(&*self.raw) as usize } - } - - /// Get the total number of insertions in a diff - pub fn insertions(&self) -> usize { - unsafe { raw::git_diff_stats_insertions(&*self.raw) as usize } - } - - /// Get the total number of deletions in a diff - pub fn deletions(&self) -> usize { - unsafe { raw::git_diff_stats_deletions(&*self.raw) as usize } - } - - /// Print diff statistics to a Buf - pub fn to_buf(&self, format: DiffStatsFormat, width: usize) -> Result<Buf, Error> { - let buf = Buf::new(); - unsafe { - try_call!(raw::git_diff_stats_to_buf( - buf.raw(), - self.raw, - format.bits(), - width as size_t - )); - } - Ok(buf) - } -} - -impl Binding for DiffStats { - type Raw = *mut raw::git_diff_stats; - - unsafe fn from_raw(raw: *mut raw::git_diff_stats) -> DiffStats { - DiffStats { raw } - } - fn raw(&self) -> *mut raw::git_diff_stats { - self.raw - } -} - -impl Drop for DiffStats { - fn drop(&mut self) { - unsafe { raw::git_diff_stats_free(self.raw) } - } -} - -impl std::fmt::Debug for DiffStats { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_struct("DiffStats") - .field("files_changed", &self.files_changed()) - .field("insertions", &self.insertions()) - .field("deletions", &self.deletions()) - .finish() - } -} - -impl<'a> DiffBinary<'a> { - /// Returns whether there is data in this binary structure or not. - /// - /// If this is `true`, then this was produced and included binary content. - /// If this is `false` then this was generated knowing only that a binary - /// file changed but without providing the data, probably from a patch that - /// said `Binary files a/file.txt and b/file.txt differ`. - pub fn contains_data(&self) -> bool { - unsafe { (*self.raw).contains_data == 1 } - } - - /// The contents of the old file. - pub fn old_file(&self) -> DiffBinaryFile<'a> { - unsafe { Binding::from_raw(&(*self.raw).old_file as *const _) } - } - - /// The contents of the new file. - pub fn new_file(&self) -> DiffBinaryFile<'a> { - unsafe { Binding::from_raw(&(*self.raw).new_file as *const _) } - } -} - -impl<'a> Binding for DiffBinary<'a> { - type Raw = *const raw::git_diff_binary; - unsafe fn from_raw(raw: *const raw::git_diff_binary) -> DiffBinary<'a> { - DiffBinary { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *const raw::git_diff_binary { - self.raw - } -} - -impl<'a> DiffBinaryFile<'a> { - /// The type of binary data for this file - pub fn kind(&self) -> DiffBinaryKind { - unsafe { Binding::from_raw((*self.raw).kind) } - } - - /// The binary data, deflated - pub fn data(&self) -> &[u8] { - unsafe { - slice::from_raw_parts((*self.raw).data as *const u8, (*self.raw).datalen as usize) - } - } - - /// The length of the binary data after inflation - pub fn inflated_len(&self) -> usize { - unsafe { (*self.raw).inflatedlen as usize } - } -} - -impl<'a> Binding for DiffBinaryFile<'a> { - type Raw = *const raw::git_diff_binary_file; - unsafe fn from_raw(raw: *const raw::git_diff_binary_file) -> DiffBinaryFile<'a> { - DiffBinaryFile { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *const raw::git_diff_binary_file { - self.raw - } -} - -impl Binding for DiffBinaryKind { - type Raw = raw::git_diff_binary_t; - unsafe fn from_raw(raw: raw::git_diff_binary_t) -> DiffBinaryKind { - match raw { - raw::GIT_DIFF_BINARY_NONE => DiffBinaryKind::None, - raw::GIT_DIFF_BINARY_LITERAL => DiffBinaryKind::Literal, - raw::GIT_DIFF_BINARY_DELTA => DiffBinaryKind::Delta, - _ => panic!("Unknown git diff binary kind"), - } - } - fn raw(&self) -> raw::git_diff_binary_t { - match *self { - DiffBinaryKind::None => raw::GIT_DIFF_BINARY_NONE, - DiffBinaryKind::Literal => raw::GIT_DIFF_BINARY_LITERAL, - DiffBinaryKind::Delta => raw::GIT_DIFF_BINARY_DELTA, - } - } -} - -impl Default for DiffFindOptions { - fn default() -> Self { - Self::new() - } -} - -impl DiffFindOptions { - /// Creates a new set of empty diff find options. - /// - /// All flags and other options are defaulted to false or their otherwise - /// zero equivalents. - pub fn new() -> DiffFindOptions { - let mut opts = DiffFindOptions { - raw: unsafe { mem::zeroed() }, - }; - assert_eq!( - unsafe { raw::git_diff_find_init_options(&mut opts.raw, 1) }, - 0 - ); - opts - } - - fn flag(&mut self, opt: u32, val: bool) -> &mut DiffFindOptions { - if val { - self.raw.flags |= opt; - } else { - self.raw.flags &= !opt; - } - self - } - - /// Reset all flags back to their unset state, indicating that - /// `diff.renames` should be used instead. This is overridden once any flag - /// is set. - pub fn by_config(&mut self) -> &mut DiffFindOptions { - self.flag(0xffffffff, false) - } - - /// Look for renames? - pub fn renames(&mut self, find: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_RENAMES, find) - } - - /// Consider old side of modified for renames? - pub fn renames_from_rewrites(&mut self, find: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_RENAMES_FROM_REWRITES, find) - } - - /// Look for copies? - pub fn copies(&mut self, find: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_COPIES, find) - } - - /// Consider unmodified as copy sources? - /// - /// For this to work correctly, use `include_unmodified` when the initial - /// diff is being generated. - pub fn copies_from_unmodified(&mut self, find: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED, find) - } - - /// Mark significant rewrites for split. - pub fn rewrites(&mut self, find: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_REWRITES, find) - } - - /// Actually split large rewrites into delete/add pairs - pub fn break_rewrites(&mut self, find: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_BREAK_REWRITES, find) - } - - #[doc(hidden)] - pub fn break_rewries(&mut self, find: bool) -> &mut DiffFindOptions { - self.break_rewrites(find) - } - - /// Find renames/copies for untracked items in working directory. - /// - /// For this to work correctly use the `include_untracked` option when the - /// initial diff is being generated. - pub fn for_untracked(&mut self, find: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_FOR_UNTRACKED, find) - } - - /// Turn on all finding features. - pub fn all(&mut self, find: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_ALL, find) - } - - /// Measure similarity ignoring leading whitespace (default) - pub fn ignore_leading_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE, ignore) - } - - /// Measure similarity ignoring all whitespace - pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_IGNORE_WHITESPACE, ignore) - } - - /// Measure similarity including all data - pub fn dont_ignore_whitespace(&mut self, dont: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE, dont) - } - - /// Measure similarity only by comparing SHAs (fast and cheap) - pub fn exact_match_only(&mut self, exact: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_EXACT_MATCH_ONLY, exact) - } - - /// Do not break rewrites unless they contribute to a rename. - /// - /// Normally, `break_rewrites` and `rewrites` will measure the - /// self-similarity of modified files and split the ones that have changed a - /// lot into a delete/add pair. Then the sides of that pair will be - /// considered candidates for rename and copy detection - /// - /// If you add this flag in and the split pair is not used for an actual - /// rename or copy, then the modified record will be restored to a regular - /// modified record instead of being split. - pub fn break_rewrites_for_renames_only(&mut self, b: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY, b) - } - - /// Remove any unmodified deltas after find_similar is done. - /// - /// Using `copies_from_unmodified` to emulate the `--find-copies-harder` - /// behavior requires building a diff with the `include_unmodified` flag. If - /// you do not want unmodified records in the final result, pas this flag to - /// have them removed. - pub fn remove_unmodified(&mut self, remove: bool) -> &mut DiffFindOptions { - self.flag(raw::GIT_DIFF_FIND_REMOVE_UNMODIFIED, remove) - } - - /// Similarity to consider a file renamed (default 50) - pub fn rename_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions { - self.raw.rename_threshold = thresh; - self - } - - /// Similarity of modified to be eligible rename source (default 50) - pub fn rename_from_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions { - self.raw.rename_from_rewrite_threshold = thresh; - self - } - - /// Similarity to consider a file copy (default 50) - pub fn copy_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions { - self.raw.copy_threshold = thresh; - self - } - - /// Similarity to split modify into delete/add pair (default 60) - pub fn break_rewrite_threshold(&mut self, thresh: u16) -> &mut DiffFindOptions { - self.raw.break_rewrite_threshold = thresh; - self - } - - /// Maximum similarity sources to examine for a file (somewhat like - /// git-diff's `-l` option or `diff.renameLimit` config) - /// - /// Defaults to 200 - pub fn rename_limit(&mut self, limit: usize) -> &mut DiffFindOptions { - self.raw.rename_limit = limit as size_t; - self - } - - // TODO: expose git_diff_similarity_metric - - /// Acquire a pointer to the underlying raw options. - pub unsafe fn raw(&mut self) -> *const raw::git_diff_find_options { - &self.raw - } -} - -impl Default for DiffFormatEmailOptions { - fn default() -> Self { - Self::new() - } -} - -impl DiffFormatEmailOptions { - /// Creates a new set of email options, - /// initialized to the default values - pub fn new() -> Self { - let mut opts = DiffFormatEmailOptions { - raw: unsafe { mem::zeroed() }, - }; - assert_eq!( - unsafe { raw::git_diff_format_email_options_init(&mut opts.raw, 1) }, - 0 - ); - opts - } - - fn flag(&mut self, opt: u32, val: bool) -> &mut Self { - if val { - self.raw.flags |= opt; - } else { - self.raw.flags &= !opt; - } - self - } - - /// Exclude `[PATCH]` from the subject header - pub fn exclude_subject_patch_header(&mut self, should_exclude: bool) -> &mut Self { - self.flag( - raw::GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER, - should_exclude, - ) - } -} - -impl DiffPatchidOptions { - /// Creates a new set of patchid options, - /// initialized to the default values - pub fn new() -> Self { - let mut opts = DiffPatchidOptions { - raw: unsafe { mem::zeroed() }, - }; - assert_eq!( - unsafe { - raw::git_diff_patchid_options_init( - &mut opts.raw, - raw::GIT_DIFF_PATCHID_OPTIONS_VERSION, - ) - }, - 0 - ); - opts - } -} - -#[cfg(test)] -mod tests { - use crate::{DiffLineType, DiffOptions, Oid, Signature, Time}; - use std::borrow::Borrow; - use std::fs::File; - use std::io::Write; - use std::path::Path; - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let diff = repo.diff_tree_to_workdir(None, None).unwrap(); - assert_eq!(diff.deltas().len(), 0); - let stats = diff.stats().unwrap(); - assert_eq!(stats.insertions(), 0); - assert_eq!(stats.deletions(), 0); - assert_eq!(stats.files_changed(), 0); - let patchid = diff.patchid(None).unwrap(); - assert_ne!(patchid, Oid::zero()); - } - - #[test] - fn foreach_smoke() { - let (_td, repo) = crate::test::repo_init(); - let diff = t!(repo.diff_tree_to_workdir(None, None)); - let mut count = 0; - t!(diff.foreach( - &mut |_file, _progress| { - count = count + 1; - true - }, - None, - None, - None - )); - assert_eq!(count, 0); - } - - #[test] - fn foreach_file_only() { - let path = Path::new("foo"); - let (td, repo) = crate::test::repo_init(); - t!(t!(File::create(&td.path().join(path))).write_all(b"bar")); - let mut opts = DiffOptions::new(); - opts.include_untracked(true); - let diff = t!(repo.diff_tree_to_workdir(None, Some(&mut opts))); - let mut count = 0; - let mut result = None; - t!(diff.foreach( - &mut |file, _progress| { - count = count + 1; - result = file.new_file().path().map(ToOwned::to_owned); - true - }, - None, - None, - None - )); - assert_eq!(result.as_ref().map(Borrow::borrow), Some(path)); - assert_eq!(count, 1); - } - - #[test] - fn foreach_file_and_hunk() { - let path = Path::new("foo"); - let (td, repo) = crate::test::repo_init(); - t!(t!(File::create(&td.path().join(path))).write_all(b"bar")); - let mut index = t!(repo.index()); - t!(index.add_path(path)); - let mut opts = DiffOptions::new(); - opts.include_untracked(true); - let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts))); - let mut new_lines = 0; - t!(diff.foreach( - &mut |_file, _progress| { true }, - None, - Some(&mut |_file, hunk| { - new_lines = hunk.new_lines(); - true - }), - None - )); - assert_eq!(new_lines, 1); - } - - #[test] - fn foreach_all_callbacks() { - let fib = vec![0, 1, 1, 2, 3, 5, 8]; - // Verified with a node implementation of deflate, might be worth - // adding a deflate lib to do this inline here. - let deflated_fib = vec![120, 156, 99, 96, 100, 100, 98, 102, 229, 0, 0, 0, 53, 0, 21]; - let foo_path = Path::new("foo"); - let bin_path = Path::new("bin"); - let (td, repo) = crate::test::repo_init(); - t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n")); - t!(t!(File::create(&td.path().join(bin_path))).write_all(&fib)); - let mut index = t!(repo.index()); - t!(index.add_path(foo_path)); - t!(index.add_path(bin_path)); - let mut opts = DiffOptions::new(); - opts.include_untracked(true).show_binary(true); - let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts))); - let mut bin_content = None; - let mut new_lines = 0; - let mut line_content = None; - t!(diff.foreach( - &mut |_file, _progress| { true }, - Some(&mut |_file, binary| { - bin_content = Some(binary.new_file().data().to_owned()); - true - }), - Some(&mut |_file, hunk| { - new_lines = hunk.new_lines(); - true - }), - Some(&mut |_file, _hunk, line| { - line_content = String::from_utf8(line.content().into()).ok(); - true - }) - )); - assert_eq!(bin_content, Some(deflated_fib)); - assert_eq!(new_lines, 1); - assert_eq!(line_content, Some("bar\n".to_string())); - } - - #[test] - fn format_email_simple() { - let (_td, repo) = crate::test::repo_init(); - const COMMIT_MESSAGE: &str = "Modify some content"; - const EXPECTED_EMAIL_START: &str = concat!( - "From f1234fb0588b6ed670779a34ba5c51ef962f285f Mon Sep 17 00:00:00 2001\n", - "From: Techcable <dummy@dummy.org>\n", - "Date: Tue, 11 Jan 1972 17:46:40 +0000\n", - "Subject: [PATCH] Modify some content\n", - "\n", - "---\n", - " file1.txt | 8 +++++---\n", - " 1 file changed, 5 insertions(+), 3 deletions(-)\n", - "\n", - "diff --git a/file1.txt b/file1.txt\n", - "index 94aaae8..af8f41d 100644\n", - "--- a/file1.txt\n", - "+++ b/file1.txt\n", - "@@ -1,15 +1,17 @@\n", - " file1.txt\n", - " file1.txt\n", - "+_file1.txt_\n", - " file1.txt\n", - " file1.txt\n", - " file1.txt\n", - " file1.txt\n", - "+\n", - "+\n", - " file1.txt\n", - " file1.txt\n", - " file1.txt\n", - " file1.txt\n", - " file1.txt\n", - "-file1.txt\n", - "-file1.txt\n", - "-file1.txt\n", - "+_file1.txt_\n", - "+_file1.txt_\n", - " file1.txt\n", - "--\n" - ); - const ORIGINAL_FILE: &str = concat!( - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n" - ); - const UPDATED_FILE: &str = concat!( - "file1.txt\n", - "file1.txt\n", - "_file1.txt_\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "\n", - "\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "file1.txt\n", - "_file1.txt_\n", - "_file1.txt_\n", - "file1.txt\n" - ); - const FILE_MODE: i32 = 0o100644; - let original_file = repo.blob(ORIGINAL_FILE.as_bytes()).unwrap(); - let updated_file = repo.blob(UPDATED_FILE.as_bytes()).unwrap(); - let mut original_tree = repo.treebuilder(None).unwrap(); - original_tree - .insert("file1.txt", original_file, FILE_MODE) - .unwrap(); - let original_tree = original_tree.write().unwrap(); - let mut updated_tree = repo.treebuilder(None).unwrap(); - updated_tree - .insert("file1.txt", updated_file, FILE_MODE) - .unwrap(); - let updated_tree = updated_tree.write().unwrap(); - let time = Time::new(64_000_000, 0); - let author = Signature::new("Techcable", "dummy@dummy.org", &time).unwrap(); - let updated_commit = repo - .commit( - None, - &author, - &author, - COMMIT_MESSAGE, - &repo.find_tree(updated_tree).unwrap(), - &[], // NOTE: Have no parents to ensure stable hash - ) - .unwrap(); - let updated_commit = repo.find_commit(updated_commit).unwrap(); - let mut diff = repo - .diff_tree_to_tree( - Some(&repo.find_tree(original_tree).unwrap()), - Some(&repo.find_tree(updated_tree).unwrap()), - None, - ) - .unwrap(); - #[allow(deprecated)] - let actual_email = diff.format_email(1, 1, &updated_commit, None).unwrap(); - let actual_email = actual_email.as_str().unwrap(); - assert!( - actual_email.starts_with(EXPECTED_EMAIL_START), - "Unexpected email:\n{}", - actual_email - ); - let mut remaining_lines = actual_email[EXPECTED_EMAIL_START.len()..].lines(); - let version_line = remaining_lines.next(); - assert!( - version_line.unwrap().starts_with("libgit2"), - "Invalid version line: {:?}", - version_line - ); - while let Some(line) = remaining_lines.next() { - assert_eq!(line.trim(), "") - } - } - - #[test] - fn foreach_diff_line_origin_value() { - let foo_path = Path::new("foo"); - let (td, repo) = crate::test::repo_init(); - t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n")); - let mut index = t!(repo.index()); - t!(index.add_path(foo_path)); - let mut opts = DiffOptions::new(); - opts.include_untracked(true); - let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts))); - let mut origin_values: Vec<DiffLineType> = Vec::new(); - t!(diff.foreach( - &mut |_file, _progress| { true }, - None, - None, - Some(&mut |_file, _hunk, line| { - origin_values.push(line.origin_value()); - true - }) - )); - assert_eq!(origin_values.len(), 1); - assert_eq!(origin_values[0], DiffLineType::Addition); - } - - #[test] - fn foreach_exits_with_euser() { - let foo_path = Path::new("foo"); - let bar_path = Path::new("foo"); - - let (td, repo) = crate::test::repo_init(); - t!(t!(File::create(&td.path().join(foo_path))).write_all(b"bar\n")); - - let mut index = t!(repo.index()); - t!(index.add_path(foo_path)); - t!(index.add_path(bar_path)); - - let mut opts = DiffOptions::new(); - opts.include_untracked(true); - let diff = t!(repo.diff_tree_to_index(None, Some(&index), Some(&mut opts))); - - let mut calls = 0; - let result = diff.foreach( - &mut |_file, _progress| { - calls += 1; - false - }, - None, - None, - None, - ); - - assert_eq!(result.unwrap_err().code(), crate::ErrorCode::User); - } -} diff --git a/extra/git2/src/email.rs b/extra/git2/src/email.rs deleted file mode 100644 index d3ebc0384..000000000 --- a/extra/git2/src/email.rs +++ /dev/null @@ -1,183 +0,0 @@ -use std::ffi::CString; -use std::{mem, ptr}; - -use crate::util::Binding; -use crate::{raw, Buf, Commit, DiffFindOptions, DiffOptions, Error, IntoCString}; -use crate::{Diff, Oid, Signature}; - -/// A structure to represent patch in mbox format for sending via email -pub struct Email { - buf: Buf, -} - -/// Options for controlling the formatting of the generated e-mail. -pub struct EmailCreateOptions { - diff_options: DiffOptions, - diff_find_options: DiffFindOptions, - subject_prefix: Option<CString>, - raw: raw::git_email_create_options, -} - -impl Default for EmailCreateOptions { - fn default() -> Self { - // Defaults options created in corresponding to `GIT_EMAIL_CREATE_OPTIONS_INIT` - let default_options = raw::git_email_create_options { - version: raw::GIT_EMAIL_CREATE_OPTIONS_VERSION, - flags: raw::GIT_EMAIL_CREATE_DEFAULT as u32, - diff_opts: unsafe { mem::zeroed() }, - diff_find_opts: unsafe { mem::zeroed() }, - subject_prefix: ptr::null(), - start_number: 1, - reroll_number: 0, - }; - let mut diff_options = DiffOptions::new(); - diff_options.show_binary(true).context_lines(3); - Self { - diff_options, - diff_find_options: DiffFindOptions::new(), - subject_prefix: None, - raw: default_options, - } - } -} - -impl EmailCreateOptions { - /// Creates a new set of email create options - /// - /// By default, options include rename detection and binary - /// diffs to match `git format-patch`. - pub fn new() -> Self { - Self::default() - } - - fn flag(&mut self, opt: raw::git_email_create_flags_t, val: bool) -> &mut Self { - let opt = opt as u32; - if val { - self.raw.flags |= opt; - } else { - self.raw.flags &= !opt; - } - self - } - - /// Flag indicating whether patch numbers are included in the subject prefix. - pub fn omit_numbers(&mut self, omit: bool) -> &mut Self { - self.flag(raw::GIT_EMAIL_CREATE_OMIT_NUMBERS, omit) - } - - /// Flag indicating whether numbers included in the subject prefix even when - /// the patch is for a single commit (1/1). - pub fn always_number(&mut self, always: bool) -> &mut Self { - self.flag(raw::GIT_EMAIL_CREATE_ALWAYS_NUMBER, always) - } - - /// Flag indicating whether rename or similarity detection are ignored. - pub fn ignore_renames(&mut self, ignore: bool) -> &mut Self { - self.flag(raw::GIT_EMAIL_CREATE_NO_RENAMES, ignore) - } - - /// Get mutable access to `DiffOptions` that are used for creating diffs. - pub fn diff_options(&mut self) -> &mut DiffOptions { - &mut self.diff_options - } - - /// Get mutable access to `DiffFindOptions` that are used for finding - /// similarities within diffs. - pub fn diff_find_options(&mut self) -> &mut DiffFindOptions { - &mut self.diff_find_options - } - - /// Set the subject prefix - /// - /// The default value for this is "PATCH". If set to an empty string ("") - /// then only the patch numbers will be shown in the prefix. - /// If the subject_prefix is empty and patch numbers are not being shown, - /// the prefix will be omitted entirely. - pub fn subject_prefix<T: IntoCString>(&mut self, t: T) -> &mut Self { - self.subject_prefix = Some(t.into_c_string().unwrap()); - self - } - - /// Set the starting patch number; this cannot be 0. - /// - /// The default value for this is 1. - pub fn start_number(&mut self, number: usize) -> &mut Self { - self.raw.start_number = number; - self - } - - /// Set the "re-roll" number. - /// - /// The default value for this is 0 (no re-roll). - pub fn reroll_number(&mut self, number: usize) -> &mut Self { - self.raw.reroll_number = number; - self - } - - /// Acquire a pointer to the underlying raw options. - /// - /// This function is unsafe as the pointer is only valid so long as this - /// structure is not moved, modified, or used elsewhere. - unsafe fn raw(&mut self) -> *const raw::git_email_create_options { - self.raw.subject_prefix = self - .subject_prefix - .as_ref() - .map(|s| s.as_ptr()) - .unwrap_or(ptr::null()); - self.raw.diff_opts = ptr::read(self.diff_options.raw()); - self.raw.diff_find_opts = ptr::read(self.diff_find_options.raw()); - &self.raw as *const _ - } -} - -impl Email { - /// Returns a byte slice with stored e-mail patch in. `Email` could be - /// created by one of the `from_*` functions. - pub fn as_slice(&self) -> &[u8] { - &self.buf - } - - /// Create a diff for a commit in mbox format for sending via email. - pub fn from_diff<T: IntoCString>( - diff: &Diff<'_>, - patch_idx: usize, - patch_count: usize, - commit_id: &Oid, - summary: T, - body: T, - author: &Signature<'_>, - opts: &mut EmailCreateOptions, - ) -> Result<Self, Error> { - let buf = Buf::new(); - let summary = summary.into_c_string()?; - let body = body.into_c_string()?; - unsafe { - try_call!(raw::git_email_create_from_diff( - buf.raw(), - Binding::raw(diff), - patch_idx, - patch_count, - Binding::raw(commit_id), - summary.as_ptr(), - body.as_ptr(), - Binding::raw(author), - opts.raw() - )); - Ok(Self { buf }) - } - } - - /// Create a diff for a commit in mbox format for sending via email. - /// The commit must not be a merge commit. - pub fn from_commit(commit: &Commit<'_>, opts: &mut EmailCreateOptions) -> Result<Self, Error> { - let buf = Buf::new(); - unsafe { - try_call!(raw::git_email_create_from_commit( - buf.raw(), - commit.raw(), - opts.raw() - )); - Ok(Self { buf }) - } - } -} diff --git a/extra/git2/src/error.rs b/extra/git2/src/error.rs deleted file mode 100644 index 6f1c4d4c7..000000000 --- a/extra/git2/src/error.rs +++ /dev/null @@ -1,399 +0,0 @@ -use libc::c_int; -use std::env::JoinPathsError; -use std::error; -use std::ffi::{CStr, NulError}; -use std::fmt; -use std::str; - -use crate::{raw, ErrorClass, ErrorCode}; - -/// A structure to represent errors coming out of libgit2. -#[derive(Debug, PartialEq)] -pub struct Error { - code: c_int, - klass: c_int, - message: String, -} - -impl Error { - /// Creates a new error. - /// - /// This is mainly intended for implementers of custom transports or - /// database backends, where it is desirable to propagate an [`Error`] - /// through `libgit2`. - pub fn new<S: AsRef<str>>(code: ErrorCode, class: ErrorClass, message: S) -> Self { - let mut err = Error::from_str(message.as_ref()); - err.set_code(code); - err.set_class(class); - err - } - - /// Returns the last error that happened with the code specified by `code`. - /// - /// The `code` argument typically comes from the return value of a function - /// call. This code will later be returned from the `code` function. - /// - /// Historically this function returned `Some` or `None` based on the return - /// value of `git_error_last` but nowadays it always returns `Some` so it's - /// safe to unwrap the return value. This API will change in the next major - /// version. - pub fn last_error(code: c_int) -> Option<Error> { - crate::init(); - unsafe { - // Note that whenever libgit2 returns an error any negative value - // indicates that an error happened. Auxiliary information is - // *usually* in `git_error_last` but unfortunately that's not always - // the case. Sometimes a negative error code is returned from - // libgit2 *without* calling `git_error_set` internally to configure - // the error. - // - // To handle this case and hopefully provide better error messages - // on our end we unconditionally call `git_error_clear` when we're done - // with an error. This is an attempt to clear it as aggressively as - // possible when we can to ensure that error information from one - // api invocation doesn't leak over to the next api invocation. - // - // Additionally if `git_error_last` returns null then we returned a - // canned error out. - let ptr = raw::git_error_last(); - let err = if ptr.is_null() { - let mut error = Error::from_str("an unknown git error occurred"); - error.code = code; - error - } else { - Error::from_raw(code, ptr) - }; - raw::git_error_clear(); - Some(err) - } - } - - unsafe fn from_raw(code: c_int, ptr: *const raw::git_error) -> Error { - let message = CStr::from_ptr((*ptr).message as *const _).to_bytes(); - let message = String::from_utf8_lossy(message).into_owned(); - Error { - code, - klass: (*ptr).klass, - message, - } - } - - /// Creates a new error from the given string as the error. - /// - /// The error returned will have the code `GIT_ERROR` and the class - /// `GIT_ERROR_NONE`. - pub fn from_str(s: &str) -> Error { - Error { - code: raw::GIT_ERROR as c_int, - klass: raw::GIT_ERROR_NONE as c_int, - message: s.to_string(), - } - } - - /// Return the error code associated with this error. - /// - /// An error code is intended to be programmatically actionable most of the - /// time. For example the code `GIT_EAGAIN` indicates that an error could be - /// fixed by trying again, while the code `GIT_ERROR` is more bland and - /// doesn't convey anything in particular. - pub fn code(&self) -> ErrorCode { - match self.raw_code() { - raw::GIT_OK => super::ErrorCode::GenericError, - raw::GIT_ERROR => super::ErrorCode::GenericError, - raw::GIT_ENOTFOUND => super::ErrorCode::NotFound, - raw::GIT_EEXISTS => super::ErrorCode::Exists, - raw::GIT_EAMBIGUOUS => super::ErrorCode::Ambiguous, - raw::GIT_EBUFS => super::ErrorCode::BufSize, - raw::GIT_EUSER => super::ErrorCode::User, - raw::GIT_EBAREREPO => super::ErrorCode::BareRepo, - raw::GIT_EUNBORNBRANCH => super::ErrorCode::UnbornBranch, - raw::GIT_EUNMERGED => super::ErrorCode::Unmerged, - raw::GIT_ENONFASTFORWARD => super::ErrorCode::NotFastForward, - raw::GIT_EINVALIDSPEC => super::ErrorCode::InvalidSpec, - raw::GIT_ECONFLICT => super::ErrorCode::Conflict, - raw::GIT_ELOCKED => super::ErrorCode::Locked, - raw::GIT_EMODIFIED => super::ErrorCode::Modified, - raw::GIT_PASSTHROUGH => super::ErrorCode::GenericError, - raw::GIT_ITEROVER => super::ErrorCode::GenericError, - raw::GIT_EAUTH => super::ErrorCode::Auth, - raw::GIT_ECERTIFICATE => super::ErrorCode::Certificate, - raw::GIT_EAPPLIED => super::ErrorCode::Applied, - raw::GIT_EPEEL => super::ErrorCode::Peel, - raw::GIT_EEOF => super::ErrorCode::Eof, - raw::GIT_EINVALID => super::ErrorCode::Invalid, - raw::GIT_EUNCOMMITTED => super::ErrorCode::Uncommitted, - raw::GIT_EDIRECTORY => super::ErrorCode::Directory, - raw::GIT_EMERGECONFLICT => super::ErrorCode::MergeConflict, - raw::GIT_EMISMATCH => super::ErrorCode::HashsumMismatch, - raw::GIT_EINDEXDIRTY => super::ErrorCode::IndexDirty, - raw::GIT_EAPPLYFAIL => super::ErrorCode::ApplyFail, - raw::GIT_EOWNER => super::ErrorCode::Owner, - _ => super::ErrorCode::GenericError, - } - } - - /// Modify the error code associated with this error. - /// - /// This is mainly intended to be used by implementers of custom transports - /// or database backends, and should be used with care. - pub fn set_code(&mut self, code: ErrorCode) { - self.code = match code { - ErrorCode::GenericError => raw::GIT_ERROR, - ErrorCode::NotFound => raw::GIT_ENOTFOUND, - ErrorCode::Exists => raw::GIT_EEXISTS, - ErrorCode::Ambiguous => raw::GIT_EAMBIGUOUS, - ErrorCode::BufSize => raw::GIT_EBUFS, - ErrorCode::User => raw::GIT_EUSER, - ErrorCode::BareRepo => raw::GIT_EBAREREPO, - ErrorCode::UnbornBranch => raw::GIT_EUNBORNBRANCH, - ErrorCode::Unmerged => raw::GIT_EUNMERGED, - ErrorCode::NotFastForward => raw::GIT_ENONFASTFORWARD, - ErrorCode::InvalidSpec => raw::GIT_EINVALIDSPEC, - ErrorCode::Conflict => raw::GIT_ECONFLICT, - ErrorCode::Locked => raw::GIT_ELOCKED, - ErrorCode::Modified => raw::GIT_EMODIFIED, - ErrorCode::Auth => raw::GIT_EAUTH, - ErrorCode::Certificate => raw::GIT_ECERTIFICATE, - ErrorCode::Applied => raw::GIT_EAPPLIED, - ErrorCode::Peel => raw::GIT_EPEEL, - ErrorCode::Eof => raw::GIT_EEOF, - ErrorCode::Invalid => raw::GIT_EINVALID, - ErrorCode::Uncommitted => raw::GIT_EUNCOMMITTED, - ErrorCode::Directory => raw::GIT_EDIRECTORY, - ErrorCode::MergeConflict => raw::GIT_EMERGECONFLICT, - ErrorCode::HashsumMismatch => raw::GIT_EMISMATCH, - ErrorCode::IndexDirty => raw::GIT_EINDEXDIRTY, - ErrorCode::ApplyFail => raw::GIT_EAPPLYFAIL, - ErrorCode::Owner => raw::GIT_EOWNER, - }; - } - - /// Return the error class associated with this error. - /// - /// Error classes are in general mostly just informative. For example the - /// class will show up in the error message but otherwise an error class is - /// typically not directly actionable. - pub fn class(&self) -> ErrorClass { - match self.raw_class() { - raw::GIT_ERROR_NONE => super::ErrorClass::None, - raw::GIT_ERROR_NOMEMORY => super::ErrorClass::NoMemory, - raw::GIT_ERROR_OS => super::ErrorClass::Os, - raw::GIT_ERROR_INVALID => super::ErrorClass::Invalid, - raw::GIT_ERROR_REFERENCE => super::ErrorClass::Reference, - raw::GIT_ERROR_ZLIB => super::ErrorClass::Zlib, - raw::GIT_ERROR_REPOSITORY => super::ErrorClass::Repository, - raw::GIT_ERROR_CONFIG => super::ErrorClass::Config, - raw::GIT_ERROR_REGEX => super::ErrorClass::Regex, - raw::GIT_ERROR_ODB => super::ErrorClass::Odb, - raw::GIT_ERROR_INDEX => super::ErrorClass::Index, - raw::GIT_ERROR_OBJECT => super::ErrorClass::Object, - raw::GIT_ERROR_NET => super::ErrorClass::Net, - raw::GIT_ERROR_TAG => super::ErrorClass::Tag, - raw::GIT_ERROR_TREE => super::ErrorClass::Tree, - raw::GIT_ERROR_INDEXER => super::ErrorClass::Indexer, - raw::GIT_ERROR_SSL => super::ErrorClass::Ssl, - raw::GIT_ERROR_SUBMODULE => super::ErrorClass::Submodule, - raw::GIT_ERROR_THREAD => super::ErrorClass::Thread, - raw::GIT_ERROR_STASH => super::ErrorClass::Stash, - raw::GIT_ERROR_CHECKOUT => super::ErrorClass::Checkout, - raw::GIT_ERROR_FETCHHEAD => super::ErrorClass::FetchHead, - raw::GIT_ERROR_MERGE => super::ErrorClass::Merge, - raw::GIT_ERROR_SSH => super::ErrorClass::Ssh, - raw::GIT_ERROR_FILTER => super::ErrorClass::Filter, - raw::GIT_ERROR_REVERT => super::ErrorClass::Revert, - raw::GIT_ERROR_CALLBACK => super::ErrorClass::Callback, - raw::GIT_ERROR_CHERRYPICK => super::ErrorClass::CherryPick, - raw::GIT_ERROR_DESCRIBE => super::ErrorClass::Describe, - raw::GIT_ERROR_REBASE => super::ErrorClass::Rebase, - raw::GIT_ERROR_FILESYSTEM => super::ErrorClass::Filesystem, - raw::GIT_ERROR_PATCH => super::ErrorClass::Patch, - raw::GIT_ERROR_WORKTREE => super::ErrorClass::Worktree, - raw::GIT_ERROR_SHA1 => super::ErrorClass::Sha1, - raw::GIT_ERROR_HTTP => super::ErrorClass::Http, - _ => super::ErrorClass::None, - } - } - - /// Modify the error class associated with this error. - /// - /// This is mainly intended to be used by implementers of custom transports - /// or database backends, and should be used with care. - pub fn set_class(&mut self, class: ErrorClass) { - self.klass = match class { - ErrorClass::None => raw::GIT_ERROR_NONE, - ErrorClass::NoMemory => raw::GIT_ERROR_NOMEMORY, - ErrorClass::Os => raw::GIT_ERROR_OS, - ErrorClass::Invalid => raw::GIT_ERROR_INVALID, - ErrorClass::Reference => raw::GIT_ERROR_REFERENCE, - ErrorClass::Zlib => raw::GIT_ERROR_ZLIB, - ErrorClass::Repository => raw::GIT_ERROR_REPOSITORY, - ErrorClass::Config => raw::GIT_ERROR_CONFIG, - ErrorClass::Regex => raw::GIT_ERROR_REGEX, - ErrorClass::Odb => raw::GIT_ERROR_ODB, - ErrorClass::Index => raw::GIT_ERROR_INDEX, - ErrorClass::Object => raw::GIT_ERROR_OBJECT, - ErrorClass::Net => raw::GIT_ERROR_NET, - ErrorClass::Tag => raw::GIT_ERROR_TAG, - ErrorClass::Tree => raw::GIT_ERROR_TREE, - ErrorClass::Indexer => raw::GIT_ERROR_INDEXER, - ErrorClass::Ssl => raw::GIT_ERROR_SSL, - ErrorClass::Submodule => raw::GIT_ERROR_SUBMODULE, - ErrorClass::Thread => raw::GIT_ERROR_THREAD, - ErrorClass::Stash => raw::GIT_ERROR_STASH, - ErrorClass::Checkout => raw::GIT_ERROR_CHECKOUT, - ErrorClass::FetchHead => raw::GIT_ERROR_FETCHHEAD, - ErrorClass::Merge => raw::GIT_ERROR_MERGE, - ErrorClass::Ssh => raw::GIT_ERROR_SSH, - ErrorClass::Filter => raw::GIT_ERROR_FILTER, - ErrorClass::Revert => raw::GIT_ERROR_REVERT, - ErrorClass::Callback => raw::GIT_ERROR_CALLBACK, - ErrorClass::CherryPick => raw::GIT_ERROR_CHERRYPICK, - ErrorClass::Describe => raw::GIT_ERROR_DESCRIBE, - ErrorClass::Rebase => raw::GIT_ERROR_REBASE, - ErrorClass::Filesystem => raw::GIT_ERROR_FILESYSTEM, - ErrorClass::Patch => raw::GIT_ERROR_PATCH, - ErrorClass::Worktree => raw::GIT_ERROR_WORKTREE, - ErrorClass::Sha1 => raw::GIT_ERROR_SHA1, - ErrorClass::Http => raw::GIT_ERROR_HTTP, - } as c_int; - } - - /// Return the raw error code associated with this error. - pub fn raw_code(&self) -> raw::git_error_code { - macro_rules! check( ($($e:ident,)*) => ( - $(if self.code == raw::$e as c_int { raw::$e }) else * - else { - raw::GIT_ERROR - } - ) ); - check!( - GIT_OK, - GIT_ERROR, - GIT_ENOTFOUND, - GIT_EEXISTS, - GIT_EAMBIGUOUS, - GIT_EBUFS, - GIT_EUSER, - GIT_EBAREREPO, - GIT_EUNBORNBRANCH, - GIT_EUNMERGED, - GIT_ENONFASTFORWARD, - GIT_EINVALIDSPEC, - GIT_ECONFLICT, - GIT_ELOCKED, - GIT_EMODIFIED, - GIT_EAUTH, - GIT_ECERTIFICATE, - GIT_EAPPLIED, - GIT_EPEEL, - GIT_EEOF, - GIT_EINVALID, - GIT_EUNCOMMITTED, - GIT_PASSTHROUGH, - GIT_ITEROVER, - GIT_RETRY, - GIT_EMISMATCH, - GIT_EINDEXDIRTY, - GIT_EAPPLYFAIL, - GIT_EOWNER, - ) - } - - /// Return the raw error class associated with this error. - pub fn raw_class(&self) -> raw::git_error_t { - macro_rules! check( ($($e:ident,)*) => ( - $(if self.klass == raw::$e as c_int { raw::$e }) else * - else { - raw::GIT_ERROR_NONE - } - ) ); - check!( - GIT_ERROR_NONE, - GIT_ERROR_NOMEMORY, - GIT_ERROR_OS, - GIT_ERROR_INVALID, - GIT_ERROR_REFERENCE, - GIT_ERROR_ZLIB, - GIT_ERROR_REPOSITORY, - GIT_ERROR_CONFIG, - GIT_ERROR_REGEX, - GIT_ERROR_ODB, - GIT_ERROR_INDEX, - GIT_ERROR_OBJECT, - GIT_ERROR_NET, - GIT_ERROR_TAG, - GIT_ERROR_TREE, - GIT_ERROR_INDEXER, - GIT_ERROR_SSL, - GIT_ERROR_SUBMODULE, - GIT_ERROR_THREAD, - GIT_ERROR_STASH, - GIT_ERROR_CHECKOUT, - GIT_ERROR_FETCHHEAD, - GIT_ERROR_MERGE, - GIT_ERROR_SSH, - GIT_ERROR_FILTER, - GIT_ERROR_REVERT, - GIT_ERROR_CALLBACK, - GIT_ERROR_CHERRYPICK, - GIT_ERROR_DESCRIBE, - GIT_ERROR_REBASE, - GIT_ERROR_FILESYSTEM, - GIT_ERROR_PATCH, - GIT_ERROR_WORKTREE, - GIT_ERROR_SHA1, - GIT_ERROR_HTTP, - ) - } - - /// Return the message associated with this error - pub fn message(&self) -> &str { - &self.message - } -} - -impl error::Error for Error {} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.message)?; - match self.class() { - ErrorClass::None => {} - other => write!(f, "; class={:?} ({})", other, self.klass)?, - } - match self.code() { - ErrorCode::GenericError => {} - other => write!(f, "; code={:?} ({})", other, self.code)?, - } - Ok(()) - } -} - -impl From<NulError> for Error { - fn from(_: NulError) -> Error { - Error::from_str( - "data contained a nul byte that could not be \ - represented as a string", - ) - } -} - -impl From<JoinPathsError> for Error { - fn from(e: JoinPathsError) -> Error { - Error::from_str(&e.to_string()) - } -} - -#[cfg(test)] -mod tests { - use crate::{ErrorClass, ErrorCode}; - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - - let err = repo.find_submodule("does_not_exist").err().unwrap(); - assert_eq!(err.code(), ErrorCode::NotFound); - assert_eq!(err.class(), ErrorClass::Submodule); - } -} diff --git a/extra/git2/src/index.rs b/extra/git2/src/index.rs deleted file mode 100644 index 0291d3cb9..000000000 --- a/extra/git2/src/index.rs +++ /dev/null @@ -1,929 +0,0 @@ -use std::ffi::{CStr, CString}; -use std::marker; -use std::ops::Range; -use std::path::Path; -use std::ptr; -use std::slice; - -use libc::{c_char, c_int, c_uint, c_void, size_t}; - -use crate::util::{self, path_to_repo_path, Binding}; -use crate::IntoCString; -use crate::{panic, raw, Error, IndexAddOption, IndexTime, Oid, Repository, Tree}; - -/// A structure to represent a git [index][1] -/// -/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects -pub struct Index { - raw: *mut raw::git_index, -} - -/// An iterator over the entries in an index -pub struct IndexEntries<'index> { - range: Range<usize>, - index: &'index Index, -} - -/// An iterator over the conflicting entries in an index -pub struct IndexConflicts<'index> { - conflict_iter: *mut raw::git_index_conflict_iterator, - _marker: marker::PhantomData<&'index Index>, -} - -/// A structure to represent the information returned when a conflict is detected in an index entry -pub struct IndexConflict { - /// The ancestor index entry of the two conflicting index entries - pub ancestor: Option<IndexEntry>, - /// The index entry originating from the user's copy of the repository. - /// Its contents conflict with 'their' index entry - pub our: Option<IndexEntry>, - /// The index entry originating from the external repository. - /// Its contents conflict with 'our' index entry - pub their: Option<IndexEntry>, -} - -/// A callback function to filter index matches. -/// -/// Used by `Index::{add_all,remove_all,update_all}`. The first argument is the -/// path, and the second is the pathspec that matched it. Return 0 to confirm -/// the operation on the item, > 0 to skip the item, and < 0 to abort the scan. -pub type IndexMatchedPath<'a> = dyn FnMut(&Path, &[u8]) -> i32 + 'a; - -/// A structure to represent an entry or a file inside of an index. -/// -/// All fields of an entry are public for modification and inspection. This is -/// also how a new index entry is created. -#[allow(missing_docs)] -#[derive(Debug)] -pub struct IndexEntry { - pub ctime: IndexTime, - pub mtime: IndexTime, - pub dev: u32, - pub ino: u32, - pub mode: u32, - pub uid: u32, - pub gid: u32, - pub file_size: u32, - pub id: Oid, - pub flags: u16, - pub flags_extended: u16, - - /// The path of this index entry as a byte vector. Regardless of the - /// current platform, the directory separator is an ASCII forward slash - /// (`0x2F`). There are no terminating or internal NUL characters, and no - /// trailing slashes. Most of the time, paths will be valid utf-8 — but - /// not always. For more information on the path storage format, see - /// [these git docs][git-index-docs]. Note that libgit2 will take care of - /// handling the prefix compression mentioned there. - /// - /// [git-index-docs]: https://github.com/git/git/blob/a08a83db2bf27f015bec9a435f6d73e223c21c5e/Documentation/technical/index-format.txt#L107-L124 - /// - /// You can turn this value into a `std::ffi::CString` with - /// `CString::new(&entry.path[..]).unwrap()`. To turn a reference into a - /// `&std::path::Path`, see the `bytes2path()` function in the private, - /// internal `util` module in this crate’s source code. - pub path: Vec<u8>, -} - -impl Index { - /// Creates a new in-memory index. - /// - /// This index object cannot be read/written to the filesystem, but may be - /// used to perform in-memory index operations. - pub fn new() -> Result<Index, Error> { - crate::init(); - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_index_new(&mut raw)); - Ok(Binding::from_raw(raw)) - } - } - - /// Create a new bare Git index object as a memory representation of the Git - /// index file in 'index_path', without a repository to back it. - /// - /// Since there is no ODB or working directory behind this index, any Index - /// methods which rely on these (e.g. add_path) will fail. - /// - /// If you need an index attached to a repository, use the `index()` method - /// on `Repository`. - pub fn open(index_path: &Path) -> Result<Index, Error> { - crate::init(); - let mut raw = ptr::null_mut(); - // Normal file path OK (does not need Windows conversion). - let index_path = index_path.into_c_string()?; - unsafe { - try_call!(raw::git_index_open(&mut raw, index_path)); - Ok(Binding::from_raw(raw)) - } - } - - /// Get index on-disk version. - /// - /// Valid return values are 2, 3, or 4. If 3 is returned, an index - /// with version 2 may be written instead, if the extension data in - /// version 3 is not necessary. - pub fn version(&self) -> u32 { - unsafe { raw::git_index_version(self.raw) } - } - - /// Set index on-disk version. - /// - /// Valid values are 2, 3, or 4. If 2 is given, git_index_write may - /// write an index with version 3 instead, if necessary to accurately - /// represent the index. - pub fn set_version(&mut self, version: u32) -> Result<(), Error> { - unsafe { - try_call!(raw::git_index_set_version(self.raw, version)); - } - Ok(()) - } - - /// Add or update an index entry from an in-memory struct - /// - /// If a previous index entry exists that has the same path and stage as the - /// given 'source_entry', it will be replaced. Otherwise, the 'source_entry' - /// will be added. - pub fn add(&mut self, entry: &IndexEntry) -> Result<(), Error> { - let path = CString::new(&entry.path[..])?; - - // libgit2 encodes the length of the path in the lower bits of the - // `flags` entry, so mask those out and recalculate here to ensure we - // don't corrupt anything. - let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK; - - if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize { - flags |= entry.path.len() as u16; - } else { - flags |= raw::GIT_INDEX_ENTRY_NAMEMASK; - } - - unsafe { - let raw = raw::git_index_entry { - dev: entry.dev, - ino: entry.ino, - mode: entry.mode, - uid: entry.uid, - gid: entry.gid, - file_size: entry.file_size, - id: *entry.id.raw(), - flags, - flags_extended: entry.flags_extended, - path: path.as_ptr(), - mtime: raw::git_index_time { - seconds: entry.mtime.seconds(), - nanoseconds: entry.mtime.nanoseconds(), - }, - ctime: raw::git_index_time { - seconds: entry.ctime.seconds(), - nanoseconds: entry.ctime.nanoseconds(), - }, - }; - try_call!(raw::git_index_add(self.raw, &raw)); - Ok(()) - } - } - - /// Add or update an index entry from a buffer in memory - /// - /// This method will create a blob in the repository that owns the index and - /// then add the index entry to the index. The path of the entry represents - /// the position of the blob relative to the repository's root folder. - /// - /// If a previous index entry exists that has the same path as the given - /// 'entry', it will be replaced. Otherwise, the 'entry' will be added. - /// The id and the file_size of the 'entry' are updated with the real value - /// of the blob. - /// - /// This forces the file to be added to the index, not looking at gitignore - /// rules. - /// - /// If this file currently is the result of a merge conflict, this file will - /// no longer be marked as conflicting. The data about the conflict will be - /// moved to the "resolve undo" (REUC) section. - pub fn add_frombuffer(&mut self, entry: &IndexEntry, data: &[u8]) -> Result<(), Error> { - let path = CString::new(&entry.path[..])?; - - // libgit2 encodes the length of the path in the lower bits of the - // `flags` entry, so mask those out and recalculate here to ensure we - // don't corrupt anything. - let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK; - - if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize { - flags |= entry.path.len() as u16; - } else { - flags |= raw::GIT_INDEX_ENTRY_NAMEMASK; - } - - unsafe { - let raw = raw::git_index_entry { - dev: entry.dev, - ino: entry.ino, - mode: entry.mode, - uid: entry.uid, - gid: entry.gid, - file_size: entry.file_size, - id: *entry.id.raw(), - flags, - flags_extended: entry.flags_extended, - path: path.as_ptr(), - mtime: raw::git_index_time { - seconds: entry.mtime.seconds(), - nanoseconds: entry.mtime.nanoseconds(), - }, - ctime: raw::git_index_time { - seconds: entry.ctime.seconds(), - nanoseconds: entry.ctime.nanoseconds(), - }, - }; - - let ptr = data.as_ptr() as *const c_void; - let len = data.len() as size_t; - try_call!(raw::git_index_add_frombuffer(self.raw, &raw, ptr, len)); - Ok(()) - } - } - - /// Add or update an index entry from a file on disk - /// - /// The file path must be relative to the repository's working folder and - /// must be readable. - /// - /// This method will fail in bare index instances. - /// - /// This forces the file to be added to the index, not looking at gitignore - /// rules. - /// - /// If this file currently is the result of a merge conflict, this file will - /// no longer be marked as conflicting. The data about the conflict will be - /// moved to the "resolve undo" (REUC) section. - pub fn add_path(&mut self, path: &Path) -> Result<(), Error> { - let posix_path = path_to_repo_path(path)?; - unsafe { - try_call!(raw::git_index_add_bypath(self.raw, posix_path)); - Ok(()) - } - } - - /// Add or update index entries matching files in the working directory. - /// - /// This method will fail in bare index instances. - /// - /// The `pathspecs` are a list of file names or shell glob patterns that - /// will matched against files in the repository's working directory. Each - /// file that matches will be added to the index (either updating an - /// existing entry or adding a new entry). You can disable glob expansion - /// and force exact matching with the `AddDisablePathspecMatch` flag. - /// - /// Files that are ignored will be skipped (unlike `add_path`). If a file is - /// already tracked in the index, then it will be updated even if it is - /// ignored. Pass the `AddForce` flag to skip the checking of ignore rules. - /// - /// To emulate `git add -A` and generate an error if the pathspec contains - /// the exact path of an ignored file (when not using `AddForce`), add the - /// `AddCheckPathspec` flag. This checks that each entry in `pathspecs` - /// that is an exact match to a filename on disk is either not ignored or - /// already in the index. If this check fails, the function will return - /// an error. - /// - /// To emulate `git add -A` with the "dry-run" option, just use a callback - /// function that always returns a positive value. See below for details. - /// - /// If any files are currently the result of a merge conflict, those files - /// will no longer be marked as conflicting. The data about the conflicts - /// will be moved to the "resolve undo" (REUC) section. - /// - /// If you provide a callback function, it will be invoked on each matching - /// item in the working directory immediately before it is added to / - /// updated in the index. Returning zero will add the item to the index, - /// greater than zero will skip the item, and less than zero will abort the - /// scan an return an error to the caller. - /// - /// # Example - /// - /// Emulate `git add *`: - /// - /// ```no_run - /// use git2::{Index, IndexAddOption, Repository}; - /// - /// let repo = Repository::open("/path/to/a/repo").expect("failed to open"); - /// let mut index = repo.index().expect("cannot get the Index file"); - /// index.add_all(["*"].iter(), IndexAddOption::DEFAULT, None); - /// index.write(); - /// ``` - pub fn add_all<T, I>( - &mut self, - pathspecs: I, - flag: IndexAddOption, - mut cb: Option<&mut IndexMatchedPath<'_>>, - ) -> Result<(), Error> - where - T: IntoCString, - I: IntoIterator<Item = T>, - { - let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?; - let ptr = cb.as_mut(); - let callback = ptr - .as_ref() - .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _); - unsafe { - try_call!(raw::git_index_add_all( - self.raw, - &raw_strarray, - flag.bits() as c_uint, - callback, - ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void - )); - } - Ok(()) - } - - /// Clear the contents (all the entries) of an index object. - /// - /// This clears the index object in memory; changes must be explicitly - /// written to disk for them to take effect persistently via `write_*`. - pub fn clear(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_index_clear(self.raw)); - } - Ok(()) - } - - /// Get the count of entries currently in the index - pub fn len(&self) -> usize { - unsafe { raw::git_index_entrycount(&*self.raw) as usize } - } - - /// Return `true` is there is no entry in the index - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Get one of the entries in the index by its position. - pub fn get(&self, n: usize) -> Option<IndexEntry> { - unsafe { - let ptr = raw::git_index_get_byindex(self.raw, n as size_t); - if ptr.is_null() { - None - } else { - Some(Binding::from_raw(*ptr)) - } - } - } - - /// Get an iterator over the entries in this index. - pub fn iter(&self) -> IndexEntries<'_> { - IndexEntries { - range: 0..self.len(), - index: self, - } - } - - /// Get an iterator over the index entries that have conflicts - pub fn conflicts(&self) -> Result<IndexConflicts<'_>, Error> { - crate::init(); - let mut conflict_iter = ptr::null_mut(); - unsafe { - try_call!(raw::git_index_conflict_iterator_new( - &mut conflict_iter, - self.raw - )); - Ok(Binding::from_raw(conflict_iter)) - } - } - - /// Get one of the entries in the index by its path. - pub fn get_path(&self, path: &Path, stage: i32) -> Option<IndexEntry> { - let path = path_to_repo_path(path).unwrap(); - unsafe { - let ptr = call!(raw::git_index_get_bypath(self.raw, path, stage as c_int)); - if ptr.is_null() { - None - } else { - Some(Binding::from_raw(*ptr)) - } - } - } - - /// Does this index have conflicts? - /// - /// Returns `true` if the index contains conflicts, `false` if it does not. - pub fn has_conflicts(&self) -> bool { - unsafe { raw::git_index_has_conflicts(self.raw) == 1 } - } - - /// Get the full path to the index file on disk. - /// - /// Returns `None` if this is an in-memory index. - pub fn path(&self) -> Option<&Path> { - unsafe { crate::opt_bytes(self, raw::git_index_path(&*self.raw)).map(util::bytes2path) } - } - - /// Update the contents of an existing index object in memory by reading - /// from the hard disk. - /// - /// If force is true, this performs a "hard" read that discards in-memory - /// changes and always reloads the on-disk index data. If there is no - /// on-disk version, the index will be cleared. - /// - /// If force is false, this does a "soft" read that reloads the index data - /// from disk only if it has changed since the last time it was loaded. - /// Purely in-memory index data will be untouched. Be aware: if there are - /// changes on disk, unwritten in-memory changes are discarded. - pub fn read(&mut self, force: bool) -> Result<(), Error> { - unsafe { - try_call!(raw::git_index_read(self.raw, force)); - } - Ok(()) - } - - /// Read a tree into the index file with stats - /// - /// The current index contents will be replaced by the specified tree. - pub fn read_tree(&mut self, tree: &Tree<'_>) -> Result<(), Error> { - unsafe { - try_call!(raw::git_index_read_tree(self.raw, &*tree.raw())); - } - Ok(()) - } - - /// Remove an entry from the index - pub fn remove(&mut self, path: &Path, stage: i32) -> Result<(), Error> { - let path = path_to_repo_path(path)?; - unsafe { - try_call!(raw::git_index_remove(self.raw, path, stage as c_int)); - } - Ok(()) - } - - /// Remove an index entry corresponding to a file on disk. - /// - /// The file path must be relative to the repository's working folder. It - /// may exist. - /// - /// If this file currently is the result of a merge conflict, this file will - /// no longer be marked as conflicting. The data about the conflict will be - /// moved to the "resolve undo" (REUC) section. - pub fn remove_path(&mut self, path: &Path) -> Result<(), Error> { - let path = path_to_repo_path(path)?; - unsafe { - try_call!(raw::git_index_remove_bypath(self.raw, path)); - } - Ok(()) - } - - /// Remove all entries from the index under a given directory. - pub fn remove_dir(&mut self, path: &Path, stage: i32) -> Result<(), Error> { - let path = path_to_repo_path(path)?; - unsafe { - try_call!(raw::git_index_remove_directory( - self.raw, - path, - stage as c_int - )); - } - Ok(()) - } - - /// Remove all matching index entries. - /// - /// If you provide a callback function, it will be invoked on each matching - /// item in the index immediately before it is removed. Return 0 to remove - /// the item, > 0 to skip the item, and < 0 to abort the scan. - pub fn remove_all<T, I>( - &mut self, - pathspecs: I, - mut cb: Option<&mut IndexMatchedPath<'_>>, - ) -> Result<(), Error> - where - T: IntoCString, - I: IntoIterator<Item = T>, - { - let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?; - let ptr = cb.as_mut(); - let callback = ptr - .as_ref() - .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _); - unsafe { - try_call!(raw::git_index_remove_all( - self.raw, - &raw_strarray, - callback, - ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void - )); - } - Ok(()) - } - - /// Update all index entries to match the working directory - /// - /// This method will fail in bare index instances. - /// - /// This scans the existing index entries and synchronizes them with the - /// working directory, deleting them if the corresponding working directory - /// file no longer exists otherwise updating the information (including - /// adding the latest version of file to the ODB if needed). - /// - /// If you provide a callback function, it will be invoked on each matching - /// item in the index immediately before it is updated (either refreshed or - /// removed depending on working directory state). Return 0 to proceed with - /// updating the item, > 0 to skip the item, and < 0 to abort the scan. - pub fn update_all<T, I>( - &mut self, - pathspecs: I, - mut cb: Option<&mut IndexMatchedPath<'_>>, - ) -> Result<(), Error> - where - T: IntoCString, - I: IntoIterator<Item = T>, - { - let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?; - let ptr = cb.as_mut(); - let callback = ptr - .as_ref() - .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _); - unsafe { - try_call!(raw::git_index_update_all( - self.raw, - &raw_strarray, - callback, - ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void - )); - } - Ok(()) - } - - /// Write an existing index object from memory back to disk using an atomic - /// file lock. - pub fn write(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_index_write(self.raw)); - } - Ok(()) - } - - /// Write the index as a tree. - /// - /// This method will scan the index and write a representation of its - /// current state back to disk; it recursively creates tree objects for each - /// of the subtrees stored in the index, but only returns the OID of the - /// root tree. This is the OID that can be used e.g. to create a commit. - /// - /// The index instance cannot be bare, and needs to be associated to an - /// existing repository. - /// - /// The index must not contain any file in conflict. - pub fn write_tree(&mut self) -> Result<Oid, Error> { - let mut raw = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_index_write_tree(&mut raw, self.raw)); - Ok(Binding::from_raw(&raw as *const _)) - } - } - - /// Write the index as a tree to the given repository - /// - /// This is the same as `write_tree` except that the destination repository - /// can be chosen. - pub fn write_tree_to(&mut self, repo: &Repository) -> Result<Oid, Error> { - let mut raw = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_index_write_tree_to(&mut raw, self.raw, repo.raw())); - Ok(Binding::from_raw(&raw as *const _)) - } - } - - /// Find the first position of any entries matching a prefix. - /// - /// To find the first position of a path inside a given folder, suffix the prefix with a '/'. - pub fn find_prefix<T: IntoCString>(&self, prefix: T) -> Result<usize, Error> { - let mut at_pos: size_t = 0; - let entry_path = prefix.into_c_string()?; - unsafe { - try_call!(raw::git_index_find_prefix( - &mut at_pos, - self.raw, - entry_path - )); - Ok(at_pos) - } - } -} - -impl Binding for Index { - type Raw = *mut raw::git_index; - unsafe fn from_raw(raw: *mut raw::git_index) -> Index { - Index { raw } - } - fn raw(&self) -> *mut raw::git_index { - self.raw - } -} - -impl<'index> Binding for IndexConflicts<'index> { - type Raw = *mut raw::git_index_conflict_iterator; - - unsafe fn from_raw(raw: *mut raw::git_index_conflict_iterator) -> IndexConflicts<'index> { - IndexConflicts { - conflict_iter: raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_index_conflict_iterator { - self.conflict_iter - } -} - -extern "C" fn index_matched_path_cb( - path: *const c_char, - matched_pathspec: *const c_char, - payload: *mut c_void, -) -> c_int { - unsafe { - let path = CStr::from_ptr(path).to_bytes(); - let matched_pathspec = CStr::from_ptr(matched_pathspec).to_bytes(); - - panic::wrap(|| { - let payload = payload as *mut &mut IndexMatchedPath<'_>; - (*payload)(util::bytes2path(path), matched_pathspec) as c_int - }) - .unwrap_or(-1) - } -} - -impl Drop for Index { - fn drop(&mut self) { - unsafe { raw::git_index_free(self.raw) } - } -} - -impl<'index> Drop for IndexConflicts<'index> { - fn drop(&mut self) { - unsafe { raw::git_index_conflict_iterator_free(self.conflict_iter) } - } -} - -impl<'index> Iterator for IndexEntries<'index> { - type Item = IndexEntry; - fn next(&mut self) -> Option<IndexEntry> { - self.range.next().map(|i| self.index.get(i).unwrap()) - } -} - -impl<'index> Iterator for IndexConflicts<'index> { - type Item = Result<IndexConflict, Error>; - fn next(&mut self) -> Option<Result<IndexConflict, Error>> { - let mut ancestor = ptr::null(); - let mut our = ptr::null(); - let mut their = ptr::null(); - unsafe { - try_call_iter!(raw::git_index_conflict_next( - &mut ancestor, - &mut our, - &mut their, - self.conflict_iter - )); - Some(Ok(IndexConflict { - ancestor: match ancestor.is_null() { - false => Some(IndexEntry::from_raw(*ancestor)), - true => None, - }, - our: match our.is_null() { - false => Some(IndexEntry::from_raw(*our)), - true => None, - }, - their: match their.is_null() { - false => Some(IndexEntry::from_raw(*their)), - true => None, - }, - })) - } - } -} - -impl Binding for IndexEntry { - type Raw = raw::git_index_entry; - - unsafe fn from_raw(raw: raw::git_index_entry) -> IndexEntry { - let raw::git_index_entry { - ctime, - mtime, - dev, - ino, - mode, - uid, - gid, - file_size, - id, - flags, - flags_extended, - path, - } = raw; - - // libgit2 encodes the length of the path in the lower bits of `flags`, - // but if the length exceeds the number of bits then the path is - // nul-terminated. - let mut pathlen = (flags & raw::GIT_INDEX_ENTRY_NAMEMASK) as usize; - if pathlen == raw::GIT_INDEX_ENTRY_NAMEMASK as usize { - pathlen = CStr::from_ptr(path).to_bytes().len(); - } - - let path = slice::from_raw_parts(path as *const u8, pathlen); - - IndexEntry { - dev, - ino, - mode, - uid, - gid, - file_size, - id: Binding::from_raw(&id as *const _), - flags, - flags_extended, - path: path.to_vec(), - mtime: Binding::from_raw(mtime), - ctime: Binding::from_raw(ctime), - } - } - - fn raw(&self) -> raw::git_index_entry { - // not implemented, may require a CString in storage - panic!() - } -} - -#[cfg(test)] -mod tests { - use std::fs::{self, File}; - use std::path::Path; - use tempfile::TempDir; - - use crate::{ErrorCode, Index, IndexEntry, IndexTime, Oid, Repository, ResetType}; - - #[test] - fn smoke() { - let mut index = Index::new().unwrap(); - assert!(index.add_path(&Path::new(".")).is_err()); - index.clear().unwrap(); - assert_eq!(index.len(), 0); - assert!(index.get(0).is_none()); - assert!(index.path().is_none()); - assert!(index.read(true).is_err()); - } - - #[test] - fn smoke_from_repo() { - let (_td, repo) = crate::test::repo_init(); - let mut index = repo.index().unwrap(); - assert_eq!( - index.path().map(|s| s.to_path_buf()), - Some(repo.path().join("index")) - ); - Index::open(&repo.path().join("index")).unwrap(); - - index.clear().unwrap(); - index.read(true).unwrap(); - index.write().unwrap(); - index.write_tree().unwrap(); - index.write_tree_to(&repo).unwrap(); - } - - #[test] - fn add_all() { - let (_td, repo) = crate::test::repo_init(); - let mut index = repo.index().unwrap(); - - let root = repo.path().parent().unwrap(); - fs::create_dir(&root.join("foo")).unwrap(); - File::create(&root.join("foo/bar")).unwrap(); - let mut called = false; - index - .add_all( - ["foo"].iter(), - crate::IndexAddOption::DEFAULT, - Some(&mut |a: &Path, b: &[u8]| { - assert!(!called); - called = true; - assert_eq!(b, b"foo"); - assert_eq!(a, Path::new("foo/bar")); - 0 - }), - ) - .unwrap(); - assert!(called); - - called = false; - index - .remove_all( - ["."].iter(), - Some(&mut |a: &Path, b: &[u8]| { - assert!(!called); - called = true; - assert_eq!(b, b"."); - assert_eq!(a, Path::new("foo/bar")); - 0 - }), - ) - .unwrap(); - assert!(called); - } - - #[test] - fn smoke_add() { - let (_td, repo) = crate::test::repo_init(); - let mut index = repo.index().unwrap(); - - let root = repo.path().parent().unwrap(); - fs::create_dir(&root.join("foo")).unwrap(); - File::create(&root.join("foo/bar")).unwrap(); - index.add_path(Path::new("foo/bar")).unwrap(); - index.write().unwrap(); - assert_eq!(index.iter().count(), 1); - - // Make sure we can use this repo somewhere else now. - let id = index.write_tree().unwrap(); - let tree = repo.find_tree(id).unwrap(); - let sig = repo.signature().unwrap(); - let id = repo.refname_to_id("HEAD").unwrap(); - let parent = repo.find_commit(id).unwrap(); - let commit = repo - .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent]) - .unwrap(); - let obj = repo.find_object(commit, None).unwrap(); - repo.reset(&obj, ResetType::Hard, None).unwrap(); - - let td2 = TempDir::new().unwrap(); - let url = crate::test::path2url(&root); - let repo = Repository::clone(&url, td2.path()).unwrap(); - let obj = repo.find_object(commit, None).unwrap(); - repo.reset(&obj, ResetType::Hard, None).unwrap(); - } - - #[test] - fn add_then_read() { - let mut index = Index::new().unwrap(); - let mut e = entry(); - e.path = b"foobar".to_vec(); - index.add(&e).unwrap(); - let e = index.get(0).unwrap(); - assert_eq!(e.path.len(), 6); - } - - #[test] - fn add_then_find() { - let mut index = Index::new().unwrap(); - let mut e = entry(); - e.path = b"foo/bar".to_vec(); - index.add(&e).unwrap(); - let mut e = entry(); - e.path = b"foo2/bar".to_vec(); - index.add(&e).unwrap(); - assert_eq!(index.get(0).unwrap().path, b"foo/bar"); - assert_eq!( - index.get_path(Path::new("foo/bar"), 0).unwrap().path, - b"foo/bar" - ); - assert_eq!(index.find_prefix(Path::new("foo2/")), Ok(1)); - assert_eq!( - index.find_prefix(Path::new("empty/")).unwrap_err().code(), - ErrorCode::NotFound - ); - } - - #[test] - fn add_frombuffer_then_read() { - let (_td, repo) = crate::test::repo_init(); - let mut index = repo.index().unwrap(); - - let mut e = entry(); - e.path = b"foobar".to_vec(); - let content = b"the contents"; - index.add_frombuffer(&e, content).unwrap(); - let e = index.get(0).unwrap(); - assert_eq!(e.path.len(), 6); - - let b = repo.find_blob(e.id).unwrap(); - assert_eq!(b.content(), content); - } - - fn entry() -> IndexEntry { - IndexEntry { - ctime: IndexTime::new(0, 0), - mtime: IndexTime::new(0, 0), - dev: 0, - ino: 0, - mode: 0o100644, - uid: 0, - gid: 0, - file_size: 0, - id: Oid::from_bytes(&[0; 20]).unwrap(), - flags: 0, - flags_extended: 0, - path: Vec::new(), - } - } -} diff --git a/extra/git2/src/indexer.rs b/extra/git2/src/indexer.rs deleted file mode 100644 index 0aaf353d5..000000000 --- a/extra/git2/src/indexer.rs +++ /dev/null @@ -1,255 +0,0 @@ -use std::ffi::CStr; -use std::path::Path; -use std::{io, marker, mem, ptr}; - -use libc::c_void; - -use crate::odb::{write_pack_progress_cb, OdbPackwriterCb}; -use crate::util::Binding; -use crate::{raw, Error, IntoCString, Odb}; - -/// Struct representing the progress by an in-flight transfer. -pub struct Progress<'a> { - pub(crate) raw: ProgressState, - pub(crate) _marker: marker::PhantomData<&'a raw::git_indexer_progress>, -} - -pub(crate) enum ProgressState { - Borrowed(*const raw::git_indexer_progress), - Owned(raw::git_indexer_progress), -} - -/// Callback to be invoked while indexing is in progress. -/// -/// This callback will be periodically called with updates to the progress of -/// the indexing so far. The return value indicates whether the indexing or -/// transfer should continue. A return value of `false` will cancel the -/// indexing or transfer. -/// -/// * `progress` - the progress being made so far. -pub type IndexerProgress<'a> = dyn FnMut(Progress<'_>) -> bool + 'a; - -impl<'a> Progress<'a> { - /// Number of objects in the packfile being downloaded - pub fn total_objects(&self) -> usize { - unsafe { (*self.raw()).total_objects as usize } - } - /// Received objects that have been hashed - pub fn indexed_objects(&self) -> usize { - unsafe { (*self.raw()).indexed_objects as usize } - } - /// Objects which have been downloaded - pub fn received_objects(&self) -> usize { - unsafe { (*self.raw()).received_objects as usize } - } - /// Locally-available objects that have been injected in order to fix a thin - /// pack. - pub fn local_objects(&self) -> usize { - unsafe { (*self.raw()).local_objects as usize } - } - /// Number of deltas in the packfile being downloaded - pub fn total_deltas(&self) -> usize { - unsafe { (*self.raw()).total_deltas as usize } - } - /// Received deltas that have been hashed. - pub fn indexed_deltas(&self) -> usize { - unsafe { (*self.raw()).indexed_deltas as usize } - } - /// Size of the packfile received up to now - pub fn received_bytes(&self) -> usize { - unsafe { (*self.raw()).received_bytes as usize } - } - - /// Convert this to an owned version of `Progress`. - pub fn to_owned(&self) -> Progress<'static> { - Progress { - raw: ProgressState::Owned(unsafe { *self.raw() }), - _marker: marker::PhantomData, - } - } -} - -impl<'a> Binding for Progress<'a> { - type Raw = *const raw::git_indexer_progress; - unsafe fn from_raw(raw: *const raw::git_indexer_progress) -> Progress<'a> { - Progress { - raw: ProgressState::Borrowed(raw), - _marker: marker::PhantomData, - } - } - - fn raw(&self) -> *const raw::git_indexer_progress { - match self.raw { - ProgressState::Borrowed(raw) => raw, - ProgressState::Owned(ref raw) => raw as *const _, - } - } -} - -/// Callback to be invoked while a transfer is in progress. -/// -/// This callback will be periodically called with updates to the progress of -/// the transfer so far. The return value indicates whether the transfer should -/// continue. A return value of `false` will cancel the transfer. -/// -/// * `progress` - the progress being made so far. -#[deprecated( - since = "0.11.0", - note = "renamed to `IndexerProgress` to match upstream" -)] -#[allow(dead_code)] -pub type TransportProgress<'a> = IndexerProgress<'a>; - -/// A stream to write and index a packfile -/// -/// This is equivalent to [`crate::OdbPackwriter`], but allows to store the pack -/// and index at an arbitrary path. It also does not require access to an object -/// database if, and only if, the pack file is self-contained (i.e. not "thin"). -pub struct Indexer<'odb> { - raw: *mut raw::git_indexer, - progress: raw::git_indexer_progress, - progress_payload_ptr: *mut OdbPackwriterCb<'odb>, -} - -impl<'a> Indexer<'a> { - /// Create a new indexer - /// - /// The [`Odb`] is used to resolve base objects when fixing thin packs. It - /// can be `None` if no thin pack is expected, in which case missing bases - /// will result in an error. - /// - /// `mode` is the permissions to use for the output files, use `0` for defaults. - /// - /// If `verify` is `false`, the indexer will bypass object connectivity checks. - pub fn new(odb: Option<&Odb<'a>>, path: &Path, mode: u32, verify: bool) -> Result<Self, Error> { - let path = path.into_c_string()?; - - let odb = odb.map(Binding::raw).unwrap_or_else(ptr::null_mut); - - let mut out = ptr::null_mut(); - let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb); - let progress_payload = Box::new(OdbPackwriterCb { cb: None }); - let progress_payload_ptr = Box::into_raw(progress_payload); - - unsafe { - let mut opts = mem::zeroed(); - try_call!(raw::git_indexer_options_init( - &mut opts, - raw::GIT_INDEXER_OPTIONS_VERSION - )); - opts.progress_cb = progress_cb; - opts.progress_cb_payload = progress_payload_ptr as *mut c_void; - opts.verify = verify.into(); - - try_call!(raw::git_indexer_new(&mut out, path, mode, odb, &mut opts)); - } - - Ok(Self { - raw: out, - progress: Default::default(), - progress_payload_ptr, - }) - } - - /// Finalize the pack and index - /// - /// Resolves any pending deltas and writes out the index file. The returned - /// string is the hexadecimal checksum of the packfile, which is also used - /// to name the pack and index files (`pack-<checksum>.pack` and - /// `pack-<checksum>.idx` respectively). - pub fn commit(mut self) -> Result<String, Error> { - unsafe { - try_call!(raw::git_indexer_commit(self.raw, &mut self.progress)); - - let name = CStr::from_ptr(raw::git_indexer_name(self.raw)); - Ok(name.to_str().expect("pack name not utf8").to_owned()) - } - } - - /// The callback through which progress is monitored. Be aware that this is - /// called inline, so performance may be affected. - pub fn progress<F>(&mut self, cb: F) -> &mut Self - where - F: FnMut(Progress<'_>) -> bool + 'a, - { - let progress_payload = - unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) }; - progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'a>>); - - self - } -} - -impl io::Write for Indexer<'_> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - unsafe { - let ptr = buf.as_ptr() as *mut c_void; - let len = buf.len(); - - let res = raw::git_indexer_append(self.raw, ptr, len, &mut self.progress); - if res < 0 { - Err(io::Error::new( - io::ErrorKind::Other, - Error::last_error(res).unwrap(), - )) - } else { - Ok(buf.len()) - } - } - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl Drop for Indexer<'_> { - fn drop(&mut self) { - unsafe { - raw::git_indexer_free(self.raw); - drop(Box::from_raw(self.progress_payload_ptr)) - } - } -} - -#[cfg(test)] -mod tests { - use crate::{Buf, Indexer}; - use std::io::prelude::*; - - #[test] - fn indexer() { - let (_td, repo_source) = crate::test::repo_init(); - let (_td, repo_target) = crate::test::repo_init(); - - let mut progress_called = false; - - // Create an in-memory packfile - let mut builder = t!(repo_source.packbuilder()); - let mut buf = Buf::new(); - let (commit_source_id, _tree) = crate::test::commit(&repo_source); - t!(builder.insert_object(commit_source_id, None)); - t!(builder.write_buf(&mut buf)); - - // Write it to the standard location in the target repo, but via indexer - let odb = repo_source.odb().unwrap(); - let mut indexer = Indexer::new( - Some(&odb), - repo_target.path().join("objects").join("pack").as_path(), - 0o644, - true, - ) - .unwrap(); - indexer.progress(|_| { - progress_called = true; - true - }); - indexer.write(&buf).unwrap(); - indexer.commit().unwrap(); - - // Assert that target repo picks it up as valid - let commit_target = repo_target.find_commit(commit_source_id).unwrap(); - assert_eq!(commit_target.id(), commit_source_id); - assert!(progress_called); - } -} diff --git a/extra/git2/src/lib.rs b/extra/git2/src/lib.rs deleted file mode 100644 index 3dd6fe92e..000000000 --- a/extra/git2/src/lib.rs +++ /dev/null @@ -1,1668 +0,0 @@ -//! # libgit2 bindings for Rust -//! -//! This library contains bindings to the [libgit2][1] C library which is used -//! to manage git repositories. The library itself is a work in progress and is -//! likely lacking some bindings here and there, so be warned. -//! -//! [1]: https://libgit2.github.com/ -//! -//! The git2-rs library strives to be as close to libgit2 as possible, but also -//! strives to make using libgit2 as safe as possible. All resource management -//! is automatic as well as adding strong types to all interfaces (including -//! `Result`) -//! -//! ## Creating a `Repository` -//! -//! The `Repository` is the source from which almost all other objects in git-rs -//! are spawned. A repository can be created through opening, initializing, or -//! cloning. -//! -//! ### Initializing a new repository -//! -//! The `init` method will create a new repository, assuming one does not -//! already exist. -//! -//! ```no_run -//! # #![allow(unstable)] -//! use git2::Repository; -//! -//! let repo = match Repository::init("/path/to/a/repo") { -//! Ok(repo) => repo, -//! Err(e) => panic!("failed to init: {}", e), -//! }; -//! ``` -//! -//! ### Opening an existing repository -//! -//! ```no_run -//! # #![allow(unstable)] -//! use git2::Repository; -//! -//! let repo = match Repository::open("/path/to/a/repo") { -//! Ok(repo) => repo, -//! Err(e) => panic!("failed to open: {}", e), -//! }; -//! ``` -//! -//! ### Cloning an existing repository -//! -//! ```no_run -//! # #![allow(unstable)] -//! use git2::Repository; -//! -//! let url = "https://github.com/alexcrichton/git2-rs"; -//! let repo = match Repository::clone(url, "/path/to/a/repo") { -//! Ok(repo) => repo, -//! Err(e) => panic!("failed to clone: {}", e), -//! }; -//! ``` -//! -//! To clone using SSH, refer to [RepoBuilder](./build/struct.RepoBuilder.html). -//! -//! ## Working with a `Repository` -//! -//! All derivative objects, references, etc are attached to the lifetime of the -//! source `Repository`, to ensure that they do not outlive the repository -//! itself. - -#![doc(html_root_url = "https://docs.rs/git2/0.18")] -#![allow(trivial_numeric_casts, trivial_casts)] -#![deny(missing_docs)] -#![warn(rust_2018_idioms)] -#![cfg_attr(test, deny(warnings))] - -use bitflags::bitflags; -use libgit2_sys as raw; - -use std::ffi::{CStr, CString}; -use std::fmt; -use std::str; -use std::sync::Once; - -pub use crate::apply::{ApplyLocation, ApplyOptions}; -pub use crate::attr::AttrValue; -pub use crate::blame::{Blame, BlameHunk, BlameIter, BlameOptions}; -pub use crate::blob::{Blob, BlobWriter}; -pub use crate::branch::{Branch, Branches}; -pub use crate::buf::Buf; -pub use crate::cherrypick::CherrypickOptions; -pub use crate::commit::{Commit, Parents}; -pub use crate::config::{Config, ConfigEntries, ConfigEntry}; -pub use crate::cred::{Cred, CredentialHelper}; -pub use crate::describe::{Describe, DescribeFormatOptions, DescribeOptions}; -pub use crate::diff::{Deltas, Diff, DiffDelta, DiffFile, DiffOptions}; -pub use crate::diff::{DiffBinary, DiffBinaryFile, DiffBinaryKind, DiffPatchidOptions}; -pub use crate::diff::{DiffFindOptions, DiffHunk, DiffLine, DiffLineType, DiffStats}; -pub use crate::email::{Email, EmailCreateOptions}; -pub use crate::error::Error; -pub use crate::index::{ - Index, IndexConflict, IndexConflicts, IndexEntries, IndexEntry, IndexMatchedPath, -}; -pub use crate::indexer::{Indexer, IndexerProgress, Progress}; -pub use crate::mailmap::Mailmap; -pub use crate::mempack::Mempack; -pub use crate::merge::{AnnotatedCommit, MergeOptions}; -pub use crate::message::{ - message_prettify, message_trailers_bytes, message_trailers_strs, MessageTrailersBytes, - MessageTrailersBytesIterator, MessageTrailersStrs, MessageTrailersStrsIterator, - DEFAULT_COMMENT_CHAR, -}; -pub use crate::note::{Note, Notes}; -pub use crate::object::Object; -pub use crate::odb::{Odb, OdbObject, OdbPackwriter, OdbReader, OdbWriter}; -pub use crate::oid::Oid; -pub use crate::packbuilder::{PackBuilder, PackBuilderStage}; -pub use crate::patch::Patch; -pub use crate::pathspec::{Pathspec, PathspecFailedEntries, PathspecMatchList}; -pub use crate::pathspec::{PathspecDiffEntries, PathspecEntries}; -pub use crate::proxy_options::ProxyOptions; -pub use crate::push_update::PushUpdate; -pub use crate::rebase::{Rebase, RebaseOperation, RebaseOperationType, RebaseOptions}; -pub use crate::reference::{Reference, ReferenceNames, References}; -pub use crate::reflog::{Reflog, ReflogEntry, ReflogIter}; -pub use crate::refspec::Refspec; -pub use crate::remote::{ - FetchOptions, PushOptions, Refspecs, Remote, RemoteConnection, RemoteHead, RemoteRedirect, -}; -pub use crate::remote_callbacks::{CertificateCheckStatus, Credentials, RemoteCallbacks}; -pub use crate::remote_callbacks::{TransportMessage, UpdateTips}; -pub use crate::repo::{Repository, RepositoryInitOptions}; -pub use crate::revert::RevertOptions; -pub use crate::revspec::Revspec; -pub use crate::revwalk::Revwalk; -pub use crate::signature::Signature; -pub use crate::stash::{StashApplyOptions, StashApplyProgressCb, StashCb, StashSaveOptions}; -pub use crate::status::{StatusEntry, StatusIter, StatusOptions, StatusShow, Statuses}; -pub use crate::submodule::{Submodule, SubmoduleUpdateOptions}; -pub use crate::tag::Tag; -pub use crate::time::{IndexTime, Time}; -pub use crate::tracing::{trace_set, TraceLevel}; -pub use crate::transaction::Transaction; -pub use crate::tree::{Tree, TreeEntry, TreeIter, TreeWalkMode, TreeWalkResult}; -pub use crate::treebuilder::TreeBuilder; -pub use crate::util::IntoCString; -pub use crate::version::Version; -pub use crate::worktree::{Worktree, WorktreeAddOptions, WorktreeLockStatus, WorktreePruneOptions}; - -// Create a convinience method on bitflag struct which checks the given flag -macro_rules! is_bit_set { - ($name:ident, $flag:expr) => { - #[allow(missing_docs)] - pub fn $name(&self) -> bool { - self.intersects($flag) - } - }; -} - -/// An enumeration of possible errors that can happen when working with a git -/// repository. -// Note: We omit a few native error codes, as they are unlikely to be propagated -// to the library user. Currently: -// -// * GIT_EPASSTHROUGH -// * GIT_ITEROVER -// * GIT_RETRY -#[derive(PartialEq, Eq, Clone, Debug, Copy)] -pub enum ErrorCode { - /// Generic error - GenericError, - /// Requested object could not be found - NotFound, - /// Object exists preventing operation - Exists, - /// More than one object matches - Ambiguous, - /// Output buffer too short to hold data - BufSize, - /// User-generated error - User, - /// Operation not allowed on bare repository - BareRepo, - /// HEAD refers to branch with no commits - UnbornBranch, - /// Merge in progress prevented operation - Unmerged, - /// Reference was not fast-forwardable - NotFastForward, - /// Name/ref spec was not in a valid format - InvalidSpec, - /// Checkout conflicts prevented operation - Conflict, - /// Lock file prevented operation - Locked, - /// Reference value does not match expected - Modified, - /// Authentication error - Auth, - /// Server certificate is invalid - Certificate, - /// Patch/merge has already been applied - Applied, - /// The requested peel operation is not possible - Peel, - /// Unexpected EOF - Eof, - /// Invalid operation or input - Invalid, - /// Uncommitted changes in index prevented operation - Uncommitted, - /// Operation was not valid for a directory - Directory, - /// A merge conflict exists and cannot continue - MergeConflict, - /// Hashsum mismatch in object - HashsumMismatch, - /// Unsaved changes in the index would be overwritten - IndexDirty, - /// Patch application failed - ApplyFail, - /// The object is not owned by the current user - Owner, -} - -/// An enumeration of possible categories of things that can have -/// errors when working with a git repository. -#[derive(PartialEq, Eq, Clone, Debug, Copy)] -pub enum ErrorClass { - /// Uncategorized - None, - /// Out of memory or insufficient allocated space - NoMemory, - /// Syscall or standard system library error - Os, - /// Invalid input - Invalid, - /// Error resolving or manipulating a reference - Reference, - /// ZLib failure - Zlib, - /// Bad repository state - Repository, - /// Bad configuration - Config, - /// Regex failure - Regex, - /// Bad object - Odb, - /// Invalid index data - Index, - /// Error creating or obtaining an object - Object, - /// Network error - Net, - /// Error manipulating a tag - Tag, - /// Invalid value in tree - Tree, - /// Hashing or packing error - Indexer, - /// Error from SSL - Ssl, - /// Error involving submodules - Submodule, - /// Threading error - Thread, - /// Error manipulating a stash - Stash, - /// Checkout failure - Checkout, - /// Invalid FETCH_HEAD - FetchHead, - /// Merge failure - Merge, - /// SSH failure - Ssh, - /// Error manipulating filters - Filter, - /// Error reverting commit - Revert, - /// Error from a user callback - Callback, - /// Error cherry-picking commit - CherryPick, - /// Can't describe object - Describe, - /// Error during rebase - Rebase, - /// Filesystem-related error - Filesystem, - /// Invalid patch data - Patch, - /// Error involving worktrees - Worktree, - /// Hash library error or SHA-1 collision - Sha1, - /// HTTP error - Http, -} - -/// A listing of the possible states that a repository can be in. -#[derive(PartialEq, Eq, Clone, Debug, Copy)] -#[allow(missing_docs)] -pub enum RepositoryState { - Clean, - Merge, - Revert, - RevertSequence, - CherryPick, - CherryPickSequence, - Bisect, - Rebase, - RebaseInteractive, - RebaseMerge, - ApplyMailbox, - ApplyMailboxOrRebase, -} - -/// An enumeration of the possible directions for a remote. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Direction { - /// Data will be fetched (read) from this remote. - Fetch, - /// Data will be pushed (written) to this remote. - Push, -} - -/// An enumeration of the operations that can be performed for the `reset` -/// method on a `Repository`. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum ResetType { - /// Move the head to the given commit. - Soft, - /// Soft plus reset the index to the commit. - Mixed, - /// Mixed plus changes in the working tree are discarded. - Hard, -} - -/// An enumeration all possible kinds objects may have. -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum ObjectType { - /// Any kind of git object - Any, - /// An object which corresponds to a git commit - Commit, - /// An object which corresponds to a git tree - Tree, - /// An object which corresponds to a git blob - Blob, - /// An object which corresponds to a git tag - Tag, -} - -/// An enumeration of all possible kinds of references. -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum ReferenceType { - /// A reference which points at an object id. - Direct, - - /// A reference which points at another reference. - Symbolic, -} - -/// An enumeration for the possible types of branches -#[derive(PartialEq, Eq, Debug, Copy, Clone)] -pub enum BranchType { - /// A local branch not on a remote. - Local, - /// A branch for a remote. - Remote, -} - -/// An enumeration of the possible priority levels of a config file. -/// -/// The levels corresponding to the escalation logic (higher to lower) when -/// searching for config entries. -#[derive(PartialEq, Eq, Debug, Copy, Clone)] -pub enum ConfigLevel { - /// System-wide on Windows, for compatibility with portable git - ProgramData = 1, - /// System-wide configuration file, e.g. /etc/gitconfig - System, - /// XDG-compatible configuration file, e.g. ~/.config/git/config - XDG, - /// User-specific configuration, e.g. ~/.gitconfig - Global, - /// Repository specific config, e.g. $PWD/.git/config - Local, - /// Application specific configuration file - App, - /// Highest level available - Highest = -1, -} - -/// Merge file favor options for `MergeOptions` instruct the file-level -/// merging functionality how to deal with conflicting regions of the files. -#[derive(PartialEq, Eq, Debug, Copy, Clone)] -pub enum FileFavor { - /// When a region of a file is changed in both branches, a conflict will be - /// recorded in the index so that git_checkout can produce a merge file with - /// conflict markers in the working directory. This is the default. - Normal, - /// When a region of a file is changed in both branches, the file created - /// in the index will contain the "ours" side of any conflicting region. - /// The index will not record a conflict. - Ours, - /// When a region of a file is changed in both branches, the file created - /// in the index will contain the "theirs" side of any conflicting region. - /// The index will not record a conflict. - Theirs, - /// When a region of a file is changed in both branches, the file created - /// in the index will contain each unique line from each side, which has - /// the result of combining both files. The index will not record a conflict. - Union, -} - -bitflags! { - /// Orderings that may be specified for Revwalk iteration. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct Sort: u32 { - /// Sort the repository contents in no particular ordering. - /// - /// This sorting is arbitrary, implementation-specific, and subject to - /// change at any time. This is the default sorting for new walkers. - const NONE = raw::GIT_SORT_NONE as u32; - - /// Sort the repository contents in topological order (children before - /// parents). - /// - /// This sorting mode can be combined with time sorting. - const TOPOLOGICAL = raw::GIT_SORT_TOPOLOGICAL as u32; - - /// Sort the repository contents by commit time. - /// - /// This sorting mode can be combined with topological sorting. - const TIME = raw::GIT_SORT_TIME as u32; - - /// Iterate through the repository contents in reverse order. - /// - /// This sorting mode can be combined with any others. - const REVERSE = raw::GIT_SORT_REVERSE as u32; - } -} - -impl Sort { - is_bit_set!(is_none, Sort::NONE); - is_bit_set!(is_topological, Sort::TOPOLOGICAL); - is_bit_set!(is_time, Sort::TIME); - is_bit_set!(is_reverse, Sort::REVERSE); -} - -bitflags! { - /// Types of credentials that can be requested by a credential callback. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct CredentialType: u32 { - #[allow(missing_docs)] - const USER_PASS_PLAINTEXT = raw::GIT_CREDTYPE_USERPASS_PLAINTEXT as u32; - #[allow(missing_docs)] - const SSH_KEY = raw::GIT_CREDTYPE_SSH_KEY as u32; - #[allow(missing_docs)] - const SSH_MEMORY = raw::GIT_CREDTYPE_SSH_MEMORY as u32; - #[allow(missing_docs)] - const SSH_CUSTOM = raw::GIT_CREDTYPE_SSH_CUSTOM as u32; - #[allow(missing_docs)] - const DEFAULT = raw::GIT_CREDTYPE_DEFAULT as u32; - #[allow(missing_docs)] - const SSH_INTERACTIVE = raw::GIT_CREDTYPE_SSH_INTERACTIVE as u32; - #[allow(missing_docs)] - const USERNAME = raw::GIT_CREDTYPE_USERNAME as u32; - } -} - -impl CredentialType { - is_bit_set!(is_user_pass_plaintext, CredentialType::USER_PASS_PLAINTEXT); - is_bit_set!(is_ssh_key, CredentialType::SSH_KEY); - is_bit_set!(is_ssh_memory, CredentialType::SSH_MEMORY); - is_bit_set!(is_ssh_custom, CredentialType::SSH_CUSTOM); - is_bit_set!(is_default, CredentialType::DEFAULT); - is_bit_set!(is_ssh_interactive, CredentialType::SSH_INTERACTIVE); - is_bit_set!(is_username, CredentialType::USERNAME); -} - -impl Default for CredentialType { - fn default() -> Self { - CredentialType::DEFAULT - } -} - -bitflags! { - /// Flags for the `flags` field of an IndexEntry. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct IndexEntryFlag: u16 { - /// Set when the `extended_flags` field is valid. - const EXTENDED = raw::GIT_INDEX_ENTRY_EXTENDED as u16; - /// "Assume valid" flag - const VALID = raw::GIT_INDEX_ENTRY_VALID as u16; - } -} - -impl IndexEntryFlag { - is_bit_set!(is_extended, IndexEntryFlag::EXTENDED); - is_bit_set!(is_valid, IndexEntryFlag::VALID); -} - -bitflags! { - /// Flags for the `extended_flags` field of an IndexEntry. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct IndexEntryExtendedFlag: u16 { - /// An "intent to add" entry from "git add -N" - const INTENT_TO_ADD = raw::GIT_INDEX_ENTRY_INTENT_TO_ADD as u16; - /// Skip the associated worktree file, for sparse checkouts - const SKIP_WORKTREE = raw::GIT_INDEX_ENTRY_SKIP_WORKTREE as u16; - - #[allow(missing_docs)] - const UPTODATE = raw::GIT_INDEX_ENTRY_UPTODATE as u16; - } -} - -impl IndexEntryExtendedFlag { - is_bit_set!(is_intent_to_add, IndexEntryExtendedFlag::INTENT_TO_ADD); - is_bit_set!(is_skip_worktree, IndexEntryExtendedFlag::SKIP_WORKTREE); - is_bit_set!(is_up_to_date, IndexEntryExtendedFlag::UPTODATE); -} - -bitflags! { - /// Flags for APIs that add files matching pathspec - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct IndexAddOption: u32 { - #[allow(missing_docs)] - const DEFAULT = raw::GIT_INDEX_ADD_DEFAULT as u32; - #[allow(missing_docs)] - const FORCE = raw::GIT_INDEX_ADD_FORCE as u32; - #[allow(missing_docs)] - const DISABLE_PATHSPEC_MATCH = - raw::GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH as u32; - #[allow(missing_docs)] - const CHECK_PATHSPEC = raw::GIT_INDEX_ADD_CHECK_PATHSPEC as u32; - } -} - -impl IndexAddOption { - is_bit_set!(is_default, IndexAddOption::DEFAULT); - is_bit_set!(is_force, IndexAddOption::FORCE); - is_bit_set!( - is_disable_pathspec_match, - IndexAddOption::DISABLE_PATHSPEC_MATCH - ); - is_bit_set!(is_check_pathspec, IndexAddOption::CHECK_PATHSPEC); -} - -impl Default for IndexAddOption { - fn default() -> Self { - IndexAddOption::DEFAULT - } -} - -bitflags! { - /// Flags for `Repository::open_ext` - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct RepositoryOpenFlags: u32 { - /// Only open the specified path; don't walk upward searching. - const NO_SEARCH = raw::GIT_REPOSITORY_OPEN_NO_SEARCH as u32; - /// Search across filesystem boundaries. - const CROSS_FS = raw::GIT_REPOSITORY_OPEN_CROSS_FS as u32; - /// Force opening as bare repository, and defer loading its config. - const BARE = raw::GIT_REPOSITORY_OPEN_BARE as u32; - /// Don't try appending `/.git` to the specified repository path. - const NO_DOTGIT = raw::GIT_REPOSITORY_OPEN_NO_DOTGIT as u32; - /// Respect environment variables like `$GIT_DIR`. - const FROM_ENV = raw::GIT_REPOSITORY_OPEN_FROM_ENV as u32; - } -} - -impl RepositoryOpenFlags { - is_bit_set!(is_no_search, RepositoryOpenFlags::NO_SEARCH); - is_bit_set!(is_cross_fs, RepositoryOpenFlags::CROSS_FS); - is_bit_set!(is_bare, RepositoryOpenFlags::BARE); - is_bit_set!(is_no_dotgit, RepositoryOpenFlags::NO_DOTGIT); - is_bit_set!(is_from_env, RepositoryOpenFlags::FROM_ENV); -} - -bitflags! { - /// Flags for the return value of `Repository::revparse` - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct RevparseMode: u32 { - /// The spec targeted a single object - const SINGLE = raw::GIT_REVPARSE_SINGLE as u32; - /// The spec targeted a range of commits - const RANGE = raw::GIT_REVPARSE_RANGE as u32; - /// The spec used the `...` operator, which invokes special semantics. - const MERGE_BASE = raw::GIT_REVPARSE_MERGE_BASE as u32; - } -} - -impl RevparseMode { - is_bit_set!(is_no_single, RevparseMode::SINGLE); - is_bit_set!(is_range, RevparseMode::RANGE); - is_bit_set!(is_merge_base, RevparseMode::MERGE_BASE); -} - -bitflags! { - /// The results of `merge_analysis` indicating the merge opportunities. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct MergeAnalysis: u32 { - /// No merge is possible. - const ANALYSIS_NONE = raw::GIT_MERGE_ANALYSIS_NONE as u32; - /// A "normal" merge; both HEAD and the given merge input have diverged - /// from their common ancestor. The divergent commits must be merged. - const ANALYSIS_NORMAL = raw::GIT_MERGE_ANALYSIS_NORMAL as u32; - /// All given merge inputs are reachable from HEAD, meaning the - /// repository is up-to-date and no merge needs to be performed. - const ANALYSIS_UP_TO_DATE = raw::GIT_MERGE_ANALYSIS_UP_TO_DATE as u32; - /// The given merge input is a fast-forward from HEAD and no merge - /// needs to be performed. Instead, the client can check out the - /// given merge input. - const ANALYSIS_FASTFORWARD = raw::GIT_MERGE_ANALYSIS_FASTFORWARD as u32; - /// The HEAD of the current repository is "unborn" and does not point to - /// a valid commit. No merge can be performed, but the caller may wish - /// to simply set HEAD to the target commit(s). - const ANALYSIS_UNBORN = raw::GIT_MERGE_ANALYSIS_UNBORN as u32; - } -} - -impl MergeAnalysis { - is_bit_set!(is_none, MergeAnalysis::ANALYSIS_NONE); - is_bit_set!(is_normal, MergeAnalysis::ANALYSIS_NORMAL); - is_bit_set!(is_up_to_date, MergeAnalysis::ANALYSIS_UP_TO_DATE); - is_bit_set!(is_fast_forward, MergeAnalysis::ANALYSIS_FASTFORWARD); - is_bit_set!(is_unborn, MergeAnalysis::ANALYSIS_UNBORN); -} - -bitflags! { - /// The user's stated preference for merges. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct MergePreference: u32 { - /// No configuration was found that suggests a preferred behavior for - /// merge. - const NONE = raw::GIT_MERGE_PREFERENCE_NONE as u32; - /// There is a `merge.ff=false` configuration setting, suggesting that - /// the user does not want to allow a fast-forward merge. - const NO_FAST_FORWARD = raw::GIT_MERGE_PREFERENCE_NO_FASTFORWARD as u32; - /// There is a `merge.ff=only` configuration setting, suggesting that - /// the user only wants fast-forward merges. - const FASTFORWARD_ONLY = raw::GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY as u32; - } -} - -impl MergePreference { - is_bit_set!(is_none, MergePreference::NONE); - is_bit_set!(is_no_fast_forward, MergePreference::NO_FAST_FORWARD); - is_bit_set!(is_fastforward_only, MergePreference::FASTFORWARD_ONLY); -} - -bitflags! { - /// Flags controlling the behavior of ODB lookup operations - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct OdbLookupFlags: u32 { - /// Don't call `git_odb_refresh` if the lookup fails. Useful when doing - /// a batch of lookup operations for objects that may legitimately not - /// exist. When using this flag, you may wish to manually call - /// `git_odb_refresh` before processing a batch of objects. - const NO_REFRESH = raw::GIT_ODB_LOOKUP_NO_REFRESH as u32; - } -} - -#[cfg(test)] -#[macro_use] -mod test; -#[macro_use] -mod panic; -mod attr; -mod call; -mod util; - -pub mod build; -pub mod cert; -pub mod oid_array; -pub mod opts; -pub mod string_array; -pub mod transport; - -mod apply; -mod blame; -mod blob; -mod branch; -mod buf; -mod cherrypick; -mod commit; -mod config; -mod cred; -mod describe; -mod diff; -mod email; -mod error; -mod index; -mod indexer; -mod mailmap; -mod mempack; -mod merge; -mod message; -mod note; -mod object; -mod odb; -mod oid; -mod packbuilder; -mod patch; -mod pathspec; -mod proxy_options; -mod push_update; -mod rebase; -mod reference; -mod reflog; -mod refspec; -mod remote; -mod remote_callbacks; -mod repo; -mod revert; -mod revspec; -mod revwalk; -mod signature; -mod stash; -mod status; -mod submodule; -mod tag; -mod tagforeach; -mod time; -mod tracing; -mod transaction; -mod tree; -mod treebuilder; -mod version; -mod worktree; - -fn init() { - static INIT: Once = Once::new(); - - INIT.call_once(|| { - openssl_env_init(); - }); - - raw::init(); -} - -#[cfg(all( - unix, - not(target_os = "macos"), - not(target_os = "ios"), - feature = "https" -))] -fn openssl_env_init() { - // Currently, libgit2 leverages OpenSSL for SSL support when cloning - // repositories over HTTPS. This means that we're picking up an OpenSSL - // dependency on non-Windows platforms (where it has its own HTTPS - // subsystem). As a result, we need to link to OpenSSL. - // - // Now actually *linking* to OpenSSL isn't so hard. We just need to make - // sure to use pkg-config to discover any relevant system dependencies for - // differences between distributions like CentOS and Ubuntu. The actual - // trickiness comes about when we start *distributing* the resulting - // binaries. Currently Cargo is distributed in binary form as nightlies, - // which means we're distributing a binary with OpenSSL linked in. - // - // For historical reasons, the Linux nightly builder is running a CentOS - // distribution in order to have as much ABI compatibility with other - // distributions as possible. Sadly, however, this compatibility does not - // extend to OpenSSL. Currently OpenSSL has two major versions, 0.9 and 1.0, - // which are incompatible (many ABI differences). The CentOS builder we - // build on has version 1.0, as do most distributions today. Some still have - // 0.9, however. This means that if we are to distribute the binaries built - // by the CentOS machine, we would only be compatible with OpenSSL 1.0 and - // we would fail to run (a dynamic linker error at runtime) on systems with - // only 9.8 installed (hopefully). - // - // But wait, the plot thickens! Apparently CentOS has dubbed their OpenSSL - // library as `libssl.so.10`, notably the `10` is included at the end. On - // the other hand Ubuntu, for example, only distributes `libssl.so`. This - // means that the binaries created at CentOS are hard-wired to probe for a - // file called `libssl.so.10` at runtime (using the LD_LIBRARY_PATH), which - // will not be found on ubuntu. The conclusion of this is that binaries - // built on CentOS cannot be distributed to Ubuntu and run successfully. - // - // There are a number of sneaky things we could do, including, but not - // limited to: - // - // 1. Create a shim program which runs "just before" cargo runs. The - // responsibility of this shim program would be to locate `libssl.so`, - // whatever it's called, on the current system, make sure there's a - // symlink *somewhere* called `libssl.so.10`, and then set up - // LD_LIBRARY_PATH and run the actual cargo. - // - // This approach definitely seems unconventional, and is borderline - // overkill for this problem. It's also dubious if we can find a - // libssl.so reliably on the target system. - // - // 2. Somehow re-work the CentOS installation so that the linked-against - // library is called libssl.so instead of libssl.so.10 - // - // The problem with this approach is that systems with 0.9 installed will - // start to silently fail, due to also having libraries called libssl.so - // (probably symlinked under a more appropriate version). - // - // 3. Compile Cargo against both OpenSSL 1.0 *and* OpenSSL 0.9, and - // distribute both. Also make sure that the linked-against name of the - // library is `libssl.so`. At runtime we determine which version is - // installed, and we then the appropriate binary. - // - // This approach clearly has drawbacks in terms of infrastructure and - // feasibility. - // - // 4. Build a nightly of Cargo for each distribution we'd like to support. - // You would then pick the appropriate Cargo nightly to install locally. - // - // So, with all this in mind, the decision was made to *statically* link - // OpenSSL. This solves any problem of relying on a downstream OpenSSL - // version being available. This does, however, open a can of worms related - // to security issues. It's generally a good idea to dynamically link - // OpenSSL as you'll get security updates over time without having to do - // anything (the system administrator will update the local openssl - // package). By statically linking, we're forfeiting this feature. - // - // The conclusion was made it is likely appropriate for the Cargo nightlies - // to statically link OpenSSL, but highly encourage distributions and - // packagers of Cargo to dynamically link OpenSSL. Packagers are targeting - // one system and are distributing to only that system, so none of the - // problems mentioned above would arise. - // - // In order to support this, a new package was made: openssl-static-sys. - // This package currently performs a fairly simple task: - // - // 1. Run pkg-config to discover where openssl is installed. - // 2. If openssl is installed in a nonstandard location, *and* static copies - // of the libraries are available, copy them to $OUT_DIR. - // - // This library will bring in libssl.a and libcrypto.a into the local build, - // allowing them to be picked up by this crate. This allows us to configure - // our own buildbots to have pkg-config point to these local pre-built - // copies of a static OpenSSL (with very few dependencies) while allowing - // most other builds of Cargo to naturally dynamically link OpenSSL. - // - // So in summary, if you're with me so far, we've statically linked OpenSSL - // to the Cargo binary (or any binary, for that matter) and we're ready to - // distribute it to *all* linux distributions. Remember that our original - // intent for openssl was for HTTPS support, which implies that we need some - // for of CA certificate store to validate certificates. This is normally - // installed in a standard system location. - // - // Unfortunately, as one might imagine, OpenSSL is configured for where this - // standard location is at *build time*, but it often varies widely - // per-system. Consequently, it was discovered that OpenSSL will respect the - // SSL_CERT_FILE and SSL_CERT_DIR environment variables in order to assist - // in discovering the location of this file (hurray!). - // - // So, finally getting to the point, this function solely exists to support - // our static builds of OpenSSL by probing for the "standard system - // location" of certificates and setting relevant environment variable to - // point to them. - // - // Ah, and as a final note, this is only a problem on Linux, not on OS X. On - // OS X the OpenSSL binaries are stable enough that we can just rely on - // dynamic linkage (plus they have some weird modifications to OpenSSL which - // means we wouldn't want to link statically). - openssl_probe::init_ssl_cert_env_vars(); -} - -#[cfg(any( - windows, - target_os = "macos", - target_os = "ios", - not(feature = "https") -))] -fn openssl_env_init() {} - -unsafe fn opt_bytes<'a, T>(_anchor: &'a T, c: *const libc::c_char) -> Option<&'a [u8]> { - if c.is_null() { - None - } else { - Some(CStr::from_ptr(c).to_bytes()) - } -} - -fn opt_cstr<T: IntoCString>(o: Option<T>) -> Result<Option<CString>, Error> { - match o { - Some(s) => s.into_c_string().map(Some), - None => Ok(None), - } -} - -impl ObjectType { - /// Convert an object type to its string representation. - pub fn str(&self) -> &'static str { - unsafe { - let ptr = call!(raw::git_object_type2string(*self)) as *const _; - let data = CStr::from_ptr(ptr).to_bytes(); - str::from_utf8(data).unwrap() - } - } - - /// Determine if the given git_object_t is a valid loose object type. - pub fn is_loose(&self) -> bool { - unsafe { call!(raw::git_object_typeisloose(*self)) == 1 } - } - - /// Convert a raw git_object_t to an ObjectType - pub fn from_raw(raw: raw::git_object_t) -> Option<ObjectType> { - match raw { - raw::GIT_OBJECT_ANY => Some(ObjectType::Any), - raw::GIT_OBJECT_COMMIT => Some(ObjectType::Commit), - raw::GIT_OBJECT_TREE => Some(ObjectType::Tree), - raw::GIT_OBJECT_BLOB => Some(ObjectType::Blob), - raw::GIT_OBJECT_TAG => Some(ObjectType::Tag), - _ => None, - } - } - - /// Convert this kind into its raw representation - pub fn raw(&self) -> raw::git_object_t { - call::convert(self) - } - - /// Convert a string object type representation to its object type. - pub fn from_str(s: &str) -> Option<ObjectType> { - let raw = unsafe { call!(raw::git_object_string2type(CString::new(s).unwrap())) }; - ObjectType::from_raw(raw) - } -} - -impl fmt::Display for ObjectType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.str().fmt(f) - } -} - -impl ReferenceType { - /// Convert an object type to its string representation. - pub fn str(&self) -> &'static str { - match self { - ReferenceType::Direct => "direct", - ReferenceType::Symbolic => "symbolic", - } - } - - /// Convert a raw git_reference_t to a ReferenceType. - pub fn from_raw(raw: raw::git_reference_t) -> Option<ReferenceType> { - match raw { - raw::GIT_REFERENCE_DIRECT => Some(ReferenceType::Direct), - raw::GIT_REFERENCE_SYMBOLIC => Some(ReferenceType::Symbolic), - _ => None, - } - } -} - -impl fmt::Display for ReferenceType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.str().fmt(f) - } -} - -impl ConfigLevel { - /// Converts a raw configuration level to a ConfigLevel - pub fn from_raw(raw: raw::git_config_level_t) -> ConfigLevel { - match raw { - raw::GIT_CONFIG_LEVEL_PROGRAMDATA => ConfigLevel::ProgramData, - raw::GIT_CONFIG_LEVEL_SYSTEM => ConfigLevel::System, - raw::GIT_CONFIG_LEVEL_XDG => ConfigLevel::XDG, - raw::GIT_CONFIG_LEVEL_GLOBAL => ConfigLevel::Global, - raw::GIT_CONFIG_LEVEL_LOCAL => ConfigLevel::Local, - raw::GIT_CONFIG_LEVEL_APP => ConfigLevel::App, - raw::GIT_CONFIG_HIGHEST_LEVEL => ConfigLevel::Highest, - n => panic!("unknown config level: {}", n), - } - } -} - -impl SubmoduleIgnore { - /// Converts a [`raw::git_submodule_ignore_t`] to a [`SubmoduleIgnore`] - pub fn from_raw(raw: raw::git_submodule_ignore_t) -> Self { - match raw { - raw::GIT_SUBMODULE_IGNORE_UNSPECIFIED => SubmoduleIgnore::Unspecified, - raw::GIT_SUBMODULE_IGNORE_NONE => SubmoduleIgnore::None, - raw::GIT_SUBMODULE_IGNORE_UNTRACKED => SubmoduleIgnore::Untracked, - raw::GIT_SUBMODULE_IGNORE_DIRTY => SubmoduleIgnore::Dirty, - raw::GIT_SUBMODULE_IGNORE_ALL => SubmoduleIgnore::All, - n => panic!("unknown submodule ignore rule: {}", n), - } - } -} - -impl SubmoduleUpdate { - /// Converts a [`raw::git_submodule_update_t`] to a [`SubmoduleUpdate`] - pub fn from_raw(raw: raw::git_submodule_update_t) -> Self { - match raw { - raw::GIT_SUBMODULE_UPDATE_CHECKOUT => SubmoduleUpdate::Checkout, - raw::GIT_SUBMODULE_UPDATE_REBASE => SubmoduleUpdate::Rebase, - raw::GIT_SUBMODULE_UPDATE_MERGE => SubmoduleUpdate::Merge, - raw::GIT_SUBMODULE_UPDATE_NONE => SubmoduleUpdate::None, - raw::GIT_SUBMODULE_UPDATE_DEFAULT => SubmoduleUpdate::Default, - n => panic!("unknown submodule update strategy: {}", n), - } - } -} - -bitflags! { - /// Status flags for a single file - /// - /// A combination of these values will be returned to indicate the status of - /// a file. Status compares the working directory, the index, and the - /// current HEAD of the repository. The `STATUS_INDEX_*` set of flags - /// represents the status of file in the index relative to the HEAD, and the - /// `STATUS_WT_*` set of flags represent the status of the file in the - /// working directory relative to the index. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct Status: u32 { - #[allow(missing_docs)] - const CURRENT = raw::GIT_STATUS_CURRENT as u32; - - #[allow(missing_docs)] - const INDEX_NEW = raw::GIT_STATUS_INDEX_NEW as u32; - #[allow(missing_docs)] - const INDEX_MODIFIED = raw::GIT_STATUS_INDEX_MODIFIED as u32; - #[allow(missing_docs)] - const INDEX_DELETED = raw::GIT_STATUS_INDEX_DELETED as u32; - #[allow(missing_docs)] - const INDEX_RENAMED = raw::GIT_STATUS_INDEX_RENAMED as u32; - #[allow(missing_docs)] - const INDEX_TYPECHANGE = raw::GIT_STATUS_INDEX_TYPECHANGE as u32; - - #[allow(missing_docs)] - const WT_NEW = raw::GIT_STATUS_WT_NEW as u32; - #[allow(missing_docs)] - const WT_MODIFIED = raw::GIT_STATUS_WT_MODIFIED as u32; - #[allow(missing_docs)] - const WT_DELETED = raw::GIT_STATUS_WT_DELETED as u32; - #[allow(missing_docs)] - const WT_TYPECHANGE = raw::GIT_STATUS_WT_TYPECHANGE as u32; - #[allow(missing_docs)] - const WT_RENAMED = raw::GIT_STATUS_WT_RENAMED as u32; - - #[allow(missing_docs)] - const IGNORED = raw::GIT_STATUS_IGNORED as u32; - #[allow(missing_docs)] - const CONFLICTED = raw::GIT_STATUS_CONFLICTED as u32; - } -} - -impl Status { - is_bit_set!(is_index_new, Status::INDEX_NEW); - is_bit_set!(is_index_modified, Status::INDEX_MODIFIED); - is_bit_set!(is_index_deleted, Status::INDEX_DELETED); - is_bit_set!(is_index_renamed, Status::INDEX_RENAMED); - is_bit_set!(is_index_typechange, Status::INDEX_TYPECHANGE); - is_bit_set!(is_wt_new, Status::WT_NEW); - is_bit_set!(is_wt_modified, Status::WT_MODIFIED); - is_bit_set!(is_wt_deleted, Status::WT_DELETED); - is_bit_set!(is_wt_typechange, Status::WT_TYPECHANGE); - is_bit_set!(is_wt_renamed, Status::WT_RENAMED); - is_bit_set!(is_ignored, Status::IGNORED); - is_bit_set!(is_conflicted, Status::CONFLICTED); -} - -bitflags! { - /// Mode options for RepositoryInitOptions - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct RepositoryInitMode: u32 { - /// Use permissions configured by umask - the default - const SHARED_UMASK = raw::GIT_REPOSITORY_INIT_SHARED_UMASK as u32; - /// Use `--shared=group` behavior, chmod'ing the new repo to be - /// group writable and \"g+sx\" for sticky group assignment - const SHARED_GROUP = raw::GIT_REPOSITORY_INIT_SHARED_GROUP as u32; - /// Use `--shared=all` behavior, adding world readability. - const SHARED_ALL = raw::GIT_REPOSITORY_INIT_SHARED_ALL as u32; - } -} - -impl RepositoryInitMode { - is_bit_set!(is_shared_umask, RepositoryInitMode::SHARED_UMASK); - is_bit_set!(is_shared_group, RepositoryInitMode::SHARED_GROUP); - is_bit_set!(is_shared_all, RepositoryInitMode::SHARED_ALL); -} - -/// What type of change is described by a `DiffDelta`? -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum Delta { - /// No changes - Unmodified, - /// Entry does not exist in old version - Added, - /// Entry does not exist in new version - Deleted, - /// Entry content changed between old and new - Modified, - /// Entry was renamed between old and new - Renamed, - /// Entry was copied from another old entry - Copied, - /// Entry is ignored item in workdir - Ignored, - /// Entry is untracked item in workdir - Untracked, - /// Type of entry changed between old and new - Typechange, - /// Entry is unreadable - Unreadable, - /// Entry in the index is conflicted - Conflicted, -} - -/// Valid modes for index and tree entries. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum FileMode { - /// Unreadable - Unreadable, - /// Tree - Tree, - /// Blob - Blob, - /// Group writable blob. Obsolete mode kept for compatibility reasons - BlobGroupWritable, - /// Blob executable - BlobExecutable, - /// Link - Link, - /// Commit - Commit, -} - -impl From<FileMode> for i32 { - fn from(mode: FileMode) -> i32 { - match mode { - FileMode::Unreadable => raw::GIT_FILEMODE_UNREADABLE as i32, - FileMode::Tree => raw::GIT_FILEMODE_TREE as i32, - FileMode::Blob => raw::GIT_FILEMODE_BLOB as i32, - FileMode::BlobGroupWritable => raw::GIT_FILEMODE_BLOB_GROUP_WRITABLE as i32, - FileMode::BlobExecutable => raw::GIT_FILEMODE_BLOB_EXECUTABLE as i32, - FileMode::Link => raw::GIT_FILEMODE_LINK as i32, - FileMode::Commit => raw::GIT_FILEMODE_COMMIT as i32, - } - } -} - -impl From<FileMode> for u32 { - fn from(mode: FileMode) -> u32 { - match mode { - FileMode::Unreadable => raw::GIT_FILEMODE_UNREADABLE as u32, - FileMode::Tree => raw::GIT_FILEMODE_TREE as u32, - FileMode::Blob => raw::GIT_FILEMODE_BLOB as u32, - FileMode::BlobGroupWritable => raw::GIT_FILEMODE_BLOB_GROUP_WRITABLE as u32, - FileMode::BlobExecutable => raw::GIT_FILEMODE_BLOB_EXECUTABLE as u32, - FileMode::Link => raw::GIT_FILEMODE_LINK as u32, - FileMode::Commit => raw::GIT_FILEMODE_COMMIT as u32, - } - } -} - -bitflags! { - /// Return codes for submodule status. - /// - /// A combination of these flags will be returned to describe the status of a - /// submodule. Depending on the "ignore" property of the submodule, some of - /// the flags may never be returned because they indicate changes that are - /// supposed to be ignored. - /// - /// Submodule info is contained in 4 places: the HEAD tree, the index, config - /// files (both .git/config and .gitmodules), and the working directory. Any - /// or all of those places might be missing information about the submodule - /// depending on what state the repo is in. We consider all four places to - /// build the combination of status flags. - /// - /// There are four values that are not really status, but give basic info - /// about what sources of submodule data are available. These will be - /// returned even if ignore is set to "ALL". - /// - /// * IN_HEAD - superproject head contains submodule - /// * IN_INDEX - superproject index contains submodule - /// * IN_CONFIG - superproject gitmodules has submodule - /// * IN_WD - superproject workdir has submodule - /// - /// The following values will be returned so long as ignore is not "ALL". - /// - /// * INDEX_ADDED - in index, not in head - /// * INDEX_DELETED - in head, not in index - /// * INDEX_MODIFIED - index and head don't match - /// * WD_UNINITIALIZED - workdir contains empty directory - /// * WD_ADDED - in workdir, not index - /// * WD_DELETED - in index, not workdir - /// * WD_MODIFIED - index and workdir head don't match - /// - /// The following can only be returned if ignore is "NONE" or "UNTRACKED". - /// - /// * WD_INDEX_MODIFIED - submodule workdir index is dirty - /// * WD_WD_MODIFIED - submodule workdir has modified files - /// - /// Lastly, the following will only be returned for ignore "NONE". - /// - /// * WD_UNTRACKED - workdir contains untracked files - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct SubmoduleStatus: u32 { - #[allow(missing_docs)] - const IN_HEAD = raw::GIT_SUBMODULE_STATUS_IN_HEAD as u32; - #[allow(missing_docs)] - const IN_INDEX = raw::GIT_SUBMODULE_STATUS_IN_INDEX as u32; - #[allow(missing_docs)] - const IN_CONFIG = raw::GIT_SUBMODULE_STATUS_IN_CONFIG as u32; - #[allow(missing_docs)] - const IN_WD = raw::GIT_SUBMODULE_STATUS_IN_WD as u32; - #[allow(missing_docs)] - const INDEX_ADDED = raw::GIT_SUBMODULE_STATUS_INDEX_ADDED as u32; - #[allow(missing_docs)] - const INDEX_DELETED = raw::GIT_SUBMODULE_STATUS_INDEX_DELETED as u32; - #[allow(missing_docs)] - const INDEX_MODIFIED = raw::GIT_SUBMODULE_STATUS_INDEX_MODIFIED as u32; - #[allow(missing_docs)] - const WD_UNINITIALIZED = - raw::GIT_SUBMODULE_STATUS_WD_UNINITIALIZED as u32; - #[allow(missing_docs)] - const WD_ADDED = raw::GIT_SUBMODULE_STATUS_WD_ADDED as u32; - #[allow(missing_docs)] - const WD_DELETED = raw::GIT_SUBMODULE_STATUS_WD_DELETED as u32; - #[allow(missing_docs)] - const WD_MODIFIED = raw::GIT_SUBMODULE_STATUS_WD_MODIFIED as u32; - #[allow(missing_docs)] - const WD_INDEX_MODIFIED = - raw::GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED as u32; - #[allow(missing_docs)] - const WD_WD_MODIFIED = raw::GIT_SUBMODULE_STATUS_WD_WD_MODIFIED as u32; - #[allow(missing_docs)] - const WD_UNTRACKED = raw::GIT_SUBMODULE_STATUS_WD_UNTRACKED as u32; - } -} - -impl SubmoduleStatus { - is_bit_set!(is_in_head, SubmoduleStatus::IN_HEAD); - is_bit_set!(is_in_index, SubmoduleStatus::IN_INDEX); - is_bit_set!(is_in_config, SubmoduleStatus::IN_CONFIG); - is_bit_set!(is_in_wd, SubmoduleStatus::IN_WD); - is_bit_set!(is_index_added, SubmoduleStatus::INDEX_ADDED); - is_bit_set!(is_index_deleted, SubmoduleStatus::INDEX_DELETED); - is_bit_set!(is_index_modified, SubmoduleStatus::INDEX_MODIFIED); - is_bit_set!(is_wd_uninitialized, SubmoduleStatus::WD_UNINITIALIZED); - is_bit_set!(is_wd_added, SubmoduleStatus::WD_ADDED); - is_bit_set!(is_wd_deleted, SubmoduleStatus::WD_DELETED); - is_bit_set!(is_wd_modified, SubmoduleStatus::WD_MODIFIED); - is_bit_set!(is_wd_wd_modified, SubmoduleStatus::WD_WD_MODIFIED); - is_bit_set!(is_wd_untracked, SubmoduleStatus::WD_UNTRACKED); -} - -/// Submodule ignore values -/// -/// These values represent settings for the `submodule.$name.ignore` -/// configuration value which says how deeply to look at the working -/// directory when getting the submodule status. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum SubmoduleIgnore { - /// Use the submodule's configuration - Unspecified, - /// Any change or untracked file is considered dirty - None, - /// Only dirty if tracked files have changed - Untracked, - /// Only dirty if HEAD has moved - Dirty, - /// Never dirty - All, -} - -/// Submodule update values -/// -/// These values represent settings for the `submodule.$name.update` -/// configuration value which says how to handle `git submodule update` -/// for this submodule. The value is usually set in the ".gitmodules" -/// file and copied to ".git/config" when the submodule is initialized. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum SubmoduleUpdate { - /// The default; when a submodule is updated, checkout the new detached - /// HEAD to the submodule directory. - Checkout, - /// Update by rebasing the current checked out branch onto the commit from - /// the superproject. - Rebase, - /// Update by merging the commit in the superproject into the current - /// checkout out branch of the submodule. - Merge, - /// Do not update this submodule even when the commit in the superproject - /// is updated. - None, - /// Not used except as static initializer when we don't want any particular - /// update rule to be specified. - Default, -} - -bitflags! { - /// ... - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct PathspecFlags: u32 { - /// Use the default pathspec matching configuration. - const DEFAULT = raw::GIT_PATHSPEC_DEFAULT as u32; - /// Force matching to ignore case, otherwise matching will use native - /// case sensitivity of the platform filesystem. - const IGNORE_CASE = raw::GIT_PATHSPEC_IGNORE_CASE as u32; - /// Force case sensitive matches, otherwise match will use the native - /// case sensitivity of the platform filesystem. - const USE_CASE = raw::GIT_PATHSPEC_USE_CASE as u32; - /// Disable glob patterns and just use simple string comparison for - /// matching. - const NO_GLOB = raw::GIT_PATHSPEC_NO_GLOB as u32; - /// Means that match functions return the error code `NotFound` if no - /// matches are found. By default no matches is a success. - const NO_MATCH_ERROR = raw::GIT_PATHSPEC_NO_MATCH_ERROR as u32; - /// Means that the list returned should track which patterns matched - /// which files so that at the end of the match we can identify patterns - /// that did not match any files. - const FIND_FAILURES = raw::GIT_PATHSPEC_FIND_FAILURES as u32; - /// Means that the list returned does not need to keep the actual - /// matching filenames. Use this to just test if there were any matches - /// at all or in combination with `PATHSPEC_FAILURES` to validate a - /// pathspec. - const FAILURES_ONLY = raw::GIT_PATHSPEC_FAILURES_ONLY as u32; - } -} - -impl PathspecFlags { - is_bit_set!(is_default, PathspecFlags::DEFAULT); - is_bit_set!(is_ignore_case, PathspecFlags::IGNORE_CASE); - is_bit_set!(is_use_case, PathspecFlags::USE_CASE); - is_bit_set!(is_no_glob, PathspecFlags::NO_GLOB); - is_bit_set!(is_no_match_error, PathspecFlags::NO_MATCH_ERROR); - is_bit_set!(is_find_failures, PathspecFlags::FIND_FAILURES); - is_bit_set!(is_failures_only, PathspecFlags::FAILURES_ONLY); -} - -impl Default for PathspecFlags { - fn default() -> Self { - PathspecFlags::DEFAULT - } -} - -bitflags! { - /// Types of notifications emitted from checkouts. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct CheckoutNotificationType: u32 { - /// Notification about a conflict. - const CONFLICT = raw::GIT_CHECKOUT_NOTIFY_CONFLICT as u32; - /// Notification about a dirty file. - const DIRTY = raw::GIT_CHECKOUT_NOTIFY_DIRTY as u32; - /// Notification about an updated file. - const UPDATED = raw::GIT_CHECKOUT_NOTIFY_UPDATED as u32; - /// Notification about an untracked file. - const UNTRACKED = raw::GIT_CHECKOUT_NOTIFY_UNTRACKED as u32; - /// Notification about an ignored file. - const IGNORED = raw::GIT_CHECKOUT_NOTIFY_IGNORED as u32; - } -} - -impl CheckoutNotificationType { - is_bit_set!(is_conflict, CheckoutNotificationType::CONFLICT); - is_bit_set!(is_dirty, CheckoutNotificationType::DIRTY); - is_bit_set!(is_updated, CheckoutNotificationType::UPDATED); - is_bit_set!(is_untracked, CheckoutNotificationType::UNTRACKED); - is_bit_set!(is_ignored, CheckoutNotificationType::IGNORED); -} - -/// Possible output formats for diff data -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum DiffFormat { - /// full git diff - Patch, - /// just the headers of the patch - PatchHeader, - /// like git diff --raw - Raw, - /// like git diff --name-only - NameOnly, - /// like git diff --name-status - NameStatus, - /// git diff as used by git patch-id - PatchId, -} - -bitflags! { - /// Formatting options for diff stats - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct DiffStatsFormat: raw::git_diff_stats_format_t { - /// Don't generate any stats - const NONE = raw::GIT_DIFF_STATS_NONE; - /// Equivalent of `--stat` in git - const FULL = raw::GIT_DIFF_STATS_FULL; - /// Equivalent of `--shortstat` in git - const SHORT = raw::GIT_DIFF_STATS_SHORT; - /// Equivalent of `--numstat` in git - const NUMBER = raw::GIT_DIFF_STATS_NUMBER; - /// Extended header information such as creations, renames and mode - /// changes, equivalent of `--summary` in git - const INCLUDE_SUMMARY = raw::GIT_DIFF_STATS_INCLUDE_SUMMARY; - } -} - -impl DiffStatsFormat { - is_bit_set!(is_none, DiffStatsFormat::NONE); - is_bit_set!(is_full, DiffStatsFormat::FULL); - is_bit_set!(is_short, DiffStatsFormat::SHORT); - is_bit_set!(is_number, DiffStatsFormat::NUMBER); - is_bit_set!(is_include_summary, DiffStatsFormat::INCLUDE_SUMMARY); -} - -/// Automatic tag following options. -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum AutotagOption { - /// Use the setting from the remote's configuration - Unspecified, - /// Ask the server for tags pointing to objects we're already downloading - Auto, - /// Don't ask for any tags beyond the refspecs - None, - /// Ask for all the tags - All, -} - -/// Configuration for how pruning is done on a fetch -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum FetchPrune { - /// Use the setting from the configuration - Unspecified, - /// Force pruning on - On, - /// Force pruning off - Off, -} - -#[allow(missing_docs)] -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum StashApplyProgress { - /// None - None, - /// Loading the stashed data from the object database - LoadingStash, - /// The stored index is being analyzed - AnalyzeIndex, - /// The modified files are being analyzed - AnalyzeModified, - /// The untracked and ignored files are being analyzed - AnalyzeUntracked, - /// The untracked files are being written to disk - CheckoutUntracked, - /// The modified files are being written to disk - CheckoutModified, - /// The stash was applied successfully - Done, -} - -bitflags! { - #[allow(missing_docs)] - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct StashApplyFlags: u32 { - #[allow(missing_docs)] - const DEFAULT = raw::GIT_STASH_APPLY_DEFAULT as u32; - /// Try to reinstate not only the working tree's changes, - /// but also the index's changes. - const REINSTATE_INDEX = raw::GIT_STASH_APPLY_REINSTATE_INDEX as u32; - } -} - -impl StashApplyFlags { - is_bit_set!(is_default, StashApplyFlags::DEFAULT); - is_bit_set!(is_reinstate_index, StashApplyFlags::REINSTATE_INDEX); -} - -impl Default for StashApplyFlags { - fn default() -> Self { - StashApplyFlags::DEFAULT - } -} - -bitflags! { - #[allow(missing_docs)] - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct StashFlags: u32 { - #[allow(missing_docs)] - const DEFAULT = raw::GIT_STASH_DEFAULT as u32; - /// All changes already added to the index are left intact in - /// the working directory - const KEEP_INDEX = raw::GIT_STASH_KEEP_INDEX as u32; - /// All untracked files are also stashed and then cleaned up - /// from the working directory - const INCLUDE_UNTRACKED = raw::GIT_STASH_INCLUDE_UNTRACKED as u32; - /// All ignored files are also stashed and then cleaned up from - /// the working directory - const INCLUDE_IGNORED = raw::GIT_STASH_INCLUDE_IGNORED as u32; - /// All changes in the index and working directory are left intact - const KEEP_ALL = raw::GIT_STASH_KEEP_ALL as u32; - } -} - -impl StashFlags { - is_bit_set!(is_default, StashFlags::DEFAULT); - is_bit_set!(is_keep_index, StashFlags::KEEP_INDEX); - is_bit_set!(is_include_untracked, StashFlags::INCLUDE_UNTRACKED); - is_bit_set!(is_include_ignored, StashFlags::INCLUDE_IGNORED); -} - -impl Default for StashFlags { - fn default() -> Self { - StashFlags::DEFAULT - } -} - -bitflags! { - #[allow(missing_docs)] - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct AttrCheckFlags: u32 { - /// Check the working directory, then the index. - const FILE_THEN_INDEX = raw::GIT_ATTR_CHECK_FILE_THEN_INDEX as u32; - /// Check the index, then the working directory. - const INDEX_THEN_FILE = raw::GIT_ATTR_CHECK_INDEX_THEN_FILE as u32; - /// Check the index only. - const INDEX_ONLY = raw::GIT_ATTR_CHECK_INDEX_ONLY as u32; - /// Do not use the system gitattributes file. - const NO_SYSTEM = raw::GIT_ATTR_CHECK_NO_SYSTEM as u32; - } -} - -impl Default for AttrCheckFlags { - fn default() -> Self { - AttrCheckFlags::FILE_THEN_INDEX - } -} - -bitflags! { - #[allow(missing_docs)] - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct DiffFlags: u32 { - /// File(s) treated as binary data. - const BINARY = raw::GIT_DIFF_FLAG_BINARY as u32; - /// File(s) treated as text data. - const NOT_BINARY = raw::GIT_DIFF_FLAG_NOT_BINARY as u32; - /// `id` value is known correct. - const VALID_ID = raw::GIT_DIFF_FLAG_VALID_ID as u32; - /// File exists at this side of the delta. - const EXISTS = raw::GIT_DIFF_FLAG_EXISTS as u32; - } -} - -impl DiffFlags { - is_bit_set!(is_binary, DiffFlags::BINARY); - is_bit_set!(is_not_binary, DiffFlags::NOT_BINARY); - is_bit_set!(has_valid_id, DiffFlags::VALID_ID); - is_bit_set!(exists, DiffFlags::EXISTS); -} - -bitflags! { - /// Options for [`Reference::normalize_name`]. - #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct ReferenceFormat: u32 { - /// No particular normalization. - const NORMAL = raw::GIT_REFERENCE_FORMAT_NORMAL as u32; - /// Control whether one-level refname are accepted (i.e., refnames that - /// do not contain multiple `/`-separated components). Those are - /// expected to be written only using uppercase letters and underscore - /// (e.g. `HEAD`, `FETCH_HEAD`). - const ALLOW_ONELEVEL = raw::GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL as u32; - /// Interpret the provided name as a reference pattern for a refspec (as - /// used with remote repositories). If this option is enabled, the name - /// is allowed to contain a single `*` in place of a full pathname - /// components (e.g., `foo/*/bar` but not `foo/bar*`). - const REFSPEC_PATTERN = raw::GIT_REFERENCE_FORMAT_REFSPEC_PATTERN as u32; - /// Interpret the name as part of a refspec in shorthand form so the - /// `ALLOW_ONELEVEL` naming rules aren't enforced and `main` becomes a - /// valid name. - const REFSPEC_SHORTHAND = raw::GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND as u32; - } -} - -impl ReferenceFormat { - is_bit_set!(is_allow_onelevel, ReferenceFormat::ALLOW_ONELEVEL); - is_bit_set!(is_refspec_pattern, ReferenceFormat::REFSPEC_PATTERN); - is_bit_set!(is_refspec_shorthand, ReferenceFormat::REFSPEC_SHORTHAND); -} - -impl Default for ReferenceFormat { - fn default() -> Self { - ReferenceFormat::NORMAL - } -} - -#[cfg(test)] -mod tests { - use super::{FileMode, ObjectType}; - - #[test] - fn convert() { - assert_eq!(ObjectType::Blob.str(), "blob"); - assert_eq!(ObjectType::from_str("blob"), Some(ObjectType::Blob)); - assert!(ObjectType::Blob.is_loose()); - } - - #[test] - fn convert_filemode() { - assert_eq!(i32::from(FileMode::Blob), 0o100644); - assert_eq!(i32::from(FileMode::BlobGroupWritable), 0o100664); - assert_eq!(i32::from(FileMode::BlobExecutable), 0o100755); - assert_eq!(u32::from(FileMode::Blob), 0o100644); - assert_eq!(u32::from(FileMode::BlobGroupWritable), 0o100664); - assert_eq!(u32::from(FileMode::BlobExecutable), 0o100755); - } - - #[test] - fn bitflags_partial_eq() { - use super::{ - AttrCheckFlags, CheckoutNotificationType, CredentialType, DiffFlags, DiffStatsFormat, - IndexAddOption, IndexEntryExtendedFlag, IndexEntryFlag, MergeAnalysis, MergePreference, - OdbLookupFlags, PathspecFlags, ReferenceFormat, RepositoryInitMode, - RepositoryOpenFlags, RevparseMode, Sort, StashApplyFlags, StashFlags, Status, - SubmoduleStatus, - }; - - assert_eq!( - AttrCheckFlags::FILE_THEN_INDEX, - AttrCheckFlags::FILE_THEN_INDEX - ); - assert_eq!( - CheckoutNotificationType::CONFLICT, - CheckoutNotificationType::CONFLICT - ); - assert_eq!( - CredentialType::USER_PASS_PLAINTEXT, - CredentialType::USER_PASS_PLAINTEXT - ); - assert_eq!(DiffFlags::BINARY, DiffFlags::BINARY); - assert_eq!( - DiffStatsFormat::INCLUDE_SUMMARY, - DiffStatsFormat::INCLUDE_SUMMARY - ); - assert_eq!( - IndexAddOption::CHECK_PATHSPEC, - IndexAddOption::CHECK_PATHSPEC - ); - assert_eq!( - IndexEntryExtendedFlag::INTENT_TO_ADD, - IndexEntryExtendedFlag::INTENT_TO_ADD - ); - assert_eq!(IndexEntryFlag::EXTENDED, IndexEntryFlag::EXTENDED); - assert_eq!( - MergeAnalysis::ANALYSIS_FASTFORWARD, - MergeAnalysis::ANALYSIS_FASTFORWARD - ); - assert_eq!( - MergePreference::FASTFORWARD_ONLY, - MergePreference::FASTFORWARD_ONLY - ); - assert_eq!(OdbLookupFlags::NO_REFRESH, OdbLookupFlags::NO_REFRESH); - assert_eq!(PathspecFlags::FAILURES_ONLY, PathspecFlags::FAILURES_ONLY); - assert_eq!( - ReferenceFormat::ALLOW_ONELEVEL, - ReferenceFormat::ALLOW_ONELEVEL - ); - assert_eq!( - RepositoryInitMode::SHARED_ALL, - RepositoryInitMode::SHARED_ALL - ); - assert_eq!(RepositoryOpenFlags::CROSS_FS, RepositoryOpenFlags::CROSS_FS); - assert_eq!(RevparseMode::RANGE, RevparseMode::RANGE); - assert_eq!(Sort::REVERSE, Sort::REVERSE); - assert_eq!( - StashApplyFlags::REINSTATE_INDEX, - StashApplyFlags::REINSTATE_INDEX - ); - assert_eq!(StashFlags::INCLUDE_IGNORED, StashFlags::INCLUDE_IGNORED); - assert_eq!(Status::WT_MODIFIED, Status::WT_MODIFIED); - assert_eq!(SubmoduleStatus::WD_ADDED, SubmoduleStatus::WD_ADDED); - } -} diff --git a/extra/git2/src/mailmap.rs b/extra/git2/src/mailmap.rs deleted file mode 100644 index 096b3227c..000000000 --- a/extra/git2/src/mailmap.rs +++ /dev/null @@ -1,134 +0,0 @@ -use std::ffi::CString; -use std::ptr; - -use crate::util::Binding; -use crate::{raw, Error, Signature}; - -/// A structure to represent a repository's .mailmap file. -/// -/// The representation cannot be written to disk. -pub struct Mailmap { - raw: *mut raw::git_mailmap, -} - -impl Binding for Mailmap { - type Raw = *mut raw::git_mailmap; - - unsafe fn from_raw(ptr: *mut raw::git_mailmap) -> Mailmap { - Mailmap { raw: ptr } - } - - fn raw(&self) -> *mut raw::git_mailmap { - self.raw - } -} - -impl Drop for Mailmap { - fn drop(&mut self) { - unsafe { - raw::git_mailmap_free(self.raw); - } - } -} - -impl Mailmap { - /// Creates an empty, in-memory mailmap object. - pub fn new() -> Result<Mailmap, Error> { - crate::init(); - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_mailmap_new(&mut ret)); - Ok(Binding::from_raw(ret)) - } - } - - /// Creates an in-memory mailmap object representing the given buffer. - pub fn from_buffer(buf: &str) -> Result<Mailmap, Error> { - crate::init(); - let mut ret = ptr::null_mut(); - let len = buf.len(); - let buf = CString::new(buf)?; - unsafe { - try_call!(raw::git_mailmap_from_buffer(&mut ret, buf, len)); - Ok(Binding::from_raw(ret)) - } - } - - /// Adds a new entry to this in-memory mailmap object. - pub fn add_entry( - &mut self, - real_name: Option<&str>, - real_email: Option<&str>, - replace_name: Option<&str>, - replace_email: &str, - ) -> Result<(), Error> { - let real_name = crate::opt_cstr(real_name)?; - let real_email = crate::opt_cstr(real_email)?; - let replace_name = crate::opt_cstr(replace_name)?; - let replace_email = CString::new(replace_email)?; - unsafe { - try_call!(raw::git_mailmap_add_entry( - self.raw, - real_name, - real_email, - replace_name, - replace_email - )); - Ok(()) - } - } - - /// Resolves a signature to its real name and email address. - pub fn resolve_signature(&self, sig: &Signature<'_>) -> Result<Signature<'static>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_mailmap_resolve_signature( - &mut ret, - &*self.raw, - sig.raw() - )); - Ok(Binding::from_raw(ret)) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn smoke() { - let sig_name = "name"; - let sig_email = "email"; - let sig = t!(Signature::now(sig_name, sig_email)); - - let mut mm = t!(Mailmap::new()); - - let mailmapped_sig = t!(mm.resolve_signature(&sig)); - assert_eq!(mailmapped_sig.name(), Some(sig_name)); - assert_eq!(mailmapped_sig.email(), Some(sig_email)); - - t!(mm.add_entry(None, None, None, sig_email)); - t!(mm.add_entry( - Some("real name"), - Some("real@email"), - Some(sig_name), - sig_email, - )); - - let mailmapped_sig = t!(mm.resolve_signature(&sig)); - assert_eq!(mailmapped_sig.name(), Some("real name")); - assert_eq!(mailmapped_sig.email(), Some("real@email")); - } - - #[test] - fn from_buffer() { - let buf = "<prøper@emæil> <email>"; - let mm = t!(Mailmap::from_buffer(&buf)); - - let sig = t!(Signature::now("name", "email")); - let mailmapped_sig = t!(mm.resolve_signature(&sig)); - assert_eq!(mailmapped_sig.name(), Some("name")); - assert_eq!(mailmapped_sig.email(), Some("prøper@emæil")); - } -} diff --git a/extra/git2/src/mempack.rs b/extra/git2/src/mempack.rs deleted file mode 100644 index a78070791..000000000 --- a/extra/git2/src/mempack.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::marker; - -use crate::util::Binding; -use crate::{raw, Buf, Error, Odb, Repository}; - -/// A structure to represent a mempack backend for the object database. The -/// Mempack is bound to the Odb that it was created from, and cannot outlive -/// that Odb. -pub struct Mempack<'odb> { - raw: *mut raw::git_odb_backend, - _marker: marker::PhantomData<&'odb Odb<'odb>>, -} - -impl<'odb> Binding for Mempack<'odb> { - type Raw = *mut raw::git_odb_backend; - - unsafe fn from_raw(raw: *mut raw::git_odb_backend) -> Mempack<'odb> { - Mempack { - raw, - _marker: marker::PhantomData, - } - } - - fn raw(&self) -> *mut raw::git_odb_backend { - self.raw - } -} - -// We don't need to implement `Drop` for Mempack because it is owned by the -// odb to which it is attached, and that will take care of freeing the mempack -// and associated memory. - -impl<'odb> Mempack<'odb> { - /// Dumps the contents of the mempack into the provided buffer. - pub fn dump(&self, repo: &Repository, buf: &mut Buf) -> Result<(), Error> { - unsafe { - try_call!(raw::git_mempack_dump(buf.raw(), repo.raw(), self.raw)); - } - Ok(()) - } - - /// Clears all data in the mempack. - pub fn reset(&self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_mempack_reset(self.raw)); - } - Ok(()) - } -} diff --git a/extra/git2/src/merge.rs b/extra/git2/src/merge.rs deleted file mode 100644 index 6bd30c10d..000000000 --- a/extra/git2/src/merge.rs +++ /dev/null @@ -1,194 +0,0 @@ -use libc::c_uint; -use std::marker; -use std::mem; -use std::str; - -use crate::call::Convert; -use crate::util::Binding; -use crate::{raw, Commit, FileFavor, Oid}; - -/// A structure to represent an annotated commit, the input to merge and rebase. -/// -/// An annotated commit contains information about how it was looked up, which -/// may be useful for functions like merge or rebase to provide context to the -/// operation. -pub struct AnnotatedCommit<'repo> { - raw: *mut raw::git_annotated_commit, - _marker: marker::PhantomData<Commit<'repo>>, -} - -/// Options to specify when merging. -pub struct MergeOptions { - raw: raw::git_merge_options, -} - -impl<'repo> AnnotatedCommit<'repo> { - /// Gets the commit ID that the given git_annotated_commit refers to - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_annotated_commit_id(self.raw)) } - } - - /// Get the refname that the given git_annotated_commit refers to - /// - /// Returns None if it is not valid utf8 - pub fn refname(&self) -> Option<&str> { - str::from_utf8(self.refname_bytes()).ok() - } - - /// Get the refname that the given git_annotated_commit refers to. - pub fn refname_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_annotated_commit_ref(&*self.raw)).unwrap() } - } -} - -impl Default for MergeOptions { - fn default() -> Self { - Self::new() - } -} - -impl MergeOptions { - /// Creates a default set of merge options. - pub fn new() -> MergeOptions { - let mut opts = MergeOptions { - raw: unsafe { mem::zeroed() }, - }; - assert_eq!(unsafe { raw::git_merge_init_options(&mut opts.raw, 1) }, 0); - opts - } - - fn flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions { - if val { - self.raw.flags |= opt; - } else { - self.raw.flags &= !opt; - } - self - } - - /// Detect file renames - pub fn find_renames(&mut self, find: bool) -> &mut MergeOptions { - self.flag(raw::GIT_MERGE_FIND_RENAMES as u32, find) - } - - /// If a conflict occurs, exit immediately instead of attempting to continue - /// resolving conflicts - pub fn fail_on_conflict(&mut self, fail: bool) -> &mut MergeOptions { - self.flag(raw::GIT_MERGE_FAIL_ON_CONFLICT as u32, fail) - } - - /// Do not write the REUC extension on the generated index - pub fn skip_reuc(&mut self, skip: bool) -> &mut MergeOptions { - self.flag(raw::GIT_MERGE_FAIL_ON_CONFLICT as u32, skip) - } - - /// If the commits being merged have multiple merge bases, do not build a - /// recursive merge base (by merging the multiple merge bases), instead - /// simply use the first base. - pub fn no_recursive(&mut self, disable: bool) -> &mut MergeOptions { - self.flag(raw::GIT_MERGE_NO_RECURSIVE as u32, disable) - } - - /// Similarity to consider a file renamed (default 50) - pub fn rename_threshold(&mut self, thresh: u32) -> &mut MergeOptions { - self.raw.rename_threshold = thresh; - self - } - - /// Maximum similarity sources to examine for renames (default 200). - /// If the number of rename candidates (add / delete pairs) is greater - /// than this value, inexact rename detection is aborted. This setting - /// overrides the `merge.renameLimit` configuration value. - pub fn target_limit(&mut self, limit: u32) -> &mut MergeOptions { - self.raw.target_limit = limit as c_uint; - self - } - - /// Maximum number of times to merge common ancestors to build a - /// virtual merge base when faced with criss-cross merges. When - /// this limit is reached, the next ancestor will simply be used - /// instead of attempting to merge it. The default is unlimited. - pub fn recursion_limit(&mut self, limit: u32) -> &mut MergeOptions { - self.raw.recursion_limit = limit as c_uint; - self - } - - /// Specify a side to favor for resolving conflicts - pub fn file_favor(&mut self, favor: FileFavor) -> &mut MergeOptions { - self.raw.file_favor = favor.convert(); - self - } - - fn file_flag(&mut self, opt: u32, val: bool) -> &mut MergeOptions { - if val { - self.raw.file_flags |= opt; - } else { - self.raw.file_flags &= !opt; - } - self - } - - /// Create standard conflicted merge files - pub fn standard_style(&mut self, standard: bool) -> &mut MergeOptions { - self.file_flag(raw::GIT_MERGE_FILE_STYLE_MERGE as u32, standard) - } - - /// Create diff3-style file - pub fn diff3_style(&mut self, diff3: bool) -> &mut MergeOptions { - self.file_flag(raw::GIT_MERGE_FILE_STYLE_DIFF3 as u32, diff3) - } - - /// Condense non-alphanumeric regions for simplified diff file - pub fn simplify_alnum(&mut self, simplify: bool) -> &mut MergeOptions { - self.file_flag(raw::GIT_MERGE_FILE_SIMPLIFY_ALNUM as u32, simplify) - } - - /// Ignore all whitespace - pub fn ignore_whitespace(&mut self, ignore: bool) -> &mut MergeOptions { - self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE as u32, ignore) - } - - /// Ignore changes in amount of whitespace - pub fn ignore_whitespace_change(&mut self, ignore: bool) -> &mut MergeOptions { - self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE as u32, ignore) - } - - /// Ignore whitespace at end of line - pub fn ignore_whitespace_eol(&mut self, ignore: bool) -> &mut MergeOptions { - self.file_flag(raw::GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL as u32, ignore) - } - - /// Use the "patience diff" algorithm - pub fn patience(&mut self, patience: bool) -> &mut MergeOptions { - self.file_flag(raw::GIT_MERGE_FILE_DIFF_PATIENCE as u32, patience) - } - - /// Take extra time to find minimal diff - pub fn minimal(&mut self, minimal: bool) -> &mut MergeOptions { - self.file_flag(raw::GIT_MERGE_FILE_DIFF_MINIMAL as u32, minimal) - } - - /// Acquire a pointer to the underlying raw options. - pub unsafe fn raw(&self) -> *const raw::git_merge_options { - &self.raw as *const _ - } -} - -impl<'repo> Binding for AnnotatedCommit<'repo> { - type Raw = *mut raw::git_annotated_commit; - unsafe fn from_raw(raw: *mut raw::git_annotated_commit) -> AnnotatedCommit<'repo> { - AnnotatedCommit { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_annotated_commit { - self.raw - } -} - -impl<'repo> Drop for AnnotatedCommit<'repo> { - fn drop(&mut self) { - unsafe { raw::git_annotated_commit_free(self.raw) } - } -} diff --git a/extra/git2/src/message.rs b/extra/git2/src/message.rs deleted file mode 100644 index a7041da3a..000000000 --- a/extra/git2/src/message.rs +++ /dev/null @@ -1,349 +0,0 @@ -use core::ops::Range; -use std::ffi::CStr; -use std::ffi::CString; -use std::iter::FusedIterator; -use std::ptr; - -use libc::{c_char, c_int}; - -use crate::util::Binding; -use crate::{raw, Buf, Error, IntoCString}; - -/// Clean up a message, removing extraneous whitespace, and ensure that the -/// message ends with a newline. If `comment_char` is `Some`, also remove comment -/// lines starting with that character. -pub fn message_prettify<T: IntoCString>( - message: T, - comment_char: Option<u8>, -) -> Result<String, Error> { - _message_prettify(message.into_c_string()?, comment_char) -} - -fn _message_prettify(message: CString, comment_char: Option<u8>) -> Result<String, Error> { - let ret = Buf::new(); - unsafe { - try_call!(raw::git_message_prettify( - ret.raw(), - message, - comment_char.is_some() as c_int, - comment_char.unwrap_or(0) as c_char - )); - } - Ok(ret.as_str().unwrap().to_string()) -} - -/// The default comment character for `message_prettify` ('#') -pub const DEFAULT_COMMENT_CHAR: Option<u8> = Some(b'#'); - -/// Get the trailers for the given message. -/// -/// Use this function when you are dealing with a UTF-8-encoded message. -pub fn message_trailers_strs(message: &str) -> Result<MessageTrailersStrs, Error> { - _message_trailers(message.into_c_string()?).map(|res| MessageTrailersStrs(res)) -} - -/// Get the trailers for the given message. -/// -/// Use this function when the message might not be UTF-8-encoded, -/// or if you want to handle the returned trailer key–value pairs -/// as bytes. -pub fn message_trailers_bytes<S: IntoCString>(message: S) -> Result<MessageTrailersBytes, Error> { - _message_trailers(message.into_c_string()?).map(|res| MessageTrailersBytes(res)) -} - -fn _message_trailers(message: CString) -> Result<MessageTrailers, Error> { - let ret = MessageTrailers::new(); - unsafe { - try_call!(raw::git_message_trailers(ret.raw(), message)); - } - Ok(ret) -} - -/// Collection of UTF-8-encoded trailers. -/// -/// Use `iter()` to get access to the values. -pub struct MessageTrailersStrs(MessageTrailers); - -impl MessageTrailersStrs { - /// Create a borrowed iterator. - pub fn iter(&self) -> MessageTrailersStrsIterator<'_> { - MessageTrailersStrsIterator(self.0.iter()) - } - /// The number of trailer key–value pairs. - pub fn len(&self) -> usize { - self.0.len() - } - /// Convert to the “bytes” variant. - pub fn to_bytes(self) -> MessageTrailersBytes { - MessageTrailersBytes(self.0) - } -} - -/// Collection of unencoded (bytes) trailers. -/// -/// Use `iter()` to get access to the values. -pub struct MessageTrailersBytes(MessageTrailers); - -impl MessageTrailersBytes { - /// Create a borrowed iterator. - pub fn iter(&self) -> MessageTrailersBytesIterator<'_> { - MessageTrailersBytesIterator(self.0.iter()) - } - /// The number of trailer key–value pairs. - pub fn len(&self) -> usize { - self.0.len() - } -} - -struct MessageTrailers { - raw: raw::git_message_trailer_array, -} - -impl MessageTrailers { - fn new() -> MessageTrailers { - crate::init(); - unsafe { - Binding::from_raw(&mut raw::git_message_trailer_array { - trailers: ptr::null_mut(), - count: 0, - _trailer_block: ptr::null_mut(), - } as *mut _) - } - } - fn iter(&self) -> MessageTrailersIterator<'_> { - MessageTrailersIterator { - trailers: self, - range: Range { - start: 0, - end: self.raw.count, - }, - } - } - fn len(&self) -> usize { - self.raw.count - } -} - -impl Drop for MessageTrailers { - fn drop(&mut self) { - unsafe { - raw::git_message_trailer_array_free(&mut self.raw); - } - } -} - -impl Binding for MessageTrailers { - type Raw = *mut raw::git_message_trailer_array; - unsafe fn from_raw(raw: *mut raw::git_message_trailer_array) -> MessageTrailers { - MessageTrailers { raw: *raw } - } - fn raw(&self) -> *mut raw::git_message_trailer_array { - &self.raw as *const _ as *mut _ - } -} - -struct MessageTrailersIterator<'a> { - trailers: &'a MessageTrailers, - range: Range<usize>, -} - -fn to_raw_tuple(trailers: &MessageTrailers, index: usize) -> (*const c_char, *const c_char) { - unsafe { - let addr = trailers.raw.trailers.wrapping_add(index); - ((*addr).key, (*addr).value) - } -} - -/// Borrowed iterator over the UTF-8-encoded trailers. -pub struct MessageTrailersStrsIterator<'a>(MessageTrailersIterator<'a>); - -impl<'pair> Iterator for MessageTrailersStrsIterator<'pair> { - type Item = (&'pair str, &'pair str); - - fn next(&mut self) -> Option<Self::Item> { - self.0 - .range - .next() - .map(|index| to_str_tuple(&self.0.trailers, index)) - } - - fn size_hint(&self) -> (usize, Option<usize>) { - self.0.range.size_hint() - } -} - -impl FusedIterator for MessageTrailersStrsIterator<'_> {} - -impl ExactSizeIterator for MessageTrailersStrsIterator<'_> { - fn len(&self) -> usize { - self.0.range.len() - } -} - -impl DoubleEndedIterator for MessageTrailersStrsIterator<'_> { - fn next_back(&mut self) -> Option<Self::Item> { - self.0 - .range - .next_back() - .map(|index| to_str_tuple(&self.0.trailers, index)) - } -} - -fn to_str_tuple(trailers: &MessageTrailers, index: usize) -> (&str, &str) { - unsafe { - let (rkey, rvalue) = to_raw_tuple(&trailers, index); - let key = CStr::from_ptr(rkey).to_str().unwrap(); - let value = CStr::from_ptr(rvalue).to_str().unwrap(); - (key, value) - } -} - -/// Borrowed iterator over the raw (bytes) trailers. -pub struct MessageTrailersBytesIterator<'a>(MessageTrailersIterator<'a>); - -impl<'pair> Iterator for MessageTrailersBytesIterator<'pair> { - type Item = (&'pair [u8], &'pair [u8]); - - fn next(&mut self) -> Option<Self::Item> { - self.0 - .range - .next() - .map(|index| to_bytes_tuple(&self.0.trailers, index)) - } - - fn size_hint(&self) -> (usize, Option<usize>) { - self.0.range.size_hint() - } -} - -impl FusedIterator for MessageTrailersBytesIterator<'_> {} - -impl ExactSizeIterator for MessageTrailersBytesIterator<'_> { - fn len(&self) -> usize { - self.0.range.len() - } -} - -impl DoubleEndedIterator for MessageTrailersBytesIterator<'_> { - fn next_back(&mut self) -> Option<Self::Item> { - self.0 - .range - .next_back() - .map(|index| to_bytes_tuple(&self.0.trailers, index)) - } -} - -fn to_bytes_tuple(trailers: &MessageTrailers, index: usize) -> (&[u8], &[u8]) { - unsafe { - let (rkey, rvalue) = to_raw_tuple(&trailers, index); - let key = CStr::from_ptr(rkey).to_bytes(); - let value = CStr::from_ptr(rvalue).to_bytes(); - (key, value) - } -} - -#[cfg(test)] -mod tests { - - #[test] - fn prettify() { - use crate::{message_prettify, DEFAULT_COMMENT_CHAR}; - - // This does not attempt to duplicate the extensive tests for - // git_message_prettify in libgit2, just a few representative values to - // make sure the interface works as expected. - assert_eq!(message_prettify("1\n\n\n2", None).unwrap(), "1\n\n2\n"); - assert_eq!( - message_prettify("1\n\n\n2\n\n\n3", None).unwrap(), - "1\n\n2\n\n3\n" - ); - assert_eq!( - message_prettify("1\n# comment\n# more", None).unwrap(), - "1\n# comment\n# more\n" - ); - assert_eq!( - message_prettify("1\n# comment\n# more", DEFAULT_COMMENT_CHAR).unwrap(), - "1\n" - ); - assert_eq!( - message_prettify("1\n; comment\n; more", Some(';' as u8)).unwrap(), - "1\n" - ); - } - - #[test] - fn trailers() { - use crate::{message_trailers_bytes, message_trailers_strs, MessageTrailersStrs}; - use std::collections::HashMap; - - // no trailers - let message1 = " -WHAT ARE WE HERE FOR - -What are we here for? - -Just to be eaten? -"; - let expected: HashMap<&str, &str> = HashMap::new(); - assert_eq!(expected, to_map(&message_trailers_strs(message1).unwrap())); - - // standard PSA - let message2 = " -Attention all - -We are out of tomatoes. - -Spoken-by: Major Turnips -Transcribed-by: Seargant Persimmons -Signed-off-by: Colonel Kale -"; - let expected: HashMap<&str, &str> = vec![ - ("Spoken-by", "Major Turnips"), - ("Transcribed-by", "Seargant Persimmons"), - ("Signed-off-by", "Colonel Kale"), - ] - .into_iter() - .collect(); - assert_eq!(expected, to_map(&message_trailers_strs(message2).unwrap())); - - // ignore everything after `---` - let message3 = " -The fate of Seargant Green-Peppers - -Seargant Green-Peppers was killed by Caterpillar Battalion 44. - -Signed-off-by: Colonel Kale ---- -I never liked that guy, anyway. - -Opined-by: Corporal Garlic -"; - let expected: HashMap<&str, &str> = vec![("Signed-off-by", "Colonel Kale")] - .into_iter() - .collect(); - assert_eq!(expected, to_map(&message_trailers_strs(message3).unwrap())); - - // Raw bytes message; not valid UTF-8 - // Source: https://stackoverflow.com/a/3886015/1725151 - let message4 = b" -Be honest guys - -Am I a malformed brussels sprout? - -Signed-off-by: Lieutenant \xe2\x28\xa1prout -"; - - let trailer = message_trailers_bytes(&message4[..]).unwrap(); - let expected = (&b"Signed-off-by"[..], &b"Lieutenant \xe2\x28\xa1prout"[..]); - let actual = trailer.iter().next().unwrap(); - assert_eq!(expected, actual); - - fn to_map(trailers: &MessageTrailersStrs) -> HashMap<&str, &str> { - let mut map = HashMap::with_capacity(trailers.len()); - for (key, value) in trailers.iter() { - map.insert(key, value); - } - map - } - } -} diff --git a/extra/git2/src/note.rs b/extra/git2/src/note.rs deleted file mode 100644 index 50e5800fe..000000000 --- a/extra/git2/src/note.rs +++ /dev/null @@ -1,147 +0,0 @@ -use std::marker; -use std::str; - -use crate::util::Binding; -use crate::{raw, signature, Error, Oid, Repository, Signature}; - -/// A structure representing a [note][note] in git. -/// -/// [note]: http://alblue.bandlem.com/2011/11/git-tip-of-week-git-notes.html -pub struct Note<'repo> { - raw: *mut raw::git_note, - - // Hmm, the current libgit2 version does not have this inside of it, but - // perhaps it's a good idea to keep it around? Can always remove it later I - // suppose... - _marker: marker::PhantomData<&'repo Repository>, -} - -/// An iterator over all of the notes within a repository. -pub struct Notes<'repo> { - raw: *mut raw::git_note_iterator, - _marker: marker::PhantomData<&'repo Repository>, -} - -impl<'repo> Note<'repo> { - /// Get the note author - pub fn author(&self) -> Signature<'_> { - unsafe { signature::from_raw_const(self, raw::git_note_author(&*self.raw)) } - } - - /// Get the note committer - pub fn committer(&self) -> Signature<'_> { - unsafe { signature::from_raw_const(self, raw::git_note_committer(&*self.raw)) } - } - - /// Get the note message, in bytes. - pub fn message_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_note_message(&*self.raw)).unwrap() } - } - - /// Get the note message as a string, returning `None` if it is not UTF-8. - pub fn message(&self) -> Option<&str> { - str::from_utf8(self.message_bytes()).ok() - } - - /// Get the note object's id - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_note_id(&*self.raw)) } - } -} - -impl<'repo> Binding for Note<'repo> { - type Raw = *mut raw::git_note; - unsafe fn from_raw(raw: *mut raw::git_note) -> Note<'repo> { - Note { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_note { - self.raw - } -} - -impl<'repo> std::fmt::Debug for Note<'repo> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_struct("Note").field("id", &self.id()).finish() - } -} - -impl<'repo> Drop for Note<'repo> { - fn drop(&mut self) { - unsafe { - raw::git_note_free(self.raw); - } - } -} - -impl<'repo> Binding for Notes<'repo> { - type Raw = *mut raw::git_note_iterator; - unsafe fn from_raw(raw: *mut raw::git_note_iterator) -> Notes<'repo> { - Notes { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_note_iterator { - self.raw - } -} - -impl<'repo> Iterator for Notes<'repo> { - type Item = Result<(Oid, Oid), Error>; - fn next(&mut self) -> Option<Result<(Oid, Oid), Error>> { - let mut note_id = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - let mut annotated_id = note_id; - unsafe { - try_call_iter!(raw::git_note_next( - &mut note_id, - &mut annotated_id, - self.raw - )); - Some(Ok(( - Binding::from_raw(¬e_id as *const _), - Binding::from_raw(&annotated_id as *const _), - ))) - } - } -} - -impl<'repo> Drop for Notes<'repo> { - fn drop(&mut self) { - unsafe { - raw::git_note_iterator_free(self.raw); - } - } -} - -#[cfg(test)] -mod tests { - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - assert!(repo.notes(None).is_err()); - - let sig = repo.signature().unwrap(); - let head = repo.head().unwrap().target().unwrap(); - let note = repo.note(&sig, &sig, None, head, "foo", false).unwrap(); - assert_eq!(repo.notes(None).unwrap().count(), 1); - - let note_obj = repo.find_note(None, head).unwrap(); - assert_eq!(note_obj.id(), note); - assert_eq!(note_obj.message(), Some("foo")); - - let (a, b) = repo.notes(None).unwrap().next().unwrap().unwrap(); - assert_eq!(a, note); - assert_eq!(b, head); - - assert_eq!(repo.note_default_ref().unwrap(), "refs/notes/commits"); - - assert_eq!(sig.name(), note_obj.author().name()); - assert_eq!(sig.name(), note_obj.committer().name()); - assert!(sig.when() == note_obj.committer().when()); - } -} diff --git a/extra/git2/src/object.rs b/extra/git2/src/object.rs deleted file mode 100644 index fcae0066c..000000000 --- a/extra/git2/src/object.rs +++ /dev/null @@ -1,248 +0,0 @@ -use std::marker; -use std::mem; -use std::ptr; - -use crate::util::Binding; -use crate::{raw, Blob, Buf, Commit, Error, ObjectType, Oid, Repository, Tag, Tree}; -use crate::{Describe, DescribeOptions}; - -/// A structure to represent a git [object][1] -/// -/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects -pub struct Object<'repo> { - raw: *mut raw::git_object, - _marker: marker::PhantomData<&'repo Repository>, -} - -impl<'repo> Object<'repo> { - /// Get the id (SHA1) of a repository object - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_object_id(&*self.raw)) } - } - - /// Get the object type of an object. - /// - /// If the type is unknown, then `None` is returned. - pub fn kind(&self) -> Option<ObjectType> { - ObjectType::from_raw(unsafe { raw::git_object_type(&*self.raw) }) - } - - /// Recursively peel an object until an object of the specified type is met. - /// - /// If you pass `Any` as the target type, then the object will be - /// peeled until the type changes (e.g. a tag will be chased until the - /// referenced object is no longer a tag). - pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> { - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_object_peel(&mut raw, &*self.raw(), kind)); - Ok(Binding::from_raw(raw)) - } - } - - /// Recursively peel an object until a blob is found - pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> { - self.peel(ObjectType::Blob) - .map(|o| o.cast_or_panic(ObjectType::Blob)) - } - - /// Recursively peel an object until a commit is found - pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> { - self.peel(ObjectType::Commit) - .map(|o| o.cast_or_panic(ObjectType::Commit)) - } - - /// Recursively peel an object until a tag is found - pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> { - self.peel(ObjectType::Tag) - .map(|o| o.cast_or_panic(ObjectType::Tag)) - } - - /// Recursively peel an object until a tree is found - pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> { - self.peel(ObjectType::Tree) - .map(|o| o.cast_or_panic(ObjectType::Tree)) - } - - /// Get a short abbreviated OID string for the object - /// - /// This starts at the "core.abbrev" length (default 7 characters) and - /// iteratively extends to a longer string if that length is ambiguous. The - /// result will be unambiguous (at least until new objects are added to the - /// repository). - pub fn short_id(&self) -> Result<Buf, Error> { - unsafe { - let buf = Buf::new(); - try_call!(raw::git_object_short_id(buf.raw(), &*self.raw())); - Ok(buf) - } - } - - /// Attempt to view this object as a commit. - /// - /// Returns `None` if the object is not actually a commit. - pub fn as_commit(&self) -> Option<&Commit<'repo>> { - self.cast(ObjectType::Commit) - } - - /// Attempt to consume this object and return a commit. - /// - /// Returns `Err(self)` if this object is not actually a commit. - pub fn into_commit(self) -> Result<Commit<'repo>, Object<'repo>> { - self.cast_into(ObjectType::Commit) - } - - /// Attempt to view this object as a tag. - /// - /// Returns `None` if the object is not actually a tag. - pub fn as_tag(&self) -> Option<&Tag<'repo>> { - self.cast(ObjectType::Tag) - } - - /// Attempt to consume this object and return a tag. - /// - /// Returns `Err(self)` if this object is not actually a tag. - pub fn into_tag(self) -> Result<Tag<'repo>, Object<'repo>> { - self.cast_into(ObjectType::Tag) - } - - /// Attempt to view this object as a tree. - /// - /// Returns `None` if the object is not actually a tree. - pub fn as_tree(&self) -> Option<&Tree<'repo>> { - self.cast(ObjectType::Tree) - } - - /// Attempt to consume this object and return a tree. - /// - /// Returns `Err(self)` if this object is not actually a tree. - pub fn into_tree(self) -> Result<Tree<'repo>, Object<'repo>> { - self.cast_into(ObjectType::Tree) - } - - /// Attempt to view this object as a blob. - /// - /// Returns `None` if the object is not actually a blob. - pub fn as_blob(&self) -> Option<&Blob<'repo>> { - self.cast(ObjectType::Blob) - } - - /// Attempt to consume this object and return a blob. - /// - /// Returns `Err(self)` if this object is not actually a blob. - pub fn into_blob(self) -> Result<Blob<'repo>, Object<'repo>> { - self.cast_into(ObjectType::Blob) - } - - /// Describes a commit - /// - /// Performs a describe operation on this commitish object. - pub fn describe(&self, opts: &DescribeOptions) -> Result<Describe<'_>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_describe_commit(&mut ret, self.raw, opts.raw())); - Ok(Binding::from_raw(ret)) - } - } - - fn cast<T>(&self, kind: ObjectType) -> Option<&T> { - assert_eq!(mem::size_of::<Object<'_>>(), mem::size_of::<T>()); - if self.kind() == Some(kind) { - unsafe { Some(&*(self as *const _ as *const T)) } - } else { - None - } - } - - fn cast_into<T>(self, kind: ObjectType) -> Result<T, Object<'repo>> { - assert_eq!(mem::size_of_val(&self), mem::size_of::<T>()); - if self.kind() == Some(kind) { - Ok(unsafe { - let other = ptr::read(&self as *const _ as *const T); - mem::forget(self); - other - }) - } else { - Err(self) - } - } -} - -/// This trait is useful to export cast_or_panic into crate but not outside -pub trait CastOrPanic { - fn cast_or_panic<T>(self, kind: ObjectType) -> T; -} - -impl<'repo> CastOrPanic for Object<'repo> { - fn cast_or_panic<T>(self, kind: ObjectType) -> T { - assert_eq!(mem::size_of_val(&self), mem::size_of::<T>()); - if self.kind() == Some(kind) { - unsafe { - let other = ptr::read(&self as *const _ as *const T); - mem::forget(self); - other - } - } else { - let buf; - let akind = match self.kind() { - Some(akind) => akind.str(), - None => { - buf = format!("unknown ({})", unsafe { raw::git_object_type(&*self.raw) }); - &buf - } - }; - panic!( - "Expected object {} to be {} but it is {}", - self.id(), - kind.str(), - akind - ) - } - } -} - -impl<'repo> Clone for Object<'repo> { - fn clone(&self) -> Object<'repo> { - let mut raw = ptr::null_mut(); - unsafe { - let rc = raw::git_object_dup(&mut raw, self.raw); - assert_eq!(rc, 0); - Binding::from_raw(raw) - } - } -} - -impl<'repo> std::fmt::Debug for Object<'repo> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - let mut ds = f.debug_struct("Object"); - match self.kind() { - Some(kind) => ds.field("kind", &kind), - None => ds.field( - "kind", - &format!("Unknow ({})", unsafe { raw::git_object_type(&*self.raw) }), - ), - }; - ds.field("id", &self.id()); - ds.finish() - } -} - -impl<'repo> Binding for Object<'repo> { - type Raw = *mut raw::git_object; - - unsafe fn from_raw(raw: *mut raw::git_object) -> Object<'repo> { - Object { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_object { - self.raw - } -} - -impl<'repo> Drop for Object<'repo> { - fn drop(&mut self) { - unsafe { raw::git_object_free(self.raw) } - } -} diff --git a/extra/git2/src/odb.rs b/extra/git2/src/odb.rs deleted file mode 100644 index 7f6da5eb3..000000000 --- a/extra/git2/src/odb.rs +++ /dev/null @@ -1,729 +0,0 @@ -use std::io; -use std::marker; -use std::ptr; -use std::slice; - -use std::ffi::CString; - -use libc::{c_char, c_int, c_uint, c_void, size_t}; - -use crate::panic; -use crate::util::Binding; -use crate::{ - raw, Error, IndexerProgress, Mempack, Object, ObjectType, OdbLookupFlags, Oid, Progress, -}; - -/// A structure to represent a git object database -pub struct Odb<'repo> { - raw: *mut raw::git_odb, - _marker: marker::PhantomData<Object<'repo>>, -} - -// `git_odb` uses locking and atomics internally. -unsafe impl<'repo> Send for Odb<'repo> {} -unsafe impl<'repo> Sync for Odb<'repo> {} - -impl<'repo> Binding for Odb<'repo> { - type Raw = *mut raw::git_odb; - - unsafe fn from_raw(raw: *mut raw::git_odb) -> Odb<'repo> { - Odb { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_odb { - self.raw - } -} - -impl<'repo> Drop for Odb<'repo> { - fn drop(&mut self) { - unsafe { raw::git_odb_free(self.raw) } - } -} - -impl<'repo> Odb<'repo> { - /// Creates an object database without any backends. - pub fn new<'a>() -> Result<Odb<'a>, Error> { - crate::init(); - unsafe { - let mut out = ptr::null_mut(); - try_call!(raw::git_odb_new(&mut out)); - Ok(Odb::from_raw(out)) - } - } - - /// Create object database reading stream. - /// - /// Note that most backends do not support streaming reads because they store their objects as compressed/delta'ed blobs. - /// If the backend does not support streaming reads, use the `read` method instead. - pub fn reader(&self, oid: Oid) -> Result<(OdbReader<'_>, usize, ObjectType), Error> { - let mut out = ptr::null_mut(); - let mut size = 0usize; - let mut otype: raw::git_object_t = ObjectType::Any.raw(); - unsafe { - try_call!(raw::git_odb_open_rstream( - &mut out, - &mut size, - &mut otype, - self.raw, - oid.raw() - )); - Ok(( - OdbReader::from_raw(out), - size, - ObjectType::from_raw(otype).unwrap(), - )) - } - } - - /// Create object database writing stream. - /// - /// The type and final length of the object must be specified when opening the stream. - /// If the backend does not support streaming writes, use the `write` method instead. - pub fn writer(&self, size: usize, obj_type: ObjectType) -> Result<OdbWriter<'_>, Error> { - let mut out = ptr::null_mut(); - unsafe { - try_call!(raw::git_odb_open_wstream( - &mut out, - self.raw, - size as raw::git_object_size_t, - obj_type.raw() - )); - Ok(OdbWriter::from_raw(out)) - } - } - - /// Iterate over all objects in the object database.s - pub fn foreach<C>(&self, mut callback: C) -> Result<(), Error> - where - C: FnMut(&Oid) -> bool, - { - unsafe { - let mut data = ForeachCbData { - callback: &mut callback, - }; - let cb: raw::git_odb_foreach_cb = Some(foreach_cb); - try_call!(raw::git_odb_foreach( - self.raw(), - cb, - &mut data as *mut _ as *mut _ - )); - Ok(()) - } - } - - /// Read an object from the database. - pub fn read(&self, oid: Oid) -> Result<OdbObject<'_>, Error> { - let mut out = ptr::null_mut(); - unsafe { - try_call!(raw::git_odb_read(&mut out, self.raw, oid.raw())); - Ok(OdbObject::from_raw(out)) - } - } - - /// Reads the header of an object from the database - /// without reading the full content. - pub fn read_header(&self, oid: Oid) -> Result<(usize, ObjectType), Error> { - let mut size: usize = 0; - let mut kind_id: i32 = ObjectType::Any.raw(); - - unsafe { - try_call!(raw::git_odb_read_header( - &mut size as *mut size_t, - &mut kind_id as *mut raw::git_object_t, - self.raw, - oid.raw() - )); - - Ok((size, ObjectType::from_raw(kind_id).unwrap())) - } - } - - /// Write an object to the database. - pub fn write(&self, kind: ObjectType, data: &[u8]) -> Result<Oid, Error> { - unsafe { - let mut out = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - try_call!(raw::git_odb_write( - &mut out, - self.raw, - data.as_ptr() as *const c_void, - data.len(), - kind.raw() - )); - Ok(Oid::from_raw(&mut out)) - } - } - - /// Create stream for writing a pack file to the ODB - pub fn packwriter(&self) -> Result<OdbPackwriter<'_>, Error> { - let mut out = ptr::null_mut(); - let progress_cb: raw::git_indexer_progress_cb = Some(write_pack_progress_cb); - let progress_payload = Box::new(OdbPackwriterCb { cb: None }); - let progress_payload_ptr = Box::into_raw(progress_payload); - - unsafe { - try_call!(raw::git_odb_write_pack( - &mut out, - self.raw, - progress_cb, - progress_payload_ptr as *mut c_void - )); - } - - Ok(OdbPackwriter { - raw: out, - progress: Default::default(), - progress_payload_ptr, - }) - } - - /// Checks if the object database has an object. - pub fn exists(&self, oid: Oid) -> bool { - unsafe { raw::git_odb_exists(self.raw, oid.raw()) != 0 } - } - - /// Checks if the object database has an object, with extended flags. - pub fn exists_ext(&self, oid: Oid, flags: OdbLookupFlags) -> bool { - unsafe { raw::git_odb_exists_ext(self.raw, oid.raw(), flags.bits() as c_uint) != 0 } - } - - /// Potentially finds an object that starts with the given prefix. - pub fn exists_prefix(&self, short_oid: Oid, len: usize) -> Result<Oid, Error> { - unsafe { - let mut out = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - try_call!(raw::git_odb_exists_prefix( - &mut out, - self.raw, - short_oid.raw(), - len - )); - Ok(Oid::from_raw(&out)) - } - } - - /// Refresh the object database. - /// This should never be needed, and is - /// provided purely for convenience. - /// The object database will automatically - /// refresh when an object is not found when - /// requested. - pub fn refresh(&self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_odb_refresh(self.raw)); - Ok(()) - } - } - - /// Adds an alternate disk backend to the object database. - pub fn add_disk_alternate(&self, path: &str) -> Result<(), Error> { - unsafe { - let path = CString::new(path)?; - try_call!(raw::git_odb_add_disk_alternate(self.raw, path)); - Ok(()) - } - } - - /// Create a new mempack backend, and add it to this odb with the given - /// priority. Higher values give the backend higher precedence. The default - /// loose and pack backends have priorities 1 and 2 respectively (hard-coded - /// in libgit2). A reference to the new mempack backend is returned on - /// success. The lifetime of the backend must be contained within the - /// lifetime of this odb, since deletion of the odb will also result in - /// deletion of the mempack backend. - /// - /// Here is an example that fails to compile because it tries to hold the - /// mempack reference beyond the Odb's lifetime: - /// - /// ```compile_fail - /// use git2::Odb; - /// let mempack = { - /// let odb = Odb::new().unwrap(); - /// odb.add_new_mempack_backend(1000).unwrap() - /// }; - /// ``` - pub fn add_new_mempack_backend<'odb>( - &'odb self, - priority: i32, - ) -> Result<Mempack<'odb>, Error> { - unsafe { - let mut mempack = ptr::null_mut(); - // The mempack backend object in libgit2 is only ever freed by an - // odb that has the backend in its list. So to avoid potentially - // leaking the mempack backend, this API ensures that the backend - // is added to the odb before returning it. The lifetime of the - // mempack is also bound to the lifetime of the odb, so that users - // can't end up with a dangling reference to a mempack object that - // was actually freed when the odb was destroyed. - try_call!(raw::git_mempack_new(&mut mempack)); - try_call!(raw::git_odb_add_backend( - self.raw, - mempack, - priority as c_int - )); - Ok(Mempack::from_raw(mempack)) - } - } -} - -/// An object from the Object Database. -pub struct OdbObject<'a> { - raw: *mut raw::git_odb_object, - _marker: marker::PhantomData<Object<'a>>, -} - -impl<'a> Binding for OdbObject<'a> { - type Raw = *mut raw::git_odb_object; - - unsafe fn from_raw(raw: *mut raw::git_odb_object) -> OdbObject<'a> { - OdbObject { - raw, - _marker: marker::PhantomData, - } - } - - fn raw(&self) -> *mut raw::git_odb_object { - self.raw - } -} - -impl<'a> Drop for OdbObject<'a> { - fn drop(&mut self) { - unsafe { raw::git_odb_object_free(self.raw) } - } -} - -impl<'a> OdbObject<'a> { - /// Get the object type. - pub fn kind(&self) -> ObjectType { - unsafe { ObjectType::from_raw(raw::git_odb_object_type(self.raw)).unwrap() } - } - - /// Get the object size. - pub fn len(&self) -> usize { - unsafe { raw::git_odb_object_size(self.raw) } - } - - /// Get the object data. - pub fn data(&self) -> &[u8] { - unsafe { - let size = self.len(); - let ptr: *const u8 = raw::git_odb_object_data(self.raw) as *const u8; - let buffer = slice::from_raw_parts(ptr, size); - return buffer; - } - } - - /// Get the object id. - pub fn id(&self) -> Oid { - unsafe { Oid::from_raw(raw::git_odb_object_id(self.raw)) } - } -} - -/// A structure to represent a git ODB rstream -pub struct OdbReader<'repo> { - raw: *mut raw::git_odb_stream, - _marker: marker::PhantomData<Object<'repo>>, -} - -// `git_odb_stream` is not thread-safe internally, so it can't use `Sync`, but moving it to another -// thread and continuing to read will work. -unsafe impl<'repo> Send for OdbReader<'repo> {} - -impl<'repo> Binding for OdbReader<'repo> { - type Raw = *mut raw::git_odb_stream; - - unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbReader<'repo> { - OdbReader { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_odb_stream { - self.raw - } -} - -impl<'repo> Drop for OdbReader<'repo> { - fn drop(&mut self) { - unsafe { raw::git_odb_stream_free(self.raw) } - } -} - -impl<'repo> io::Read for OdbReader<'repo> { - fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { - unsafe { - let ptr = buf.as_ptr() as *mut c_char; - let len = buf.len(); - let res = raw::git_odb_stream_read(self.raw, ptr, len); - if res < 0 { - Err(io::Error::new(io::ErrorKind::Other, "Read error")) - } else { - Ok(len) - } - } - } -} - -/// A structure to represent a git ODB wstream -pub struct OdbWriter<'repo> { - raw: *mut raw::git_odb_stream, - _marker: marker::PhantomData<Object<'repo>>, -} - -// `git_odb_stream` is not thread-safe internally, so it can't use `Sync`, but moving it to another -// thread and continuing to write will work. -unsafe impl<'repo> Send for OdbWriter<'repo> {} - -impl<'repo> OdbWriter<'repo> { - /// Finish writing to an ODB stream - /// - /// This method can be used to finalize writing object to the database and get an identifier. - /// The object will take its final name and will be available to the odb. - /// This method will fail if the total number of received bytes differs from the size declared with odb_writer() - /// Attempting write after finishing will be ignored. - pub fn finalize(&mut self) -> Result<Oid, Error> { - let mut raw = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_odb_stream_finalize_write(&mut raw, self.raw)); - Ok(Binding::from_raw(&raw as *const _)) - } - } -} - -impl<'repo> Binding for OdbWriter<'repo> { - type Raw = *mut raw::git_odb_stream; - - unsafe fn from_raw(raw: *mut raw::git_odb_stream) -> OdbWriter<'repo> { - OdbWriter { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_odb_stream { - self.raw - } -} - -impl<'repo> Drop for OdbWriter<'repo> { - fn drop(&mut self) { - unsafe { raw::git_odb_stream_free(self.raw) } - } -} - -impl<'repo> io::Write for OdbWriter<'repo> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - unsafe { - let ptr = buf.as_ptr() as *const c_char; - let len = buf.len(); - let res = raw::git_odb_stream_write(self.raw, ptr, len); - if res < 0 { - Err(io::Error::new(io::ErrorKind::Other, "Write error")) - } else { - Ok(buf.len()) - } - } - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -pub(crate) struct OdbPackwriterCb<'repo> { - pub(crate) cb: Option<Box<IndexerProgress<'repo>>>, -} - -/// A stream to write a packfile to the ODB -pub struct OdbPackwriter<'repo> { - raw: *mut raw::git_odb_writepack, - progress: raw::git_indexer_progress, - progress_payload_ptr: *mut OdbPackwriterCb<'repo>, -} - -impl<'repo> OdbPackwriter<'repo> { - /// Finish writing the packfile - pub fn commit(&mut self) -> Result<i32, Error> { - unsafe { - let writepack = &*self.raw; - let res = match writepack.commit { - Some(commit) => commit(self.raw, &mut self.progress), - None => -1, - }; - - if res < 0 { - Err(Error::last_error(res).unwrap()) - } else { - Ok(res) - } - } - } - - /// The callback through which progress is monitored. Be aware that this is - /// called inline, so performance may be affected. - pub fn progress<F>(&mut self, cb: F) -> &mut OdbPackwriter<'repo> - where - F: FnMut(Progress<'_>) -> bool + 'repo, - { - let progress_payload = - unsafe { &mut *(self.progress_payload_ptr as *mut OdbPackwriterCb<'_>) }; - - progress_payload.cb = Some(Box::new(cb) as Box<IndexerProgress<'repo>>); - self - } -} - -impl<'repo> io::Write for OdbPackwriter<'repo> { - fn write(&mut self, buf: &[u8]) -> io::Result<usize> { - unsafe { - let ptr = buf.as_ptr() as *mut c_void; - let len = buf.len(); - - let writepack = &*self.raw; - let res = match writepack.append { - Some(append) => append(self.raw, ptr, len, &mut self.progress), - None => -1, - }; - - if res < 0 { - Err(io::Error::new(io::ErrorKind::Other, "Write error")) - } else { - Ok(buf.len()) - } - } - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -impl<'repo> Drop for OdbPackwriter<'repo> { - fn drop(&mut self) { - unsafe { - let writepack = &*self.raw; - match writepack.free { - Some(free) => free(self.raw), - None => (), - }; - - drop(Box::from_raw(self.progress_payload_ptr)); - } - } -} - -pub type ForeachCb<'a> = dyn FnMut(&Oid) -> bool + 'a; - -struct ForeachCbData<'a> { - pub callback: &'a mut ForeachCb<'a>, -} - -extern "C" fn foreach_cb(id: *const raw::git_oid, payload: *mut c_void) -> c_int { - panic::wrap(|| unsafe { - let data = &mut *(payload as *mut ForeachCbData<'_>); - let res = { - let callback = &mut data.callback; - callback(&Binding::from_raw(id)) - }; - - if res { - 0 - } else { - 1 - } - }) - .unwrap_or(1) -} - -pub(crate) extern "C" fn write_pack_progress_cb( - stats: *const raw::git_indexer_progress, - payload: *mut c_void, -) -> c_int { - let ok = panic::wrap(|| unsafe { - let payload = &mut *(payload as *mut OdbPackwriterCb<'_>); - - let callback = match payload.cb { - Some(ref mut cb) => cb, - None => return true, - }; - - let progress: Progress<'_> = Binding::from_raw(stats); - callback(progress) - }); - if ok == Some(true) { - 0 - } else { - -1 - } -} - -#[cfg(test)] -mod tests { - use crate::{Buf, ObjectType, Oid, Repository}; - use std::io::prelude::*; - use tempfile::TempDir; - - #[test] - fn read() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let dat = [4, 3, 5, 6, 9]; - let id = repo.blob(&dat).unwrap(); - let db = repo.odb().unwrap(); - let obj = db.read(id).unwrap(); - let data = obj.data(); - let size = obj.len(); - assert_eq!(size, 5); - assert_eq!(dat, data); - assert_eq!(id, obj.id()); - } - - #[test] - fn read_header() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let dat = [4, 3, 5, 6, 9]; - let id = repo.blob(&dat).unwrap(); - let db = repo.odb().unwrap(); - let (size, kind) = db.read_header(id).unwrap(); - - assert_eq!(size, 5); - assert_eq!(kind, ObjectType::Blob); - } - - #[test] - fn write() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let dat = [4, 3, 5, 6, 9]; - let db = repo.odb().unwrap(); - let id = db.write(ObjectType::Blob, &dat).unwrap(); - let blob = repo.find_blob(id).unwrap(); - assert_eq!(blob.content(), dat); - } - - #[test] - fn writer() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let dat = [4, 3, 5, 6, 9]; - let db = repo.odb().unwrap(); - let mut ws = db.writer(dat.len(), ObjectType::Blob).unwrap(); - let wl = ws.write(&dat[0..3]).unwrap(); - assert_eq!(wl, 3); - let wl = ws.write(&dat[3..5]).unwrap(); - assert_eq!(wl, 2); - let id = ws.finalize().unwrap(); - let blob = repo.find_blob(id).unwrap(); - assert_eq!(blob.content(), dat); - } - - #[test] - fn exists() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let dat = [4, 3, 5, 6, 9]; - let db = repo.odb().unwrap(); - let id = db.write(ObjectType::Blob, &dat).unwrap(); - assert!(db.exists(id)); - } - - #[test] - fn exists_prefix() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let dat = [4, 3, 5, 6, 9]; - let db = repo.odb().unwrap(); - let id = db.write(ObjectType::Blob, &dat).unwrap(); - let id_prefix_str = &id.to_string()[0..10]; - let id_prefix = Oid::from_str(id_prefix_str).unwrap(); - let found_oid = db.exists_prefix(id_prefix, 10).unwrap(); - assert_eq!(found_oid, id); - } - - #[test] - fn packwriter() { - let (_td, repo_source) = crate::test::repo_init(); - let (_td, repo_target) = crate::test::repo_init(); - let mut builder = t!(repo_source.packbuilder()); - let mut buf = Buf::new(); - let (commit_source_id, _tree) = crate::test::commit(&repo_source); - t!(builder.insert_object(commit_source_id, None)); - t!(builder.write_buf(&mut buf)); - let db = repo_target.odb().unwrap(); - let mut packwriter = db.packwriter().unwrap(); - packwriter.write(&buf).unwrap(); - packwriter.commit().unwrap(); - let commit_target = repo_target.find_commit(commit_source_id).unwrap(); - assert_eq!(commit_target.id(), commit_source_id); - } - - #[test] - fn packwriter_progress() { - let mut progress_called = false; - { - let (_td, repo_source) = crate::test::repo_init(); - let (_td, repo_target) = crate::test::repo_init(); - let mut builder = t!(repo_source.packbuilder()); - let mut buf = Buf::new(); - let (commit_source_id, _tree) = crate::test::commit(&repo_source); - t!(builder.insert_object(commit_source_id, None)); - t!(builder.write_buf(&mut buf)); - let db = repo_target.odb().unwrap(); - let mut packwriter = db.packwriter().unwrap(); - packwriter.progress(|_| { - progress_called = true; - true - }); - packwriter.write(&buf).unwrap(); - packwriter.commit().unwrap(); - } - assert_eq!(progress_called, true); - } - - #[test] - fn write_with_mempack() { - use crate::{Buf, ResetType}; - use std::io::Write; - use std::path::Path; - - // Create a repo, add a mempack backend - let (_td, repo) = crate::test::repo_init(); - let odb = repo.odb().unwrap(); - let mempack = odb.add_new_mempack_backend(1000).unwrap(); - - // Sanity check that foo doesn't exist initially - let foo_file = Path::new(repo.workdir().unwrap()).join("foo"); - assert!(!foo_file.exists()); - - // Make a commit that adds foo. This writes new stuff into the mempack - // backend. - 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()); - - // Dump the mempack modifications into a buf, and reset it. This "erases" - // commit-related objects from the repository. Ensure the commit appears - // to have become invalid, by checking for failure in `reset --hard`. - let mut buf = Buf::new(); - mempack.dump(&repo, &mut buf).unwrap(); - mempack.reset().unwrap(); - assert!(repo - .reset(commit1.as_object(), ResetType::Hard, None) - .is_err()); - - // Write the buf into a packfile in the repo. This brings back the - // missing objects, and we verify everything is good again. - let mut packwriter = odb.packwriter().unwrap(); - packwriter.write(&buf).unwrap(); - packwriter.commit().unwrap(); - t!(repo.reset(commit1.as_object(), ResetType::Hard, None)); - assert!(foo_file.exists()); - } -} diff --git a/extra/git2/src/oid.rs b/extra/git2/src/oid.rs deleted file mode 100644 index 145458aec..000000000 --- a/extra/git2/src/oid.rs +++ /dev/null @@ -1,259 +0,0 @@ -use libc; -use std::cmp::Ordering; -use std::fmt; -use std::hash::{Hash, Hasher}; -use std::path::Path; -use std::str; - -use crate::{raw, Error, IntoCString, ObjectType}; - -use crate::util::{c_cmp_to_ordering, Binding}; - -/// Unique identity of any object (commit, tree, blob, tag). -#[derive(Copy, Clone)] -#[repr(C)] -pub struct Oid { - raw: raw::git_oid, -} - -impl Oid { - /// Parse a hex-formatted object id into an Oid structure. - /// - /// # Errors - /// - /// Returns an error if the string is empty, is longer than 40 hex - /// characters, or contains any non-hex characters. - pub fn from_str(s: &str) -> Result<Oid, Error> { - crate::init(); - let mut raw = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_oid_fromstrn( - &mut raw, - s.as_bytes().as_ptr() as *const libc::c_char, - s.len() as libc::size_t - )); - } - Ok(Oid { raw }) - } - - /// Parse a raw object id into an Oid structure. - /// - /// If the array given is not 20 bytes in length, an error is returned. - pub fn from_bytes(bytes: &[u8]) -> Result<Oid, Error> { - crate::init(); - let mut raw = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - if bytes.len() != raw::GIT_OID_RAWSZ { - Err(Error::from_str("raw byte array must be 20 bytes")) - } else { - unsafe { - try_call!(raw::git_oid_fromraw(&mut raw, bytes.as_ptr())); - } - Ok(Oid { raw }) - } - } - - /// Creates an all zero Oid structure. - pub fn zero() -> Oid { - let out = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - Oid { raw: out } - } - - /// Hashes the provided data as an object of the provided type, and returns - /// an Oid corresponding to the result. This does not store the object - /// inside any object database or repository. - pub fn hash_object(kind: ObjectType, bytes: &[u8]) -> Result<Oid, Error> { - crate::init(); - - let mut out = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_odb_hash( - &mut out, - bytes.as_ptr() as *const libc::c_void, - bytes.len(), - kind.raw() - )); - } - - Ok(Oid { raw: out }) - } - - /// Hashes the content of the provided file as an object of the provided type, - /// and returns an Oid corresponding to the result. This does not store the object - /// inside any object database or repository. - pub fn hash_file<P: AsRef<Path>>(kind: ObjectType, path: P) -> Result<Oid, Error> { - crate::init(); - - // Normal file path OK (does not need Windows conversion). - let rpath = path.as_ref().into_c_string()?; - - let mut out = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_odb_hashfile(&mut out, rpath, kind.raw())); - } - - Ok(Oid { raw: out }) - } - - /// View this OID as a byte-slice 20 bytes in length. - pub fn as_bytes(&self) -> &[u8] { - &self.raw.id - } - - /// Test if this OID is all zeros. - pub fn is_zero(&self) -> bool { - unsafe { raw::git_oid_iszero(&self.raw) == 1 } - } -} - -impl Binding for Oid { - type Raw = *const raw::git_oid; - - unsafe fn from_raw(oid: *const raw::git_oid) -> Oid { - Oid { raw: *oid } - } - fn raw(&self) -> *const raw::git_oid { - &self.raw as *const _ - } -} - -impl fmt::Debug for Oid { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl fmt::Display for Oid { - /// Hex-encode this Oid into a formatter. - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut dst = [0u8; raw::GIT_OID_HEXSZ + 1]; - unsafe { - raw::git_oid_tostr( - dst.as_mut_ptr() as *mut libc::c_char, - dst.len() as libc::size_t, - &self.raw, - ); - } - let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()]; - str::from_utf8(s).unwrap().fmt(f) - } -} - -impl str::FromStr for Oid { - type Err = Error; - - /// Parse a hex-formatted object id into an Oid structure. - /// - /// # Errors - /// - /// Returns an error if the string is empty, is longer than 40 hex - /// characters, or contains any non-hex characters. - fn from_str(s: &str) -> Result<Oid, Error> { - Oid::from_str(s) - } -} - -impl PartialEq for Oid { - fn eq(&self, other: &Oid) -> bool { - unsafe { raw::git_oid_equal(&self.raw, &other.raw) != 0 } - } -} -impl Eq for Oid {} - -impl PartialOrd for Oid { - fn partial_cmp(&self, other: &Oid) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl Ord for Oid { - fn cmp(&self, other: &Oid) -> Ordering { - c_cmp_to_ordering(unsafe { raw::git_oid_cmp(&self.raw, &other.raw) }) - } -} - -impl Hash for Oid { - fn hash<H: Hasher>(&self, into: &mut H) { - self.raw.id.hash(into) - } -} - -impl AsRef<[u8]> for Oid { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -#[cfg(test)] -mod tests { - use std::fs::File; - use std::io::prelude::*; - - use super::Error; - use super::Oid; - use crate::ObjectType; - use tempfile::TempDir; - - #[test] - fn conversions() { - assert!(Oid::from_str("foo").is_err()); - assert!(Oid::from_str("decbf2be529ab6557d5429922251e5ee36519817").is_ok()); - assert!(Oid::from_bytes(b"foo").is_err()); - assert!(Oid::from_bytes(b"00000000000000000000").is_ok()); - } - - #[test] - fn comparisons() -> Result<(), Error> { - assert_eq!(Oid::from_str("decbf2b")?, Oid::from_str("decbf2b")?); - assert!(Oid::from_str("decbf2b")? <= Oid::from_str("decbf2b")?); - assert!(Oid::from_str("decbf2b")? >= Oid::from_str("decbf2b")?); - { - let o = Oid::from_str("decbf2b")?; - assert_eq!(o, o); - assert!(o <= o); - assert!(o >= o); - } - assert_eq!( - Oid::from_str("decbf2b")?, - Oid::from_str("decbf2b000000000000000000000000000000000")? - ); - assert!( - Oid::from_bytes(b"00000000000000000000")? < Oid::from_bytes(b"00000000000000000001")? - ); - assert!(Oid::from_bytes(b"00000000000000000000")? < Oid::from_str("decbf2b")?); - assert_eq!( - Oid::from_bytes(b"00000000000000000000")?, - Oid::from_str("3030303030303030303030303030303030303030")? - ); - Ok(()) - } - - #[test] - fn zero_is_zero() { - assert!(Oid::zero().is_zero()); - } - - #[test] - fn hash_object() { - let bytes = "Hello".as_bytes(); - assert!(Oid::hash_object(ObjectType::Blob, bytes).is_ok()); - } - - #[test] - fn hash_file() { - let td = TempDir::new().unwrap(); - let path = td.path().join("hello.txt"); - let mut file = File::create(&path).unwrap(); - file.write_all("Hello".as_bytes()).unwrap(); - assert!(Oid::hash_file(ObjectType::Blob, &path).is_ok()); - } -} diff --git a/extra/git2/src/oid_array.rs b/extra/git2/src/oid_array.rs deleted file mode 100644 index 0d87ce995..000000000 --- a/extra/git2/src/oid_array.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! Bindings to libgit2's raw `git_oidarray` type - -use std::ops::Deref; - -use crate::oid::Oid; -use crate::raw; -use crate::util::Binding; -use std::mem; -use std::slice; - -/// An oid array structure used by libgit2 -/// -/// Some APIs return arrays of OIDs which originate from libgit2. This -/// wrapper type behaves a little like `Vec<&Oid>` but does so without copying -/// the underlying Oids until necessary. -pub struct OidArray { - raw: raw::git_oidarray, -} - -impl Deref for OidArray { - type Target = [Oid]; - - fn deref(&self) -> &[Oid] { - unsafe { - debug_assert_eq!(mem::size_of::<Oid>(), mem::size_of_val(&*self.raw.ids)); - - slice::from_raw_parts(self.raw.ids as *const Oid, self.raw.count as usize) - } - } -} - -impl Binding for OidArray { - type Raw = raw::git_oidarray; - unsafe fn from_raw(raw: raw::git_oidarray) -> OidArray { - OidArray { raw } - } - fn raw(&self) -> raw::git_oidarray { - self.raw - } -} - -impl<'repo> std::fmt::Debug for OidArray { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_tuple("OidArray").field(&self.deref()).finish() - } -} - -impl Drop for OidArray { - fn drop(&mut self) { - unsafe { raw::git_oidarray_free(&mut self.raw) } - } -} diff --git a/extra/git2/src/opts.rs b/extra/git2/src/opts.rs deleted file mode 100644 index e90bea0b1..000000000 --- a/extra/git2/src/opts.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! Bindings to libgit2's git_libgit2_opts function. - -use std::ffi::CString; -use std::ptr; - -use crate::string_array::StringArray; -use crate::util::Binding; -use crate::{raw, Buf, ConfigLevel, Error, IntoCString}; - -/// Set the search path for a level of config data. The search path applied to -/// shared attributes and ignore files, too. -/// -/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`], -/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`]. -/// -/// `path` lists directories delimited by `GIT_PATH_LIST_SEPARATOR`. -/// Use magic path `$PATH` to include the old value of the path -/// (if you want to prepend or append, for instance). -/// -/// This function is unsafe as it mutates the global state but cannot guarantee -/// thread-safety. It needs to be externally synchronized with calls to access -/// the global state. -pub unsafe fn set_search_path<P>(level: ConfigLevel, path: P) -> Result<(), Error> -where - P: IntoCString, -{ - crate::init(); - try_call!(raw::git_libgit2_opts( - raw::GIT_OPT_SET_SEARCH_PATH as libc::c_int, - level as libc::c_int, - path.into_c_string()?.as_ptr() - )); - Ok(()) -} - -/// Reset the search path for a given level of config data to the default -/// (generally based on environment variables). -/// -/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`], -/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`]. -/// -/// This function is unsafe as it mutates the global state but cannot guarantee -/// thread-safety. It needs to be externally synchronized with calls to access -/// the global state. -pub unsafe fn reset_search_path(level: ConfigLevel) -> Result<(), Error> { - crate::init(); - try_call!(raw::git_libgit2_opts( - raw::GIT_OPT_SET_SEARCH_PATH as libc::c_int, - level as libc::c_int, - core::ptr::null::<u8>() - )); - Ok(()) -} - -/// Get the search path for a given level of config data. -/// -/// `level` must be one of [`ConfigLevel::System`], [`ConfigLevel::Global`], -/// [`ConfigLevel::XDG`], [`ConfigLevel::ProgramData`]. -/// -/// This function is unsafe as it mutates the global state but cannot guarantee -/// thread-safety. It needs to be externally synchronized with calls to access -/// the global state. -pub unsafe fn get_search_path(level: ConfigLevel) -> Result<CString, Error> { - crate::init(); - let buf = Buf::new(); - try_call!(raw::git_libgit2_opts( - raw::GIT_OPT_GET_SEARCH_PATH as libc::c_int, - level as libc::c_int, - buf.raw() as *const _ - )); - buf.into_c_string() -} - -/// Controls whether or not libgit2 will cache loaded objects. Enabled by -/// default, but disabling this can improve performance and memory usage if -/// loading a large number of objects that will not be referenced again. -/// Disabling this will cause repository objects to clear their caches when next -/// accessed. -pub fn enable_caching(enabled: bool) { - crate::init(); - let error = unsafe { - raw::git_libgit2_opts( - raw::GIT_OPT_ENABLE_CACHING as libc::c_int, - enabled as libc::c_int, - ) - }; - // This function cannot actually fail, but the function has an error return - // for other options that can. - debug_assert!(error >= 0); -} - -/// Controls whether or not libgit2 will verify when writing an object that all -/// objects it references are valid. Enabled by default, but disabling this can -/// significantly improve performance, at the cost of potentially allowing the -/// creation of objects that reference invalid objects (due to programming -/// error or repository corruption). -pub fn strict_object_creation(enabled: bool) { - crate::init(); - let error = unsafe { - raw::git_libgit2_opts( - raw::GIT_OPT_ENABLE_STRICT_OBJECT_CREATION as libc::c_int, - enabled as libc::c_int, - ) - }; - // This function cannot actually fail, but the function has an error return - // for other options that can. - debug_assert!(error >= 0); -} - -/// Controls whether or not libgit2 will verify that objects loaded have the -/// expected hash. Enabled by default, but disabling this can significantly -/// improve performance, at the cost of relying on repository integrity -/// without checking it. -pub fn strict_hash_verification(enabled: bool) { - crate::init(); - let error = unsafe { - raw::git_libgit2_opts( - raw::GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION as libc::c_int, - enabled as libc::c_int, - ) - }; - // This function cannot actually fail, but the function has an error return - // for other options that can. - debug_assert!(error >= 0); -} - -/// Returns the list of git extensions that are supported. This is the list of -/// built-in extensions supported by libgit2 and custom extensions that have -/// been added with [`set_extensions`]. Extensions that have been negated will -/// not be returned. -/// -/// # Safety -/// -/// libgit2 stores user extensions in a static variable. -/// This function is effectively reading a `static mut` and should be treated as such -pub unsafe fn get_extensions() -> Result<StringArray, Error> { - crate::init(); - - let mut extensions = raw::git_strarray { - strings: ptr::null_mut(), - count: 0, - }; - - try_call!(raw::git_libgit2_opts( - raw::GIT_OPT_GET_EXTENSIONS as libc::c_int, - &mut extensions - )); - - Ok(StringArray::from_raw(extensions)) -} - -/// Set that the given git extensions are supported by the caller. Extensions -/// supported by libgit2 may be negated by prefixing them with a `!`. -/// For example: setting extensions to `[ "!noop", "newext" ]` indicates that -/// the caller does not want to support repositories with the `noop` extension -/// but does want to support repositories with the `newext` extension. -/// -/// # Safety -/// -/// libgit2 stores user extensions in a static variable. -/// This function is effectively modifying a `static mut` and should be treated as such -pub unsafe fn set_extensions<E>(extensions: &[E]) -> Result<(), Error> -where - for<'x> &'x E: IntoCString, -{ - crate::init(); - - let extensions = extensions - .iter() - .map(|e| e.into_c_string()) - .collect::<Result<Vec<_>, _>>()?; - - let extension_ptrs = extensions.iter().map(|e| e.as_ptr()).collect::<Vec<_>>(); - - try_call!(raw::git_libgit2_opts( - raw::GIT_OPT_SET_EXTENSIONS as libc::c_int, - extension_ptrs.as_ptr(), - extension_ptrs.len() as libc::size_t - )); - - Ok(()) -} - -/// Set whether or not to verify ownership before performing a repository. -/// Enabled by default, but disabling this can lead to code execution vulnerabilities. -pub unsafe fn set_verify_owner_validation(enabled: bool) -> Result<(), Error> { - crate::init(); - let error = raw::git_libgit2_opts( - raw::GIT_OPT_SET_OWNER_VALIDATION as libc::c_int, - enabled as libc::c_int, - ); - // This function cannot actually fail, but the function has an error return - // for other options that can. - debug_assert!(error >= 0); - Ok(()) -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn smoke() { - strict_hash_verification(false); - } -} diff --git a/extra/git2/src/packbuilder.rs b/extra/git2/src/packbuilder.rs deleted file mode 100644 index 9b93e7654..000000000 --- a/extra/git2/src/packbuilder.rs +++ /dev/null @@ -1,413 +0,0 @@ -use libc::{c_int, c_uint, c_void, size_t}; -use std::marker; -use std::ptr; -use std::slice; -use std::str; - -use crate::util::Binding; -use crate::{panic, raw, Buf, Error, Oid, Repository, Revwalk}; - -#[derive(PartialEq, Eq, Clone, Debug, Copy)] -/// Stages that are reported by the `PackBuilder` progress callback. -pub enum PackBuilderStage { - /// Adding objects to the pack - AddingObjects, - /// Deltafication of the pack - Deltafication, -} - -pub type ProgressCb<'a> = dyn FnMut(PackBuilderStage, u32, u32) -> bool + 'a; -pub type ForEachCb<'a> = dyn FnMut(&[u8]) -> bool + 'a; - -/// A builder for creating a packfile -pub struct PackBuilder<'repo> { - raw: *mut raw::git_packbuilder, - _progress: Option<Box<Box<ProgressCb<'repo>>>>, - _marker: marker::PhantomData<&'repo Repository>, -} - -impl<'repo> PackBuilder<'repo> { - /// Insert a single object. For an optimal pack it's mandatory to insert - /// objects in recency order, commits followed by trees and blobs. - pub fn insert_object(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> { - let name = crate::opt_cstr(name)?; - unsafe { - try_call!(raw::git_packbuilder_insert(self.raw, id.raw(), name)); - } - Ok(()) - } - - /// Insert a root tree object. This will add the tree as well as all - /// referenced trees and blobs. - pub fn insert_tree(&mut self, id: Oid) -> Result<(), Error> { - unsafe { - try_call!(raw::git_packbuilder_insert_tree(self.raw, id.raw())); - } - Ok(()) - } - - /// Insert a commit object. This will add a commit as well as the completed - /// referenced tree. - pub fn insert_commit(&mut self, id: Oid) -> Result<(), Error> { - unsafe { - try_call!(raw::git_packbuilder_insert_commit(self.raw, id.raw())); - } - Ok(()) - } - - /// Insert objects as given by the walk. Those commits and all objects they - /// reference will be inserted into the packbuilder. - pub fn insert_walk(&mut self, walk: &mut Revwalk<'_>) -> Result<(), Error> { - unsafe { - try_call!(raw::git_packbuilder_insert_walk(self.raw, walk.raw())); - } - Ok(()) - } - - /// Recursively insert an object and its referenced objects. Insert the - /// object as well as any object it references. - pub fn insert_recursive(&mut self, id: Oid, name: Option<&str>) -> Result<(), Error> { - let name = crate::opt_cstr(name)?; - unsafe { - try_call!(raw::git_packbuilder_insert_recur(self.raw, id.raw(), name)); - } - Ok(()) - } - - /// Write the contents of the packfile to an in-memory buffer. The contents - /// of the buffer will become a valid packfile, even though there will be - /// no attached index. - pub fn write_buf(&mut self, buf: &mut Buf) -> Result<(), Error> { - unsafe { - try_call!(raw::git_packbuilder_write_buf(buf.raw(), self.raw)); - } - Ok(()) - } - - /// Create the new pack and pass each object to the callback. - pub fn foreach<F>(&mut self, mut cb: F) -> Result<(), Error> - where - F: FnMut(&[u8]) -> bool, - { - let mut cb = &mut cb as &mut ForEachCb<'_>; - let ptr = &mut cb as *mut _; - let foreach: raw::git_packbuilder_foreach_cb = Some(foreach_c); - unsafe { - try_call!(raw::git_packbuilder_foreach( - self.raw, - foreach, - ptr as *mut _ - )); - } - Ok(()) - } - - /// `progress` will be called with progress information during pack - /// building. Be aware that this is called inline with pack building - /// operations, so performance may be affected. - /// - /// There can only be one progress callback attached, this will replace any - /// existing one. See `unset_progress_callback` to remove the current - /// progress callback without attaching a new one. - pub fn set_progress_callback<F>(&mut self, progress: F) -> Result<(), Error> - where - F: FnMut(PackBuilderStage, u32, u32) -> bool + 'repo, - { - let mut progress = Box::new(Box::new(progress) as Box<ProgressCb<'_>>); - let ptr = &mut *progress as *mut _; - let progress_c: raw::git_packbuilder_progress = Some(progress_c); - unsafe { - try_call!(raw::git_packbuilder_set_callbacks( - self.raw, - progress_c, - ptr as *mut _ - )); - } - self._progress = Some(progress); - Ok(()) - } - - /// Remove the current progress callback. See `set_progress_callback` to - /// set the progress callback. - pub fn unset_progress_callback(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_packbuilder_set_callbacks( - self.raw, - None, - ptr::null_mut() - )); - self._progress = None; - } - Ok(()) - } - - /// Set the number of threads to be used. - /// - /// Returns the number of threads to be used. - pub fn set_threads(&mut self, threads: u32) -> u32 { - unsafe { raw::git_packbuilder_set_threads(self.raw, threads) } - } - - /// Get the total number of objects the packbuilder will write out. - pub fn object_count(&self) -> usize { - unsafe { raw::git_packbuilder_object_count(self.raw) } - } - - /// Get the number of objects the packbuilder has already written out. - pub fn written(&self) -> usize { - unsafe { raw::git_packbuilder_written(self.raw) } - } - - /// Get the packfile's hash. A packfile's name is derived from the sorted - /// hashing of all object names. This is only correct after the packfile - /// has been written. - #[deprecated = "use `name()` to retrieve the filename"] - #[allow(deprecated)] - pub fn hash(&self) -> Option<Oid> { - if self.object_count() == 0 { - unsafe { Some(Binding::from_raw(raw::git_packbuilder_hash(self.raw))) } - } else { - None - } - } - - /// Get the unique name for the resulting packfile. - /// - /// The packfile's name is derived from the packfile's content. This is only - /// correct after the packfile has been written. - /// - /// Returns `None` if the packfile has not been written or if the name is - /// not valid utf-8. - pub fn name(&self) -> Option<&str> { - self.name_bytes().and_then(|s| str::from_utf8(s).ok()) - } - - /// Get the unique name for the resulting packfile, in bytes. - /// - /// The packfile's name is derived from the packfile's content. This is only - /// correct after the packfile has been written. - pub fn name_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_packbuilder_name(self.raw)) } - } -} - -impl<'repo> Binding for PackBuilder<'repo> { - type Raw = *mut raw::git_packbuilder; - unsafe fn from_raw(ptr: *mut raw::git_packbuilder) -> PackBuilder<'repo> { - PackBuilder { - raw: ptr, - _progress: None, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_packbuilder { - self.raw - } -} - -impl<'repo> Drop for PackBuilder<'repo> { - fn drop(&mut self) { - unsafe { - raw::git_packbuilder_set_callbacks(self.raw, None, ptr::null_mut()); - raw::git_packbuilder_free(self.raw); - } - } -} - -impl Binding for PackBuilderStage { - type Raw = raw::git_packbuilder_stage_t; - unsafe fn from_raw(raw: raw::git_packbuilder_stage_t) -> PackBuilderStage { - match raw { - raw::GIT_PACKBUILDER_ADDING_OBJECTS => PackBuilderStage::AddingObjects, - raw::GIT_PACKBUILDER_DELTAFICATION => PackBuilderStage::Deltafication, - _ => panic!("Unknown git diff binary kind"), - } - } - fn raw(&self) -> raw::git_packbuilder_stage_t { - match *self { - PackBuilderStage::AddingObjects => raw::GIT_PACKBUILDER_ADDING_OBJECTS, - PackBuilderStage::Deltafication => raw::GIT_PACKBUILDER_DELTAFICATION, - } - } -} - -extern "C" fn foreach_c(buf: *const c_void, size: size_t, data: *mut c_void) -> c_int { - unsafe { - let buf = slice::from_raw_parts(buf as *const u8, size as usize); - - let r = panic::wrap(|| { - let data = data as *mut &mut ForEachCb<'_>; - (*data)(buf) - }); - if r == Some(true) { - 0 - } else { - -1 - } - } -} - -extern "C" fn progress_c( - stage: raw::git_packbuilder_stage_t, - current: c_uint, - total: c_uint, - data: *mut c_void, -) -> c_int { - unsafe { - let stage = Binding::from_raw(stage); - - let r = panic::wrap(|| { - let data = data as *mut Box<ProgressCb<'_>>; - (*data)(stage, current, total) - }); - if r == Some(true) { - 0 - } else { - -1 - } - } -} - -#[cfg(test)] -mod tests { - use crate::Buf; - - fn pack_header(len: u8) -> Vec<u8> { - [].iter() - .chain(b"PACK") // signature - .chain(&[0, 0, 0, 2]) // version number - .chain(&[0, 0, 0, len]) // number of objects - .cloned() - .collect::<Vec<u8>>() - } - - fn empty_pack_header() -> Vec<u8> { - pack_header(0) - .iter() - .chain(&[ - 0x02, 0x9d, 0x08, 0x82, 0x3b, // ^ - 0xd8, 0xa8, 0xea, 0xb5, 0x10, // | SHA-1 of the zero - 0xad, 0x6a, 0xc7, 0x5c, 0x82, // | object pack header - 0x3c, 0xfd, 0x3e, 0xd3, 0x1e, - ]) // v - .cloned() - .collect::<Vec<u8>>() - } - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let _builder = t!(repo.packbuilder()); - } - - #[test] - fn smoke_write_buf() { - let (_td, repo) = crate::test::repo_init(); - let mut builder = t!(repo.packbuilder()); - let mut buf = Buf::new(); - t!(builder.write_buf(&mut buf)); - #[allow(deprecated)] - { - assert!(builder.hash().unwrap().is_zero()); - } - assert!(builder.name().is_none()); - assert_eq!(&*buf, &*empty_pack_header()); - } - - #[test] - fn smoke_foreach() { - let (_td, repo) = crate::test::repo_init(); - let mut builder = t!(repo.packbuilder()); - let mut buf = Vec::<u8>::new(); - t!(builder.foreach(|bytes| { - buf.extend(bytes); - true - })); - assert_eq!(&*buf, &*empty_pack_header()); - } - - #[test] - fn insert_write_buf() { - let (_td, repo) = crate::test::repo_init(); - let mut builder = t!(repo.packbuilder()); - let mut buf = Buf::new(); - let (commit, _tree) = crate::test::commit(&repo); - t!(builder.insert_object(commit, None)); - assert_eq!(builder.object_count(), 1); - t!(builder.write_buf(&mut buf)); - // Just check that the correct number of objects are written - assert_eq!(&buf[0..12], &*pack_header(1)); - } - - #[test] - fn insert_tree_write_buf() { - let (_td, repo) = crate::test::repo_init(); - let mut builder = t!(repo.packbuilder()); - let mut buf = Buf::new(); - let (_commit, tree) = crate::test::commit(&repo); - // will insert the tree itself and the blob, 2 objects - t!(builder.insert_tree(tree)); - assert_eq!(builder.object_count(), 2); - t!(builder.write_buf(&mut buf)); - // Just check that the correct number of objects are written - assert_eq!(&buf[0..12], &*pack_header(2)); - } - - #[test] - fn insert_commit_write_buf() { - let (_td, repo) = crate::test::repo_init(); - let mut builder = t!(repo.packbuilder()); - let mut buf = Buf::new(); - let (commit, _tree) = crate::test::commit(&repo); - // will insert the commit, its tree and the blob, 3 objects - t!(builder.insert_commit(commit)); - assert_eq!(builder.object_count(), 3); - t!(builder.write_buf(&mut buf)); - // Just check that the correct number of objects are written - assert_eq!(&buf[0..12], &*pack_header(3)); - } - - #[test] - fn progress_callback() { - let mut progress_called = false; - { - let (_td, repo) = crate::test::repo_init(); - let mut builder = t!(repo.packbuilder()); - let (commit, _tree) = crate::test::commit(&repo); - t!(builder.set_progress_callback(|_, _, _| { - progress_called = true; - true - })); - t!(builder.insert_commit(commit)); - t!(builder.write_buf(&mut Buf::new())); - } - assert_eq!(progress_called, true); - } - - #[test] - fn clear_progress_callback() { - let mut progress_called = false; - { - let (_td, repo) = crate::test::repo_init(); - let mut builder = t!(repo.packbuilder()); - let (commit, _tree) = crate::test::commit(&repo); - t!(builder.set_progress_callback(|_, _, _| { - progress_called = true; - true - })); - t!(builder.unset_progress_callback()); - t!(builder.insert_commit(commit)); - t!(builder.write_buf(&mut Buf::new())); - } - assert_eq!(progress_called, false); - } - - #[test] - fn set_threads() { - let (_td, repo) = crate::test::repo_init(); - let mut builder = t!(repo.packbuilder()); - let used = builder.set_threads(4); - // Will be 1 if not compiled with threading. - assert!(used == 1 || used == 4); - } -} diff --git a/extra/git2/src/panic.rs b/extra/git2/src/panic.rs deleted file mode 100644 index 3e1b208bc..000000000 --- a/extra/git2/src/panic.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::any::Any; -use std::cell::RefCell; - -thread_local!(static LAST_ERROR: RefCell<Option<Box<dyn Any + Send>>> = { - RefCell::new(None) -}); - -pub fn wrap<T, F: FnOnce() -> T + std::panic::UnwindSafe>(f: F) -> Option<T> { - use std::panic; - if LAST_ERROR.with(|slot| slot.borrow().is_some()) { - return None; - } - match panic::catch_unwind(f) { - Ok(ret) => Some(ret), - Err(e) => { - LAST_ERROR.with(move |slot| { - *slot.borrow_mut() = Some(e); - }); - None - } - } -} - -pub fn check() { - let err = LAST_ERROR.with(|slot| slot.borrow_mut().take()); - if let Some(err) = err { - std::panic::resume_unwind(err); - } -} - -pub fn panicked() -> bool { - LAST_ERROR.with(|slot| slot.borrow().is_some()) -} diff --git a/extra/git2/src/patch.rs b/extra/git2/src/patch.rs deleted file mode 100644 index 67b84c0f0..000000000 --- a/extra/git2/src/patch.rs +++ /dev/null @@ -1,235 +0,0 @@ -use libc::{c_int, c_void}; -use std::marker::PhantomData; -use std::path::Path; -use std::ptr; - -use crate::diff::{print_cb, LineCb}; -use crate::util::{into_opt_c_string, Binding}; -use crate::{raw, Blob, Buf, Diff, DiffDelta, DiffHunk, DiffLine, DiffOptions, Error}; - -/// A structure representing the text changes in a single diff delta. -/// -/// This is an opaque structure. -pub struct Patch<'buffers> { - raw: *mut raw::git_patch, - buffers: PhantomData<&'buffers ()>, -} - -unsafe impl<'buffers> Send for Patch<'buffers> {} - -impl<'buffers> Binding for Patch<'buffers> { - type Raw = *mut raw::git_patch; - unsafe fn from_raw(raw: Self::Raw) -> Self { - Patch { - raw, - buffers: PhantomData, - } - } - fn raw(&self) -> Self::Raw { - self.raw - } -} - -impl<'buffers> Drop for Patch<'buffers> { - fn drop(&mut self) { - unsafe { raw::git_patch_free(self.raw) } - } -} - -impl<'buffers> Patch<'buffers> { - /// Return a Patch for one file in a Diff. - /// - /// Returns Ok(None) for an unchanged or binary file. - pub fn from_diff(diff: &Diff<'buffers>, idx: usize) -> Result<Option<Self>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_patch_from_diff(&mut ret, diff.raw(), idx)); - Ok(Binding::from_raw_opt(ret)) - } - } - - /// Generate a Patch by diffing two blobs. - pub fn from_blobs( - old_blob: &Blob<'buffers>, - old_path: Option<&Path>, - new_blob: &Blob<'buffers>, - new_path: Option<&Path>, - opts: Option<&mut DiffOptions>, - ) -> Result<Self, Error> { - let mut ret = ptr::null_mut(); - let old_path = into_opt_c_string(old_path)?; - let new_path = into_opt_c_string(new_path)?; - unsafe { - try_call!(raw::git_patch_from_blobs( - &mut ret, - old_blob.raw(), - old_path, - new_blob.raw(), - new_path, - opts.map(|s| s.raw()) - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Generate a Patch by diffing a blob and a buffer. - pub fn from_blob_and_buffer( - old_blob: &Blob<'buffers>, - old_path: Option<&Path>, - new_buffer: &'buffers [u8], - new_path: Option<&Path>, - opts: Option<&mut DiffOptions>, - ) -> Result<Self, Error> { - let mut ret = ptr::null_mut(); - let old_path = into_opt_c_string(old_path)?; - let new_path = into_opt_c_string(new_path)?; - unsafe { - try_call!(raw::git_patch_from_blob_and_buffer( - &mut ret, - old_blob.raw(), - old_path, - new_buffer.as_ptr() as *const c_void, - new_buffer.len(), - new_path, - opts.map(|s| s.raw()) - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Generate a Patch by diffing two buffers. - pub fn from_buffers( - old_buffer: &'buffers [u8], - old_path: Option<&Path>, - new_buffer: &'buffers [u8], - new_path: Option<&Path>, - opts: Option<&mut DiffOptions>, - ) -> Result<Self, Error> { - crate::init(); - let mut ret = ptr::null_mut(); - let old_path = into_opt_c_string(old_path)?; - let new_path = into_opt_c_string(new_path)?; - unsafe { - try_call!(raw::git_patch_from_buffers( - &mut ret, - old_buffer.as_ptr() as *const c_void, - old_buffer.len(), - old_path, - new_buffer.as_ptr() as *const c_void, - new_buffer.len(), - new_path, - opts.map(|s| s.raw()) - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Get the DiffDelta associated with the Patch. - pub fn delta(&self) -> DiffDelta<'buffers> { - unsafe { Binding::from_raw(raw::git_patch_get_delta(self.raw) as *mut _) } - } - - /// Get the number of hunks in the Patch. - pub fn num_hunks(&self) -> usize { - unsafe { raw::git_patch_num_hunks(self.raw) } - } - - /// Get the number of lines of context, additions, and deletions in the Patch. - pub fn line_stats(&self) -> Result<(usize, usize, usize), Error> { - let mut context = 0; - let mut additions = 0; - let mut deletions = 0; - unsafe { - try_call!(raw::git_patch_line_stats( - &mut context, - &mut additions, - &mut deletions, - self.raw - )); - } - Ok((context, additions, deletions)) - } - - /// Get a DiffHunk and its total line count from the Patch. - pub fn hunk(&self, hunk_idx: usize) -> Result<(DiffHunk<'buffers>, usize), Error> { - let mut ret = ptr::null(); - let mut lines = 0; - unsafe { - try_call!(raw::git_patch_get_hunk( - &mut ret, &mut lines, self.raw, hunk_idx - )); - Ok((Binding::from_raw(ret), lines)) - } - } - - /// Get the number of lines in a hunk. - pub fn num_lines_in_hunk(&self, hunk_idx: usize) -> Result<usize, Error> { - unsafe { Ok(try_call!(raw::git_patch_num_lines_in_hunk(self.raw, hunk_idx)) as usize) } - } - - /// Get a DiffLine from a hunk of the Patch. - pub fn line_in_hunk( - &self, - hunk_idx: usize, - line_of_hunk: usize, - ) -> Result<DiffLine<'buffers>, Error> { - let mut ret = ptr::null(); - unsafe { - try_call!(raw::git_patch_get_line_in_hunk( - &mut ret, - self.raw, - hunk_idx, - line_of_hunk - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Get the size of a Patch's diff data in bytes. - pub fn size( - &self, - include_context: bool, - include_hunk_headers: bool, - include_file_headers: bool, - ) -> usize { - unsafe { - raw::git_patch_size( - self.raw, - include_context as c_int, - include_hunk_headers as c_int, - include_file_headers as c_int, - ) - } - } - - /// Print the Patch to text via a callback. - pub fn print(&mut self, mut line_cb: &mut LineCb<'_>) -> Result<(), Error> { - let ptr = &mut line_cb as *mut _ as *mut c_void; - unsafe { - let cb: raw::git_diff_line_cb = Some(print_cb); - try_call!(raw::git_patch_print(self.raw, cb, ptr)); - Ok(()) - } - } - - /// Get the Patch text as a Buf. - pub fn to_buf(&mut self) -> Result<Buf, Error> { - let buf = Buf::new(); - unsafe { - try_call!(raw::git_patch_to_buf(buf.raw(), self.raw)); - } - Ok(buf) - } -} - -impl<'buffers> std::fmt::Debug for Patch<'buffers> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - let mut ds = f.debug_struct("Patch"); - ds.field("delta", &self.delta()) - .field("num_hunks", &self.num_hunks()); - if let Ok(line_stats) = &self.line_stats() { - ds.field("line_stats", line_stats); - } - ds.finish() - } -} diff --git a/extra/git2/src/pathspec.rs b/extra/git2/src/pathspec.rs deleted file mode 100644 index 48174fcc1..000000000 --- a/extra/git2/src/pathspec.rs +++ /dev/null @@ -1,368 +0,0 @@ -use libc::size_t; -use std::iter::{FusedIterator, IntoIterator}; -use std::marker; -use std::ops::Range; -use std::path::Path; -use std::ptr; - -use crate::util::{path_to_repo_path, Binding}; -use crate::{raw, Diff, DiffDelta, Error, Index, IntoCString, PathspecFlags, Repository, Tree}; - -/// Structure representing a compiled pathspec used for matching against various -/// structures. -pub struct Pathspec { - raw: *mut raw::git_pathspec, -} - -/// List of filenames matching a pathspec. -pub struct PathspecMatchList<'ps> { - raw: *mut raw::git_pathspec_match_list, - _marker: marker::PhantomData<&'ps Pathspec>, -} - -/// Iterator over the matched paths in a pathspec. -pub struct PathspecEntries<'list> { - range: Range<usize>, - list: &'list PathspecMatchList<'list>, -} - -/// Iterator over the matching diff deltas. -pub struct PathspecDiffEntries<'list> { - range: Range<usize>, - list: &'list PathspecMatchList<'list>, -} - -/// Iterator over the failed list of pathspec items that did not match. -pub struct PathspecFailedEntries<'list> { - range: Range<usize>, - list: &'list PathspecMatchList<'list>, -} - -impl Pathspec { - /// Creates a new pathspec from a list of specs to match against. - pub fn new<I, T>(specs: I) -> Result<Pathspec, Error> - where - T: IntoCString, - I: IntoIterator<Item = T>, - { - crate::init(); - let (_a, _b, arr) = crate::util::iter2cstrs_paths(specs)?; - unsafe { - let mut ret = ptr::null_mut(); - try_call!(raw::git_pathspec_new(&mut ret, &arr)); - Ok(Binding::from_raw(ret)) - } - } - - /// Match a pathspec against files in a diff. - /// - /// The list returned contains the list of all matched filenames (unless you - /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the - /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is - /// specified. - pub fn match_diff( - &self, - diff: &Diff<'_>, - flags: PathspecFlags, - ) -> Result<PathspecMatchList<'_>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_pathspec_match_diff( - &mut ret, - diff.raw(), - flags.bits(), - self.raw - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Match a pathspec against files in a tree. - /// - /// The list returned contains the list of all matched filenames (unless you - /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the - /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is - /// specified. - pub fn match_tree( - &self, - tree: &Tree<'_>, - flags: PathspecFlags, - ) -> Result<PathspecMatchList<'_>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_pathspec_match_tree( - &mut ret, - tree.raw(), - flags.bits(), - self.raw - )); - Ok(Binding::from_raw(ret)) - } - } - - /// This matches the pathspec against the files in the repository index. - /// - /// The list returned contains the list of all matched filenames (unless you - /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the - /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is - /// specified. - pub fn match_index( - &self, - index: &Index, - flags: PathspecFlags, - ) -> Result<PathspecMatchList<'_>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_pathspec_match_index( - &mut ret, - index.raw(), - flags.bits(), - self.raw - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Match a pathspec against the working directory of a repository. - /// - /// This matches the pathspec against the current files in the working - /// directory of the repository. It is an error to invoke this on a bare - /// repo. This handles git ignores (i.e. ignored files will not be - /// considered to match the pathspec unless the file is tracked in the - /// index). - /// - /// The list returned contains the list of all matched filenames (unless you - /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the - /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is - /// specified. - pub fn match_workdir( - &self, - repo: &Repository, - flags: PathspecFlags, - ) -> Result<PathspecMatchList<'_>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_pathspec_match_workdir( - &mut ret, - repo.raw(), - flags.bits(), - self.raw - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Try to match a path against a pathspec - /// - /// Unlike most of the other pathspec matching functions, this will not fall - /// back on the native case-sensitivity for your platform. You must - /// explicitly pass flags to control case sensitivity or else this will fall - /// back on being case sensitive. - pub fn matches_path(&self, path: &Path, flags: PathspecFlags) -> bool { - let path = path_to_repo_path(path).unwrap(); - unsafe { raw::git_pathspec_matches_path(&*self.raw, flags.bits(), path.as_ptr()) == 1 } - } -} - -impl Binding for Pathspec { - type Raw = *mut raw::git_pathspec; - - unsafe fn from_raw(raw: *mut raw::git_pathspec) -> Pathspec { - Pathspec { raw } - } - fn raw(&self) -> *mut raw::git_pathspec { - self.raw - } -} - -impl Drop for Pathspec { - fn drop(&mut self) { - unsafe { raw::git_pathspec_free(self.raw) } - } -} - -impl<'ps> PathspecMatchList<'ps> { - fn entrycount(&self) -> usize { - unsafe { raw::git_pathspec_match_list_entrycount(&*self.raw) as usize } - } - - fn failed_entrycount(&self) -> usize { - unsafe { raw::git_pathspec_match_list_failed_entrycount(&*self.raw) as usize } - } - - /// Returns an iterator over the matching filenames in this list. - pub fn entries(&self) -> PathspecEntries<'_> { - let n = self.entrycount(); - let n = if n > 0 && self.entry(0).is_none() { - 0 - } else { - n - }; - PathspecEntries { - range: 0..n, - list: self, - } - } - - /// Get a matching filename by position. - /// - /// If this list was generated from a diff, then the return value will - /// always be `None. - pub fn entry(&self, i: usize) -> Option<&[u8]> { - unsafe { - let ptr = raw::git_pathspec_match_list_entry(&*self.raw, i as size_t); - crate::opt_bytes(self, ptr) - } - } - - /// Returns an iterator over the matching diff entries in this list. - pub fn diff_entries(&self) -> PathspecDiffEntries<'_> { - let n = self.entrycount(); - let n = if n > 0 && self.diff_entry(0).is_none() { - 0 - } else { - n - }; - PathspecDiffEntries { - range: 0..n, - list: self, - } - } - - /// Get a matching diff delta by position. - /// - /// If the list was not generated from a diff, then the return value will - /// always be `None`. - pub fn diff_entry(&self, i: usize) -> Option<DiffDelta<'_>> { - unsafe { - let ptr = raw::git_pathspec_match_list_diff_entry(&*self.raw, i as size_t); - Binding::from_raw_opt(ptr as *mut _) - } - } - - /// Returns an iterator over the non-matching entries in this list. - pub fn failed_entries(&self) -> PathspecFailedEntries<'_> { - let n = self.failed_entrycount(); - let n = if n > 0 && self.failed_entry(0).is_none() { - 0 - } else { - n - }; - PathspecFailedEntries { - range: 0..n, - list: self, - } - } - - /// Get an original pathspec string that had no matches. - pub fn failed_entry(&self, i: usize) -> Option<&[u8]> { - unsafe { - let ptr = raw::git_pathspec_match_list_failed_entry(&*self.raw, i as size_t); - crate::opt_bytes(self, ptr) - } - } -} - -impl<'ps> Binding for PathspecMatchList<'ps> { - type Raw = *mut raw::git_pathspec_match_list; - - unsafe fn from_raw(raw: *mut raw::git_pathspec_match_list) -> PathspecMatchList<'ps> { - PathspecMatchList { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_pathspec_match_list { - self.raw - } -} - -impl<'ps> Drop for PathspecMatchList<'ps> { - fn drop(&mut self) { - unsafe { raw::git_pathspec_match_list_free(self.raw) } - } -} - -impl<'list> Iterator for PathspecEntries<'list> { - type Item = &'list [u8]; - fn next(&mut self) -> Option<&'list [u8]> { - self.range.next().and_then(|i| self.list.entry(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'list> DoubleEndedIterator for PathspecEntries<'list> { - fn next_back(&mut self) -> Option<&'list [u8]> { - self.range.next_back().and_then(|i| self.list.entry(i)) - } -} -impl<'list> FusedIterator for PathspecEntries<'list> {} -impl<'list> ExactSizeIterator for PathspecEntries<'list> {} - -impl<'list> Iterator for PathspecDiffEntries<'list> { - type Item = DiffDelta<'list>; - fn next(&mut self) -> Option<DiffDelta<'list>> { - self.range.next().and_then(|i| self.list.diff_entry(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'list> DoubleEndedIterator for PathspecDiffEntries<'list> { - fn next_back(&mut self) -> Option<DiffDelta<'list>> { - self.range.next_back().and_then(|i| self.list.diff_entry(i)) - } -} -impl<'list> FusedIterator for PathspecDiffEntries<'list> {} -impl<'list> ExactSizeIterator for PathspecDiffEntries<'list> {} - -impl<'list> Iterator for PathspecFailedEntries<'list> { - type Item = &'list [u8]; - fn next(&mut self) -> Option<&'list [u8]> { - self.range.next().and_then(|i| self.list.failed_entry(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'list> DoubleEndedIterator for PathspecFailedEntries<'list> { - fn next_back(&mut self) -> Option<&'list [u8]> { - self.range - .next_back() - .and_then(|i| self.list.failed_entry(i)) - } -} -impl<'list> FusedIterator for PathspecFailedEntries<'list> {} -impl<'list> ExactSizeIterator for PathspecFailedEntries<'list> {} - -#[cfg(test)] -mod tests { - use super::Pathspec; - use crate::PathspecFlags; - use std::fs::File; - use std::path::Path; - - #[test] - fn smoke() { - let ps = Pathspec::new(["a"].iter()).unwrap(); - assert!(ps.matches_path(Path::new("a"), PathspecFlags::DEFAULT)); - assert!(ps.matches_path(Path::new("a/b"), PathspecFlags::DEFAULT)); - assert!(!ps.matches_path(Path::new("b"), PathspecFlags::DEFAULT)); - assert!(!ps.matches_path(Path::new("ab/c"), PathspecFlags::DEFAULT)); - - let (td, repo) = crate::test::repo_init(); - let list = ps.match_workdir(&repo, PathspecFlags::DEFAULT).unwrap(); - assert_eq!(list.entries().len(), 0); - assert_eq!(list.diff_entries().len(), 0); - assert_eq!(list.failed_entries().len(), 0); - - File::create(&td.path().join("a")).unwrap(); - - let list = ps - .match_workdir(&repo, crate::PathspecFlags::FIND_FAILURES) - .unwrap(); - assert_eq!(list.entries().len(), 1); - assert_eq!(list.entries().next(), Some("a".as_bytes())); - } -} diff --git a/extra/git2/src/proxy_options.rs b/extra/git2/src/proxy_options.rs deleted file mode 100644 index b19ba3a52..000000000 --- a/extra/git2/src/proxy_options.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::ffi::CString; -use std::marker; -use std::ptr; - -use crate::raw; -use crate::util::Binding; - -/// Options which can be specified to various fetch operations. -#[derive(Default)] -pub struct ProxyOptions<'a> { - url: Option<CString>, - proxy_kind: raw::git_proxy_t, - _marker: marker::PhantomData<&'a i32>, -} - -impl<'a> ProxyOptions<'a> { - /// Creates a new set of proxy options ready to be configured. - pub fn new() -> ProxyOptions<'a> { - Default::default() - } - - /// Try to auto-detect the proxy from the git configuration. - /// - /// Note that this will override `url` specified before. - pub fn auto(&mut self) -> &mut Self { - self.proxy_kind = raw::GIT_PROXY_AUTO; - self - } - - /// Specify the exact URL of the proxy to use. - /// - /// Note that this will override `auto` specified before. - pub fn url(&mut self, url: &str) -> &mut Self { - self.proxy_kind = raw::GIT_PROXY_SPECIFIED; - self.url = Some(CString::new(url).unwrap()); - self - } -} - -impl<'a> Binding for ProxyOptions<'a> { - type Raw = raw::git_proxy_options; - unsafe fn from_raw(_raw: raw::git_proxy_options) -> ProxyOptions<'a> { - panic!("can't create proxy from raw options") - } - - fn raw(&self) -> raw::git_proxy_options { - raw::git_proxy_options { - version: raw::GIT_PROXY_OPTIONS_VERSION, - kind: self.proxy_kind, - url: self.url.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null()), - credentials: None, - certificate_check: None, - payload: ptr::null_mut(), - } - } -} diff --git a/extra/git2/src/push_update.rs b/extra/git2/src/push_update.rs deleted file mode 100644 index 3f74a2506..000000000 --- a/extra/git2/src/push_update.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::util::Binding; -use crate::{raw, Oid}; -use std::marker; -use std::str; - -/// Represents an update which will be performed on the remote during push. -pub struct PushUpdate<'a> { - raw: *const raw::git_push_update, - _marker: marker::PhantomData<&'a raw::git_push_update>, -} - -impl<'a> Binding for PushUpdate<'a> { - type Raw = *const raw::git_push_update; - unsafe fn from_raw(raw: *const raw::git_push_update) -> PushUpdate<'a> { - PushUpdate { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> Self::Raw { - self.raw - } -} - -impl PushUpdate<'_> { - /// Returns the source name of the reference as a byte slice. - pub fn src_refname_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, (*self.raw).src_refname).unwrap() } - } - - /// Returns the source name of the reference. - pub fn src_refname(&self) -> Option<&str> { - str::from_utf8(self.src_refname_bytes()).ok() - } - - /// Returns the destination name of the reference as a byte slice. - pub fn dst_refname_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, (*self.raw).dst_refname).unwrap() } - } - - /// Returns the destination name of the reference. - pub fn dst_refname(&self) -> Option<&str> { - str::from_utf8(self.dst_refname_bytes()).ok() - } - - /// Returns the current target of the reference. - pub fn src(&self) -> Oid { - unsafe { Binding::from_raw(&(*self.raw).src as *const _) } - } - - /// Returns the new target for the reference. - pub fn dst(&self) -> Oid { - unsafe { Binding::from_raw(&(*self.raw).dst as *const _) } - } -} diff --git a/extra/git2/src/rebase.rs b/extra/git2/src/rebase.rs deleted file mode 100644 index 2bf8fe3e8..000000000 --- a/extra/git2/src/rebase.rs +++ /dev/null @@ -1,441 +0,0 @@ -use std::ffi::CString; -use std::{marker, mem, ptr, str}; - -use crate::build::CheckoutBuilder; -use crate::util::Binding; -use crate::{raw, Error, Index, MergeOptions, Oid, Signature}; - -/// Rebase options -/// -/// Use to tell the rebase machinery how to operate. -pub struct RebaseOptions<'cb> { - raw: raw::git_rebase_options, - rewrite_notes_ref: Option<CString>, - merge_options: Option<MergeOptions>, - checkout_options: Option<CheckoutBuilder<'cb>>, -} - -impl<'cb> Default for RebaseOptions<'cb> { - fn default() -> Self { - Self::new() - } -} - -impl<'cb> RebaseOptions<'cb> { - /// Creates a new default set of rebase options. - pub fn new() -> RebaseOptions<'cb> { - let mut opts = RebaseOptions { - raw: unsafe { mem::zeroed() }, - rewrite_notes_ref: None, - merge_options: None, - checkout_options: None, - }; - assert_eq!(unsafe { raw::git_rebase_init_options(&mut opts.raw, 1) }, 0); - opts - } - - /// Used by `Repository::rebase`, this will instruct other clients working on this - /// rebase that you want a quiet rebase experience, which they may choose to - /// provide in an application-specific manner. This has no effect upon - /// libgit2 directly, but is provided for interoperability between Git - /// tools. - pub fn quiet(&mut self, quiet: bool) -> &mut RebaseOptions<'cb> { - self.raw.quiet = quiet as i32; - self - } - - /// Used by `Repository::rebase`, this will begin an in-memory rebase, - /// which will allow callers to step through the rebase operations and - /// commit the rebased changes, but will not rewind HEAD or update the - /// repository to be in a rebasing state. This will not interfere with - /// the working directory (if there is one). - pub fn inmemory(&mut self, inmemory: bool) -> &mut RebaseOptions<'cb> { - self.raw.inmemory = inmemory as i32; - self - } - - /// Used by `finish()`, this is the name of the notes reference - /// used to rewrite notes for rebased commits when finishing the rebase; - /// if NULL, the contents of the configuration option `notes.rewriteRef` - /// is examined, unless the configuration option `notes.rewrite.rebase` - /// is set to false. If `notes.rewriteRef` is also NULL, notes will - /// not be rewritten. - pub fn rewrite_notes_ref(&mut self, rewrite_notes_ref: &str) -> &mut RebaseOptions<'cb> { - self.rewrite_notes_ref = Some(CString::new(rewrite_notes_ref).unwrap()); - self - } - - /// Options to control how trees are merged during `next()`. - pub fn merge_options(&mut self, opts: MergeOptions) -> &mut RebaseOptions<'cb> { - self.merge_options = Some(opts); - self - } - - /// Options to control how files are written during `Repository::rebase`, - /// `next()` and `abort()`. Note that a minimum strategy of - /// `GIT_CHECKOUT_SAFE` is defaulted in `init` and `next`, and a minimum - /// strategy of `GIT_CHECKOUT_FORCE` is defaulted in `abort` to match git - /// semantics. - pub fn checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut RebaseOptions<'cb> { - self.checkout_options = Some(opts); - self - } - - /// Acquire a pointer to the underlying raw options. - pub fn raw(&mut self) -> *const raw::git_rebase_options { - unsafe { - if let Some(opts) = self.merge_options.as_mut().take() { - ptr::copy_nonoverlapping(opts.raw(), &mut self.raw.merge_options, 1); - } - if let Some(opts) = self.checkout_options.as_mut() { - opts.configure(&mut self.raw.checkout_options); - } - self.raw.rewrite_notes_ref = self - .rewrite_notes_ref - .as_ref() - .map(|s| s.as_ptr()) - .unwrap_or(ptr::null()); - } - &self.raw - } -} - -/// Representation of a rebase -pub struct Rebase<'repo> { - raw: *mut raw::git_rebase, - _marker: marker::PhantomData<&'repo raw::git_rebase>, -} - -impl<'repo> Rebase<'repo> { - /// Gets the count of rebase operations that are to be applied. - pub fn len(&self) -> usize { - unsafe { raw::git_rebase_operation_entrycount(self.raw) } - } - - /// Gets the original `HEAD` ref name for merge rebases. - pub fn orig_head_name(&self) -> Option<&str> { - let name_bytes = - unsafe { crate::opt_bytes(self, raw::git_rebase_orig_head_name(self.raw)) }; - name_bytes.and_then(|s| str::from_utf8(s).ok()) - } - - /// Gets the original HEAD id for merge rebases. - pub fn orig_head_id(&self) -> Option<Oid> { - unsafe { Oid::from_raw_opt(raw::git_rebase_orig_head_id(self.raw)) } - } - - /// Gets the rebase operation specified by the given index. - pub fn nth(&mut self, n: usize) -> Option<RebaseOperation<'_>> { - unsafe { - let op = raw::git_rebase_operation_byindex(self.raw, n); - if op.is_null() { - None - } else { - Some(RebaseOperation::from_raw(op)) - } - } - } - - /// Gets the index of the rebase operation that is currently being applied. - /// If the first operation has not yet been applied (because you have called - /// `init` but not yet `next`) then this returns None. - pub fn operation_current(&mut self) -> Option<usize> { - let cur = unsafe { raw::git_rebase_operation_current(self.raw) }; - if cur == raw::GIT_REBASE_NO_OPERATION { - None - } else { - Some(cur) - } - } - - /// Gets the index produced by the last operation, which is the result of - /// `next()` and which will be committed by the next invocation of - /// `commit()`. This is useful for resolving conflicts in an in-memory - /// rebase before committing them. - /// - /// This is only applicable for in-memory rebases; for rebases within a - /// working directory, the changes were applied to the repository's index. - pub fn inmemory_index(&mut self) -> Result<Index, Error> { - let mut idx = ptr::null_mut(); - unsafe { - try_call!(raw::git_rebase_inmemory_index(&mut idx, self.raw)); - Ok(Binding::from_raw(idx)) - } - } - - /// Commits the current patch. You must have resolved any conflicts that - /// were introduced during the patch application from the `git_rebase_next` - /// invocation. To keep the author and message from the original commit leave - /// them as None - pub fn commit( - &mut self, - author: Option<&Signature<'_>>, - committer: &Signature<'_>, - message: Option<&str>, - ) -> Result<Oid, Error> { - let mut id: raw::git_oid = unsafe { mem::zeroed() }; - let message = crate::opt_cstr(message)?; - unsafe { - try_call!(raw::git_rebase_commit( - &mut id, - self.raw, - author.map(|a| a.raw()), - committer.raw(), - ptr::null(), - message - )); - Ok(Binding::from_raw(&id as *const _)) - } - } - - /// Aborts a rebase that is currently in progress, resetting the repository - /// and working directory to their state before rebase began. - pub fn abort(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_rebase_abort(self.raw)); - } - - Ok(()) - } - - /// Finishes a rebase that is currently in progress once all patches have - /// been applied. - pub fn finish(&mut self, signature: Option<&Signature<'_>>) -> Result<(), Error> { - unsafe { - try_call!(raw::git_rebase_finish(self.raw, signature.map(|s| s.raw()))); - } - - Ok(()) - } -} - -impl<'rebase> Iterator for Rebase<'rebase> { - type Item = Result<RebaseOperation<'rebase>, Error>; - - /// Performs the next rebase operation and returns the information about it. - /// If the operation is one that applies a patch (which is any operation except - /// GitRebaseOperation::Exec) then the patch will be applied and the index and - /// working directory will be updated with the changes. If there are conflicts, - /// you will need to address those before committing the changes. - fn next(&mut self) -> Option<Result<RebaseOperation<'rebase>, Error>> { - let mut out = ptr::null_mut(); - unsafe { - try_call_iter!(raw::git_rebase_next(&mut out, self.raw)); - Some(Ok(RebaseOperation::from_raw(out))) - } - } -} - -impl<'repo> Binding for Rebase<'repo> { - type Raw = *mut raw::git_rebase; - unsafe fn from_raw(raw: *mut raw::git_rebase) -> Rebase<'repo> { - Rebase { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_rebase { - self.raw - } -} - -impl<'repo> Drop for Rebase<'repo> { - fn drop(&mut self) { - unsafe { raw::git_rebase_free(self.raw) } - } -} - -/// A rebase operation -/// -/// Describes a single instruction/operation to be performed during the -/// rebase. -#[derive(Debug, PartialEq)] -pub enum RebaseOperationType { - /// The given commit is to be cherry-picked. The client should commit the - /// changes and continue if there are no conflicts. - Pick, - - /// The given commit is to be cherry-picked, but the client should prompt - /// the user to provide an updated commit message. - Reword, - - /// The given commit is to be cherry-picked, but the client should stop to - /// allow the user to edit the changes before committing them. - Edit, - - /// The given commit is to be squashed into the previous commit. The commit - /// message will be merged with the previous message. - Squash, - - /// The given commit is to be squashed into the previous commit. The commit - /// message from this commit will be discarded. - Fixup, - - /// No commit will be cherry-picked. The client should run the given command - /// and (if successful) continue. - Exec, -} - -impl RebaseOperationType { - /// Convert from the int into an enum. Returns None if invalid. - pub fn from_raw(raw: raw::git_rebase_operation_t) -> Option<RebaseOperationType> { - match raw { - raw::GIT_REBASE_OPERATION_PICK => Some(RebaseOperationType::Pick), - raw::GIT_REBASE_OPERATION_REWORD => Some(RebaseOperationType::Reword), - raw::GIT_REBASE_OPERATION_EDIT => Some(RebaseOperationType::Edit), - raw::GIT_REBASE_OPERATION_SQUASH => Some(RebaseOperationType::Squash), - raw::GIT_REBASE_OPERATION_FIXUP => Some(RebaseOperationType::Fixup), - raw::GIT_REBASE_OPERATION_EXEC => Some(RebaseOperationType::Exec), - _ => None, - } - } -} - -/// A rebase operation -/// -/// Describes a single instruction/operation to be performed during the -/// rebase. -#[derive(Debug)] -pub struct RebaseOperation<'rebase> { - raw: *const raw::git_rebase_operation, - _marker: marker::PhantomData<Rebase<'rebase>>, -} - -impl<'rebase> RebaseOperation<'rebase> { - /// The type of rebase operation - pub fn kind(&self) -> Option<RebaseOperationType> { - unsafe { RebaseOperationType::from_raw((*self.raw).kind) } - } - - /// The commit ID being cherry-picked. This will be populated for all - /// operations except those of type `GIT_REBASE_OPERATION_EXEC`. - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(&(*self.raw).id as *const _) } - } - - ///The executable the user has requested be run. This will only - /// be populated for operations of type RebaseOperationType::Exec - pub fn exec(&self) -> Option<&str> { - unsafe { str::from_utf8(crate::opt_bytes(self, (*self.raw).exec).unwrap()).ok() } - } -} - -impl<'rebase> Binding for RebaseOperation<'rebase> { - type Raw = *const raw::git_rebase_operation; - unsafe fn from_raw(raw: *const raw::git_rebase_operation) -> RebaseOperation<'rebase> { - RebaseOperation { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *const raw::git_rebase_operation { - self.raw - } -} - -#[cfg(test)] -mod tests { - use crate::{RebaseOperationType, RebaseOptions, Signature}; - use std::{fs, path}; - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let head_target = repo.head().unwrap().target().unwrap(); - let tip = repo.find_commit(head_target).unwrap(); - let sig = tip.author(); - let tree = tip.tree().unwrap(); - - // We just want to see the iteration work so we can create commits with - // no changes - let c1 = repo - .commit(Some("refs/heads/main"), &sig, &sig, "foo", &tree, &[&tip]) - .unwrap(); - let c1 = repo.find_commit(c1).unwrap(); - let c2 = repo - .commit(Some("refs/heads/main"), &sig, &sig, "foo", &tree, &[&c1]) - .unwrap(); - - let head = repo.find_reference("refs/heads/main").unwrap(); - let branch = repo.reference_to_annotated_commit(&head).unwrap(); - let upstream = repo.find_annotated_commit(tip.id()).unwrap(); - let mut rebase = repo - .rebase(Some(&branch), Some(&upstream), None, None) - .unwrap(); - - assert_eq!(Some("refs/heads/main"), rebase.orig_head_name()); - assert_eq!(Some(c2), rebase.orig_head_id()); - - assert_eq!(rebase.len(), 2); - { - let op = rebase.next().unwrap().unwrap(); - assert_eq!(op.kind(), Some(RebaseOperationType::Pick)); - assert_eq!(op.id(), c1.id()); - } - { - let op = rebase.next().unwrap().unwrap(); - assert_eq!(op.kind(), Some(RebaseOperationType::Pick)); - assert_eq!(op.id(), c2); - } - { - let op = rebase.next(); - assert!(op.is_none()); - } - } - - #[test] - fn keeping_original_author_msg() { - let (td, repo) = crate::test::repo_init(); - let head_target = repo.head().unwrap().target().unwrap(); - let tip = repo.find_commit(head_target).unwrap(); - let sig = Signature::now("testname", "testemail").unwrap(); - let mut index = repo.index().unwrap(); - - fs::File::create(td.path().join("file_a")).unwrap(); - index.add_path(path::Path::new("file_a")).unwrap(); - index.write().unwrap(); - let tree_id_a = index.write_tree().unwrap(); - let tree_a = repo.find_tree(tree_id_a).unwrap(); - let c1 = repo - .commit(Some("refs/heads/main"), &sig, &sig, "A", &tree_a, &[&tip]) - .unwrap(); - let c1 = repo.find_commit(c1).unwrap(); - - fs::File::create(td.path().join("file_b")).unwrap(); - index.add_path(path::Path::new("file_b")).unwrap(); - index.write().unwrap(); - let tree_id_b = index.write_tree().unwrap(); - let tree_b = repo.find_tree(tree_id_b).unwrap(); - let c2 = repo - .commit(Some("refs/heads/main"), &sig, &sig, "B", &tree_b, &[&c1]) - .unwrap(); - - let branch = repo.find_annotated_commit(c2).unwrap(); - let upstream = repo.find_annotated_commit(tip.id()).unwrap(); - let mut opts: RebaseOptions<'_> = Default::default(); - let mut rebase = repo - .rebase(Some(&branch), Some(&upstream), None, Some(&mut opts)) - .unwrap(); - - assert_eq!(rebase.len(), 2); - - { - rebase.next().unwrap().unwrap(); - let id = rebase.commit(None, &sig, None).unwrap(); - let commit = repo.find_commit(id).unwrap(); - assert_eq!(commit.message(), Some("A")); - assert_eq!(commit.author().name(), Some("testname")); - assert_eq!(commit.author().email(), Some("testemail")); - } - - { - rebase.next().unwrap().unwrap(); - let id = rebase.commit(None, &sig, None).unwrap(); - let commit = repo.find_commit(id).unwrap(); - assert_eq!(commit.message(), Some("B")); - assert_eq!(commit.author().name(), Some("testname")); - assert_eq!(commit.author().email(), Some("testemail")); - } - rebase.finish(None).unwrap(); - } -} diff --git a/extra/git2/src/reference.rs b/extra/git2/src/reference.rs deleted file mode 100644 index 92eb18c63..000000000 --- a/extra/git2/src/reference.rs +++ /dev/null @@ -1,586 +0,0 @@ -use std::cmp::Ordering; -use std::ffi::CString; -use std::marker; -use std::mem; -use std::ptr; -use std::str; - -use crate::object::CastOrPanic; -use crate::util::{c_cmp_to_ordering, Binding}; -use crate::{ - call, raw, Blob, Commit, Error, Object, ObjectType, Oid, ReferenceFormat, ReferenceType, - Repository, Tag, Tree, -}; - -// Not in the public header files (yet?), but a hard limit used by libgit2 -// internally -const GIT_REFNAME_MAX: usize = 1024; - -struct Refdb<'repo>(&'repo Repository); - -/// A structure to represent a git [reference][1]. -/// -/// [1]: http://git-scm.com/book/en/Git-Internals-Git-References -pub struct Reference<'repo> { - raw: *mut raw::git_reference, - _marker: marker::PhantomData<Refdb<'repo>>, -} - -/// An iterator over the references in a repository. -pub struct References<'repo> { - raw: *mut raw::git_reference_iterator, - _marker: marker::PhantomData<Refdb<'repo>>, -} - -/// An iterator over the names of references in a repository. -pub struct ReferenceNames<'repo, 'references> { - inner: &'references mut References<'repo>, -} - -impl<'repo> Reference<'repo> { - /// Ensure the reference name is well-formed. - /// - /// Validation is performed as if [`ReferenceFormat::ALLOW_ONELEVEL`] - /// was given to [`Reference::normalize_name`]. No normalization is - /// performed, however. - /// - /// ```rust - /// use git2::Reference; - /// - /// assert!(Reference::is_valid_name("HEAD")); - /// assert!(Reference::is_valid_name("refs/heads/main")); - /// - /// // But: - /// assert!(!Reference::is_valid_name("main")); - /// assert!(!Reference::is_valid_name("refs/heads/*")); - /// assert!(!Reference::is_valid_name("foo//bar")); - /// ``` - /// - /// [`ReferenceFormat::ALLOW_ONELEVEL`]: - /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL - /// [`Reference::normalize_name`]: struct.Reference#method.normalize_name - pub fn is_valid_name(refname: &str) -> bool { - crate::init(); - let refname = CString::new(refname).unwrap(); - let mut valid: libc::c_int = 0; - unsafe { - call::c_try(raw::git_reference_name_is_valid( - &mut valid, - refname.as_ptr(), - )) - .unwrap(); - } - valid == 1 - } - - /// Normalize reference name and check validity. - /// - /// This will normalize the reference name by collapsing runs of adjacent - /// slashes between name components into a single slash. It also validates - /// the name according to the following rules: - /// - /// 1. If [`ReferenceFormat::ALLOW_ONELEVEL`] is given, the name may - /// contain only capital letters and underscores, and must begin and end - /// with a letter. (e.g. "HEAD", "ORIG_HEAD"). - /// 2. The flag [`ReferenceFormat::REFSPEC_SHORTHAND`] has an effect - /// only when combined with [`ReferenceFormat::ALLOW_ONELEVEL`]. If - /// it is given, "shorthand" branch names (i.e. those not prefixed by - /// `refs/`, but consisting of a single word without `/` separators) - /// become valid. For example, "main" would be accepted. - /// 3. If [`ReferenceFormat::REFSPEC_PATTERN`] is given, the name may - /// contain a single `*` in place of a full pathname component (e.g. - /// `foo/*/bar`, `foo/bar*`). - /// 4. Names prefixed with "refs/" can be almost anything. You must avoid - /// the characters '~', '^', ':', '\\', '?', '[', and '*', and the - /// sequences ".." and "@{" which have special meaning to revparse. - /// - /// If the reference passes validation, it is returned in normalized form, - /// otherwise an [`Error`] with [`ErrorCode::InvalidSpec`] is returned. - /// - /// ```rust - /// use git2::{Reference, ReferenceFormat}; - /// - /// assert_eq!( - /// Reference::normalize_name( - /// "foo//bar", - /// ReferenceFormat::NORMAL - /// ) - /// .unwrap(), - /// "foo/bar".to_owned() - /// ); - /// - /// assert_eq!( - /// Reference::normalize_name( - /// "HEAD", - /// ReferenceFormat::ALLOW_ONELEVEL - /// ) - /// .unwrap(), - /// "HEAD".to_owned() - /// ); - /// - /// assert_eq!( - /// Reference::normalize_name( - /// "refs/heads/*", - /// ReferenceFormat::REFSPEC_PATTERN - /// ) - /// .unwrap(), - /// "refs/heads/*".to_owned() - /// ); - /// - /// assert_eq!( - /// Reference::normalize_name( - /// "main", - /// ReferenceFormat::ALLOW_ONELEVEL | ReferenceFormat::REFSPEC_SHORTHAND - /// ) - /// .unwrap(), - /// "main".to_owned() - /// ); - /// ``` - /// - /// [`ReferenceFormat::ALLOW_ONELEVEL`]: - /// struct.ReferenceFormat#associatedconstant.ALLOW_ONELEVEL - /// [`ReferenceFormat::REFSPEC_SHORTHAND`]: - /// struct.ReferenceFormat#associatedconstant.REFSPEC_SHORTHAND - /// [`ReferenceFormat::REFSPEC_PATTERN`]: - /// struct.ReferenceFormat#associatedconstant.REFSPEC_PATTERN - /// [`Error`]: struct.Error - /// [`ErrorCode::InvalidSpec`]: enum.ErrorCode#variant.InvalidSpec - pub fn normalize_name(refname: &str, flags: ReferenceFormat) -> Result<String, Error> { - crate::init(); - let mut dst = [0u8; GIT_REFNAME_MAX]; - let refname = CString::new(refname)?; - unsafe { - try_call!(raw::git_reference_normalize_name( - dst.as_mut_ptr() as *mut libc::c_char, - dst.len() as libc::size_t, - refname, - flags.bits() - )); - let s = &dst[..dst.iter().position(|&a| a == 0).unwrap()]; - Ok(str::from_utf8(s).unwrap().to_owned()) - } - } - - /// Get access to the underlying raw pointer. - pub fn raw(&self) -> *mut raw::git_reference { - self.raw - } - - /// Delete an existing reference. - /// - /// This method works for both direct and symbolic references. The reference - /// will be immediately removed on disk. - /// - /// This function will return an error if the reference has changed from the - /// time it was looked up. - pub fn delete(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_reference_delete(self.raw)); - } - Ok(()) - } - - /// Check if a reference is a local branch. - pub fn is_branch(&self) -> bool { - unsafe { raw::git_reference_is_branch(&*self.raw) == 1 } - } - - /// Check if a reference is a note. - pub fn is_note(&self) -> bool { - unsafe { raw::git_reference_is_note(&*self.raw) == 1 } - } - - /// Check if a reference is a remote tracking branch - pub fn is_remote(&self) -> bool { - unsafe { raw::git_reference_is_remote(&*self.raw) == 1 } - } - - /// Check if a reference is a tag - pub fn is_tag(&self) -> bool { - unsafe { raw::git_reference_is_tag(&*self.raw) == 1 } - } - - /// Get the reference type of a reference. - /// - /// If the type is unknown, then `None` is returned. - pub fn kind(&self) -> Option<ReferenceType> { - ReferenceType::from_raw(unsafe { raw::git_reference_type(&*self.raw) }) - } - - /// Get the full name of a reference. - /// - /// Returns `None` if the name is not valid utf-8. - pub fn name(&self) -> Option<&str> { - str::from_utf8(self.name_bytes()).ok() - } - - /// Get the full name of a reference. - pub fn name_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_reference_name(&*self.raw)).unwrap() } - } - - /// Get the full shorthand of a reference. - /// - /// This will transform the reference name into a name "human-readable" - /// version. If no shortname is appropriate, it will return the full name. - /// - /// Returns `None` if the shorthand is not valid utf-8. - pub fn shorthand(&self) -> Option<&str> { - str::from_utf8(self.shorthand_bytes()).ok() - } - - /// Get the full shorthand of a reference. - pub fn shorthand_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_reference_shorthand(&*self.raw)).unwrap() } - } - - /// Get the OID pointed to by a direct reference. - /// - /// Only available if the reference is direct (i.e. an object id reference, - /// not a symbolic one). - pub fn target(&self) -> Option<Oid> { - unsafe { Binding::from_raw_opt(raw::git_reference_target(&*self.raw)) } - } - - /// Return the peeled OID target of this reference. - /// - /// This peeled OID only applies to direct references that point to a hard - /// Tag object: it is the result of peeling such Tag. - pub fn target_peel(&self) -> Option<Oid> { - unsafe { Binding::from_raw_opt(raw::git_reference_target_peel(&*self.raw)) } - } - - /// Get full name to the reference pointed to by a symbolic reference. - /// - /// May return `None` if the reference is either not symbolic or not a - /// valid utf-8 string. - pub fn symbolic_target(&self) -> Option<&str> { - self.symbolic_target_bytes() - .and_then(|s| str::from_utf8(s).ok()) - } - - /// Get full name to the reference pointed to by a symbolic reference. - /// - /// Only available if the reference is symbolic. - pub fn symbolic_target_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_reference_symbolic_target(&*self.raw)) } - } - - /// Resolve a symbolic reference to a direct reference. - /// - /// This method iteratively peels a symbolic reference until it resolves to - /// a direct reference to an OID. - /// - /// If a direct reference is passed as an argument, a copy of that - /// reference is returned. - pub fn resolve(&self) -> Result<Reference<'repo>, Error> { - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_reference_resolve(&mut raw, &*self.raw)); - Ok(Binding::from_raw(raw)) - } - } - - /// Peel a reference to an object - /// - /// This method recursively peels the reference until it reaches - /// an object of the specified type. - pub fn peel(&self, kind: ObjectType) -> Result<Object<'repo>, Error> { - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_reference_peel(&mut raw, self.raw, kind)); - Ok(Binding::from_raw(raw)) - } - } - - /// Peel a reference to a blob - /// - /// This method recursively peels the reference until it reaches - /// a blob. - pub fn peel_to_blob(&self) -> Result<Blob<'repo>, Error> { - Ok(self.peel(ObjectType::Blob)?.cast_or_panic(ObjectType::Blob)) - } - - /// Peel a reference to a commit - /// - /// This method recursively peels the reference until it reaches - /// a commit. - pub fn peel_to_commit(&self) -> Result<Commit<'repo>, Error> { - Ok(self - .peel(ObjectType::Commit)? - .cast_or_panic(ObjectType::Commit)) - } - - /// Peel a reference to a tree - /// - /// This method recursively peels the reference until it reaches - /// a tree. - pub fn peel_to_tree(&self) -> Result<Tree<'repo>, Error> { - Ok(self.peel(ObjectType::Tree)?.cast_or_panic(ObjectType::Tree)) - } - - /// Peel a reference to a tag - /// - /// This method recursively peels the reference until it reaches - /// a tag. - pub fn peel_to_tag(&self) -> Result<Tag<'repo>, Error> { - Ok(self.peel(ObjectType::Tag)?.cast_or_panic(ObjectType::Tag)) - } - - /// Rename an existing reference. - /// - /// This method works for both direct and symbolic references. - /// - /// If the force flag is not enabled, and there's already a reference with - /// the given name, the renaming will fail. - pub fn rename( - &mut self, - new_name: &str, - force: bool, - msg: &str, - ) -> Result<Reference<'repo>, Error> { - let mut raw = ptr::null_mut(); - let new_name = CString::new(new_name)?; - let msg = CString::new(msg)?; - unsafe { - try_call!(raw::git_reference_rename( - &mut raw, self.raw, new_name, force, msg - )); - Ok(Binding::from_raw(raw)) - } - } - - /// Conditionally create a new reference with the same name as the given - /// reference but a different OID target. The reference must be a direct - /// reference, otherwise this will fail. - /// - /// The new reference will be written to disk, overwriting the given - /// reference. - pub fn set_target(&mut self, id: Oid, reflog_msg: &str) -> Result<Reference<'repo>, Error> { - let mut raw = ptr::null_mut(); - let msg = CString::new(reflog_msg)?; - unsafe { - try_call!(raw::git_reference_set_target( - &mut raw, - self.raw, - id.raw(), - msg - )); - Ok(Binding::from_raw(raw)) - } - } - - /// Create a new reference with the same name as the given reference but a - /// different symbolic target. The reference must be a symbolic reference, - /// otherwise this will fail. - /// - /// The new reference will be written to disk, overwriting the given - /// reference. - /// - /// The target name will be checked for validity. See - /// [`Repository::reference_symbolic`] for rules about valid names. - /// - /// 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. - pub fn symbolic_set_target( - &mut self, - target: &str, - reflog_msg: &str, - ) -> Result<Reference<'repo>, Error> { - let mut raw = ptr::null_mut(); - let target = CString::new(target)?; - let msg = CString::new(reflog_msg)?; - unsafe { - try_call!(raw::git_reference_symbolic_set_target( - &mut raw, self.raw, target, msg - )); - Ok(Binding::from_raw(raw)) - } - } -} - -impl<'repo> PartialOrd for Reference<'repo> { - fn partial_cmp(&self, other: &Reference<'repo>) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl<'repo> Ord for Reference<'repo> { - fn cmp(&self, other: &Reference<'repo>) -> Ordering { - c_cmp_to_ordering(unsafe { raw::git_reference_cmp(&*self.raw, &*other.raw) }) - } -} - -impl<'repo> PartialEq for Reference<'repo> { - fn eq(&self, other: &Reference<'repo>) -> bool { - self.cmp(other) == Ordering::Equal - } -} - -impl<'repo> Eq for Reference<'repo> {} - -impl<'repo> Binding for Reference<'repo> { - type Raw = *mut raw::git_reference; - unsafe fn from_raw(raw: *mut raw::git_reference) -> Reference<'repo> { - Reference { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_reference { - self.raw - } -} - -impl<'repo> Drop for Reference<'repo> { - fn drop(&mut self) { - unsafe { raw::git_reference_free(self.raw) } - } -} - -impl<'repo> References<'repo> { - /// Consumes a `References` iterator to create an iterator over just the - /// name of some references. - /// - /// This is more efficient if only the names are desired of references as - /// the references themselves don't have to be allocated and deallocated. - /// - /// The returned iterator will yield strings as opposed to a `Reference`. - pub fn names<'a>(&'a mut self) -> ReferenceNames<'repo, 'a> { - ReferenceNames { inner: self } - } -} - -impl<'repo> Binding for References<'repo> { - type Raw = *mut raw::git_reference_iterator; - unsafe fn from_raw(raw: *mut raw::git_reference_iterator) -> References<'repo> { - References { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_reference_iterator { - self.raw - } -} - -impl<'repo> Iterator for References<'repo> { - type Item = Result<Reference<'repo>, Error>; - fn next(&mut self) -> Option<Result<Reference<'repo>, Error>> { - let mut out = ptr::null_mut(); - unsafe { - try_call_iter!(raw::git_reference_next(&mut out, self.raw)); - Some(Ok(Binding::from_raw(out))) - } - } -} - -impl<'repo> Drop for References<'repo> { - fn drop(&mut self) { - unsafe { raw::git_reference_iterator_free(self.raw) } - } -} - -impl<'repo, 'references> Iterator for ReferenceNames<'repo, 'references> { - type Item = Result<&'references str, Error>; - fn next(&mut self) -> Option<Result<&'references str, Error>> { - let mut out = ptr::null(); - unsafe { - try_call_iter!(raw::git_reference_next_name(&mut out, self.inner.raw)); - let bytes = crate::opt_bytes(self, out).unwrap(); - let s = str::from_utf8(bytes).unwrap(); - Some(Ok(mem::transmute::<&str, &'references str>(s))) - } - } -} - -#[cfg(test)] -mod tests { - use crate::{ObjectType, Reference, ReferenceType}; - - #[test] - fn is_valid_name() { - assert!(Reference::is_valid_name("refs/foo")); - assert!(!Reference::is_valid_name("foo")); - assert!(Reference::is_valid_name("FOO_BAR")); - - assert!(!Reference::is_valid_name("foo")); - assert!(!Reference::is_valid_name("_FOO_BAR")); - } - - #[test] - #[should_panic] - fn is_valid_name_for_invalid_ref() { - Reference::is_valid_name("ab\012"); - } - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let mut head = repo.head().unwrap(); - assert!(head.is_branch()); - assert!(!head.is_remote()); - assert!(!head.is_tag()); - assert!(!head.is_note()); - - // HEAD is a symbolic reference but git_repository_head resolves it - // so it is a GIT_REFERENCE_DIRECT. - assert_eq!(head.kind().unwrap(), ReferenceType::Direct); - - assert!(head == repo.head().unwrap()); - assert_eq!(head.name(), Some("refs/heads/main")); - - assert!(head == repo.find_reference("refs/heads/main").unwrap()); - assert_eq!( - repo.refname_to_id("refs/heads/main").unwrap(), - head.target().unwrap() - ); - - assert!(head.symbolic_target().is_none()); - assert!(head.target_peel().is_none()); - - assert_eq!(head.shorthand(), Some("main")); - assert!(head.resolve().unwrap() == head); - - let mut tag1 = repo - .reference("refs/tags/tag1", head.target().unwrap(), false, "test") - .unwrap(); - assert!(tag1.is_tag()); - assert_eq!(tag1.kind().unwrap(), ReferenceType::Direct); - - let peeled_commit = tag1.peel(ObjectType::Commit).unwrap(); - assert_eq!(ObjectType::Commit, peeled_commit.kind().unwrap()); - assert_eq!(tag1.target().unwrap(), peeled_commit.id()); - - tag1.delete().unwrap(); - - let mut sym1 = repo - .reference_symbolic("refs/tags/tag1", "refs/heads/main", false, "test") - .unwrap(); - assert_eq!(sym1.kind().unwrap(), ReferenceType::Symbolic); - let mut sym2 = repo - .reference_symbolic("refs/tags/tag2", "refs/heads/main", false, "test") - .unwrap() - .symbolic_set_target("refs/tags/tag1", "test") - .unwrap(); - assert_eq!(sym2.kind().unwrap(), ReferenceType::Symbolic); - assert_eq!(sym2.symbolic_target().unwrap(), "refs/tags/tag1"); - sym2.delete().unwrap(); - sym1.delete().unwrap(); - - { - assert!(repo.references().unwrap().count() == 1); - assert!(repo.references().unwrap().next().unwrap().unwrap() == head); - let mut names = repo.references().unwrap(); - let mut names = names.names(); - assert_eq!(names.next().unwrap().unwrap(), "refs/heads/main"); - assert!(names.next().is_none()); - assert!(repo.references_glob("foo").unwrap().count() == 0); - assert!(repo.references_glob("refs/heads/*").unwrap().count() == 1); - } - - let mut head = head.rename("refs/foo", true, "test").unwrap(); - head.delete().unwrap(); - } -} diff --git a/extra/git2/src/reflog.rs b/extra/git2/src/reflog.rs deleted file mode 100644 index bbd2140ab..000000000 --- a/extra/git2/src/reflog.rs +++ /dev/null @@ -1,196 +0,0 @@ -use libc::size_t; -use std::iter::FusedIterator; -use std::marker; -use std::ops::Range; -use std::str; - -use crate::util::Binding; -use crate::{raw, signature, Error, Oid, Signature}; - -/// A reference log of a git repository. -pub struct Reflog { - raw: *mut raw::git_reflog, -} - -/// An entry inside the reflog of a repository -pub struct ReflogEntry<'reflog> { - raw: *const raw::git_reflog_entry, - _marker: marker::PhantomData<&'reflog Reflog>, -} - -/// An iterator over the entries inside of a reflog. -pub struct ReflogIter<'reflog> { - range: Range<usize>, - reflog: &'reflog Reflog, -} - -impl Reflog { - /// Add a new entry to the in-memory reflog. - pub fn append( - &mut self, - new_oid: Oid, - committer: &Signature<'_>, - msg: Option<&str>, - ) -> Result<(), Error> { - let msg = crate::opt_cstr(msg)?; - unsafe { - try_call!(raw::git_reflog_append( - self.raw, - new_oid.raw(), - committer.raw(), - msg - )); - } - Ok(()) - } - - /// Remove an entry from the reflog by its index - /// - /// To ensure there's no gap in the log history, set rewrite_previous_entry - /// param value to `true`. When deleting entry n, member old_oid of entry - /// n-1 (if any) will be updated with the value of member new_oid of entry - /// n+1. - pub fn remove(&mut self, i: usize, rewrite_previous_entry: bool) -> Result<(), Error> { - unsafe { - try_call!(raw::git_reflog_drop( - self.raw, - i as size_t, - rewrite_previous_entry - )); - } - Ok(()) - } - - /// Lookup an entry by its index - /// - /// Requesting the reflog entry with an index of 0 (zero) will return the - /// most recently created entry. - pub fn get(&self, i: usize) -> Option<ReflogEntry<'_>> { - unsafe { - let ptr = raw::git_reflog_entry_byindex(self.raw, i as size_t); - Binding::from_raw_opt(ptr) - } - } - - /// Get the number of log entries in a reflog - pub fn len(&self) -> usize { - unsafe { raw::git_reflog_entrycount(self.raw) as usize } - } - - /// Return `true ` is there is no log entry in a reflog - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Get an iterator to all entries inside of this reflog - pub fn iter(&self) -> ReflogIter<'_> { - ReflogIter { - range: 0..self.len(), - reflog: self, - } - } - - /// Write an existing in-memory reflog object back to disk using an atomic - /// file lock. - pub fn write(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_reflog_write(self.raw)); - } - Ok(()) - } -} - -impl Binding for Reflog { - type Raw = *mut raw::git_reflog; - - unsafe fn from_raw(raw: *mut raw::git_reflog) -> Reflog { - Reflog { raw } - } - fn raw(&self) -> *mut raw::git_reflog { - self.raw - } -} - -impl Drop for Reflog { - fn drop(&mut self) { - unsafe { raw::git_reflog_free(self.raw) } - } -} - -impl<'reflog> ReflogEntry<'reflog> { - /// Get the committer of this entry - pub fn committer(&self) -> Signature<'_> { - unsafe { - let ptr = raw::git_reflog_entry_committer(self.raw); - signature::from_raw_const(self, ptr) - } - } - - /// Get the new oid - pub fn id_new(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_reflog_entry_id_new(self.raw)) } - } - - /// Get the old oid - pub fn id_old(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_reflog_entry_id_old(self.raw)) } - } - - /// Get the log message, returning `None` on invalid UTF-8. - pub fn message(&self) -> Option<&str> { - self.message_bytes().and_then(|s| str::from_utf8(s).ok()) - } - - /// Get the log message as a byte array. - pub fn message_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_reflog_entry_message(self.raw)) } - } -} - -impl<'reflog> Binding for ReflogEntry<'reflog> { - type Raw = *const raw::git_reflog_entry; - - unsafe fn from_raw(raw: *const raw::git_reflog_entry) -> ReflogEntry<'reflog> { - ReflogEntry { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *const raw::git_reflog_entry { - self.raw - } -} - -impl<'reflog> Iterator for ReflogIter<'reflog> { - type Item = ReflogEntry<'reflog>; - fn next(&mut self) -> Option<ReflogEntry<'reflog>> { - self.range.next().and_then(|i| self.reflog.get(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'reflog> DoubleEndedIterator for ReflogIter<'reflog> { - fn next_back(&mut self) -> Option<ReflogEntry<'reflog>> { - self.range.next_back().and_then(|i| self.reflog.get(i)) - } -} -impl<'reflog> FusedIterator for ReflogIter<'reflog> {} -impl<'reflog> ExactSizeIterator for ReflogIter<'reflog> {} - -#[cfg(test)] -mod tests { - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let mut reflog = repo.reflog("HEAD").unwrap(); - assert_eq!(reflog.iter().len(), 1); - reflog.write().unwrap(); - - let entry = reflog.iter().next().unwrap(); - assert!(entry.message().is_some()); - - repo.reflog_rename("HEAD", "refs/heads/foo").unwrap(); - repo.reflog_delete("refs/heads/foo").unwrap(); - } -} diff --git a/extra/git2/src/refspec.rs b/extra/git2/src/refspec.rs deleted file mode 100644 index 3f62e991c..000000000 --- a/extra/git2/src/refspec.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::ffi::CString; -use std::marker; -use std::str; - -use crate::util::Binding; -use crate::{raw, Buf, Direction, Error}; - -/// A structure to represent a git [refspec][1]. -/// -/// Refspecs are currently mainly accessed/created through a `Remote`. -/// -/// [1]: http://git-scm.com/book/en/Git-Internals-The-Refspec -pub struct Refspec<'remote> { - raw: *const raw::git_refspec, - _marker: marker::PhantomData<&'remote raw::git_remote>, -} - -impl<'remote> Refspec<'remote> { - /// Get the refspec's direction. - pub fn direction(&self) -> Direction { - match unsafe { raw::git_refspec_direction(self.raw) } { - raw::GIT_DIRECTION_FETCH => Direction::Fetch, - raw::GIT_DIRECTION_PUSH => Direction::Push, - n => panic!("unknown refspec direction: {}", n), - } - } - - /// Get the destination specifier. - /// - /// If the destination is not utf-8, None is returned. - pub fn dst(&self) -> Option<&str> { - str::from_utf8(self.dst_bytes()).ok() - } - - /// Get the destination specifier, in bytes. - pub fn dst_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_refspec_dst(self.raw)).unwrap() } - } - - /// Check if a refspec's destination descriptor matches a reference - pub fn dst_matches(&self, refname: &str) -> bool { - let refname = CString::new(refname).unwrap(); - unsafe { raw::git_refspec_dst_matches(self.raw, refname.as_ptr()) == 1 } - } - - /// Get the source specifier. - /// - /// If the source is not utf-8, None is returned. - pub fn src(&self) -> Option<&str> { - str::from_utf8(self.src_bytes()).ok() - } - - /// Get the source specifier, in bytes. - pub fn src_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_refspec_src(self.raw)).unwrap() } - } - - /// Check if a refspec's source descriptor matches a reference - pub fn src_matches(&self, refname: &str) -> bool { - let refname = CString::new(refname).unwrap(); - unsafe { raw::git_refspec_src_matches(self.raw, refname.as_ptr()) == 1 } - } - - /// Get the force update setting. - pub fn is_force(&self) -> bool { - unsafe { raw::git_refspec_force(self.raw) == 1 } - } - - /// Get the refspec's string. - /// - /// Returns None if the string is not valid utf8. - pub fn str(&self) -> Option<&str> { - str::from_utf8(self.bytes()).ok() - } - - /// Get the refspec's string as a byte array - pub fn bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_refspec_string(self.raw)).unwrap() } - } - - /// Transform a reference to its target following the refspec's rules - pub fn transform(&self, name: &str) -> Result<Buf, Error> { - let name = CString::new(name).unwrap(); - unsafe { - let buf = Buf::new(); - try_call!(raw::git_refspec_transform( - buf.raw(), - self.raw, - name.as_ptr() - )); - Ok(buf) - } - } - - /// Transform a target reference to its source reference following the refspec's rules - pub fn rtransform(&self, name: &str) -> Result<Buf, Error> { - let name = CString::new(name).unwrap(); - unsafe { - let buf = Buf::new(); - try_call!(raw::git_refspec_rtransform( - buf.raw(), - self.raw, - name.as_ptr() - )); - Ok(buf) - } - } -} - -impl<'remote> Binding for Refspec<'remote> { - type Raw = *const raw::git_refspec; - - unsafe fn from_raw(raw: *const raw::git_refspec) -> Refspec<'remote> { - Refspec { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *const raw::git_refspec { - self.raw - } -} diff --git a/extra/git2/src/remote.rs b/extra/git2/src/remote.rs deleted file mode 100644 index c8f5a935a..000000000 --- a/extra/git2/src/remote.rs +++ /dev/null @@ -1,1123 +0,0 @@ -use libc; -use raw::git_strarray; -use std::iter::FusedIterator; -use std::marker; -use std::mem; -use std::ops::Range; -use std::ptr; -use std::slice; -use std::str; -use std::{ffi::CString, os::raw::c_char}; - -use crate::string_array::StringArray; -use crate::util::Binding; -use crate::{call, raw, Buf, Direction, Error, FetchPrune, Oid, ProxyOptions, Refspec}; -use crate::{AutotagOption, Progress, RemoteCallbacks, Repository}; - -/// A structure representing a [remote][1] of a git repository. -/// -/// [1]: http://git-scm.com/book/en/Git-Basics-Working-with-Remotes -/// -/// The lifetime is the lifetime of the repository that it is attached to. The -/// remote is used to manage fetches and pushes as well as refspecs. -pub struct Remote<'repo> { - raw: *mut raw::git_remote, - _marker: marker::PhantomData<&'repo Repository>, -} - -/// An iterator over the refspecs that a remote contains. -pub struct Refspecs<'remote> { - range: Range<usize>, - remote: &'remote Remote<'remote>, -} - -/// Description of a reference advertised by a remote server, given out on calls -/// to `list`. -pub struct RemoteHead<'remote> { - raw: *const raw::git_remote_head, - _marker: marker::PhantomData<&'remote str>, -} - -/// Options which can be specified to various fetch operations. -pub struct FetchOptions<'cb> { - callbacks: Option<RemoteCallbacks<'cb>>, - depth: i32, - proxy: Option<ProxyOptions<'cb>>, - prune: FetchPrune, - update_fetchhead: bool, - download_tags: AutotagOption, - follow_redirects: RemoteRedirect, - custom_headers: Vec<CString>, - custom_headers_ptrs: Vec<*const c_char>, -} - -/// Options to control the behavior of a git push. -pub struct PushOptions<'cb> { - callbacks: Option<RemoteCallbacks<'cb>>, - proxy: Option<ProxyOptions<'cb>>, - pb_parallelism: u32, - follow_redirects: RemoteRedirect, - custom_headers: Vec<CString>, - custom_headers_ptrs: Vec<*const c_char>, -} - -/// Holds callbacks for a connection to a `Remote`. Disconnects when dropped -pub struct RemoteConnection<'repo, 'connection, 'cb> { - _callbacks: Box<RemoteCallbacks<'cb>>, - _proxy: ProxyOptions<'cb>, - remote: &'connection mut Remote<'repo>, -} - -/// Remote redirection settings; whether redirects to another host are -/// permitted. -/// -/// By default, git will follow a redirect on the initial request -/// (`/info/refs`), but not subsequent requests. -pub enum RemoteRedirect { - /// Do not follow any off-site redirects at any stage of the fetch or push. - None, - /// Allow off-site redirects only upon the initial request. This is the - /// default. - Initial, - /// Allow redirects at any stage in the fetch or push. - All, -} - -pub fn remote_into_raw(remote: Remote<'_>) -> *mut raw::git_remote { - let ret = remote.raw; - mem::forget(remote); - ret -} - -impl<'repo> Remote<'repo> { - /// Ensure the remote name is well-formed. - pub fn is_valid_name(remote_name: &str) -> bool { - crate::init(); - let remote_name = CString::new(remote_name).unwrap(); - let mut valid: libc::c_int = 0; - unsafe { - call::c_try(raw::git_remote_name_is_valid( - &mut valid, - remote_name.as_ptr(), - )) - .unwrap(); - } - valid == 1 - } - - /// Create a detached remote - /// - /// Create a remote with the given URL in-memory. You can use this - /// when you have a URL instead of a remote's name. - /// Contrasted with an anonymous remote, a detached remote will not - /// consider any repo configuration values. - pub fn create_detached<S: Into<Vec<u8>>>(url: S) -> Result<Remote<'repo>, Error> { - crate::init(); - let mut ret = ptr::null_mut(); - let url = CString::new(url)?; - unsafe { - try_call!(raw::git_remote_create_detached(&mut ret, url)); - Ok(Binding::from_raw(ret)) - } - } - - /// Get the remote's name. - /// - /// Returns `None` if this remote has not yet been named or if the name is - /// not valid utf-8 - pub fn name(&self) -> Option<&str> { - self.name_bytes().and_then(|s| str::from_utf8(s).ok()) - } - - /// Get the remote's name, in bytes. - /// - /// Returns `None` if this remote has not yet been named - pub fn name_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_remote_name(&*self.raw)) } - } - - /// Get the remote's URL. - /// - /// Returns `None` if the URL is not valid utf-8 - pub fn url(&self) -> Option<&str> { - str::from_utf8(self.url_bytes()).ok() - } - - /// Get the remote's URL as a byte array. - pub fn url_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_remote_url(&*self.raw)).unwrap() } - } - - /// Get the remote's pushurl. - /// - /// Returns `None` if the pushurl is not valid utf-8 - pub fn pushurl(&self) -> Option<&str> { - self.pushurl_bytes().and_then(|s| str::from_utf8(s).ok()) - } - - /// Get the remote's pushurl as a byte array. - pub fn pushurl_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_remote_pushurl(&*self.raw)) } - } - - /// Get the remote's default branch. - /// - /// The remote (or more exactly its transport) must have connected to the - /// remote repository. This default branch is available as soon as the - /// connection to the remote is initiated and it remains available after - /// disconnecting. - pub fn default_branch(&self) -> Result<Buf, Error> { - unsafe { - let buf = Buf::new(); - try_call!(raw::git_remote_default_branch(buf.raw(), self.raw)); - Ok(buf) - } - } - - /// Open a connection to a remote. - pub fn connect(&mut self, dir: Direction) -> Result<(), Error> { - // TODO: can callbacks be exposed safely? - unsafe { - try_call!(raw::git_remote_connect( - self.raw, - dir, - ptr::null(), - ptr::null(), - ptr::null() - )); - } - Ok(()) - } - - /// Open a connection to a remote with callbacks and proxy settings - /// - /// Returns a `RemoteConnection` that will disconnect once dropped - pub fn connect_auth<'connection, 'cb>( - &'connection mut self, - dir: Direction, - cb: Option<RemoteCallbacks<'cb>>, - proxy_options: Option<ProxyOptions<'cb>>, - ) -> Result<RemoteConnection<'repo, 'connection, 'cb>, Error> { - let cb = Box::new(cb.unwrap_or_else(RemoteCallbacks::new)); - let proxy_options = proxy_options.unwrap_or_else(ProxyOptions::new); - unsafe { - try_call!(raw::git_remote_connect( - self.raw, - dir, - &cb.raw(), - &proxy_options.raw(), - ptr::null() - )); - } - - Ok(RemoteConnection { - _callbacks: cb, - _proxy: proxy_options, - remote: self, - }) - } - - /// Check whether the remote is connected - pub fn connected(&mut self) -> bool { - unsafe { raw::git_remote_connected(self.raw) == 1 } - } - - /// Disconnect from the remote - pub fn disconnect(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_remote_disconnect(self.raw)); - } - Ok(()) - } - - /// Download and index the packfile - /// - /// Connect to the remote if it hasn't been done yet, negotiate with the - /// remote git which objects are missing, download and index the packfile. - /// - /// The .idx file will be created and both it and the packfile with be - /// renamed to their final name. - /// - /// The `specs` argument is a list of refspecs to use for this negotiation - /// and download. Use an empty array to use the base refspecs. - pub fn download<Str: AsRef<str> + crate::IntoCString + Clone>( - &mut self, - specs: &[Str], - opts: Option<&mut FetchOptions<'_>>, - ) -> Result<(), Error> { - let (_a, _b, arr) = crate::util::iter2cstrs(specs.iter())?; - let raw = opts.map(|o| o.raw()); - unsafe { - try_call!(raw::git_remote_download(self.raw, &arr, raw.as_ref())); - } - Ok(()) - } - - /// Cancel the operation - /// - /// At certain points in its operation, the network code checks whether the - /// operation has been canceled and if so stops the operation. - pub fn stop(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_remote_stop(self.raw)); - } - Ok(()) - } - - /// Get the number of refspecs for a remote - pub fn refspecs(&self) -> Refspecs<'_> { - let cnt = unsafe { raw::git_remote_refspec_count(&*self.raw) as usize }; - Refspecs { - range: 0..cnt, - remote: self, - } - } - - /// Get the `nth` refspec from this remote. - /// - /// The `refspecs` iterator can be used to iterate over all refspecs. - pub fn get_refspec(&self, i: usize) -> Option<Refspec<'repo>> { - unsafe { - let ptr = raw::git_remote_get_refspec(&*self.raw, i as libc::size_t); - Binding::from_raw_opt(ptr) - } - } - - /// Download new data and update tips - /// - /// Convenience function to connect to a remote, download the data, - /// disconnect and update the remote-tracking branches. - /// - /// # Examples - /// - /// Example of functionality similar to `git fetch origin/main`: - /// - /// ```no_run - /// fn fetch_origin_main(repo: git2::Repository) -> Result<(), git2::Error> { - /// repo.find_remote("origin")?.fetch(&["main"], None, None) - /// } - /// - /// let repo = git2::Repository::discover("rust").unwrap(); - /// fetch_origin_main(repo).unwrap(); - /// ``` - pub fn fetch<Str: AsRef<str> + crate::IntoCString + Clone>( - &mut self, - refspecs: &[Str], - opts: Option<&mut FetchOptions<'_>>, - reflog_msg: Option<&str>, - ) -> Result<(), Error> { - let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?; - let msg = crate::opt_cstr(reflog_msg)?; - let raw = opts.map(|o| o.raw()); - unsafe { - try_call!(raw::git_remote_fetch(self.raw, &arr, raw.as_ref(), msg)); - } - Ok(()) - } - - /// Update the tips to the new state - pub fn update_tips( - &mut self, - callbacks: Option<&mut RemoteCallbacks<'_>>, - update_fetchhead: bool, - download_tags: AutotagOption, - msg: Option<&str>, - ) -> Result<(), Error> { - let msg = crate::opt_cstr(msg)?; - let cbs = callbacks.map(|cb| cb.raw()); - unsafe { - try_call!(raw::git_remote_update_tips( - self.raw, - cbs.as_ref(), - update_fetchhead, - download_tags, - msg - )); - } - Ok(()) - } - - /// Perform a push - /// - /// Perform all the steps for a push. If no refspecs are passed then the - /// configured refspecs will be used. - /// - /// Note that you'll likely want to use `RemoteCallbacks` and set - /// `push_update_reference` to test whether all the references were pushed - /// successfully. - pub fn push<Str: AsRef<str> + crate::IntoCString + Clone>( - &mut self, - refspecs: &[Str], - opts: Option<&mut PushOptions<'_>>, - ) -> Result<(), Error> { - let (_a, _b, arr) = crate::util::iter2cstrs(refspecs.iter())?; - let raw = opts.map(|o| o.raw()); - unsafe { - try_call!(raw::git_remote_push(self.raw, &arr, raw.as_ref())); - } - Ok(()) - } - - /// Get the statistics structure that is filled in by the fetch operation. - pub fn stats(&self) -> Progress<'_> { - unsafe { Binding::from_raw(raw::git_remote_stats(self.raw)) } - } - - /// Get the remote repository's reference advertisement list. - /// - /// Get the list of references with which the server responds to a new - /// connection. - /// - /// The remote (or more exactly its transport) must have connected to the - /// remote repository. This list is available as soon as the connection to - /// the remote is initiated and it remains available after disconnecting. - pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> { - let mut size = 0; - let mut base = ptr::null_mut(); - unsafe { - try_call!(raw::git_remote_ls(&mut base, &mut size, self.raw)); - assert_eq!( - mem::size_of::<RemoteHead<'_>>(), - mem::size_of::<*const raw::git_remote_head>() - ); - let slice = slice::from_raw_parts(base as *const _, size as usize); - Ok(mem::transmute::< - &[*const raw::git_remote_head], - &[RemoteHead<'_>], - >(slice)) - } - } - - /// Prune tracking refs that are no longer present on remote - pub fn prune(&mut self, callbacks: Option<RemoteCallbacks<'_>>) -> Result<(), Error> { - let cbs = Box::new(callbacks.unwrap_or_else(RemoteCallbacks::new)); - unsafe { - try_call!(raw::git_remote_prune(self.raw, &cbs.raw())); - } - Ok(()) - } - - /// Get the remote's list of fetch refspecs - pub fn fetch_refspecs(&self) -> Result<StringArray, Error> { - unsafe { - let mut raw: raw::git_strarray = mem::zeroed(); - try_call!(raw::git_remote_get_fetch_refspecs(&mut raw, self.raw)); - Ok(StringArray::from_raw(raw)) - } - } - - /// Get the remote's list of push refspecs - pub fn push_refspecs(&self) -> Result<StringArray, Error> { - unsafe { - let mut raw: raw::git_strarray = mem::zeroed(); - try_call!(raw::git_remote_get_push_refspecs(&mut raw, self.raw)); - Ok(StringArray::from_raw(raw)) - } - } -} - -impl<'repo> Clone for Remote<'repo> { - fn clone(&self) -> Remote<'repo> { - let mut ret = ptr::null_mut(); - let rc = unsafe { call!(raw::git_remote_dup(&mut ret, self.raw)) }; - assert_eq!(rc, 0); - Remote { - raw: ret, - _marker: marker::PhantomData, - } - } -} - -impl<'repo> Binding for Remote<'repo> { - type Raw = *mut raw::git_remote; - - unsafe fn from_raw(raw: *mut raw::git_remote) -> Remote<'repo> { - Remote { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_remote { - self.raw - } -} - -impl<'repo> Drop for Remote<'repo> { - fn drop(&mut self) { - unsafe { raw::git_remote_free(self.raw) } - } -} - -impl<'repo> Iterator for Refspecs<'repo> { - type Item = Refspec<'repo>; - fn next(&mut self) -> Option<Refspec<'repo>> { - self.range.next().and_then(|i| self.remote.get_refspec(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'repo> DoubleEndedIterator for Refspecs<'repo> { - fn next_back(&mut self) -> Option<Refspec<'repo>> { - self.range - .next_back() - .and_then(|i| self.remote.get_refspec(i)) - } -} -impl<'repo> FusedIterator for Refspecs<'repo> {} -impl<'repo> ExactSizeIterator for Refspecs<'repo> {} - -#[allow(missing_docs)] // not documented in libgit2 :( -impl<'remote> RemoteHead<'remote> { - /// Flag if this is available locally. - pub fn is_local(&self) -> bool { - unsafe { (*self.raw).local != 0 } - } - - pub fn oid(&self) -> Oid { - unsafe { Binding::from_raw(&(*self.raw).oid as *const _) } - } - pub fn loid(&self) -> Oid { - unsafe { Binding::from_raw(&(*self.raw).loid as *const _) } - } - - pub fn name(&self) -> &str { - let b = unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() }; - str::from_utf8(b).unwrap() - } - - pub fn symref_target(&self) -> Option<&str> { - let b = unsafe { crate::opt_bytes(self, (*self.raw).symref_target) }; - b.map(|b| str::from_utf8(b).unwrap()) - } -} - -impl<'cb> Default for FetchOptions<'cb> { - fn default() -> Self { - Self::new() - } -} - -impl<'cb> FetchOptions<'cb> { - /// Creates a new blank set of fetch options - pub fn new() -> FetchOptions<'cb> { - FetchOptions { - callbacks: None, - proxy: None, - prune: FetchPrune::Unspecified, - update_fetchhead: true, - download_tags: AutotagOption::Unspecified, - follow_redirects: RemoteRedirect::Initial, - custom_headers: Vec::new(), - custom_headers_ptrs: Vec::new(), - depth: 0, // Not limited depth - } - } - - /// Set the callbacks to use for the fetch operation. - pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self { - self.callbacks = Some(cbs); - self - } - - /// Set the proxy options to use for the fetch operation. - pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self { - self.proxy = Some(opts); - self - } - - /// Set whether to perform a prune after the fetch. - pub fn prune(&mut self, prune: FetchPrune) -> &mut Self { - self.prune = prune; - self - } - - /// Set whether to write the results to FETCH_HEAD. - /// - /// Defaults to `true`. - pub fn update_fetchhead(&mut self, update: bool) -> &mut Self { - self.update_fetchhead = update; - self - } - - /// Set fetch depth, a value less or equal to 0 is interpreted as pull - /// everything (effectively the same as not declaring a limit depth). - - // FIXME(blyxyas): We currently don't have a test for shallow functions - // because libgit2 doesn't support local shallow clones. - // https://github.com/rust-lang/git2-rs/pull/979#issuecomment-1716299900 - pub fn depth(&mut self, depth: i32) -> &mut Self { - self.depth = depth.max(0); - self - } - - /// Set how to behave regarding tags on the remote, such as auto-downloading - /// tags for objects we're downloading or downloading all of them. - /// - /// The default is to auto-follow tags. - pub fn download_tags(&mut self, opt: AutotagOption) -> &mut Self { - self.download_tags = opt; - self - } - - /// Set remote redirection settings; whether redirects to another host are - /// permitted. - /// - /// By default, git will follow a redirect on the initial request - /// (`/info/refs`), but not subsequent requests. - pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self { - self.follow_redirects = redirect; - self - } - - /// Set extra headers for this fetch operation. - pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self { - self.custom_headers = custom_headers - .iter() - .map(|&s| CString::new(s).unwrap()) - .collect(); - self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect(); - self - } -} - -impl<'cb> Binding for FetchOptions<'cb> { - type Raw = raw::git_fetch_options; - - unsafe fn from_raw(_raw: raw::git_fetch_options) -> FetchOptions<'cb> { - panic!("unimplemented"); - } - fn raw(&self) -> raw::git_fetch_options { - raw::git_fetch_options { - version: 1, - callbacks: self - .callbacks - .as_ref() - .map(|m| m.raw()) - .unwrap_or_else(|| RemoteCallbacks::new().raw()), - proxy_opts: self - .proxy - .as_ref() - .map(|m| m.raw()) - .unwrap_or_else(|| ProxyOptions::new().raw()), - prune: crate::call::convert(&self.prune), - update_fetchhead: crate::call::convert(&self.update_fetchhead), - download_tags: crate::call::convert(&self.download_tags), - depth: self.depth, - follow_redirects: self.follow_redirects.raw(), - custom_headers: git_strarray { - count: self.custom_headers_ptrs.len(), - strings: self.custom_headers_ptrs.as_ptr() as *mut _, - }, - } - } -} - -impl<'cb> Default for PushOptions<'cb> { - fn default() -> Self { - Self::new() - } -} - -impl<'cb> PushOptions<'cb> { - /// Creates a new blank set of push options - pub fn new() -> PushOptions<'cb> { - PushOptions { - callbacks: None, - proxy: None, - pb_parallelism: 1, - follow_redirects: RemoteRedirect::Initial, - custom_headers: Vec::new(), - custom_headers_ptrs: Vec::new(), - } - } - - /// Set the callbacks to use for the push operation. - pub fn remote_callbacks(&mut self, cbs: RemoteCallbacks<'cb>) -> &mut Self { - self.callbacks = Some(cbs); - self - } - - /// Set the proxy options to use for the push operation. - pub fn proxy_options(&mut self, opts: ProxyOptions<'cb>) -> &mut Self { - self.proxy = Some(opts); - self - } - - /// If the transport being used to push to the remote requires the creation - /// of a pack file, this controls the number of worker threads used by the - /// packbuilder when creating that pack file to be sent to the remote. - /// - /// if set to 0 the packbuilder will auto-detect the number of threads to - /// create, and the default value is 1. - pub fn packbuilder_parallelism(&mut self, parallel: u32) -> &mut Self { - self.pb_parallelism = parallel; - self - } - - /// Set remote redirection settings; whether redirects to another host are - /// permitted. - /// - /// By default, git will follow a redirect on the initial request - /// (`/info/refs`), but not subsequent requests. - pub fn follow_redirects(&mut self, redirect: RemoteRedirect) -> &mut Self { - self.follow_redirects = redirect; - self - } - - /// Set extra headers for this push operation. - pub fn custom_headers(&mut self, custom_headers: &[&str]) -> &mut Self { - self.custom_headers = custom_headers - .iter() - .map(|&s| CString::new(s).unwrap()) - .collect(); - self.custom_headers_ptrs = self.custom_headers.iter().map(|s| s.as_ptr()).collect(); - self - } -} - -impl<'cb> Binding for PushOptions<'cb> { - type Raw = raw::git_push_options; - - unsafe fn from_raw(_raw: raw::git_push_options) -> PushOptions<'cb> { - panic!("unimplemented"); - } - fn raw(&self) -> raw::git_push_options { - raw::git_push_options { - version: 1, - callbacks: self - .callbacks - .as_ref() - .map(|m| m.raw()) - .unwrap_or_else(|| RemoteCallbacks::new().raw()), - proxy_opts: self - .proxy - .as_ref() - .map(|m| m.raw()) - .unwrap_or_else(|| ProxyOptions::new().raw()), - pb_parallelism: self.pb_parallelism as libc::c_uint, - follow_redirects: self.follow_redirects.raw(), - custom_headers: git_strarray { - count: self.custom_headers_ptrs.len(), - strings: self.custom_headers_ptrs.as_ptr() as *mut _, - }, - } - } -} - -impl<'repo, 'connection, 'cb> RemoteConnection<'repo, 'connection, 'cb> { - /// Check whether the remote is (still) connected - pub fn connected(&mut self) -> bool { - self.remote.connected() - } - - /// Get the remote repository's reference advertisement list. - /// - /// This list is available as soon as the connection to - /// the remote is initiated and it remains available after disconnecting. - pub fn list(&self) -> Result<&[RemoteHead<'_>], Error> { - self.remote.list() - } - - /// Get the remote's default branch. - /// - /// This default branch is available as soon as the connection to the remote - /// is initiated and it remains available after disconnecting. - pub fn default_branch(&self) -> Result<Buf, Error> { - self.remote.default_branch() - } - - /// access remote bound to this connection - pub fn remote(&mut self) -> &mut Remote<'repo> { - self.remote - } -} - -impl<'repo, 'connection, 'cb> Drop for RemoteConnection<'repo, 'connection, 'cb> { - fn drop(&mut self) { - drop(self.remote.disconnect()); - } -} - -impl Default for RemoteRedirect { - fn default() -> Self { - RemoteRedirect::Initial - } -} - -impl RemoteRedirect { - fn raw(&self) -> raw::git_remote_redirect_t { - match self { - RemoteRedirect::None => raw::GIT_REMOTE_REDIRECT_NONE, - RemoteRedirect::Initial => raw::GIT_REMOTE_REDIRECT_INITIAL, - RemoteRedirect::All => raw::GIT_REMOTE_REDIRECT_ALL, - } - } -} - -#[cfg(test)] -mod tests { - use crate::{AutotagOption, PushOptions}; - use crate::{Direction, FetchOptions, Remote, RemoteCallbacks, Repository}; - use std::cell::Cell; - use tempfile::TempDir; - - #[test] - fn smoke() { - let (td, repo) = crate::test::repo_init(); - t!(repo.remote("origin", "/path/to/nowhere")); - drop(repo); - - let repo = t!(Repository::init(td.path())); - let mut origin = t!(repo.find_remote("origin")); - assert_eq!(origin.name(), Some("origin")); - assert_eq!(origin.url(), Some("/path/to/nowhere")); - assert_eq!(origin.pushurl(), None); - - t!(repo.remote_set_url("origin", "/path/to/elsewhere")); - t!(repo.remote_set_pushurl("origin", Some("/path/to/elsewhere"))); - - let stats = origin.stats(); - assert_eq!(stats.total_objects(), 0); - - t!(origin.stop()); - } - - #[test] - fn create_remote() { - let td = TempDir::new().unwrap(); - let remote = td.path().join("remote"); - Repository::init_bare(&remote).unwrap(); - - let (_td, repo) = crate::test::repo_init(); - let url = if cfg!(unix) { - format!("file://{}", remote.display()) - } else { - format!( - "file:///{}", - remote.display().to_string().replace("\\", "/") - ) - }; - - let mut origin = repo.remote("origin", &url).unwrap(); - assert_eq!(origin.name(), Some("origin")); - assert_eq!(origin.url(), Some(&url[..])); - assert_eq!(origin.pushurl(), None); - - { - let mut specs = origin.refspecs(); - let spec = specs.next().unwrap(); - assert!(specs.next().is_none()); - assert_eq!(spec.str(), Some("+refs/heads/*:refs/remotes/origin/*")); - assert_eq!(spec.dst(), Some("refs/remotes/origin/*")); - assert_eq!(spec.src(), Some("refs/heads/*")); - assert!(spec.is_force()); - } - assert!(origin.refspecs().next_back().is_some()); - { - let remotes = repo.remotes().unwrap(); - assert_eq!(remotes.len(), 1); - assert_eq!(remotes.get(0), Some("origin")); - assert_eq!(remotes.iter().count(), 1); - assert_eq!(remotes.iter().next().unwrap(), Some("origin")); - } - - origin.connect(Direction::Push).unwrap(); - assert!(origin.connected()); - origin.disconnect().unwrap(); - - origin.connect(Direction::Fetch).unwrap(); - assert!(origin.connected()); - origin.download(&[] as &[&str], None).unwrap(); - origin.disconnect().unwrap(); - - { - let mut connection = origin.connect_auth(Direction::Push, None, None).unwrap(); - assert!(connection.connected()); - } - assert!(!origin.connected()); - - { - let mut connection = origin.connect_auth(Direction::Fetch, None, None).unwrap(); - assert!(connection.connected()); - } - assert!(!origin.connected()); - - origin.fetch(&[] as &[&str], None, None).unwrap(); - origin.fetch(&[] as &[&str], None, Some("foo")).unwrap(); - origin - .update_tips(None, true, AutotagOption::Unspecified, None) - .unwrap(); - origin - .update_tips(None, true, AutotagOption::All, Some("foo")) - .unwrap(); - - t!(repo.remote_add_fetch("origin", "foo")); - t!(repo.remote_add_fetch("origin", "bar")); - } - - #[test] - fn rename_remote() { - let (_td, repo) = crate::test::repo_init(); - repo.remote("origin", "foo").unwrap(); - drop(repo.remote_rename("origin", "foo")); - drop(repo.remote_delete("foo")); - } - - #[test] - fn create_remote_anonymous() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - - let origin = repo.remote_anonymous("/path/to/nowhere").unwrap(); - assert_eq!(origin.name(), None); - drop(origin.clone()); - } - - #[test] - fn is_valid_name() { - assert!(Remote::is_valid_name("foobar")); - assert!(!Remote::is_valid_name("\x01")); - } - - #[test] - #[should_panic] - fn is_valid_name_for_invalid_remote() { - Remote::is_valid_name("ab\012"); - } - - #[test] - fn transfer_cb() { - let (td, _repo) = crate::test::repo_init(); - let td2 = TempDir::new().unwrap(); - let url = crate::test::path2url(&td.path()); - - let repo = Repository::init(td2.path()).unwrap(); - let progress_hit = Cell::new(false); - { - let mut callbacks = RemoteCallbacks::new(); - let mut origin = repo.remote("origin", &url).unwrap(); - - callbacks.transfer_progress(|_progress| { - progress_hit.set(true); - true - }); - origin - .fetch( - &[] as &[&str], - Some(FetchOptions::new().remote_callbacks(callbacks)), - None, - ) - .unwrap(); - - let list = t!(origin.list()); - assert_eq!(list.len(), 2); - assert_eq!(list[0].name(), "HEAD"); - assert!(!list[0].is_local()); - assert_eq!(list[1].name(), "refs/heads/main"); - assert!(!list[1].is_local()); - } - assert!(progress_hit.get()); - } - - /// This test is meant to assure that the callbacks provided to connect will not cause - /// segfaults - #[test] - fn connect_list() { - let (td, _repo) = crate::test::repo_init(); - let td2 = TempDir::new().unwrap(); - let url = crate::test::path2url(&td.path()); - - let repo = Repository::init(td2.path()).unwrap(); - let mut callbacks = RemoteCallbacks::new(); - callbacks.sideband_progress(|_progress| { - // no-op - true - }); - - let mut origin = repo.remote("origin", &url).unwrap(); - - { - let mut connection = origin - .connect_auth(Direction::Fetch, Some(callbacks), None) - .unwrap(); - assert!(connection.connected()); - - let list = t!(connection.list()); - assert_eq!(list.len(), 2); - assert_eq!(list[0].name(), "HEAD"); - assert!(!list[0].is_local()); - assert_eq!(list[1].name(), "refs/heads/main"); - assert!(!list[1].is_local()); - } - assert!(!origin.connected()); - } - - #[test] - fn push() { - let (_td, repo) = crate::test::repo_init(); - let td2 = TempDir::new().unwrap(); - let td3 = TempDir::new().unwrap(); - let url = crate::test::path2url(&td2.path()); - - let mut opts = crate::RepositoryInitOptions::new(); - opts.bare(true); - opts.initial_head("main"); - Repository::init_opts(td2.path(), &opts).unwrap(); - // git push - let mut remote = repo.remote("origin", &url).unwrap(); - let mut updated = false; - { - let mut callbacks = RemoteCallbacks::new(); - callbacks.push_update_reference(|refname, status| { - updated = true; - assert_eq!(refname, "refs/heads/main"); - assert_eq!(status, None); - Ok(()) - }); - let mut options = PushOptions::new(); - options.remote_callbacks(callbacks); - remote - .push(&["refs/heads/main"], Some(&mut options)) - .unwrap(); - } - assert!(updated); - - let repo = Repository::clone(&url, td3.path()).unwrap(); - let commit = repo.head().unwrap().target().unwrap(); - let commit = repo.find_commit(commit).unwrap(); - assert_eq!(commit.message(), Some("initial\n\nbody")); - } - - #[test] - fn prune() { - let (td, remote_repo) = crate::test::repo_init(); - let oid = remote_repo.head().unwrap().target().unwrap(); - let commit = remote_repo.find_commit(oid).unwrap(); - remote_repo.branch("stale", &commit, true).unwrap(); - - let td2 = TempDir::new().unwrap(); - let url = crate::test::path2url(&td.path()); - let repo = Repository::clone(&url, &td2).unwrap(); - - fn assert_branch_count(repo: &Repository, count: usize) { - assert_eq!( - repo.branches(Some(crate::BranchType::Remote)) - .unwrap() - .filter(|b| b.as_ref().unwrap().0.name().unwrap() == Some("origin/stale")) - .count(), - count, - ); - } - - assert_branch_count(&repo, 1); - - // delete `stale` branch on remote repo - let mut stale_branch = remote_repo - .find_branch("stale", crate::BranchType::Local) - .unwrap(); - stale_branch.delete().unwrap(); - - // prune - let mut remote = repo.find_remote("origin").unwrap(); - remote.connect(Direction::Push).unwrap(); - let mut callbacks = RemoteCallbacks::new(); - callbacks.update_tips(|refname, _a, b| { - assert_eq!(refname, "refs/remotes/origin/stale"); - assert!(b.is_zero()); - true - }); - remote.prune(Some(callbacks)).unwrap(); - assert_branch_count(&repo, 0); - } - - #[test] - fn push_negotiation() { - let (_td, repo) = crate::test::repo_init(); - let oid = repo.head().unwrap().target().unwrap(); - - let td2 = TempDir::new().unwrap(); - let url = crate::test::path2url(td2.path()); - let mut opts = crate::RepositoryInitOptions::new(); - opts.bare(true); - opts.initial_head("main"); - let remote_repo = Repository::init_opts(td2.path(), &opts).unwrap(); - - // reject pushing a branch - let mut remote = repo.remote("origin", &url).unwrap(); - let mut updated = false; - { - let mut callbacks = RemoteCallbacks::new(); - callbacks.push_negotiation(|updates| { - assert!(!updated); - updated = true; - assert_eq!(updates.len(), 1); - let u = &updates[0]; - assert_eq!(u.src_refname().unwrap(), "refs/heads/main"); - assert!(u.src().is_zero()); - assert_eq!(u.dst_refname().unwrap(), "refs/heads/main"); - assert_eq!(u.dst(), oid); - Err(crate::Error::from_str("rejected")) - }); - let mut options = PushOptions::new(); - options.remote_callbacks(callbacks); - assert!(remote - .push(&["refs/heads/main"], Some(&mut options)) - .is_err()); - } - assert!(updated); - assert_eq!(remote_repo.branches(None).unwrap().count(), 0); - - // push 3 branches - let commit = repo.find_commit(oid).unwrap(); - repo.branch("new1", &commit, true).unwrap(); - repo.branch("new2", &commit, true).unwrap(); - let mut flag = 0; - updated = false; - { - let mut callbacks = RemoteCallbacks::new(); - callbacks.push_negotiation(|updates| { - assert!(!updated); - updated = true; - assert_eq!(updates.len(), 3); - for u in updates { - assert!(u.src().is_zero()); - assert_eq!(u.dst(), oid); - let src_name = u.src_refname().unwrap(); - let dst_name = u.dst_refname().unwrap(); - match src_name { - "refs/heads/main" => { - assert_eq!(dst_name, src_name); - flag |= 1; - } - "refs/heads/new1" => { - assert_eq!(dst_name, "refs/heads/dev1"); - flag |= 2; - } - "refs/heads/new2" => { - assert_eq!(dst_name, "refs/heads/dev2"); - flag |= 4; - } - _ => panic!("unexpected refname: {}", src_name), - } - } - Ok(()) - }); - let mut options = PushOptions::new(); - options.remote_callbacks(callbacks); - remote - .push( - &[ - "refs/heads/main", - "refs/heads/new1:refs/heads/dev1", - "refs/heads/new2:refs/heads/dev2", - ], - Some(&mut options), - ) - .unwrap(); - } - assert!(updated); - assert_eq!(flag, 7); - assert_eq!(remote_repo.branches(None).unwrap().count(), 3); - } -} diff --git a/extra/git2/src/remote_callbacks.rs b/extra/git2/src/remote_callbacks.rs deleted file mode 100644 index 1169420bd..000000000 --- a/extra/git2/src/remote_callbacks.rs +++ /dev/null @@ -1,518 +0,0 @@ -use libc::{c_char, c_int, c_uint, c_void, size_t}; -use std::ffi::{CStr, CString}; -use std::mem; -use std::ptr; -use std::slice; -use std::str; - -use crate::cert::Cert; -use crate::util::Binding; -use crate::{ - panic, raw, Cred, CredentialType, Error, IndexerProgress, Oid, PackBuilderStage, Progress, - PushUpdate, -}; - -/// A structure to contain the callbacks which are invoked when a repository is -/// being updated or downloaded. -/// -/// These callbacks are used to manage facilities such as authentication, -/// transfer progress, etc. -pub struct RemoteCallbacks<'a> { - push_progress: Option<Box<PushTransferProgress<'a>>>, - progress: Option<Box<IndexerProgress<'a>>>, - pack_progress: Option<Box<PackProgress<'a>>>, - credentials: Option<Box<Credentials<'a>>>, - sideband_progress: Option<Box<TransportMessage<'a>>>, - update_tips: Option<Box<UpdateTips<'a>>>, - certificate_check: Option<Box<CertificateCheck<'a>>>, - push_update_reference: Option<Box<PushUpdateReference<'a>>>, - push_negotiation: Option<Box<PushNegotiation<'a>>>, -} - -/// Callback used to acquire credentials for when a remote is fetched. -/// -/// * `url` - the resource for which the credentials are required. -/// * `username_from_url` - the username that was embedded in the URL, or `None` -/// if it was not included. -/// * `allowed_types` - a bitmask stating which cred types are OK to return. -pub type Credentials<'a> = - dyn FnMut(&str, Option<&str>, CredentialType) -> Result<Cred, Error> + 'a; - -/// Callback for receiving messages delivered by the transport. -/// -/// The return value indicates whether the network operation should continue. -pub type TransportMessage<'a> = dyn FnMut(&[u8]) -> bool + 'a; - -/// Callback for whenever a reference is updated locally. -pub type UpdateTips<'a> = dyn FnMut(&str, Oid, Oid) -> bool + 'a; - -/// Callback for a custom certificate check. -/// -/// The first argument is the certificate received on the connection. -/// Certificates are typically either an SSH or X509 certificate. -/// -/// The second argument is the hostname for the connection is passed as the last -/// argument. -pub type CertificateCheck<'a> = - dyn FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a; - -/// The return value for the [`RemoteCallbacks::certificate_check`] callback. -pub enum CertificateCheckStatus { - /// Indicates that the certificate should be accepted. - CertificateOk, - /// Indicates that the certificate callback is neither accepting nor - /// rejecting the certificate. The result of the certificate checks - /// built-in to libgit2 will be used instead. - CertificatePassthrough, -} - -/// Callback for each updated reference on push. -/// -/// The first argument here is the `refname` of the reference, and the second is -/// the status message sent by a server. If the status is `Some` then the update -/// was rejected by the remote server with a reason why. -pub type PushUpdateReference<'a> = dyn FnMut(&str, Option<&str>) -> Result<(), Error> + 'a; - -/// Callback for push transfer progress -/// -/// Parameters: -/// * current -/// * total -/// * bytes -pub type PushTransferProgress<'a> = dyn FnMut(usize, usize, usize) + 'a; - -/// Callback for pack progress -/// -/// Parameters: -/// * stage -/// * current -/// * total -pub type PackProgress<'a> = dyn FnMut(PackBuilderStage, usize, usize) + 'a; - -/// Callback used to inform of upcoming updates. -/// -/// The argument is a slice containing the updates which will be sent as -/// commands to the destination. -/// -/// The push is cancelled if an error is returned. -pub type PushNegotiation<'a> = dyn FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a; - -impl<'a> Default for RemoteCallbacks<'a> { - fn default() -> Self { - Self::new() - } -} - -impl<'a> RemoteCallbacks<'a> { - /// Creates a new set of empty callbacks - pub fn new() -> RemoteCallbacks<'a> { - RemoteCallbacks { - credentials: None, - progress: None, - pack_progress: None, - sideband_progress: None, - update_tips: None, - certificate_check: None, - push_update_reference: None, - push_progress: None, - push_negotiation: None, - } - } - - /// The callback through which to fetch credentials if required. - /// - /// # Example - /// - /// Prepare a callback to authenticate using the `$HOME/.ssh/id_rsa` SSH key, and - /// extracting the username from the URL (i.e. git@github.com:rust-lang/git2-rs.git): - /// - /// ```no_run - /// use git2::{Cred, RemoteCallbacks}; - /// use std::env; - /// - /// let mut callbacks = RemoteCallbacks::new(); - /// callbacks.credentials(|_url, username_from_url, _allowed_types| { - /// Cred::ssh_key( - /// username_from_url.unwrap(), - /// None, - /// std::path::Path::new(&format!("{}/.ssh/id_rsa", env::var("HOME").unwrap())), - /// None, - /// ) - /// }); - /// ``` - pub fn credentials<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a> - where - F: FnMut(&str, Option<&str>, CredentialType) -> Result<Cred, Error> + 'a, - { - self.credentials = Some(Box::new(cb) as Box<Credentials<'a>>); - self - } - - /// The callback through which progress is monitored. - pub fn transfer_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a> - where - F: FnMut(Progress<'_>) -> bool + 'a, - { - self.progress = Some(Box::new(cb) as Box<IndexerProgress<'a>>); - self - } - - /// Textual progress from the remote. - /// - /// Text sent over the progress side-band will be passed to this function - /// (this is the 'counting objects' output). - pub fn sideband_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a> - where - F: FnMut(&[u8]) -> bool + 'a, - { - self.sideband_progress = Some(Box::new(cb) as Box<TransportMessage<'a>>); - self - } - - /// Each time a reference is updated locally, the callback will be called - /// with information about it. - pub fn update_tips<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a> - where - F: FnMut(&str, Oid, Oid) -> bool + 'a, - { - self.update_tips = Some(Box::new(cb) as Box<UpdateTips<'a>>); - self - } - - /// If certificate verification fails, then this callback will be invoked to - /// let the caller make the final decision of whether to allow the - /// connection to proceed. - pub fn certificate_check<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a> - where - F: FnMut(&Cert<'_>, &str) -> Result<CertificateCheckStatus, Error> + 'a, - { - self.certificate_check = Some(Box::new(cb) as Box<CertificateCheck<'a>>); - self - } - - /// Set a callback to get invoked for each updated reference on a push. - /// - /// The first argument to the callback is the name of the reference and the - /// second is a status message sent by the server. If the status is `Some` - /// then the push was rejected. - pub fn push_update_reference<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a> - where - F: FnMut(&str, Option<&str>) -> Result<(), Error> + 'a, - { - self.push_update_reference = Some(Box::new(cb) as Box<PushUpdateReference<'a>>); - self - } - - /// The callback through which progress of push transfer is monitored - pub fn push_transfer_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a> - where - F: FnMut(usize, usize, usize) + 'a, - { - self.push_progress = Some(Box::new(cb) as Box<PushTransferProgress<'a>>); - self - } - - /// Function to call with progress information during pack building. - /// Be aware that this is called inline with pack building operations, - /// so performance may be affected. - pub fn pack_progress<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a> - where - F: FnMut(PackBuilderStage, usize, usize) + 'a, - { - self.pack_progress = Some(Box::new(cb) as Box<PackProgress<'a>>); - self - } - - /// The callback is called once between the negotiation step and the upload. - /// It provides information about what updates will be performed. - pub fn push_negotiation<F>(&mut self, cb: F) -> &mut RemoteCallbacks<'a> - where - F: FnMut(&[PushUpdate<'_>]) -> Result<(), Error> + 'a, - { - self.push_negotiation = Some(Box::new(cb) as Box<PushNegotiation<'a>>); - self - } -} - -impl<'a> Binding for RemoteCallbacks<'a> { - type Raw = raw::git_remote_callbacks; - unsafe fn from_raw(_raw: raw::git_remote_callbacks) -> RemoteCallbacks<'a> { - panic!("unimplemented"); - } - - fn raw(&self) -> raw::git_remote_callbacks { - unsafe { - let mut callbacks: raw::git_remote_callbacks = mem::zeroed(); - assert_eq!( - raw::git_remote_init_callbacks(&mut callbacks, raw::GIT_REMOTE_CALLBACKS_VERSION), - 0 - ); - if self.progress.is_some() { - callbacks.transfer_progress = Some(transfer_progress_cb); - } - if self.credentials.is_some() { - callbacks.credentials = Some(credentials_cb); - } - if self.sideband_progress.is_some() { - callbacks.sideband_progress = Some(sideband_progress_cb); - } - if self.certificate_check.is_some() { - callbacks.certificate_check = Some(certificate_check_cb); - } - if self.push_update_reference.is_some() { - callbacks.push_update_reference = Some(push_update_reference_cb); - } - if self.push_progress.is_some() { - callbacks.push_transfer_progress = Some(push_transfer_progress_cb); - } - if self.pack_progress.is_some() { - callbacks.pack_progress = Some(pack_progress_cb); - } - if self.update_tips.is_some() { - let f: extern "C" fn( - *const c_char, - *const raw::git_oid, - *const raw::git_oid, - *mut c_void, - ) -> c_int = update_tips_cb; - callbacks.update_tips = Some(f); - } - if self.push_negotiation.is_some() { - callbacks.push_negotiation = Some(push_negotiation_cb); - } - callbacks.payload = self as *const _ as *mut _; - callbacks - } - } -} - -extern "C" fn credentials_cb( - ret: *mut *mut raw::git_cred, - url: *const c_char, - username_from_url: *const c_char, - allowed_types: c_uint, - payload: *mut c_void, -) -> c_int { - unsafe { - let ok = panic::wrap(|| { - let payload = &mut *(payload as *mut RemoteCallbacks<'_>); - let callback = payload - .credentials - .as_mut() - .ok_or(raw::GIT_PASSTHROUGH as c_int)?; - *ret = ptr::null_mut(); - let url = str::from_utf8(CStr::from_ptr(url).to_bytes()) - .map_err(|_| raw::GIT_PASSTHROUGH as c_int)?; - let username_from_url = match crate::opt_bytes(&url, username_from_url) { - Some(username) => { - Some(str::from_utf8(username).map_err(|_| raw::GIT_PASSTHROUGH as c_int)?) - } - None => None, - }; - - let cred_type = CredentialType::from_bits_truncate(allowed_types as u32); - - callback(url, username_from_url, cred_type).map_err(|e| { - let s = CString::new(e.to_string()).unwrap(); - raw::git_error_set_str(e.class() as c_int, s.as_ptr()); - e.raw_code() as c_int - }) - }); - match ok { - Some(Ok(cred)) => { - // Turns out it's a memory safety issue if we pass through any - // and all credentials into libgit2 - if allowed_types & (cred.credtype() as c_uint) != 0 { - *ret = cred.unwrap(); - 0 - } else { - raw::GIT_PASSTHROUGH as c_int - } - } - Some(Err(e)) => e, - None => -1, - } - } -} - -extern "C" fn transfer_progress_cb( - stats: *const raw::git_indexer_progress, - payload: *mut c_void, -) -> c_int { - let ok = panic::wrap(|| unsafe { - let payload = &mut *(payload as *mut RemoteCallbacks<'_>); - let callback = match payload.progress { - Some(ref mut c) => c, - None => return true, - }; - let progress = Binding::from_raw(stats); - callback(progress) - }); - if ok == Some(true) { - 0 - } else { - -1 - } -} - -extern "C" fn sideband_progress_cb(str: *const c_char, len: c_int, payload: *mut c_void) -> c_int { - let ok = panic::wrap(|| unsafe { - let payload = &mut *(payload as *mut RemoteCallbacks<'_>); - let callback = match payload.sideband_progress { - Some(ref mut c) => c, - None => return true, - }; - let buf = slice::from_raw_parts(str as *const u8, len as usize); - callback(buf) - }); - if ok == Some(true) { - 0 - } else { - -1 - } -} - -extern "C" fn update_tips_cb( - refname: *const c_char, - a: *const raw::git_oid, - b: *const raw::git_oid, - data: *mut c_void, -) -> c_int { - let ok = panic::wrap(|| unsafe { - let payload = &mut *(data as *mut RemoteCallbacks<'_>); - let callback = match payload.update_tips { - Some(ref mut c) => c, - None => return true, - }; - let refname = str::from_utf8(CStr::from_ptr(refname).to_bytes()).unwrap(); - let a = Binding::from_raw(a); - let b = Binding::from_raw(b); - callback(refname, a, b) - }); - if ok == Some(true) { - 0 - } else { - -1 - } -} - -extern "C" fn certificate_check_cb( - cert: *mut raw::git_cert, - _valid: c_int, - hostname: *const c_char, - data: *mut c_void, -) -> c_int { - let ok = panic::wrap(|| unsafe { - let payload = &mut *(data as *mut RemoteCallbacks<'_>); - let callback = match payload.certificate_check { - Some(ref mut c) => c, - None => return Ok(CertificateCheckStatus::CertificatePassthrough), - }; - let cert = Binding::from_raw(cert); - let hostname = str::from_utf8(CStr::from_ptr(hostname).to_bytes()).unwrap(); - callback(&cert, hostname) - }); - match ok { - Some(Ok(CertificateCheckStatus::CertificateOk)) => 0, - Some(Ok(CertificateCheckStatus::CertificatePassthrough)) => raw::GIT_PASSTHROUGH as c_int, - Some(Err(e)) => { - let s = CString::new(e.message()).unwrap(); - unsafe { - raw::git_error_set_str(e.class() as c_int, s.as_ptr()); - } - e.raw_code() as c_int - } - None => { - // Panic. The *should* get resumed by some future call to check(). - -1 - } - } -} - -extern "C" fn push_update_reference_cb( - refname: *const c_char, - status: *const c_char, - data: *mut c_void, -) -> c_int { - panic::wrap(|| unsafe { - let payload = &mut *(data as *mut RemoteCallbacks<'_>); - let callback = match payload.push_update_reference { - Some(ref mut c) => c, - None => return 0, - }; - let refname = str::from_utf8(CStr::from_ptr(refname).to_bytes()).unwrap(); - let status = if status.is_null() { - None - } else { - Some(str::from_utf8(CStr::from_ptr(status).to_bytes()).unwrap()) - }; - match callback(refname, status) { - Ok(()) => 0, - Err(e) => e.raw_code(), - } - }) - .unwrap_or(-1) -} - -extern "C" fn push_transfer_progress_cb( - progress: c_uint, - total: c_uint, - bytes: size_t, - data: *mut c_void, -) -> c_int { - panic::wrap(|| unsafe { - let payload = &mut *(data as *mut RemoteCallbacks<'_>); - let callback = match payload.push_progress { - Some(ref mut c) => c, - None => return 0, - }; - - callback(progress as usize, total as usize, bytes as usize); - - 0 - }) - .unwrap_or(-1) -} - -extern "C" fn pack_progress_cb( - stage: raw::git_packbuilder_stage_t, - current: c_uint, - total: c_uint, - data: *mut c_void, -) -> c_int { - panic::wrap(|| unsafe { - let payload = &mut *(data as *mut RemoteCallbacks<'_>); - let callback = match payload.pack_progress { - Some(ref mut c) => c, - None => return 0, - }; - - let stage = Binding::from_raw(stage); - - callback(stage, current as usize, total as usize); - - 0 - }) - .unwrap_or(-1) -} - -extern "C" fn push_negotiation_cb( - updates: *mut *const raw::git_push_update, - len: size_t, - payload: *mut c_void, -) -> c_int { - panic::wrap(|| unsafe { - let payload = &mut *(payload as *mut RemoteCallbacks<'_>); - let callback = match payload.push_negotiation { - Some(ref mut c) => c, - None => return 0, - }; - - let updates = slice::from_raw_parts(updates as *mut PushUpdate<'_>, len); - match callback(updates) { - Ok(()) => 0, - Err(e) => e.raw_code(), - } - }) - .unwrap_or(-1) -} diff --git a/extra/git2/src/repo.rs b/extra/git2/src/repo.rs deleted file mode 100644 index 921e2b30e..000000000 --- a/extra/git2/src/repo.rs +++ /dev/null @@ -1,4226 +0,0 @@ -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, StashSaveOptions}; -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 _)) - } - } - - /// Like `stash_save` but with more options like selective statshing via path patterns. - pub fn stash_save_ext( - &mut self, - opts: Option<&mut StashSaveOptions<'_>>, - ) -> Result<Oid, Error> { - unsafe { - let mut raw_oid = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - let opts = opts.map(|opts| opts.raw()); - try_call!(raw::git_stash_save_with_opts( - &mut raw_oid, - self.raw(), - opts - )); - 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()); - } -} diff --git a/extra/git2/src/revert.rs b/extra/git2/src/revert.rs deleted file mode 100644 index 55d702600..000000000 --- a/extra/git2/src/revert.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::mem; - -use crate::build::CheckoutBuilder; -use crate::merge::MergeOptions; -use crate::raw; -use std::ptr; - -/// Options to specify when reverting -pub struct RevertOptions<'cb> { - mainline: u32, - checkout_builder: Option<CheckoutBuilder<'cb>>, - merge_opts: Option<MergeOptions>, -} - -impl<'cb> RevertOptions<'cb> { - /// Creates a default set of revert options - pub fn new() -> RevertOptions<'cb> { - RevertOptions { - mainline: 0, - checkout_builder: None, - merge_opts: None, - } - } - - /// Set the mainline value - /// - /// For merge commits, the "mainline" is treated as the parent. - pub fn mainline(&mut self, mainline: u32) -> &mut Self { - self.mainline = mainline; - self - } - - /// Set the checkout builder - pub fn checkout_builder(&mut self, cb: CheckoutBuilder<'cb>) -> &mut Self { - self.checkout_builder = Some(cb); - self - } - - /// Set the merge options - pub fn merge_opts(&mut self, merge_opts: MergeOptions) -> &mut Self { - self.merge_opts = Some(merge_opts); - self - } - - /// Obtain the raw struct - pub fn raw(&mut self) -> raw::git_revert_options { - unsafe { - let mut checkout_opts: raw::git_checkout_options = mem::zeroed(); - raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION); - if let Some(ref mut cb) = self.checkout_builder { - cb.configure(&mut checkout_opts); - } - - let mut merge_opts: raw::git_merge_options = mem::zeroed(); - raw::git_merge_init_options(&mut merge_opts, raw::GIT_MERGE_OPTIONS_VERSION); - if let Some(ref opts) = self.merge_opts { - ptr::copy(opts.raw(), &mut merge_opts, 1); - } - - let mut revert_opts: raw::git_revert_options = mem::zeroed(); - raw::git_revert_options_init(&mut revert_opts, raw::GIT_REVERT_OPTIONS_VERSION); - revert_opts.mainline = self.mainline; - revert_opts.checkout_opts = checkout_opts; - revert_opts.merge_opts = merge_opts; - - revert_opts - } - } -} diff --git a/extra/git2/src/revspec.rs b/extra/git2/src/revspec.rs deleted file mode 100644 index d2e08670a..000000000 --- a/extra/git2/src/revspec.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{Object, RevparseMode}; - -/// A revspec represents a range of revisions within a repository. -pub struct Revspec<'repo> { - from: Option<Object<'repo>>, - to: Option<Object<'repo>>, - mode: RevparseMode, -} - -impl<'repo> Revspec<'repo> { - /// Assembles a new revspec from the from/to components. - pub fn from_objects( - from: Option<Object<'repo>>, - to: Option<Object<'repo>>, - mode: RevparseMode, - ) -> Revspec<'repo> { - Revspec { from, to, mode } - } - - /// Access the `from` range of this revspec. - pub fn from(&self) -> Option<&Object<'repo>> { - self.from.as_ref() - } - - /// Access the `to` range of this revspec. - pub fn to(&self) -> Option<&Object<'repo>> { - self.to.as_ref() - } - - /// Returns the intent of the revspec. - pub fn mode(&self) -> RevparseMode { - self.mode - } -} diff --git a/extra/git2/src/revwalk.rs b/extra/git2/src/revwalk.rs deleted file mode 100644 index 7837f00d6..000000000 --- a/extra/git2/src/revwalk.rs +++ /dev/null @@ -1,316 +0,0 @@ -use libc::{c_int, c_uint, c_void}; -use std::ffi::CString; -use std::marker; - -use crate::util::Binding; -use crate::{panic, raw, Error, Oid, Repository, Sort}; - -/// A revwalk allows traversal of the commit graph defined by including one or -/// more leaves and excluding one or more roots. -pub struct Revwalk<'repo> { - raw: *mut raw::git_revwalk, - _marker: marker::PhantomData<&'repo Repository>, -} - -/// A `Revwalk` with an associated "hide callback", see `with_hide_callback` -pub struct RevwalkWithHideCb<'repo, 'cb, C> -where - C: FnMut(Oid) -> bool, -{ - revwalk: Revwalk<'repo>, - _marker: marker::PhantomData<&'cb C>, -} - -extern "C" fn revwalk_hide_cb<C>(commit_id: *const raw::git_oid, payload: *mut c_void) -> c_int -where - C: FnMut(Oid) -> bool, -{ - panic::wrap(|| unsafe { - let hide_cb = payload as *mut C; - if (*hide_cb)(Oid::from_raw(commit_id)) { - 1 - } else { - 0 - } - }) - .unwrap_or(-1) -} - -impl<'repo, 'cb, C: FnMut(Oid) -> bool> RevwalkWithHideCb<'repo, 'cb, C> { - /// Consumes the `RevwalkWithHideCb` and returns the contained `Revwalk`. - /// - /// Note that this will reset the `Revwalk`. - pub fn into_inner(mut self) -> Result<Revwalk<'repo>, Error> { - self.revwalk.reset()?; - Ok(self.revwalk) - } -} - -impl<'repo> Revwalk<'repo> { - /// Reset a revwalk to allow re-configuring it. - /// - /// The revwalk is automatically reset when iteration of its commits - /// completes. - pub fn reset(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_revwalk_reset(self.raw())); - } - Ok(()) - } - - /// Set the order in which commits are visited. - pub fn set_sorting(&mut self, sort_mode: Sort) -> Result<(), Error> { - unsafe { - try_call!(raw::git_revwalk_sorting( - self.raw(), - sort_mode.bits() as c_uint - )); - } - Ok(()) - } - - /// Simplify the history by first-parent - /// - /// No parents other than the first for each commit will be enqueued. - pub fn simplify_first_parent(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_revwalk_simplify_first_parent(self.raw)); - } - Ok(()) - } - - /// Mark a commit to start traversal from. - /// - /// The given OID must belong to a commitish on the walked repository. - /// - /// The given commit will be used as one of the roots when starting the - /// revision walk. At least one commit must be pushed onto the walker before - /// a walk can be started. - pub fn push(&mut self, oid: Oid) -> Result<(), Error> { - unsafe { - try_call!(raw::git_revwalk_push(self.raw(), oid.raw())); - } - Ok(()) - } - - /// Push the repository's HEAD - /// - /// For more information, see `push`. - pub fn push_head(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_revwalk_push_head(self.raw())); - } - Ok(()) - } - - /// Push matching references - /// - /// The OIDs pointed to by the references that match the given glob pattern - /// will be pushed to the revision walker. - /// - /// A leading 'refs/' is implied if not present as well as a trailing `/ \ - /// *` if the glob lacks '?', ' \ *' or '['. - /// - /// Any references matching this glob which do not point to a commitish - /// will be ignored. - pub fn push_glob(&mut self, glob: &str) -> Result<(), Error> { - let glob = CString::new(glob)?; - unsafe { - try_call!(raw::git_revwalk_push_glob(self.raw, glob)); - } - Ok(()) - } - - /// Push and hide the respective endpoints of the given range. - /// - /// The range should be of the form `<commit>..<commit>` where each - /// `<commit>` is in the form accepted by `revparse_single`. The left-hand - /// commit will be hidden and the right-hand commit pushed. - pub fn push_range(&mut self, range: &str) -> Result<(), Error> { - let range = CString::new(range)?; - unsafe { - try_call!(raw::git_revwalk_push_range(self.raw, range)); - } - Ok(()) - } - - /// Push the OID pointed to by a reference - /// - /// The reference must point to a commitish. - pub fn push_ref(&mut self, reference: &str) -> Result<(), Error> { - let reference = CString::new(reference)?; - unsafe { - try_call!(raw::git_revwalk_push_ref(self.raw, reference)); - } - Ok(()) - } - - /// Mark a commit as not of interest to this revwalk. - pub fn hide(&mut self, oid: Oid) -> Result<(), Error> { - unsafe { - try_call!(raw::git_revwalk_hide(self.raw(), oid.raw())); - } - Ok(()) - } - - /// Hide all commits for which the callback returns true from - /// the walk. - pub fn with_hide_callback<'cb, C>( - self, - callback: &'cb mut C, - ) -> Result<RevwalkWithHideCb<'repo, 'cb, C>, Error> - where - C: FnMut(Oid) -> bool, - { - let r = RevwalkWithHideCb { - revwalk: self, - _marker: marker::PhantomData, - }; - unsafe { - raw::git_revwalk_add_hide_cb( - r.revwalk.raw(), - Some(revwalk_hide_cb::<C>), - callback as *mut _ as *mut c_void, - ); - }; - Ok(r) - } - - /// Hide the repository's HEAD - /// - /// For more information, see `hide`. - pub fn hide_head(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_revwalk_hide_head(self.raw())); - } - Ok(()) - } - - /// Hide matching references. - /// - /// The OIDs pointed to by the references that match the given glob pattern - /// and their ancestors will be hidden from the output on the revision walk. - /// - /// A leading 'refs/' is implied if not present as well as a trailing `/ \ - /// *` if the glob lacks '?', ' \ *' or '['. - /// - /// Any references matching this glob which do not point to a commitish - /// will be ignored. - pub fn hide_glob(&mut self, glob: &str) -> Result<(), Error> { - let glob = CString::new(glob)?; - unsafe { - try_call!(raw::git_revwalk_hide_glob(self.raw, glob)); - } - Ok(()) - } - - /// Hide the OID pointed to by a reference. - /// - /// The reference must point to a commitish. - pub fn hide_ref(&mut self, reference: &str) -> Result<(), Error> { - let reference = CString::new(reference)?; - unsafe { - try_call!(raw::git_revwalk_hide_ref(self.raw, reference)); - } - Ok(()) - } -} - -impl<'repo> Binding for Revwalk<'repo> { - type Raw = *mut raw::git_revwalk; - unsafe fn from_raw(raw: *mut raw::git_revwalk) -> Revwalk<'repo> { - Revwalk { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_revwalk { - self.raw - } -} - -impl<'repo> Drop for Revwalk<'repo> { - fn drop(&mut self) { - unsafe { raw::git_revwalk_free(self.raw) } - } -} - -impl<'repo> Iterator for Revwalk<'repo> { - type Item = Result<Oid, Error>; - fn next(&mut self) -> Option<Result<Oid, Error>> { - let mut out: raw::git_oid = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call_iter!(raw::git_revwalk_next(&mut out, self.raw())); - Some(Ok(Binding::from_raw(&out as *const _))) - } - } -} - -impl<'repo, 'cb, C: FnMut(Oid) -> bool> Iterator for RevwalkWithHideCb<'repo, 'cb, C> { - type Item = Result<Oid, Error>; - fn next(&mut self) -> Option<Result<Oid, Error>> { - let out = self.revwalk.next(); - crate::panic::check(); - out - } -} - -#[cfg(test)] -mod tests { - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let head = repo.head().unwrap(); - let target = head.target().unwrap(); - - let mut walk = repo.revwalk().unwrap(); - walk.push(target).unwrap(); - - let oids: Vec<crate::Oid> = walk.by_ref().collect::<Result<Vec<_>, _>>().unwrap(); - - assert_eq!(oids.len(), 1); - assert_eq!(oids[0], target); - - walk.reset().unwrap(); - walk.push_head().unwrap(); - assert_eq!(walk.by_ref().count(), 1); - - walk.reset().unwrap(); - walk.push_head().unwrap(); - walk.hide_head().unwrap(); - assert_eq!(walk.by_ref().count(), 0); - } - - #[test] - fn smoke_hide_cb() { - let (_td, repo) = crate::test::repo_init(); - let head = repo.head().unwrap(); - let target = head.target().unwrap(); - - let mut walk = repo.revwalk().unwrap(); - walk.push(target).unwrap(); - - let oids: Vec<crate::Oid> = walk.by_ref().collect::<Result<Vec<_>, _>>().unwrap(); - - assert_eq!(oids.len(), 1); - assert_eq!(oids[0], target); - - walk.reset().unwrap(); - walk.push_head().unwrap(); - assert_eq!(walk.by_ref().count(), 1); - - walk.reset().unwrap(); - walk.push_head().unwrap(); - - let mut hide_cb = |oid| oid == target; - let mut walk = walk.with_hide_callback(&mut hide_cb).unwrap(); - - assert_eq!(walk.by_ref().count(), 0); - - let mut walk = walk.into_inner().unwrap(); - walk.push_head().unwrap(); - assert_eq!(walk.by_ref().count(), 1); - } -} diff --git a/extra/git2/src/signature.rs b/extra/git2/src/signature.rs deleted file mode 100644 index 83fbbf593..000000000 --- a/extra/git2/src/signature.rs +++ /dev/null @@ -1,189 +0,0 @@ -use libc; -use std::ffi::CString; -use std::fmt; -use std::marker; -use std::mem; -use std::ptr; -use std::str; - -use crate::util::Binding; -use crate::{raw, Error, Time}; - -/// A Signature is used to indicate authorship of various actions throughout the -/// library. -/// -/// Signatures contain a name, email, and timestamp. All fields can be specified -/// with `new` while the `now` constructor omits the timestamp. The -/// [`Repository::signature`] method can be used to create a default signature -/// with name and email values read from the configuration. -/// -/// [`Repository::signature`]: struct.Repository.html#method.signature -pub struct Signature<'a> { - raw: *mut raw::git_signature, - _marker: marker::PhantomData<&'a str>, - owned: bool, -} - -impl<'a> Signature<'a> { - /// Create a new action signature with a timestamp of 'now'. - /// - /// See `new` for more information - pub fn now(name: &str, email: &str) -> Result<Signature<'static>, Error> { - crate::init(); - let mut ret = ptr::null_mut(); - let name = CString::new(name)?; - let email = CString::new(email)?; - unsafe { - try_call!(raw::git_signature_now(&mut ret, name, email)); - Ok(Binding::from_raw(ret)) - } - } - - /// Create a new action signature. - /// - /// The `time` specified is in seconds since the epoch, and the `offset` is - /// the time zone offset in minutes. - /// - /// Returns error if either `name` or `email` contain angle brackets. - pub fn new(name: &str, email: &str, time: &Time) -> Result<Signature<'static>, Error> { - crate::init(); - let mut ret = ptr::null_mut(); - let name = CString::new(name)?; - let email = CString::new(email)?; - unsafe { - try_call!(raw::git_signature_new( - &mut ret, - name, - email, - time.seconds() as raw::git_time_t, - time.offset_minutes() as libc::c_int - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Gets the name on the signature. - /// - /// Returns `None` if the name is not valid utf-8 - pub fn name(&self) -> Option<&str> { - str::from_utf8(self.name_bytes()).ok() - } - - /// Gets the name on the signature as a byte slice. - pub fn name_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, (*self.raw).name).unwrap() } - } - - /// Gets the email on the signature. - /// - /// Returns `None` if the email is not valid utf-8 - pub fn email(&self) -> Option<&str> { - str::from_utf8(self.email_bytes()).ok() - } - - /// Gets the email on the signature as a byte slice. - pub fn email_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, (*self.raw).email).unwrap() } - } - - /// Get the `when` of this signature. - pub fn when(&self) -> Time { - unsafe { Binding::from_raw((*self.raw).when) } - } - - /// Convert a signature of any lifetime into an owned signature with a - /// static lifetime. - pub fn to_owned(&self) -> Signature<'static> { - unsafe { - let me = mem::transmute::<&Signature<'a>, &Signature<'static>>(self); - me.clone() - } - } -} - -impl<'a> Binding for Signature<'a> { - type Raw = *mut raw::git_signature; - unsafe fn from_raw(raw: *mut raw::git_signature) -> Signature<'a> { - Signature { - raw, - _marker: marker::PhantomData, - owned: true, - } - } - fn raw(&self) -> *mut raw::git_signature { - self.raw - } -} - -/// Creates a new signature from the give raw pointer, tied to the lifetime -/// of the given object. -/// -/// This function is unsafe as there is no guarantee that `raw` is valid for -/// `'a` nor if it's a valid pointer. -pub unsafe fn from_raw_const<'b, T>(_lt: &'b T, raw: *const raw::git_signature) -> Signature<'b> { - Signature { - raw: raw as *mut raw::git_signature, - _marker: marker::PhantomData, - owned: false, - } -} - -impl Clone for Signature<'static> { - fn clone(&self) -> Signature<'static> { - // TODO: can this be defined for 'a and just do a plain old copy if the - // lifetime isn't static? - let mut raw = ptr::null_mut(); - let rc = unsafe { raw::git_signature_dup(&mut raw, &*self.raw) }; - assert_eq!(rc, 0); - unsafe { Binding::from_raw(raw) } - } -} - -impl<'a> Drop for Signature<'a> { - fn drop(&mut self) { - if self.owned { - unsafe { raw::git_signature_free(self.raw) } - } - } -} - -impl<'a> fmt::Display for Signature<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "{} <{}>", - String::from_utf8_lossy(self.name_bytes()), - String::from_utf8_lossy(self.email_bytes()) - ) - } -} - -impl PartialEq for Signature<'_> { - fn eq(&self, other: &Self) -> bool { - self.when() == other.when() - && self.email_bytes() == other.email_bytes() - && self.name_bytes() == other.name_bytes() - } -} - -impl Eq for Signature<'_> {} - -#[cfg(test)] -mod tests { - use crate::{Signature, Time}; - - #[test] - fn smoke() { - Signature::new("foo", "bar", &Time::new(89, 0)).unwrap(); - Signature::now("foo", "bar").unwrap(); - assert!(Signature::new("<foo>", "bar", &Time::new(89, 0)).is_err()); - assert!(Signature::now("<foo>", "bar").is_err()); - - let s = Signature::now("foo", "bar").unwrap(); - assert_eq!(s.name(), Some("foo")); - assert_eq!(s.email(), Some("bar")); - - drop(s.clone()); - drop(s.to_owned()); - } -} diff --git a/extra/git2/src/stash.rs b/extra/git2/src/stash.rs deleted file mode 100644 index ea898e46b..000000000 --- a/extra/git2/src/stash.rs +++ /dev/null @@ -1,348 +0,0 @@ -use crate::build::CheckoutBuilder; -use crate::util::{self, Binding}; -use crate::{panic, raw, IntoCString, Oid, Signature, StashApplyProgress, StashFlags}; -use libc::{c_char, c_int, c_void, size_t}; -use std::ffi::{c_uint, CStr, CString}; -use std::mem; - -/// Stash application options structure -pub struct StashSaveOptions<'a> { - message: Option<CString>, - flags: Option<StashFlags>, - stasher: Signature<'a>, - pathspec: Vec<CString>, - pathspec_ptrs: Vec<*const c_char>, - raw_opts: raw::git_stash_save_options, -} - -impl<'a> StashSaveOptions<'a> { - /// Creates a default - pub fn new(stasher: Signature<'a>) -> Self { - let mut opts = Self { - message: None, - flags: None, - stasher, - pathspec: Vec::new(), - pathspec_ptrs: Vec::new(), - raw_opts: unsafe { mem::zeroed() }, - }; - assert_eq!( - unsafe { - raw::git_stash_save_options_init( - &mut opts.raw_opts, - raw::GIT_STASH_SAVE_OPTIONS_VERSION, - ) - }, - 0 - ); - opts - } - - /// Customize optional `flags` field - pub fn flags(&mut self, flags: Option<StashFlags>) -> &mut Self { - self.flags = flags; - self - } - - /// Add to the array of paths patterns to build the stash. - pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut Self { - let s = util::cstring_to_repo_path(pathspec).unwrap(); - self.pathspec_ptrs.push(s.as_ptr()); - self.pathspec.push(s); - self - } - - /// Acquire a pointer to the underlying raw options. - /// - /// This function is unsafe as the pointer is only valid so long as this - /// structure is not moved, modified, or used elsewhere. - pub unsafe fn raw(&mut self) -> *const raw::git_stash_save_options { - self.raw_opts.flags = self.flags.unwrap_or_else(StashFlags::empty).bits() as c_uint; - self.raw_opts.message = crate::call::convert(&self.message); - self.raw_opts.paths.count = self.pathspec_ptrs.len() as size_t; - self.raw_opts.paths.strings = self.pathspec_ptrs.as_ptr() as *mut _; - self.raw_opts.stasher = self.stasher.raw(); - - &self.raw_opts as *const _ - } -} - -/// Stash application progress notification function. -/// -/// Return `true` to continue processing, or `false` to -/// abort the stash application. -// FIXME: This probably should have been pub(crate) since it is not used anywhere. -pub type StashApplyProgressCb<'a> = dyn FnMut(StashApplyProgress) -> bool + 'a; - -/// This is a callback function you can provide to iterate over all the -/// stashed states that will be invoked per entry. -// FIXME: This probably should have been pub(crate) since it is not used anywhere. -pub type StashCb<'a> = dyn FnMut(usize, &str, &Oid) -> bool + 'a; - -/// Stash application options structure -pub struct StashApplyOptions<'cb> { - progress: Option<Box<StashApplyProgressCb<'cb>>>, - checkout_options: Option<CheckoutBuilder<'cb>>, - raw_opts: raw::git_stash_apply_options, -} - -impl<'cb> Default for StashApplyOptions<'cb> { - fn default() -> Self { - Self::new() - } -} - -impl<'cb> StashApplyOptions<'cb> { - /// Creates a default set of merge options. - pub fn new() -> StashApplyOptions<'cb> { - let mut opts = StashApplyOptions { - progress: None, - checkout_options: None, - raw_opts: unsafe { mem::zeroed() }, - }; - assert_eq!( - unsafe { raw::git_stash_apply_init_options(&mut opts.raw_opts, 1) }, - 0 - ); - opts - } - - /// Set stash application flag to GIT_STASH_APPLY_REINSTATE_INDEX - pub fn reinstantiate_index(&mut self) -> &mut StashApplyOptions<'cb> { - self.raw_opts.flags = raw::GIT_STASH_APPLY_REINSTATE_INDEX as u32; - self - } - - /// Options to use when writing files to the working directory - pub fn checkout_options(&mut self, opts: CheckoutBuilder<'cb>) -> &mut StashApplyOptions<'cb> { - self.checkout_options = Some(opts); - self - } - - /// Optional callback to notify the consumer of application progress. - /// - /// Return `true` to continue processing, or `false` to - /// abort the stash application. - pub fn progress_cb<C>(&mut self, callback: C) -> &mut StashApplyOptions<'cb> - where - C: FnMut(StashApplyProgress) -> bool + 'cb, - { - self.progress = Some(Box::new(callback) as Box<StashApplyProgressCb<'cb>>); - self.raw_opts.progress_cb = Some(stash_apply_progress_cb); - self.raw_opts.progress_payload = self as *mut _ as *mut _; - self - } - - /// Pointer to a raw git_stash_apply_options - pub fn raw(&mut self) -> &raw::git_stash_apply_options { - unsafe { - if let Some(opts) = self.checkout_options.as_mut() { - opts.configure(&mut self.raw_opts.checkout_options); - } - } - &self.raw_opts - } -} - -pub(crate) struct StashCbData<'a> { - pub callback: &'a mut StashCb<'a>, -} - -pub(crate) extern "C" fn stash_cb( - index: size_t, - message: *const c_char, - stash_id: *const raw::git_oid, - payload: *mut c_void, -) -> c_int { - panic::wrap(|| unsafe { - let data = &mut *(payload as *mut StashCbData<'_>); - let res = { - let callback = &mut data.callback; - callback( - index, - CStr::from_ptr(message).to_str().unwrap(), - &Binding::from_raw(stash_id), - ) - }; - - if res { - 0 - } else { - 1 - } - }) - .unwrap_or(1) -} - -fn convert_progress(progress: raw::git_stash_apply_progress_t) -> StashApplyProgress { - match progress { - raw::GIT_STASH_APPLY_PROGRESS_NONE => StashApplyProgress::None, - raw::GIT_STASH_APPLY_PROGRESS_LOADING_STASH => StashApplyProgress::LoadingStash, - raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_INDEX => StashApplyProgress::AnalyzeIndex, - raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_MODIFIED => StashApplyProgress::AnalyzeModified, - raw::GIT_STASH_APPLY_PROGRESS_ANALYZE_UNTRACKED => StashApplyProgress::AnalyzeUntracked, - raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_UNTRACKED => StashApplyProgress::CheckoutUntracked, - raw::GIT_STASH_APPLY_PROGRESS_CHECKOUT_MODIFIED => StashApplyProgress::CheckoutModified, - raw::GIT_STASH_APPLY_PROGRESS_DONE => StashApplyProgress::Done, - - _ => StashApplyProgress::None, - } -} - -extern "C" fn stash_apply_progress_cb( - progress: raw::git_stash_apply_progress_t, - payload: *mut c_void, -) -> c_int { - panic::wrap(|| unsafe { - let options = &mut *(payload as *mut StashApplyOptions<'_>); - let res = { - let callback = options.progress.as_mut().unwrap(); - callback(convert_progress(progress)) - }; - - if res { - 0 - } else { - -1 - } - }) - .unwrap_or(-1) -} - -#[cfg(test)] -mod tests { - use crate::stash::{StashApplyOptions, StashSaveOptions}; - use crate::test::repo_init; - use crate::{IndexAddOption, Repository, StashFlags, Status}; - use std::fs; - use std::path::{Path, PathBuf}; - - fn make_stash<C>(next: C) - where - C: FnOnce(&mut Repository), - { - let (_td, mut repo) = repo_init(); - let signature = repo.signature().unwrap(); - - let p = Path::new(repo.workdir().unwrap()).join("file_b.txt"); - println!("using path {:?}", p); - - fs::write(&p, "data".as_bytes()).unwrap(); - - let rel_p = Path::new("file_b.txt"); - assert!(repo.status_file(&rel_p).unwrap() == Status::WT_NEW); - - repo.stash_save(&signature, "msg1", Some(StashFlags::INCLUDE_UNTRACKED)) - .unwrap(); - - assert!(repo.status_file(&rel_p).is_err()); - - let mut count = 0; - repo.stash_foreach(|index, name, _oid| { - count += 1; - assert!(index == 0); - assert!(name == "On main: msg1"); - true - }) - .unwrap(); - - assert!(count == 1); - next(&mut repo); - } - - fn count_stash(repo: &mut Repository) -> usize { - let mut count = 0; - repo.stash_foreach(|_, _, _| { - count += 1; - true - }) - .unwrap(); - count - } - - #[test] - fn smoke_stash_save_drop() { - make_stash(|repo| { - repo.stash_drop(0).unwrap(); - assert!(count_stash(repo) == 0) - }) - } - - #[test] - fn smoke_stash_save_pop() { - make_stash(|repo| { - repo.stash_pop(0, None).unwrap(); - assert!(count_stash(repo) == 0) - }) - } - - #[test] - fn smoke_stash_save_apply() { - make_stash(|repo| { - let mut options = StashApplyOptions::new(); - options.progress_cb(|progress| { - println!("{:?}", progress); - true - }); - - repo.stash_apply(0, Some(&mut options)).unwrap(); - assert!(count_stash(repo) == 1) - }) - } - - #[test] - fn test_stash_save2_msg_none() { - let (_td, mut repo) = repo_init(); - let signature = repo.signature().unwrap(); - - let p = Path::new(repo.workdir().unwrap()).join("file_b.txt"); - - fs::write(&p, "data".as_bytes()).unwrap(); - - repo.stash_save2(&signature, None, Some(StashFlags::INCLUDE_UNTRACKED)) - .unwrap(); - - let mut stash_name = String::new(); - repo.stash_foreach(|index, name, _oid| { - assert_eq!(index, 0); - stash_name = name.to_string(); - true - }) - .unwrap(); - - assert!(stash_name.starts_with("WIP on main:")); - } - - fn create_file(r: &Repository, name: &str, data: &str) -> PathBuf { - let p = Path::new(r.workdir().unwrap()).join(name); - fs::write(&p, data).unwrap(); - p - } - - #[test] - fn test_stash_save_ext() { - let (_td, mut repo) = repo_init(); - let signature = repo.signature().unwrap(); - - create_file(&repo, "file_a", "foo"); - create_file(&repo, "file_b", "foo"); - - let mut index = repo.index().unwrap(); - index - .add_all(["*"].iter(), IndexAddOption::DEFAULT, None) - .unwrap(); - index.write().unwrap(); - - assert_eq!(repo.statuses(None).unwrap().len(), 2); - - let mut opt = StashSaveOptions::new(signature); - opt.pathspec("file_a"); - repo.stash_save_ext(Some(&mut opt)).unwrap(); - - assert_eq!(repo.statuses(None).unwrap().len(), 0); - - repo.stash_pop(0, None).unwrap(); - - assert_eq!(repo.statuses(None).unwrap().len(), 1); - } -} diff --git a/extra/git2/src/status.rs b/extra/git2/src/status.rs deleted file mode 100644 index a5a8cffd3..000000000 --- a/extra/git2/src/status.rs +++ /dev/null @@ -1,435 +0,0 @@ -use libc::{c_char, c_uint, size_t}; -use std::ffi::CString; -use std::iter::FusedIterator; -use std::marker; -use std::mem; -use std::ops::Range; -use std::str; - -use crate::util::{self, Binding}; -use crate::{raw, DiffDelta, IntoCString, Repository, Status}; - -/// Options that can be provided to `repo.statuses()` to control how the status -/// information is gathered. -pub struct StatusOptions { - raw: raw::git_status_options, - pathspec: Vec<CString>, - ptrs: Vec<*const c_char>, -} - -/// Enumeration of possible methods of what can be shown through a status -/// operation. -#[derive(Copy, Clone)] -pub enum StatusShow { - /// Only gives status based on HEAD to index comparison, not looking at - /// working directory changes. - Index, - - /// Only gives status based on index to working directory comparison, not - /// comparing the index to the HEAD. - Workdir, - - /// The default, this roughly matches `git status --porcelain` regarding - /// which files are included and in what order. - IndexAndWorkdir, -} - -/// A container for a list of status information about a repository. -/// -/// Each instance appears as if it were a collection, having a length and -/// allowing indexing, as well as providing an iterator. -pub struct Statuses<'repo> { - raw: *mut raw::git_status_list, - - // Hm, not currently present, but can't hurt? - _marker: marker::PhantomData<&'repo Repository>, -} - -/// An iterator over the statuses in a `Statuses` instance. -pub struct StatusIter<'statuses> { - statuses: &'statuses Statuses<'statuses>, - range: Range<usize>, -} - -/// A structure representing an entry in the `Statuses` structure. -/// -/// Instances are created through the `.iter()` method or the `.get()` method. -pub struct StatusEntry<'statuses> { - raw: *const raw::git_status_entry, - _marker: marker::PhantomData<&'statuses DiffDelta<'statuses>>, -} - -impl Default for StatusOptions { - fn default() -> Self { - Self::new() - } -} - -impl StatusOptions { - /// Creates a new blank set of status options. - pub fn new() -> StatusOptions { - unsafe { - let mut raw = mem::zeroed(); - let r = raw::git_status_init_options(&mut raw, raw::GIT_STATUS_OPTIONS_VERSION); - assert_eq!(r, 0); - StatusOptions { - raw, - pathspec: Vec::new(), - ptrs: Vec::new(), - } - } - } - - /// Select the files on which to report status. - /// - /// The default, if unspecified, is to show the index and the working - /// directory. - pub fn show(&mut self, show: StatusShow) -> &mut StatusOptions { - self.raw.show = match show { - StatusShow::Index => raw::GIT_STATUS_SHOW_INDEX_ONLY, - StatusShow::Workdir => raw::GIT_STATUS_SHOW_WORKDIR_ONLY, - StatusShow::IndexAndWorkdir => raw::GIT_STATUS_SHOW_INDEX_AND_WORKDIR, - }; - self - } - - /// Add a path pattern to match (using fnmatch-style matching). - /// - /// If the `disable_pathspec_match` option is given, then this is a literal - /// path to match. If this is not called, then there will be no patterns to - /// match and the entire directory will be used. - pub fn pathspec<T: IntoCString>(&mut self, pathspec: T) -> &mut StatusOptions { - let s = util::cstring_to_repo_path(pathspec).unwrap(); - self.ptrs.push(s.as_ptr()); - self.pathspec.push(s); - self - } - - fn flag(&mut self, flag: raw::git_status_opt_t, val: bool) -> &mut StatusOptions { - if val { - self.raw.flags |= flag as c_uint; - } else { - self.raw.flags &= !(flag as c_uint); - } - self - } - - /// Flag whether untracked files will be included. - /// - /// Untracked files will only be included if the workdir files are included - /// in the status "show" option. - pub fn include_untracked(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNTRACKED, include) - } - - /// Flag whether ignored files will be included. - /// - /// The files will only be included if the workdir files are included - /// in the status "show" option. - pub fn include_ignored(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_INCLUDE_IGNORED, include) - } - - /// Flag to include unmodified files. - pub fn include_unmodified(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNMODIFIED, include) - } - - /// Flag that submodules should be skipped. - /// - /// This only applies if there are no pending typechanges to the submodule - /// (either from or to another type). - pub fn exclude_submodules(&mut self, exclude: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_EXCLUDE_SUBMODULES, exclude) - } - - /// Flag that all files in untracked directories should be included. - /// - /// Normally if an entire directory is new then just the top-level directory - /// is included (with a trailing slash on the entry name). - pub fn recurse_untracked_dirs(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS, include) - } - - /// Indicates that the given paths should be treated as literals paths, note - /// patterns. - pub fn disable_pathspec_match(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH, include) - } - - /// Indicates that the contents of ignored directories should be included in - /// the status. - pub fn recurse_ignored_dirs(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_RECURSE_IGNORED_DIRS, include) - } - - /// Indicates that rename detection should be processed between the head. - pub fn renames_head_to_index(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX, include) - } - - /// Indicates that rename detection should be run between the index and the - /// working directory. - pub fn renames_index_to_workdir(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR, include) - } - - /// Override the native case sensitivity for the file system and force the - /// output to be in case sensitive order. - pub fn sort_case_sensitively(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_SORT_CASE_SENSITIVELY, include) - } - - /// Override the native case sensitivity for the file system and force the - /// output to be in case-insensitive order. - pub fn sort_case_insensitively(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY, include) - } - - /// Indicates that rename detection should include rewritten files. - pub fn renames_from_rewrites(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_RENAMES_FROM_REWRITES, include) - } - - /// Bypasses the default status behavior of doing a "soft" index reload. - pub fn no_refresh(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_NO_REFRESH, include) - } - - /// Refresh the stat cache in the index for files are unchanged but have - /// out of date stat information in the index. - /// - /// This will result in less work being done on subsequent calls to fetching - /// the status. - pub fn update_index(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_UPDATE_INDEX, include) - } - - // erm... - #[allow(missing_docs)] - pub fn include_unreadable(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNREADABLE, include) - } - - // erm... - #[allow(missing_docs)] - pub fn include_unreadable_as_untracked(&mut self, include: bool) -> &mut StatusOptions { - self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED, include) - } - - /// Set threshold above which similar files will be considered renames. - /// - /// This is equivalent to the `-M` option. Defaults to 50. - pub fn rename_threshold(&mut self, threshold: u16) -> &mut StatusOptions { - self.raw.rename_threshold = threshold; - self - } - - /// Get a pointer to the inner list of status options. - /// - /// This function is unsafe as the returned structure has interior pointers - /// and may no longer be valid if these options continue to be mutated. - pub unsafe fn raw(&mut self) -> *const raw::git_status_options { - self.raw.pathspec.strings = self.ptrs.as_ptr() as *mut _; - self.raw.pathspec.count = self.ptrs.len() as size_t; - &self.raw - } -} - -impl<'repo> Statuses<'repo> { - /// Gets a status entry from this list at the specified index. - /// - /// Returns `None` if the index is out of bounds. - pub fn get(&self, index: usize) -> Option<StatusEntry<'_>> { - unsafe { - let p = raw::git_status_byindex(self.raw, index as size_t); - Binding::from_raw_opt(p) - } - } - - /// Gets the count of status entries in this list. - /// - /// If there are no changes in status (according to the options given - /// when the status list was created), this should return 0. - pub fn len(&self) -> usize { - unsafe { raw::git_status_list_entrycount(self.raw) as usize } - } - - /// Return `true` if there is no status entry in this list. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns an iterator over the statuses in this list. - pub fn iter(&self) -> StatusIter<'_> { - StatusIter { - statuses: self, - range: 0..self.len(), - } - } -} - -impl<'repo> Binding for Statuses<'repo> { - type Raw = *mut raw::git_status_list; - unsafe fn from_raw(raw: *mut raw::git_status_list) -> Statuses<'repo> { - Statuses { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_status_list { - self.raw - } -} - -impl<'repo> Drop for Statuses<'repo> { - fn drop(&mut self) { - unsafe { - raw::git_status_list_free(self.raw); - } - } -} - -impl<'a> Iterator for StatusIter<'a> { - type Item = StatusEntry<'a>; - fn next(&mut self) -> Option<StatusEntry<'a>> { - self.range.next().and_then(|i| self.statuses.get(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'a> DoubleEndedIterator for StatusIter<'a> { - fn next_back(&mut self) -> Option<StatusEntry<'a>> { - self.range.next_back().and_then(|i| self.statuses.get(i)) - } -} -impl<'a> FusedIterator for StatusIter<'a> {} -impl<'a> ExactSizeIterator for StatusIter<'a> {} - -impl<'a> IntoIterator for &'a Statuses<'a> { - type Item = StatusEntry<'a>; - type IntoIter = StatusIter<'a>; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'statuses> StatusEntry<'statuses> { - /// Access the bytes for this entry's corresponding pathname - pub fn path_bytes(&self) -> &[u8] { - unsafe { - if (*self.raw).head_to_index.is_null() { - crate::opt_bytes(self, (*(*self.raw).index_to_workdir).old_file.path) - } else { - crate::opt_bytes(self, (*(*self.raw).head_to_index).old_file.path) - } - .unwrap() - } - } - - /// Access this entry's path name as a string. - /// - /// Returns `None` if the path is not valid utf-8. - pub fn path(&self) -> Option<&str> { - str::from_utf8(self.path_bytes()).ok() - } - - /// Access the status flags for this file - pub fn status(&self) -> Status { - Status::from_bits_truncate(unsafe { (*self.raw).status as u32 }) - } - - /// Access detailed information about the differences between the file in - /// HEAD and the file in the index. - pub fn head_to_index(&self) -> Option<DiffDelta<'statuses>> { - unsafe { Binding::from_raw_opt((*self.raw).head_to_index) } - } - - /// Access detailed information about the differences between the file in - /// the index and the file in the working directory. - pub fn index_to_workdir(&self) -> Option<DiffDelta<'statuses>> { - unsafe { Binding::from_raw_opt((*self.raw).index_to_workdir) } - } -} - -impl<'statuses> Binding for StatusEntry<'statuses> { - type Raw = *const raw::git_status_entry; - - unsafe fn from_raw(raw: *const raw::git_status_entry) -> StatusEntry<'statuses> { - StatusEntry { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *const raw::git_status_entry { - self.raw - } -} - -#[cfg(test)] -mod tests { - use super::StatusOptions; - use std::fs::File; - use std::io::prelude::*; - use std::path::Path; - - #[test] - fn smoke() { - let (td, repo) = crate::test::repo_init(); - assert_eq!(repo.statuses(None).unwrap().len(), 0); - File::create(&td.path().join("foo")).unwrap(); - let statuses = repo.statuses(None).unwrap(); - assert_eq!(statuses.iter().count(), 1); - let status = statuses.iter().next().unwrap(); - assert_eq!(status.path(), Some("foo")); - assert!(status.status().contains(crate::Status::WT_NEW)); - assert!(!status.status().contains(crate::Status::INDEX_NEW)); - assert!(status.head_to_index().is_none()); - let diff = status.index_to_workdir().unwrap(); - assert_eq!(diff.old_file().path_bytes().unwrap(), b"foo"); - assert_eq!(diff.new_file().path_bytes().unwrap(), b"foo"); - } - - #[test] - fn filter() { - let (td, repo) = crate::test::repo_init(); - t!(File::create(&td.path().join("foo"))); - t!(File::create(&td.path().join("bar"))); - let mut opts = StatusOptions::new(); - opts.include_untracked(true).pathspec("foo"); - - let statuses = t!(repo.statuses(Some(&mut opts))); - assert_eq!(statuses.iter().count(), 1); - let status = statuses.iter().next().unwrap(); - assert_eq!(status.path(), Some("foo")); - } - - #[test] - fn gitignore() { - let (td, repo) = crate::test::repo_init(); - t!(t!(File::create(td.path().join(".gitignore"))).write_all(b"foo\n")); - assert!(!t!(repo.status_should_ignore(Path::new("bar")))); - assert!(t!(repo.status_should_ignore(Path::new("foo")))); - } - - #[test] - fn status_file() { - let (td, repo) = crate::test::repo_init(); - assert!(repo.status_file(Path::new("foo")).is_err()); - if cfg!(windows) { - assert!(repo.status_file(Path::new("bar\\foo.txt")).is_err()); - } - t!(File::create(td.path().join("foo"))); - if cfg!(windows) { - t!(::std::fs::create_dir_all(td.path().join("bar"))); - t!(File::create(td.path().join("bar").join("foo.txt"))); - } - let status = t!(repo.status_file(Path::new("foo"))); - assert!(status.contains(crate::Status::WT_NEW)); - if cfg!(windows) { - let status = t!(repo.status_file(Path::new("bar\\foo.txt"))); - assert!(status.contains(crate::Status::WT_NEW)); - } - } -} diff --git a/extra/git2/src/string_array.rs b/extra/git2/src/string_array.rs deleted file mode 100644 index c77ccdab9..000000000 --- a/extra/git2/src/string_array.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Bindings to libgit2's raw `git_strarray` type - -use std::iter::FusedIterator; -use std::ops::Range; -use std::str; - -use crate::raw; -use crate::util::Binding; - -/// A string array structure used by libgit2 -/// -/// Some APIs return arrays of strings which originate from libgit2. This -/// wrapper type behaves a little like `Vec<&str>` but does so without copying -/// the underlying strings until necessary. -pub struct StringArray { - raw: raw::git_strarray, -} - -/// A forward iterator over the strings of an array, casted to `&str`. -pub struct Iter<'a> { - range: Range<usize>, - arr: &'a StringArray, -} - -/// A forward iterator over the strings of an array, casted to `&[u8]`. -pub struct IterBytes<'a> { - range: Range<usize>, - arr: &'a StringArray, -} - -impl StringArray { - /// Returns None if the i'th string is not utf8 or if i is out of bounds. - pub fn get(&self, i: usize) -> Option<&str> { - self.get_bytes(i).and_then(|s| str::from_utf8(s).ok()) - } - - /// Returns None if `i` is out of bounds. - pub fn get_bytes(&self, i: usize) -> Option<&[u8]> { - if i < self.raw.count as usize { - unsafe { - let ptr = *self.raw.strings.add(i) as *const _; - Some(crate::opt_bytes(self, ptr).unwrap()) - } - } else { - None - } - } - - /// Returns an iterator over the strings contained within this array. - /// - /// The iterator yields `Option<&str>` as it is unknown whether the contents - /// are utf-8 or not. - pub fn iter(&self) -> Iter<'_> { - Iter { - range: 0..self.len(), - arr: self, - } - } - - /// Returns an iterator over the strings contained within this array, - /// yielding byte slices. - pub fn iter_bytes(&self) -> IterBytes<'_> { - IterBytes { - range: 0..self.len(), - arr: self, - } - } - - /// Returns the number of strings in this array. - pub fn len(&self) -> usize { - self.raw.count as usize - } - - /// Return `true` if this array is empty. - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -impl Binding for StringArray { - type Raw = raw::git_strarray; - unsafe fn from_raw(raw: raw::git_strarray) -> StringArray { - StringArray { raw } - } - fn raw(&self) -> raw::git_strarray { - self.raw - } -} - -impl<'a> IntoIterator for &'a StringArray { - type Item = Option<&'a str>; - type IntoIter = Iter<'a>; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a> Iterator for Iter<'a> { - type Item = Option<&'a str>; - fn next(&mut self) -> Option<Option<&'a str>> { - self.range.next().map(|i| self.arr.get(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'a> DoubleEndedIterator for Iter<'a> { - fn next_back(&mut self) -> Option<Option<&'a str>> { - self.range.next_back().map(|i| self.arr.get(i)) - } -} -impl<'a> FusedIterator for Iter<'a> {} -impl<'a> ExactSizeIterator for Iter<'a> {} - -impl<'a> Iterator for IterBytes<'a> { - type Item = &'a [u8]; - fn next(&mut self) -> Option<&'a [u8]> { - self.range.next().and_then(|i| self.arr.get_bytes(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'a> DoubleEndedIterator for IterBytes<'a> { - fn next_back(&mut self) -> Option<&'a [u8]> { - self.range.next_back().and_then(|i| self.arr.get_bytes(i)) - } -} -impl<'a> FusedIterator for IterBytes<'a> {} -impl<'a> ExactSizeIterator for IterBytes<'a> {} - -impl Drop for StringArray { - fn drop(&mut self) { - unsafe { raw::git_strarray_free(&mut self.raw) } - } -} diff --git a/extra/git2/src/submodule.rs b/extra/git2/src/submodule.rs deleted file mode 100644 index 06a635940..000000000 --- a/extra/git2/src/submodule.rs +++ /dev/null @@ -1,471 +0,0 @@ -use std::marker; -use std::mem; -use std::os::raw::c_int; -use std::path::Path; -use std::ptr; -use std::str; - -use crate::util::{self, Binding}; -use crate::{build::CheckoutBuilder, SubmoduleIgnore, SubmoduleUpdate}; -use crate::{raw, Error, FetchOptions, Oid, Repository}; - -/// A structure to represent a git [submodule][1] -/// -/// [1]: http://git-scm.com/book/en/Git-Tools-Submodules -pub struct Submodule<'repo> { - raw: *mut raw::git_submodule, - _marker: marker::PhantomData<&'repo Repository>, -} - -impl<'repo> Submodule<'repo> { - /// Get the submodule's branch. - /// - /// Returns `None` if the branch is not valid utf-8 or if the branch is not - /// yet available. - pub fn branch(&self) -> Option<&str> { - self.branch_bytes().and_then(|s| str::from_utf8(s).ok()) - } - - /// Get the branch for the submodule. - /// - /// Returns `None` if the branch is not yet available. - pub fn branch_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_submodule_branch(self.raw)) } - } - - /// Perform the clone step for a newly created submodule. - /// - /// This performs the necessary `git_clone` to setup a newly-created submodule. - pub fn clone( - &mut self, - opts: Option<&mut SubmoduleUpdateOptions<'_>>, - ) -> Result<Repository, Error> { - unsafe { - let raw_opts = opts.map(|o| o.raw()); - let mut raw_repo = ptr::null_mut(); - try_call!(raw::git_submodule_clone( - &mut raw_repo, - self.raw, - raw_opts.as_ref() - )); - Ok(Binding::from_raw(raw_repo)) - } - } - - /// Get the submodule's URL. - /// - /// Returns `None` if the URL is not valid utf-8 or if the URL isn't present - pub fn url(&self) -> Option<&str> { - self.opt_url_bytes().and_then(|b| str::from_utf8(b).ok()) - } - - /// Get the URL for the submodule. - #[doc(hidden)] - #[deprecated(note = "renamed to `opt_url_bytes`")] - pub fn url_bytes(&self) -> &[u8] { - self.opt_url_bytes().unwrap() - } - - /// Get the URL for the submodule. - /// - /// Returns `None` if the URL isn't present - // TODO: delete this method and fix the signature of `url_bytes` on next - // major version bump - pub fn opt_url_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_submodule_url(self.raw)) } - } - - /// Get the submodule's name. - /// - /// Returns `None` if the name is not valid utf-8 - pub fn name(&self) -> Option<&str> { - str::from_utf8(self.name_bytes()).ok() - } - - /// Get the name for the submodule. - pub fn name_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_submodule_name(self.raw)).unwrap() } - } - - /// Get the path for the submodule. - pub fn path(&self) -> &Path { - util::bytes2path(unsafe { - crate::opt_bytes(self, raw::git_submodule_path(self.raw)).unwrap() - }) - } - - /// Get the OID for the submodule in the current HEAD tree. - pub fn head_id(&self) -> Option<Oid> { - unsafe { Binding::from_raw_opt(raw::git_submodule_head_id(self.raw)) } - } - - /// Get the OID for the submodule in the index. - pub fn index_id(&self) -> Option<Oid> { - unsafe { Binding::from_raw_opt(raw::git_submodule_index_id(self.raw)) } - } - - /// Get the OID for the submodule in the current working directory. - /// - /// This returns the OID that corresponds to looking up 'HEAD' in the - /// checked out submodule. If there are pending changes in the index or - /// anything else, this won't notice that. - pub fn workdir_id(&self) -> Option<Oid> { - unsafe { Binding::from_raw_opt(raw::git_submodule_wd_id(self.raw)) } - } - - /// Get the ignore rule that will be used for the submodule. - pub fn ignore_rule(&self) -> SubmoduleIgnore { - SubmoduleIgnore::from_raw(unsafe { raw::git_submodule_ignore(self.raw) }) - } - - /// Get the update rule that will be used for the submodule. - pub fn update_strategy(&self) -> SubmoduleUpdate { - SubmoduleUpdate::from_raw(unsafe { raw::git_submodule_update_strategy(self.raw) }) - } - - /// Copy submodule info into ".git/config" file. - /// - /// Just like "git submodule init", this copies information about the - /// submodule into ".git/config". You can use the accessor functions above - /// to alter the in-memory git_submodule object and control what is written - /// to the config, overriding what is in .gitmodules. - /// - /// By default, existing entries will not be overwritten, but passing `true` - /// for `overwrite` forces them to be updated. - pub fn init(&mut self, overwrite: bool) -> Result<(), Error> { - unsafe { - try_call!(raw::git_submodule_init(self.raw, overwrite)); - } - Ok(()) - } - - /// Set up the subrepository for a submodule in preparation for clone. - /// - /// This function can be called to init and set up a submodule repository - /// from a submodule in preparation to clone it from its remote. - - /// use_gitlink: Should the workdir contain a gitlink to the repo in - /// .git/modules vs. repo directly in workdir. - pub fn repo_init(&mut self, use_gitlink: bool) -> Result<Repository, Error> { - unsafe { - let mut raw_repo = ptr::null_mut(); - try_call!(raw::git_submodule_repo_init( - &mut raw_repo, - self.raw, - use_gitlink - )); - Ok(Binding::from_raw(raw_repo)) - } - } - - /// Open the repository for a submodule. - /// - /// This will only work if the submodule is checked out into the working - /// directory. - pub fn open(&self) -> Result<Repository, Error> { - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_submodule_open(&mut raw, self.raw)); - Ok(Binding::from_raw(raw)) - } - } - - /// Reread submodule info from config, index, and HEAD. - /// - /// Call this to reread cached submodule information for this submodule if - /// you have reason to believe that it has changed. - /// - /// If `force` is `true`, then data will be reloaded even if it doesn't seem - /// out of date - pub fn reload(&mut self, force: bool) -> Result<(), Error> { - unsafe { - try_call!(raw::git_submodule_reload(self.raw, force)); - } - Ok(()) - } - - /// Copy submodule remote info into submodule repo. - /// - /// This copies the information about the submodules URL into the checked - /// out submodule config, acting like "git submodule sync". This is useful - /// if you have altered the URL for the submodule (or it has been altered - /// by a fetch of upstream changes) and you need to update your local repo. - pub fn sync(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_submodule_sync(self.raw)); - } - Ok(()) - } - - /// Add current submodule HEAD commit to index of superproject. - /// - /// If `write_index` is true, then the index file will be immediately - /// written. Otherwise you must explicitly call `write()` on an `Index` - /// later on. - pub fn add_to_index(&mut self, write_index: bool) -> Result<(), Error> { - unsafe { - try_call!(raw::git_submodule_add_to_index(self.raw, write_index)); - } - Ok(()) - } - - /// Resolve the setup of a new git submodule. - /// - /// This should be called on a submodule once you have called add setup and - /// done the clone of the submodule. This adds the .gitmodules file and the - /// newly cloned submodule to the index to be ready to be committed (but - /// doesn't actually do the commit). - pub fn add_finalize(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_submodule_add_finalize(self.raw)); - } - Ok(()) - } - - /// Update submodule. - /// - /// This will clone a missing submodule and check out the subrepository to - /// the commit specified in the index of the containing repository. If - /// the submodule repository doesn't contain the target commit, then the - /// submodule is fetched using the fetch options supplied in `opts`. - /// - /// `init` indicates if the submodule should be initialized first if it has - /// not been initialized yet. - pub fn update( - &mut self, - init: bool, - opts: Option<&mut SubmoduleUpdateOptions<'_>>, - ) -> Result<(), Error> { - unsafe { - let mut raw_opts = opts.map(|o| o.raw()); - try_call!(raw::git_submodule_update( - self.raw, - init as c_int, - raw_opts.as_mut().map_or(ptr::null_mut(), |o| o) - )); - } - Ok(()) - } -} - -impl<'repo> Binding for Submodule<'repo> { - type Raw = *mut raw::git_submodule; - unsafe fn from_raw(raw: *mut raw::git_submodule) -> Submodule<'repo> { - Submodule { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_submodule { - self.raw - } -} - -impl<'repo> Drop for Submodule<'repo> { - fn drop(&mut self) { - unsafe { raw::git_submodule_free(self.raw) } - } -} - -/// Options to update a submodule. -pub struct SubmoduleUpdateOptions<'cb> { - checkout_builder: CheckoutBuilder<'cb>, - fetch_opts: FetchOptions<'cb>, - allow_fetch: bool, -} - -impl<'cb> SubmoduleUpdateOptions<'cb> { - /// Return default options. - pub fn new() -> Self { - SubmoduleUpdateOptions { - checkout_builder: CheckoutBuilder::new(), - fetch_opts: FetchOptions::new(), - allow_fetch: true, - } - } - - unsafe fn raw(&mut self) -> raw::git_submodule_update_options { - let mut checkout_opts: raw::git_checkout_options = mem::zeroed(); - let init_res = - raw::git_checkout_init_options(&mut checkout_opts, raw::GIT_CHECKOUT_OPTIONS_VERSION); - assert_eq!(0, init_res); - self.checkout_builder.configure(&mut checkout_opts); - let opts = raw::git_submodule_update_options { - version: raw::GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, - checkout_opts, - fetch_opts: self.fetch_opts.raw(), - allow_fetch: self.allow_fetch as c_int, - }; - opts - } - - /// Set checkout options. - pub fn checkout(&mut self, opts: CheckoutBuilder<'cb>) -> &mut Self { - self.checkout_builder = opts; - self - } - - /// Set fetch options and allow fetching. - pub fn fetch(&mut self, opts: FetchOptions<'cb>) -> &mut Self { - self.fetch_opts = opts; - self.allow_fetch = true; - self - } - - /// Allow or disallow fetching. - pub fn allow_fetch(&mut self, b: bool) -> &mut Self { - self.allow_fetch = b; - self - } -} - -impl<'cb> Default for SubmoduleUpdateOptions<'cb> { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod tests { - use std::fs; - use std::path::Path; - use tempfile::TempDir; - use url::Url; - - use crate::Repository; - use crate::SubmoduleUpdateOptions; - - #[test] - fn smoke() { - let td = TempDir::new().unwrap(); - let repo = Repository::init(td.path()).unwrap(); - let mut s1 = repo - .submodule("/path/to/nowhere", Path::new("foo"), true) - .unwrap(); - s1.init(false).unwrap(); - s1.sync().unwrap(); - - let s2 = repo - .submodule("/path/to/nowhere", Path::new("bar"), true) - .unwrap(); - drop((s1, s2)); - - let mut submodules = repo.submodules().unwrap(); - assert_eq!(submodules.len(), 2); - let mut s = submodules.remove(0); - assert_eq!(s.name(), Some("bar")); - assert_eq!(s.url(), Some("/path/to/nowhere")); - assert_eq!(s.branch(), None); - assert!(s.head_id().is_none()); - assert!(s.index_id().is_none()); - assert!(s.workdir_id().is_none()); - - repo.find_submodule("bar").unwrap(); - s.open().unwrap(); - assert!(s.path() == Path::new("bar")); - s.reload(true).unwrap(); - } - - #[test] - fn add_a_submodule() { - let (_td, repo1) = crate::test::repo_init(); - let (td, repo2) = crate::test::repo_init(); - - let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap(); - let mut s = repo2 - .submodule(&url.to_string(), Path::new("bar"), true) - .unwrap(); - t!(fs::remove_dir_all(td.path().join("bar"))); - t!(Repository::clone(&url.to_string(), td.path().join("bar"))); - t!(s.add_to_index(false)); - t!(s.add_finalize()); - } - - #[test] - fn update_submodule() { - // ----------------------------------- - // Same as `add_a_submodule()` - let (_td, repo1) = crate::test::repo_init(); - let (td, repo2) = crate::test::repo_init(); - - let url = Url::from_file_path(&repo1.workdir().unwrap()).unwrap(); - let mut s = repo2 - .submodule(&url.to_string(), Path::new("bar"), true) - .unwrap(); - t!(fs::remove_dir_all(td.path().join("bar"))); - t!(Repository::clone(&url.to_string(), td.path().join("bar"))); - t!(s.add_to_index(false)); - t!(s.add_finalize()); - // ----------------------------------- - - // Attempt to update submodule - let submodules = t!(repo1.submodules()); - for mut submodule in submodules { - let mut submodule_options = SubmoduleUpdateOptions::new(); - let init = true; - let opts = Some(&mut submodule_options); - - t!(submodule.update(init, opts)); - } - } - - #[test] - fn clone_submodule() { - // ----------------------------------- - // Same as `add_a_submodule()` - let (_td, repo1) = crate::test::repo_init(); - let (_td, repo2) = crate::test::repo_init(); - let (_td, parent) = crate::test::repo_init(); - - let url1 = Url::from_file_path(&repo1.workdir().unwrap()).unwrap(); - let url2 = Url::from_file_path(&repo2.workdir().unwrap()).unwrap(); - let mut s1 = parent - .submodule(&url1.to_string(), Path::new("bar"), true) - .unwrap(); - let mut s2 = parent - .submodule(&url2.to_string(), Path::new("bar2"), true) - .unwrap(); - // ----------------------------------- - - t!(s1.clone(Some(&mut SubmoduleUpdateOptions::default()))); - t!(s2.clone(None)); - } - - #[test] - fn repo_init_submodule() { - // ----------------------------------- - // Same as `clone_submodule()` - let (_td, child) = crate::test::repo_init(); - let (_td, parent) = crate::test::repo_init(); - - let url_child = Url::from_file_path(&child.workdir().unwrap()).unwrap(); - let url_parent = Url::from_file_path(&parent.workdir().unwrap()).unwrap(); - let mut sub = parent - .submodule(&url_child.to_string(), Path::new("bar"), true) - .unwrap(); - - // ----------------------------------- - // Let's commit the submodule for later clone - t!(sub.clone(None)); - t!(sub.add_to_index(true)); - t!(sub.add_finalize()); - - crate::test::commit(&parent); - - // Clone the parent to init its submodules - let td = TempDir::new().unwrap(); - let new_parent = Repository::clone(&url_parent.to_string(), &td).unwrap(); - - let mut submodules = new_parent.submodules().unwrap(); - let child = submodules.first_mut().unwrap(); - - // First init child - t!(child.init(false)); - assert_eq!(child.url().unwrap(), url_child.as_str()); - - // open() is not possible before initializing the repo - assert!(child.open().is_err()); - t!(child.repo_init(true)); - assert!(child.open().is_ok()); - } -} diff --git a/extra/git2/src/tag.rs b/extra/git2/src/tag.rs deleted file mode 100644 index 6986c7c16..000000000 --- a/extra/git2/src/tag.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::ffi::CString; -use std::marker; -use std::mem; -use std::ptr; -use std::str; - -use crate::util::Binding; -use crate::{call, raw, signature, Error, Object, ObjectType, Oid, Signature}; - -/// A structure to represent a git [tag][1] -/// -/// [1]: http://git-scm.com/book/en/Git-Basics-Tagging -pub struct Tag<'repo> { - raw: *mut raw::git_tag, - _marker: marker::PhantomData<Object<'repo>>, -} - -impl<'repo> Tag<'repo> { - /// Determine whether a tag name is valid, meaning that (when prefixed with refs/tags/) that - /// it is a valid reference name, and that any additional tag name restrictions are imposed - /// (eg, it cannot start with a -). - pub fn is_valid_name(tag_name: &str) -> bool { - crate::init(); - let tag_name = CString::new(tag_name).unwrap(); - let mut valid: libc::c_int = 0; - unsafe { - call::c_try(raw::git_tag_name_is_valid(&mut valid, tag_name.as_ptr())).unwrap(); - } - valid == 1 - } - - /// Get the id (SHA1) of a repository tag - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_tag_id(&*self.raw)) } - } - - /// Get the message of a tag - /// - /// Returns None if there is no message or if it is not valid utf8 - pub fn message(&self) -> Option<&str> { - self.message_bytes().and_then(|s| str::from_utf8(s).ok()) - } - - /// Get the message of a tag - /// - /// Returns None if there is no message - pub fn message_bytes(&self) -> Option<&[u8]> { - unsafe { crate::opt_bytes(self, raw::git_tag_message(&*self.raw)) } - } - - /// Get the name of a tag - /// - /// Returns None if it is not valid utf8 - pub fn name(&self) -> Option<&str> { - str::from_utf8(self.name_bytes()).ok() - } - - /// Get the name of a tag - pub fn name_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_tag_name(&*self.raw)).unwrap() } - } - - /// Recursively peel a tag until a non tag git_object is found - pub fn peel(&self) -> Result<Object<'repo>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_tag_peel(&mut ret, &*self.raw)); - Ok(Binding::from_raw(ret)) - } - } - - /// Get the tagger (author) of a tag - /// - /// If the author is unspecified, then `None` is returned. - pub fn tagger(&self) -> Option<Signature<'_>> { - unsafe { - let ptr = raw::git_tag_tagger(&*self.raw); - if ptr.is_null() { - None - } else { - Some(signature::from_raw_const(self, ptr)) - } - } - } - - /// Get the tagged object of a tag - /// - /// This method performs a repository lookup for the given object and - /// returns it - pub fn target(&self) -> Result<Object<'repo>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_tag_target(&mut ret, &*self.raw)); - Ok(Binding::from_raw(ret)) - } - } - - /// Get the OID of the tagged object of a tag - pub fn target_id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_tag_target_id(&*self.raw)) } - } - - /// Get the ObjectType of the tagged object of a tag - pub fn target_type(&self) -> Option<ObjectType> { - unsafe { ObjectType::from_raw(raw::git_tag_target_type(&*self.raw)) } - } - - /// Casts this Tag to be usable as an `Object` - pub fn as_object(&self) -> &Object<'repo> { - unsafe { &*(self as *const _ as *const Object<'repo>) } - } - - /// Consumes Tag to be returned as an `Object` - pub fn into_object(self) -> Object<'repo> { - assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>()); - unsafe { mem::transmute(self) } - } -} - -impl<'repo> std::fmt::Debug for Tag<'repo> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - let mut ds = f.debug_struct("Tag"); - if let Some(name) = self.name() { - ds.field("name", &name); - } - ds.field("id", &self.id()); - ds.finish() - } -} - -impl<'repo> Binding for Tag<'repo> { - type Raw = *mut raw::git_tag; - unsafe fn from_raw(raw: *mut raw::git_tag) -> Tag<'repo> { - Tag { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_tag { - self.raw - } -} - -impl<'repo> Clone for Tag<'repo> { - fn clone(&self) -> Self { - self.as_object().clone().into_tag().ok().unwrap() - } -} - -impl<'repo> Drop for Tag<'repo> { - fn drop(&mut self) { - unsafe { raw::git_tag_free(self.raw) } - } -} - -#[cfg(test)] -mod tests { - use crate::Tag; - - // Reference -- https://git-scm.com/docs/git-check-ref-format - #[test] - fn name_is_valid() { - assert_eq!(Tag::is_valid_name("blah_blah"), true); - assert_eq!(Tag::is_valid_name("v1.2.3"), true); - assert_eq!(Tag::is_valid_name("my/tag"), true); - assert_eq!(Tag::is_valid_name("@"), true); - - assert_eq!(Tag::is_valid_name("-foo"), false); - assert_eq!(Tag::is_valid_name("foo:bar"), false); - assert_eq!(Tag::is_valid_name("foo^bar"), false); - assert_eq!(Tag::is_valid_name("foo."), false); - assert_eq!(Tag::is_valid_name("@{"), false); - assert_eq!(Tag::is_valid_name("as\\cd"), false); - } - - #[test] - #[should_panic] - fn is_valid_name_for_invalid_tag() { - Tag::is_valid_name("ab\012"); - } - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let head = repo.head().unwrap(); - let id = head.target().unwrap(); - assert!(repo.find_tag(id).is_err()); - - let obj = repo.find_object(id, None).unwrap(); - let sig = repo.signature().unwrap(); - let tag_id = repo.tag("foo", &obj, &sig, "msg", false).unwrap(); - let tag = repo.find_tag(tag_id).unwrap(); - assert_eq!(tag.id(), tag_id); - - let tags = repo.tag_names(None).unwrap(); - assert_eq!(tags.len(), 1); - assert_eq!(tags.get(0), Some("foo")); - - assert_eq!(tag.name(), Some("foo")); - assert_eq!(tag.message(), Some("msg")); - assert_eq!(tag.peel().unwrap().id(), obj.id()); - assert_eq!(tag.target_id(), obj.id()); - assert_eq!(tag.target_type(), Some(crate::ObjectType::Commit)); - - assert_eq!(tag.tagger().unwrap().name(), sig.name()); - tag.target().unwrap(); - tag.into_object(); - - repo.find_object(tag_id, None).unwrap().as_tag().unwrap(); - repo.find_object(tag_id, None) - .unwrap() - .into_tag() - .ok() - .unwrap(); - - repo.tag_delete("foo").unwrap(); - } - - #[test] - fn lite() { - let (_td, repo) = crate::test::repo_init(); - let head = t!(repo.head()); - let id = head.target().unwrap(); - let obj = t!(repo.find_object(id, None)); - let tag_id = t!(repo.tag_lightweight("foo", &obj, false)); - assert!(repo.find_tag(tag_id).is_err()); - assert_eq!(t!(repo.refname_to_id("refs/tags/foo")), id); - - let tags = t!(repo.tag_names(Some("f*"))); - assert_eq!(tags.len(), 1); - let tags = t!(repo.tag_names(Some("b*"))); - assert_eq!(tags.len(), 0); - } -} diff --git a/extra/git2/src/tagforeach.rs b/extra/git2/src/tagforeach.rs deleted file mode 100644 index 425eea5a4..000000000 --- a/extra/git2/src/tagforeach.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! git_tag_foreach support -//! see original: <https://libgit2.org/libgit2/#HEAD/group/tag/git_tag_foreach> - -use crate::{panic, raw, util::Binding, Oid}; -use libc::{c_char, c_int}; -use raw::git_oid; -use std::ffi::{c_void, CStr}; - -/// boxed callback type -pub(crate) type TagForeachCB<'a> = Box<dyn FnMut(Oid, &[u8]) -> bool + 'a>; - -/// helper type to be able to pass callback to payload -pub(crate) struct TagForeachData<'a> { - /// callback - pub(crate) cb: TagForeachCB<'a>, -} - -/// c callback forwarding to rust callback inside `TagForeachData` -/// see original: <https://libgit2.org/libgit2/#HEAD/group/callback/git_tag_foreach_cb> -pub(crate) extern "C" fn tag_foreach_cb( - name: *const c_char, - oid: *mut git_oid, - payload: *mut c_void, -) -> c_int { - panic::wrap(|| unsafe { - let id: Oid = Binding::from_raw(oid as *const _); - - let name = CStr::from_ptr(name); - let name = name.to_bytes(); - - let payload = &mut *(payload as *mut TagForeachData<'_>); - let cb = &mut payload.cb; - - let res = cb(id, name); - - if res { - 0 - } else { - -1 - } - }) - .unwrap_or(-1) -} - -#[cfg(test)] -mod tests { - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - let head = repo.head().unwrap(); - let id = head.target().unwrap(); - assert!(repo.find_tag(id).is_err()); - - let obj = repo.find_object(id, None).unwrap(); - let sig = repo.signature().unwrap(); - let tag_id = repo.tag("foo", &obj, &sig, "msg", false).unwrap(); - - let mut tags = Vec::new(); - repo.tag_foreach(|id, name| { - tags.push((id, String::from_utf8(name.into()).unwrap())); - true - }) - .unwrap(); - - assert_eq!(tags[0].0, tag_id); - assert_eq!(tags[0].1, "refs/tags/foo"); - } -} diff --git a/extra/git2/src/test.rs b/extra/git2/src/test.rs deleted file mode 100644 index c1ff1de21..000000000 --- a/extra/git2/src/test.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::fs::File; -use std::io; -use std::path::{Path, PathBuf}; -#[cfg(unix)] -use std::ptr; -use tempfile::TempDir; -use url::Url; - -use crate::{Branch, Oid, Repository, RepositoryInitOptions}; - -macro_rules! t { - ($e:expr) => { - match $e { - Ok(e) => e, - Err(e) => panic!("{} failed with {}", stringify!($e), e), - } - }; -} - -pub fn repo_init() -> (TempDir, Repository) { - let td = TempDir::new().unwrap(); - let mut opts = RepositoryInitOptions::new(); - opts.initial_head("main"); - let repo = Repository::init_opts(td.path(), &opts).unwrap(); - { - let mut config = repo.config().unwrap(); - config.set_str("user.name", "name").unwrap(); - config.set_str("user.email", "email").unwrap(); - let mut index = repo.index().unwrap(); - let id = index.write_tree().unwrap(); - - let tree = repo.find_tree(id).unwrap(); - let sig = repo.signature().unwrap(); - repo.commit(Some("HEAD"), &sig, &sig, "initial\n\nbody", &tree, &[]) - .unwrap(); - } - (td, repo) -} - -pub fn commit(repo: &Repository) -> (Oid, Oid) { - let mut index = t!(repo.index()); - let root = repo.path().parent().unwrap(); - t!(File::create(&root.join("foo"))); - t!(index.add_path(Path::new("foo"))); - - let tree_id = t!(index.write_tree()); - let tree = t!(repo.find_tree(tree_id)); - let sig = t!(repo.signature()); - let head_id = t!(repo.refname_to_id("HEAD")); - let parent = t!(repo.find_commit(head_id)); - let commit = t!(repo.commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])); - (commit, tree_id) -} - -pub fn path2url(path: &Path) -> String { - Url::from_file_path(path).unwrap().to_string() -} - -pub fn worktrees_env_init(repo: &Repository) -> (TempDir, Branch<'_>) { - let oid = repo.head().unwrap().target().unwrap(); - let commit = repo.find_commit(oid).unwrap(); - let branch = repo.branch("wt-branch", &commit, true).unwrap(); - let wtdir = TempDir::new().unwrap(); - (wtdir, branch) -} - -#[cfg(windows)] -pub fn realpath(original: &Path) -> io::Result<PathBuf> { - Ok(original.to_path_buf()) -} -#[cfg(unix)] -pub fn realpath(original: &Path) -> io::Result<PathBuf> { - use libc::c_char; - use std::ffi::{CStr, CString, OsString}; - use std::os::unix::prelude::*; - extern "C" { - fn realpath(name: *const c_char, resolved: *mut c_char) -> *mut c_char; - } - unsafe { - let cstr = CString::new(original.as_os_str().as_bytes())?; - let ptr = realpath(cstr.as_ptr(), ptr::null_mut()); - if ptr.is_null() { - return Err(io::Error::last_os_error()); - } - let bytes = CStr::from_ptr(ptr).to_bytes().to_vec(); - libc::free(ptr as *mut _); - Ok(PathBuf::from(OsString::from_vec(bytes))) - } -} diff --git a/extra/git2/src/time.rs b/extra/git2/src/time.rs deleted file mode 100644 index 46b5bd3f9..000000000 --- a/extra/git2/src/time.rs +++ /dev/null @@ -1,127 +0,0 @@ -use std::cmp::Ordering; - -use libc::{c_char, c_int}; - -use crate::raw; -use crate::util::Binding; - -/// Time in a signature -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Time { - raw: raw::git_time, -} - -/// Time structure used in a git index entry. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct IndexTime { - raw: raw::git_index_time, -} - -impl Time { - /// Creates a new time structure from its components. - pub fn new(time: i64, offset: i32) -> Time { - unsafe { - Binding::from_raw(raw::git_time { - time: time as raw::git_time_t, - offset: offset as c_int, - sign: if offset < 0 { '-' } else { '+' } as c_char, - }) - } - } - - /// Return the time, in seconds, from epoch - pub fn seconds(&self) -> i64 { - self.raw.time as i64 - } - - /// Return the timezone offset, in minutes - pub fn offset_minutes(&self) -> i32 { - self.raw.offset as i32 - } - - /// Return whether the offset was positive or negative. Primarily useful - /// in case the offset is specified as a negative zero. - pub fn sign(&self) -> char { - self.raw.sign as u8 as char - } -} - -impl PartialOrd for Time { - fn partial_cmp(&self, other: &Time) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl Ord for Time { - fn cmp(&self, other: &Time) -> Ordering { - (self.raw.time, self.raw.offset).cmp(&(other.raw.time, other.raw.offset)) - } -} - -impl Binding for Time { - type Raw = raw::git_time; - unsafe fn from_raw(raw: raw::git_time) -> Time { - Time { raw } - } - fn raw(&self) -> raw::git_time { - self.raw - } -} - -impl IndexTime { - /// Creates a new time structure from its components. - pub fn new(seconds: i32, nanoseconds: u32) -> IndexTime { - unsafe { - Binding::from_raw(raw::git_index_time { - seconds, - nanoseconds, - }) - } - } - - /// Returns the number of seconds in the second component of this time. - pub fn seconds(&self) -> i32 { - self.raw.seconds - } - /// Returns the nanosecond component of this time. - pub fn nanoseconds(&self) -> u32 { - self.raw.nanoseconds - } -} - -impl Binding for IndexTime { - type Raw = raw::git_index_time; - unsafe fn from_raw(raw: raw::git_index_time) -> IndexTime { - IndexTime { raw } - } - fn raw(&self) -> raw::git_index_time { - self.raw - } -} - -impl PartialOrd for IndexTime { - fn partial_cmp(&self, other: &IndexTime) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl Ord for IndexTime { - fn cmp(&self, other: &IndexTime) -> Ordering { - let me = (self.raw.seconds, self.raw.nanoseconds); - let other = (other.raw.seconds, other.raw.nanoseconds); - me.cmp(&other) - } -} - -#[cfg(test)] -mod tests { - use crate::Time; - - #[test] - fn smoke() { - assert_eq!(Time::new(1608839587, -300).seconds(), 1608839587); - assert_eq!(Time::new(1608839587, -300).offset_minutes(), -300); - assert_eq!(Time::new(1608839587, -300).sign(), '-'); - assert_eq!(Time::new(1608839587, 300).sign(), '+'); - } -} diff --git a/extra/git2/src/tracing.rs b/extra/git2/src/tracing.rs deleted file mode 100644 index 5acae8a85..000000000 --- a/extra/git2/src/tracing.rs +++ /dev/null @@ -1,85 +0,0 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; - -use libc::c_char; - -use crate::{panic, raw, util::Binding}; - -/// Available tracing levels. When tracing is set to a particular level, -/// callers will be provided tracing at the given level and all lower levels. -#[derive(Copy, Clone, Debug)] -pub enum TraceLevel { - /// No tracing will be performed. - None, - - /// Severe errors that may impact the program's execution - Fatal, - - /// Errors that do not impact the program's execution - Error, - - /// Warnings that suggest abnormal data - Warn, - - /// Informational messages about program execution - Info, - - /// Detailed data that allows for debugging - Debug, - - /// Exceptionally detailed debugging data - Trace, -} - -impl Binding for TraceLevel { - type Raw = raw::git_trace_level_t; - unsafe fn from_raw(raw: raw::git_trace_level_t) -> Self { - match raw { - raw::GIT_TRACE_NONE => Self::None, - raw::GIT_TRACE_FATAL => Self::Fatal, - raw::GIT_TRACE_ERROR => Self::Error, - raw::GIT_TRACE_WARN => Self::Warn, - raw::GIT_TRACE_INFO => Self::Info, - raw::GIT_TRACE_DEBUG => Self::Debug, - raw::GIT_TRACE_TRACE => Self::Trace, - _ => panic!("Unknown git trace level"), - } - } - fn raw(&self) -> raw::git_trace_level_t { - match *self { - Self::None => raw::GIT_TRACE_NONE, - Self::Fatal => raw::GIT_TRACE_FATAL, - Self::Error => raw::GIT_TRACE_ERROR, - Self::Warn => raw::GIT_TRACE_WARN, - Self::Info => raw::GIT_TRACE_INFO, - Self::Debug => raw::GIT_TRACE_DEBUG, - Self::Trace => raw::GIT_TRACE_TRACE, - } - } -} - -//TODO: pass raw &[u8] and leave conversion to consumer (breaking API) -/// Callback type used to pass tracing events to the subscriber. -/// see `trace_set` to register a subscriber. -pub type TracingCb = fn(TraceLevel, &str); - -static CALLBACK: AtomicUsize = AtomicUsize::new(0); - -/// -pub fn trace_set(level: TraceLevel, cb: TracingCb) -> bool { - CALLBACK.store(cb as usize, Ordering::SeqCst); - - unsafe { - raw::git_trace_set(level.raw(), Some(tracing_cb_c)); - } - - return true; -} - -extern "C" fn tracing_cb_c(level: raw::git_trace_level_t, msg: *const c_char) { - let cb = CALLBACK.load(Ordering::SeqCst); - panic::wrap(|| unsafe { - let cb: TracingCb = std::mem::transmute(cb); - let msg = std::ffi::CStr::from_ptr(msg).to_string_lossy(); - cb(Binding::from_raw(level), msg.as_ref()); - }); -} diff --git a/extra/git2/src/transaction.rs b/extra/git2/src/transaction.rs deleted file mode 100644 index 4f661f1d4..000000000 --- a/extra/git2/src/transaction.rs +++ /dev/null @@ -1,285 +0,0 @@ -use std::ffi::CString; -use std::marker; - -use crate::{raw, util::Binding, Error, Oid, Reflog, Repository, Signature}; - -/// A structure representing a transactional update of a repository's references. -/// -/// Transactions work by locking loose refs for as long as the [`Transaction`] -/// is held, and committing all changes to disk when [`Transaction::commit`] is -/// called. Note that committing is not atomic: if an operation fails, the -/// transaction aborts, but previous successful operations are not rolled back. -pub struct Transaction<'repo> { - raw: *mut raw::git_transaction, - _marker: marker::PhantomData<&'repo Repository>, -} - -impl Drop for Transaction<'_> { - fn drop(&mut self) { - unsafe { raw::git_transaction_free(self.raw) } - } -} - -impl<'repo> Binding for Transaction<'repo> { - type Raw = *mut raw::git_transaction; - - unsafe fn from_raw(ptr: *mut raw::git_transaction) -> Transaction<'repo> { - Transaction { - raw: ptr, - _marker: marker::PhantomData, - } - } - - fn raw(&self) -> *mut raw::git_transaction { - self.raw - } -} - -impl<'repo> Transaction<'repo> { - /// Lock the specified reference by name. - pub fn lock_ref(&mut self, refname: &str) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - unsafe { - try_call!(raw::git_transaction_lock_ref(self.raw, refname)); - } - - Ok(()) - } - - /// Set the target of the specified reference. - /// - /// The reference must have been locked via `lock_ref`. - /// - /// If `reflog_signature` is `None`, the [`Signature`] is read from the - /// repository config. - pub fn set_target( - &mut self, - refname: &str, - target: Oid, - reflog_signature: Option<&Signature<'_>>, - reflog_message: &str, - ) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - let reflog_message = CString::new(reflog_message).unwrap(); - unsafe { - try_call!(raw::git_transaction_set_target( - self.raw, - refname, - target.raw(), - reflog_signature.map(|s| s.raw()), - reflog_message - )); - } - - Ok(()) - } - - /// Set the target of the specified symbolic reference. - /// - /// The reference must have been locked via `lock_ref`. - /// - /// If `reflog_signature` is `None`, the [`Signature`] is read from the - /// repository config. - pub fn set_symbolic_target( - &mut self, - refname: &str, - target: &str, - reflog_signature: Option<&Signature<'_>>, - reflog_message: &str, - ) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - let target = CString::new(target).unwrap(); - let reflog_message = CString::new(reflog_message).unwrap(); - unsafe { - try_call!(raw::git_transaction_set_symbolic_target( - self.raw, - refname, - target, - reflog_signature.map(|s| s.raw()), - reflog_message - )); - } - - Ok(()) - } - - /// Add a [`Reflog`] to the transaction. - /// - /// This commit the in-memory [`Reflog`] to disk when the transaction commits. - /// Note that atomicity is **not* guaranteed: if the transaction fails to - /// modify `refname`, the reflog may still have been committed to disk. - /// - /// If this is combined with setting the target, that update won't be - /// written to the log (i.e. the `reflog_signature` and `reflog_message` - /// parameters will be ignored). - pub fn set_reflog(&mut self, refname: &str, reflog: Reflog) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - unsafe { - try_call!(raw::git_transaction_set_reflog( - self.raw, - refname, - reflog.raw() - )); - } - - Ok(()) - } - - /// Remove a reference. - /// - /// The reference must have been locked via `lock_ref`. - pub fn remove(&mut self, refname: &str) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - unsafe { - try_call!(raw::git_transaction_remove(self.raw, refname)); - } - - Ok(()) - } - - /// Commit the changes from the transaction. - /// - /// The updates will be made one by one, and the first failure will stop the - /// processing. - pub fn commit(self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_transaction_commit(self.raw)); - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::{Error, ErrorClass, ErrorCode, Oid, Repository}; - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - - let mut tx = t!(repo.transaction()); - - t!(tx.lock_ref("refs/heads/main")); - t!(tx.lock_ref("refs/heads/next")); - - t!(tx.set_target("refs/heads/main", Oid::zero(), None, "set main to zero")); - t!(tx.set_symbolic_target( - "refs/heads/next", - "refs/heads/main", - None, - "set next to main", - )); - - t!(tx.commit()); - - assert_eq!(repo.refname_to_id("refs/heads/main").unwrap(), Oid::zero()); - assert_eq!( - repo.find_reference("refs/heads/next") - .unwrap() - .symbolic_target() - .unwrap(), - "refs/heads/main" - ); - } - - #[test] - fn locks_same_repo_handle() { - let (_td, repo) = crate::test::repo_init(); - - let mut tx1 = t!(repo.transaction()); - t!(tx1.lock_ref("refs/heads/seen")); - - let mut tx2 = t!(repo.transaction()); - assert!(matches!(tx2.lock_ref("refs/heads/seen"), Err(e) if e.code() == ErrorCode::Locked)) - } - - #[test] - fn locks_across_repo_handles() { - let (td, repo1) = crate::test::repo_init(); - let repo2 = t!(Repository::open(&td)); - - let mut tx1 = t!(repo1.transaction()); - t!(tx1.lock_ref("refs/heads/seen")); - - let mut tx2 = t!(repo2.transaction()); - assert!(matches!(tx2.lock_ref("refs/heads/seen"), Err(e) if e.code() == ErrorCode::Locked)) - } - - #[test] - fn drop_unlocks() { - let (_td, repo) = crate::test::repo_init(); - - let mut tx = t!(repo.transaction()); - t!(tx.lock_ref("refs/heads/seen")); - drop(tx); - - let mut tx2 = t!(repo.transaction()); - t!(tx2.lock_ref("refs/heads/seen")) - } - - #[test] - fn commit_unlocks() { - let (_td, repo) = crate::test::repo_init(); - - let mut tx = t!(repo.transaction()); - t!(tx.lock_ref("refs/heads/seen")); - t!(tx.commit()); - - let mut tx2 = t!(repo.transaction()); - t!(tx2.lock_ref("refs/heads/seen")); - } - - #[test] - fn prevents_non_transactional_updates() { - let (_td, repo) = crate::test::repo_init(); - let head = t!(repo.refname_to_id("HEAD")); - - let mut tx = t!(repo.transaction()); - t!(tx.lock_ref("refs/heads/seen")); - - assert!(matches!( - repo.reference("refs/heads/seen", head, true, "competing with lock"), - Err(e) if e.code() == ErrorCode::Locked - )); - } - - #[test] - fn remove() { - let (_td, repo) = crate::test::repo_init(); - let head = t!(repo.refname_to_id("HEAD")); - let next = "refs/heads/next"; - - t!(repo.reference( - next, - head, - true, - "refs/heads/next@{0}: branch: Created from HEAD" - )); - - { - let mut tx = t!(repo.transaction()); - t!(tx.lock_ref(next)); - t!(tx.remove(next)); - t!(tx.commit()); - } - assert!(matches!(repo.refname_to_id(next), Err(e) if e.code() == ErrorCode::NotFound)) - } - - #[test] - fn must_lock_ref() { - let (_td, repo) = crate::test::repo_init(); - - // 🤷 - fn is_not_locked_err(e: &Error) -> bool { - e.code() == ErrorCode::NotFound - && e.class() == ErrorClass::Reference - && e.message() == "the specified reference is not locked" - } - - let mut tx = t!(repo.transaction()); - assert!(matches!( - tx.set_target("refs/heads/main", Oid::zero(), None, "set main to zero"), - Err(e) if is_not_locked_err(&e) - )) - } -} diff --git a/extra/git2/src/transport.rs b/extra/git2/src/transport.rs deleted file mode 100644 index 74446d0ca..000000000 --- a/extra/git2/src/transport.rs +++ /dev/null @@ -1,429 +0,0 @@ -//! Interfaces for adding custom transports to libgit2 - -use libc::{c_char, c_int, c_uint, c_void, size_t}; -use std::ffi::{CStr, CString}; -use std::io; -use std::io::prelude::*; -use std::mem; -use std::ptr; -use std::slice; -use std::str; - -use crate::util::Binding; -use crate::{panic, raw, Error, Remote}; - -/// A transport is a structure which knows how to transfer data to and from a -/// remote. -/// -/// This transport is a representation of the raw transport underneath it, which -/// is similar to a trait object in Rust. -#[allow(missing_copy_implementations)] -pub struct Transport { - raw: *mut raw::git_transport, - owned: bool, -} - -/// Interface used by smart transports. -/// -/// The full-fledged definition of transports has to deal with lots of -/// nitty-gritty details of the git protocol, but "smart transports" largely -/// only need to deal with read() and write() of data over a channel. -/// -/// A smart subtransport is contained within an instance of a smart transport -/// and is delegated to in order to actually conduct network activity to push or -/// pull data from a remote. -pub trait SmartSubtransport: Send + 'static { - /// Indicates that this subtransport will be performing the specified action - /// on the specified URL. - /// - /// This function is responsible for making any network connections and - /// returns a stream which can be read and written from in order to - /// negotiate the git protocol. - fn action(&self, url: &str, action: Service) - -> Result<Box<dyn SmartSubtransportStream>, Error>; - - /// Terminates a connection with the remote. - /// - /// Each subtransport is guaranteed a call to close() between calls to - /// action(), except for the following two natural progressions of actions - /// against a constant URL. - /// - /// 1. UploadPackLs -> UploadPack - /// 2. ReceivePackLs -> ReceivePack - fn close(&self) -> Result<(), Error>; -} - -/// Actions that a smart transport can ask a subtransport to perform -#[derive(Copy, Clone, PartialEq)] -#[allow(missing_docs)] -pub enum Service { - UploadPackLs, - UploadPack, - ReceivePackLs, - ReceivePack, -} - -/// An instance of a stream over which a smart transport will communicate with a -/// remote. -/// -/// Currently this only requires the standard `Read` and `Write` traits. This -/// trait also does not need to be implemented manually as long as the `Read` -/// and `Write` traits are implemented. -pub trait SmartSubtransportStream: Read + Write + Send + 'static {} - -impl<T: Read + Write + Send + 'static> SmartSubtransportStream for T {} - -type TransportFactory = dyn Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static; - -/// Boxed data payload used for registering new transports. -/// -/// Currently only contains a field which knows how to create transports. -struct TransportData { - factory: Box<TransportFactory>, -} - -/// Instance of a `git_smart_subtransport`, must use `#[repr(C)]` to ensure that -/// the C fields come first. -#[repr(C)] -struct RawSmartSubtransport { - raw: raw::git_smart_subtransport, - stream: Option<*mut raw::git_smart_subtransport_stream>, - rpc: bool, - obj: Box<dyn SmartSubtransport>, -} - -/// Instance of a `git_smart_subtransport_stream`, must use `#[repr(C)]` to -/// ensure that the C fields come first. -#[repr(C)] -struct RawSmartSubtransportStream { - raw: raw::git_smart_subtransport_stream, - obj: Box<dyn SmartSubtransportStream>, -} - -/// Add a custom transport definition, to be used in addition to the built-in -/// set of transports that come with libgit2. -/// -/// This function is unsafe as it needs to be externally synchronized with calls -/// to creation of other transports. -pub unsafe fn register<F>(prefix: &str, factory: F) -> Result<(), Error> -where - F: Fn(&Remote<'_>) -> Result<Transport, Error> + Send + Sync + 'static, -{ - crate::init(); - let mut data = Box::new(TransportData { - factory: Box::new(factory), - }); - let prefix = CString::new(prefix)?; - let datap = (&mut *data) as *mut TransportData as *mut c_void; - let factory: raw::git_transport_cb = Some(transport_factory); - try_call!(raw::git_transport_register(prefix, factory, datap)); - mem::forget(data); - Ok(()) -} - -impl Transport { - /// Creates a new transport which will use the "smart" transport protocol - /// for transferring data. - /// - /// A smart transport requires a *subtransport* over which data is actually - /// communicated, but this subtransport largely just needs to be able to - /// read() and write(). The subtransport provided will be used to make - /// connections which can then be read/written from. - /// - /// The `rpc` argument is `true` if the protocol is stateless, false - /// otherwise. For example `http://` is stateless but `git://` is not. - pub fn smart<S>(remote: &Remote<'_>, rpc: bool, subtransport: S) -> Result<Transport, Error> - where - S: SmartSubtransport, - { - let mut ret = ptr::null_mut(); - - let mut raw = Box::new(RawSmartSubtransport { - raw: raw::git_smart_subtransport { - action: Some(subtransport_action), - close: Some(subtransport_close), - free: Some(subtransport_free), - }, - stream: None, - rpc, - obj: Box::new(subtransport), - }); - let mut defn = raw::git_smart_subtransport_definition { - callback: Some(smart_factory), - rpc: rpc as c_uint, - param: &mut *raw as *mut _ as *mut _, - }; - - // Currently there's no way to pass a payload via the - // git_smart_subtransport_definition structure, but it's only used as a - // configuration for the initial creation of the smart transport (verified - // by reading the current code, hopefully it doesn't change!). - // - // We, however, need some state (gotta pass in our - // `RawSmartSubtransport`). This also means that this block must be - // entirely synchronized with a lock (boo!) - unsafe { - try_call!(raw::git_transport_smart( - &mut ret, - remote.raw(), - &mut defn as *mut _ as *mut _ - )); - mem::forget(raw); // ownership transport to `ret` - } - return Ok(Transport { - raw: ret, - owned: true, - }); - - extern "C" fn smart_factory( - out: *mut *mut raw::git_smart_subtransport, - _owner: *mut raw::git_transport, - ptr: *mut c_void, - ) -> c_int { - unsafe { - *out = ptr as *mut raw::git_smart_subtransport; - 0 - } - } - } -} - -impl Drop for Transport { - fn drop(&mut self) { - if self.owned { - unsafe { (*self.raw).free.unwrap()(self.raw) } - } - } -} - -// callback used by register() to create new transports -extern "C" fn transport_factory( - out: *mut *mut raw::git_transport, - owner: *mut raw::git_remote, - param: *mut c_void, -) -> c_int { - struct Bomb<'a> { - remote: Option<Remote<'a>>, - } - impl<'a> Drop for Bomb<'a> { - fn drop(&mut self) { - // TODO: maybe a method instead? - mem::forget(self.remote.take()); - } - } - - panic::wrap(|| unsafe { - let remote = Bomb { - remote: Some(Binding::from_raw(owner)), - }; - let data = &mut *(param as *mut TransportData); - match (data.factory)(remote.remote.as_ref().unwrap()) { - Ok(mut transport) => { - *out = transport.raw; - transport.owned = false; - 0 - } - Err(e) => e.raw_code() as c_int, - } - }) - .unwrap_or(-1) -} - -// callback used by smart transports to delegate an action to a -// `SmartSubtransport` trait object. -extern "C" fn subtransport_action( - stream: *mut *mut raw::git_smart_subtransport_stream, - raw_transport: *mut raw::git_smart_subtransport, - url: *const c_char, - action: raw::git_smart_service_t, -) -> c_int { - panic::wrap(|| unsafe { - let url = CStr::from_ptr(url).to_bytes(); - let url = match str::from_utf8(url).ok() { - Some(s) => s, - None => return -1, - }; - let action = match action { - raw::GIT_SERVICE_UPLOADPACK_LS => Service::UploadPackLs, - raw::GIT_SERVICE_UPLOADPACK => Service::UploadPack, - raw::GIT_SERVICE_RECEIVEPACK_LS => Service::ReceivePackLs, - raw::GIT_SERVICE_RECEIVEPACK => Service::ReceivePack, - n => panic!("unknown action: {}", n), - }; - - let transport = &mut *(raw_transport as *mut RawSmartSubtransport); - // Note: we only need to generate if rpc is on. Else, for receive-pack and upload-pack - // libgit2 reuses the stream generated for receive-pack-ls or upload-pack-ls. - let generate_stream = - transport.rpc || action == Service::UploadPackLs || action == Service::ReceivePackLs; - if generate_stream { - let obj = match transport.obj.action(url, action) { - Ok(s) => s, - Err(e) => { - set_err(&e); - return e.raw_code() as c_int; - } - }; - *stream = mem::transmute(Box::new(RawSmartSubtransportStream { - raw: raw::git_smart_subtransport_stream { - subtransport: raw_transport, - read: Some(stream_read), - write: Some(stream_write), - free: Some(stream_free), - }, - obj, - })); - transport.stream = Some(*stream); - } else { - if transport.stream.is_none() { - return -1; - } - *stream = transport.stream.unwrap(); - } - 0 - }) - .unwrap_or(-1) -} - -// callback used by smart transports to close a `SmartSubtransport` trait -// object. -extern "C" fn subtransport_close(transport: *mut raw::git_smart_subtransport) -> c_int { - let ret = panic::wrap(|| unsafe { - let transport = &mut *(transport as *mut RawSmartSubtransport); - transport.obj.close() - }); - match ret { - Some(Ok(())) => 0, - Some(Err(e)) => e.raw_code() as c_int, - None => -1, - } -} - -// callback used by smart transports to free a `SmartSubtransport` trait -// object. -extern "C" fn subtransport_free(transport: *mut raw::git_smart_subtransport) { - let _ = panic::wrap(|| unsafe { - mem::transmute::<_, Box<RawSmartSubtransport>>(transport); - }); -} - -// callback used by smart transports to read from a `SmartSubtransportStream` -// object. -extern "C" fn stream_read( - stream: *mut raw::git_smart_subtransport_stream, - buffer: *mut c_char, - buf_size: size_t, - bytes_read: *mut size_t, -) -> c_int { - let ret = panic::wrap(|| unsafe { - let transport = &mut *(stream as *mut RawSmartSubtransportStream); - let buf = slice::from_raw_parts_mut(buffer as *mut u8, buf_size as usize); - match transport.obj.read(buf) { - Ok(n) => { - *bytes_read = n as size_t; - Ok(n) - } - e => e, - } - }); - match ret { - Some(Ok(_)) => 0, - Some(Err(e)) => unsafe { - set_err_io(&e); - -2 - }, - None => -1, - } -} - -// callback used by smart transports to write to a `SmartSubtransportStream` -// object. -extern "C" fn stream_write( - stream: *mut raw::git_smart_subtransport_stream, - buffer: *const c_char, - len: size_t, -) -> c_int { - let ret = panic::wrap(|| unsafe { - let transport = &mut *(stream as *mut RawSmartSubtransportStream); - let buf = slice::from_raw_parts(buffer as *const u8, len as usize); - transport.obj.write_all(buf) - }); - match ret { - Some(Ok(())) => 0, - Some(Err(e)) => unsafe { - set_err_io(&e); - -2 - }, - None => -1, - } -} - -unsafe fn set_err_io(e: &io::Error) { - let s = CString::new(e.to_string()).unwrap(); - raw::git_error_set_str(raw::GIT_ERROR_NET as c_int, s.as_ptr()); -} - -unsafe fn set_err(e: &Error) { - let s = CString::new(e.message()).unwrap(); - raw::git_error_set_str(e.raw_class() as c_int, s.as_ptr()); -} - -// callback used by smart transports to free a `SmartSubtransportStream` -// object. -extern "C" fn stream_free(stream: *mut raw::git_smart_subtransport_stream) { - let _ = panic::wrap(|| unsafe { - mem::transmute::<_, Box<RawSmartSubtransportStream>>(stream); - }); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ErrorClass, ErrorCode}; - use std::sync::Once; - - struct DummyTransport; - - // in lieu of lazy_static - fn dummy_error() -> Error { - Error::new(ErrorCode::Ambiguous, ErrorClass::Net, "bleh") - } - - impl SmartSubtransport for DummyTransport { - fn action( - &self, - _url: &str, - _service: Service, - ) -> Result<Box<dyn SmartSubtransportStream>, Error> { - Err(dummy_error()) - } - - fn close(&self) -> Result<(), Error> { - Ok(()) - } - } - - #[test] - fn transport_error_propagates() { - static INIT: Once = Once::new(); - - unsafe { - INIT.call_once(|| { - register("dummy", move |remote| { - Transport::smart(&remote, true, DummyTransport) - }) - .unwrap(); - }) - } - - let (_td, repo) = crate::test::repo_init(); - t!(repo.remote("origin", "dummy://ball")); - - let mut origin = t!(repo.find_remote("origin")); - - match origin.fetch(&["main"], None, None) { - Ok(()) => unreachable!(), - Err(e) => assert_eq!(e, dummy_error()), - } - } -} diff --git a/extra/git2/src/tree.rs b/extra/git2/src/tree.rs deleted file mode 100644 index 9a38244cf..000000000 --- a/extra/git2/src/tree.rs +++ /dev/null @@ -1,570 +0,0 @@ -use libc::{self, c_char, c_int, c_void}; -use std::cmp::Ordering; -use std::ffi::{CStr, CString}; -use std::iter::FusedIterator; -use std::marker; -use std::mem; -use std::ops::Range; -use std::path::Path; -use std::ptr; -use std::str; - -use crate::util::{c_cmp_to_ordering, path_to_repo_path, Binding}; -use crate::{panic, raw, Error, Object, ObjectType, Oid, Repository}; - -/// A structure to represent a git [tree][1] -/// -/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects -pub struct Tree<'repo> { - raw: *mut raw::git_tree, - _marker: marker::PhantomData<Object<'repo>>, -} - -/// A structure representing an entry inside of a tree. An entry is borrowed -/// from a tree. -pub struct TreeEntry<'tree> { - raw: *mut raw::git_tree_entry, - owned: bool, - _marker: marker::PhantomData<&'tree raw::git_tree_entry>, -} - -/// An iterator over the entries in a tree. -pub struct TreeIter<'tree> { - range: Range<usize>, - tree: &'tree Tree<'tree>, -} - -/// A binary indicator of whether a tree walk should be performed in pre-order -/// or post-order. -pub enum TreeWalkMode { - /// Runs the traversal in pre-order. - PreOrder = 0, - /// Runs the traversal in post-order. - PostOrder = 1, -} - -/// Possible return codes for tree walking callback functions. -#[repr(i32)] -pub enum TreeWalkResult { - /// Continue with the traversal as normal. - Ok = 0, - /// Skip the current node (in pre-order mode). - Skip = 1, - /// Completely stop the traversal. - Abort = raw::GIT_EUSER, -} - -impl Into<i32> for TreeWalkResult { - fn into(self) -> i32 { - self as i32 - } -} - -impl Into<raw::git_treewalk_mode> for TreeWalkMode { - #[cfg(target_env = "msvc")] - fn into(self) -> raw::git_treewalk_mode { - self as i32 - } - #[cfg(not(target_env = "msvc"))] - fn into(self) -> raw::git_treewalk_mode { - self as u32 - } -} - -impl<'repo> Tree<'repo> { - /// Get the id (SHA1) of a repository object - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_tree_id(&*self.raw)) } - } - - /// Get the number of entries listed in this tree. - pub fn len(&self) -> usize { - unsafe { raw::git_tree_entrycount(&*self.raw) as usize } - } - - /// Return `true` if there is not entry - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Returns an iterator over the entries in this tree. - pub fn iter(&self) -> TreeIter<'_> { - TreeIter { - range: 0..self.len(), - tree: self, - } - } - - /// Traverse the entries in a tree and its subtrees in post or pre-order. - /// The callback function will be run on each node of the tree that's - /// walked. The return code of this function will determine how the walk - /// continues. - /// - /// libgit2 requires that the callback be an integer, where 0 indicates a - /// successful visit, 1 skips the node, and -1 aborts the traversal completely. - /// You may opt to use the enum [`TreeWalkResult`](TreeWalkResult) instead. - /// - /// ```ignore - /// let mut ct = 0; - /// tree.walk(TreeWalkMode::PreOrder, |_, entry| { - /// assert_eq!(entry.name(), Some("foo")); - /// ct += 1; - /// TreeWalkResult::Ok - /// }).unwrap(); - /// assert_eq!(ct, 1); - /// ``` - /// - /// See [libgit2 documentation][1] for more information. - /// - /// [1]: https://libgit2.org/libgit2/#HEAD/group/tree/git_tree_walk - pub fn walk<C, T>(&self, mode: TreeWalkMode, mut callback: C) -> Result<(), Error> - where - C: FnMut(&str, &TreeEntry<'_>) -> T, - T: Into<i32>, - { - unsafe { - let mut data = TreeWalkCbData { - callback: &mut callback, - }; - raw::git_tree_walk( - self.raw(), - mode.into(), - Some(treewalk_cb::<T>), - &mut data as *mut _ as *mut c_void, - ); - Ok(()) - } - } - - /// Lookup a tree entry by SHA value. - pub fn get_id(&self, id: Oid) -> Option<TreeEntry<'_>> { - unsafe { - let ptr = raw::git_tree_entry_byid(&*self.raw(), &*id.raw()); - if ptr.is_null() { - None - } else { - Some(entry_from_raw_const(ptr)) - } - } - } - - /// Lookup a tree entry by its position in the tree - pub fn get(&self, n: usize) -> Option<TreeEntry<'_>> { - unsafe { - let ptr = raw::git_tree_entry_byindex(&*self.raw(), n as libc::size_t); - if ptr.is_null() { - None - } else { - Some(entry_from_raw_const(ptr)) - } - } - } - - /// Lookup a tree entry by its filename - pub fn get_name(&self, filename: &str) -> Option<TreeEntry<'_>> { - self.get_name_bytes(filename.as_bytes()) - } - - /// Lookup a tree entry by its filename, specified as bytes. - /// - /// This allows for non-UTF-8 filenames. - pub fn get_name_bytes(&self, filename: &[u8]) -> Option<TreeEntry<'_>> { - let filename = CString::new(filename).unwrap(); - unsafe { - let ptr = call!(raw::git_tree_entry_byname(&*self.raw(), filename)); - if ptr.is_null() { - None - } else { - Some(entry_from_raw_const(ptr)) - } - } - } - - /// Retrieve a tree entry contained in a tree or in any of its subtrees, - /// given its relative path. - pub fn get_path(&self, path: &Path) -> Result<TreeEntry<'static>, Error> { - let path = path_to_repo_path(path)?; - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_tree_entry_bypath(&mut ret, &*self.raw(), path)); - Ok(Binding::from_raw(ret)) - } - } - - /// Casts this Tree to be usable as an `Object` - pub fn as_object(&self) -> &Object<'repo> { - unsafe { &*(self as *const _ as *const Object<'repo>) } - } - - /// Consumes this Tree to be returned as an `Object` - pub fn into_object(self) -> Object<'repo> { - assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>()); - unsafe { mem::transmute(self) } - } -} - -type TreeWalkCb<'a, T> = dyn FnMut(&str, &TreeEntry<'_>) -> T + 'a; - -struct TreeWalkCbData<'a, T> { - callback: &'a mut TreeWalkCb<'a, T>, -} - -extern "C" fn treewalk_cb<T: Into<i32>>( - root: *const c_char, - entry: *const raw::git_tree_entry, - payload: *mut c_void, -) -> c_int { - match panic::wrap(|| unsafe { - let root = match CStr::from_ptr(root).to_str() { - Ok(value) => value, - _ => return -1, - }; - let entry = entry_from_raw_const(entry); - let payload = &mut *(payload as *mut TreeWalkCbData<'_, T>); - let callback = &mut payload.callback; - callback(root, &entry).into() - }) { - Some(value) => value, - None => -1, - } -} - -impl<'repo> Binding for Tree<'repo> { - type Raw = *mut raw::git_tree; - - unsafe fn from_raw(raw: *mut raw::git_tree) -> Tree<'repo> { - Tree { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_tree { - self.raw - } -} - -impl<'repo> std::fmt::Debug for Tree<'repo> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - f.debug_struct("Tree").field("id", &self.id()).finish() - } -} - -impl<'repo> Clone for Tree<'repo> { - fn clone(&self) -> Self { - self.as_object().clone().into_tree().ok().unwrap() - } -} - -impl<'repo> Drop for Tree<'repo> { - fn drop(&mut self) { - unsafe { raw::git_tree_free(self.raw) } - } -} - -impl<'repo, 'iter> IntoIterator for &'iter Tree<'repo> { - type Item = TreeEntry<'iter>; - type IntoIter = TreeIter<'iter>; - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -/// Create a new tree entry from the raw pointer provided. -/// -/// The lifetime of the entry is tied to the tree provided and the function -/// is unsafe because the validity of the pointer cannot be guaranteed. -pub unsafe fn entry_from_raw_const<'tree>(raw: *const raw::git_tree_entry) -> TreeEntry<'tree> { - TreeEntry { - raw: raw as *mut raw::git_tree_entry, - owned: false, - _marker: marker::PhantomData, - } -} - -impl<'tree> TreeEntry<'tree> { - /// Get the id of the object pointed by the entry - pub fn id(&self) -> Oid { - unsafe { Binding::from_raw(raw::git_tree_entry_id(&*self.raw)) } - } - - /// Get the filename of a tree entry - /// - /// Returns `None` if the name is not valid utf-8 - pub fn name(&self) -> Option<&str> { - str::from_utf8(self.name_bytes()).ok() - } - - /// Get the filename of a tree entry - pub fn name_bytes(&self) -> &[u8] { - unsafe { crate::opt_bytes(self, raw::git_tree_entry_name(&*self.raw())).unwrap() } - } - - /// Convert a tree entry to the object it points to. - pub fn to_object<'a>(&self, repo: &'a Repository) -> Result<Object<'a>, Error> { - let mut ret = ptr::null_mut(); - unsafe { - try_call!(raw::git_tree_entry_to_object( - &mut ret, - repo.raw(), - &*self.raw() - )); - Ok(Binding::from_raw(ret)) - } - } - - /// Get the type of the object pointed by the entry - pub fn kind(&self) -> Option<ObjectType> { - ObjectType::from_raw(unsafe { raw::git_tree_entry_type(&*self.raw) }) - } - - /// Get the UNIX file attributes of a tree entry - pub fn filemode(&self) -> i32 { - unsafe { raw::git_tree_entry_filemode(&*self.raw) as i32 } - } - - /// Get the raw UNIX file attributes of a tree entry - pub fn filemode_raw(&self) -> i32 { - unsafe { raw::git_tree_entry_filemode_raw(&*self.raw) as i32 } - } - - /// Convert this entry of any lifetime into an owned signature with a static - /// lifetime. - /// - /// This will use the `Clone::clone` implementation under the hood. - pub fn to_owned(&self) -> TreeEntry<'static> { - unsafe { - let me = mem::transmute::<&TreeEntry<'tree>, &TreeEntry<'static>>(self); - me.clone() - } - } -} - -impl<'a> Binding for TreeEntry<'a> { - type Raw = *mut raw::git_tree_entry; - unsafe fn from_raw(raw: *mut raw::git_tree_entry) -> TreeEntry<'a> { - TreeEntry { - raw, - owned: true, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_tree_entry { - self.raw - } -} - -impl<'a> Clone for TreeEntry<'a> { - fn clone(&self) -> TreeEntry<'a> { - let mut ret = ptr::null_mut(); - unsafe { - assert_eq!(raw::git_tree_entry_dup(&mut ret, &*self.raw()), 0); - Binding::from_raw(ret) - } - } -} - -impl<'a> PartialOrd for TreeEntry<'a> { - fn partial_cmp(&self, other: &TreeEntry<'a>) -> Option<Ordering> { - Some(self.cmp(other)) - } -} -impl<'a> Ord for TreeEntry<'a> { - fn cmp(&self, other: &TreeEntry<'a>) -> Ordering { - c_cmp_to_ordering(unsafe { raw::git_tree_entry_cmp(&*self.raw(), &*other.raw()) }) - } -} - -impl<'a> PartialEq for TreeEntry<'a> { - fn eq(&self, other: &TreeEntry<'a>) -> bool { - self.cmp(other) == Ordering::Equal - } -} -impl<'a> Eq for TreeEntry<'a> {} - -impl<'a> Drop for TreeEntry<'a> { - fn drop(&mut self) { - if self.owned { - unsafe { raw::git_tree_entry_free(self.raw) } - } - } -} - -impl<'tree> Iterator for TreeIter<'tree> { - type Item = TreeEntry<'tree>; - fn next(&mut self) -> Option<TreeEntry<'tree>> { - self.range.next().and_then(|i| self.tree.get(i)) - } - fn size_hint(&self) -> (usize, Option<usize>) { - self.range.size_hint() - } -} -impl<'tree> DoubleEndedIterator for TreeIter<'tree> { - fn next_back(&mut self) -> Option<TreeEntry<'tree>> { - self.range.next_back().and_then(|i| self.tree.get(i)) - } -} -impl<'tree> FusedIterator for TreeIter<'tree> {} -impl<'tree> ExactSizeIterator for TreeIter<'tree> {} - -#[cfg(test)] -mod tests { - use super::{TreeWalkMode, TreeWalkResult}; - use crate::{Object, ObjectType, Repository, Tree, TreeEntry}; - use std::fs::File; - use std::io::prelude::*; - use std::path::Path; - use tempfile::TempDir; - - pub struct TestTreeIter<'a> { - entries: Vec<TreeEntry<'a>>, - repo: &'a Repository, - } - - impl<'a> Iterator for TestTreeIter<'a> { - type Item = TreeEntry<'a>; - - fn next(&mut self) -> Option<TreeEntry<'a>> { - if self.entries.is_empty() { - None - } else { - let entry = self.entries.remove(0); - - match entry.kind() { - Some(ObjectType::Tree) => { - let obj: Object<'a> = entry.to_object(self.repo).unwrap(); - - let tree: &Tree<'a> = obj.as_tree().unwrap(); - - for entry in tree.iter() { - self.entries.push(entry.to_owned()); - } - } - _ => {} - } - - Some(entry) - } - } - } - - fn tree_iter<'repo>(tree: &Tree<'repo>, repo: &'repo Repository) -> TestTreeIter<'repo> { - let mut initial = vec![]; - - for entry in tree.iter() { - initial.push(entry.to_owned()); - } - - TestTreeIter { - entries: initial, - repo: repo, - } - } - - #[test] - fn smoke_tree_iter() { - let (td, repo) = crate::test::repo_init(); - - setup_repo(&td, &repo); - - let head = repo.head().unwrap(); - let target = head.target().unwrap(); - let commit = repo.find_commit(target).unwrap(); - - let tree = repo.find_tree(commit.tree_id()).unwrap(); - assert_eq!(tree.id(), commit.tree_id()); - assert_eq!(tree.len(), 1); - - for entry in tree_iter(&tree, &repo) { - println!("iter entry {:?}", entry.name()); - } - } - - fn setup_repo(td: &TempDir, repo: &Repository) { - let mut index = repo.index().unwrap(); - File::create(&td.path().join("foo")) - .unwrap() - .write_all(b"foo") - .unwrap(); - index.add_path(Path::new("foo")).unwrap(); - let id = index.write_tree().unwrap(); - let sig = repo.signature().unwrap(); - let tree = repo.find_tree(id).unwrap(); - let parent = repo - .find_commit(repo.head().unwrap().target().unwrap()) - .unwrap(); - repo.commit( - Some("HEAD"), - &sig, - &sig, - "another commit", - &tree, - &[&parent], - ) - .unwrap(); - } - - #[test] - fn smoke() { - let (td, repo) = crate::test::repo_init(); - - setup_repo(&td, &repo); - - let head = repo.head().unwrap(); - let target = head.target().unwrap(); - let commit = repo.find_commit(target).unwrap(); - - let tree = repo.find_tree(commit.tree_id()).unwrap(); - assert_eq!(tree.id(), commit.tree_id()); - assert_eq!(tree.len(), 1); - { - let e1 = tree.get(0).unwrap(); - assert!(e1 == tree.get_id(e1.id()).unwrap()); - assert!(e1 == tree.get_name("foo").unwrap()); - assert!(e1 == tree.get_name_bytes(b"foo").unwrap()); - assert!(e1 == tree.get_path(Path::new("foo")).unwrap()); - assert_eq!(e1.name(), Some("foo")); - e1.to_object(&repo).unwrap(); - } - tree.into_object(); - - repo.find_object(commit.tree_id(), None) - .unwrap() - .as_tree() - .unwrap(); - repo.find_object(commit.tree_id(), None) - .unwrap() - .into_tree() - .ok() - .unwrap(); - } - - #[test] - fn tree_walk() { - let (td, repo) = crate::test::repo_init(); - - setup_repo(&td, &repo); - - let head = repo.head().unwrap(); - let target = head.target().unwrap(); - let commit = repo.find_commit(target).unwrap(); - let tree = repo.find_tree(commit.tree_id()).unwrap(); - - let mut ct = 0; - tree.walk(TreeWalkMode::PreOrder, |_, entry| { - assert_eq!(entry.name(), Some("foo")); - ct += 1; - 0 - }) - .unwrap(); - assert_eq!(ct, 1); - - let mut ct = 0; - tree.walk(TreeWalkMode::PreOrder, |_, entry| { - assert_eq!(entry.name(), Some("foo")); - ct += 1; - TreeWalkResult::Ok - }) - .unwrap(); - assert_eq!(ct, 1); - } -} diff --git a/extra/git2/src/treebuilder.rs b/extra/git2/src/treebuilder.rs deleted file mode 100644 index 1548a048c..000000000 --- a/extra/git2/src/treebuilder.rs +++ /dev/null @@ -1,234 +0,0 @@ -use std::marker; -use std::ptr; - -use libc::{c_int, c_void}; - -use crate::util::{Binding, IntoCString}; -use crate::{panic, raw, tree, Error, Oid, Repository, TreeEntry}; - -/// Constructor for in-memory trees (low-level) -/// -/// You probably want to use [`build::TreeUpdateBuilder`] instead. -/// -/// This is the more raw of the two tree update facilities. It -/// handles only one level of a nested tree structure at a time. Each -/// path passed to `insert` etc. must be a single component. -/// -/// [`build::TreeUpdateBuilder`]: crate::build::TreeUpdateBuilder -pub struct TreeBuilder<'repo> { - raw: *mut raw::git_treebuilder, - _marker: marker::PhantomData<&'repo Repository>, -} - -impl<'repo> TreeBuilder<'repo> { - /// Clear all the entries in the builder - pub fn clear(&mut self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_treebuilder_clear(self.raw)); - } - Ok(()) - } - - /// Get the number of entries - pub fn len(&self) -> usize { - unsafe { raw::git_treebuilder_entrycount(self.raw) as usize } - } - - /// Return `true` if there is no entry - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Get en entry from the builder from its filename - pub fn get<P>(&self, filename: P) -> Result<Option<TreeEntry<'_>>, Error> - where - P: IntoCString, - { - let filename = filename.into_c_string()?; - unsafe { - let ret = raw::git_treebuilder_get(self.raw, filename.as_ptr()); - if ret.is_null() { - Ok(None) - } else { - Ok(Some(tree::entry_from_raw_const(ret))) - } - } - } - - /// Add or update an entry in the builder - /// - /// No attempt is made to ensure that the provided Oid points to - /// an object of a reasonable type (or any object at all). - /// - /// The mode given must be one of 0o040000, 0o100644, 0o100755, 0o120000 or - /// 0o160000 currently. - pub fn insert<P: IntoCString>( - &mut self, - filename: P, - oid: Oid, - filemode: i32, - ) -> Result<TreeEntry<'_>, Error> { - let filename = filename.into_c_string()?; - let filemode = filemode as raw::git_filemode_t; - - let mut ret = ptr::null(); - unsafe { - try_call!(raw::git_treebuilder_insert( - &mut ret, - self.raw, - filename, - oid.raw(), - filemode - )); - Ok(tree::entry_from_raw_const(ret)) - } - } - - /// Remove an entry from the builder by its filename - pub fn remove<P: IntoCString>(&mut self, filename: P) -> Result<(), Error> { - let filename = filename.into_c_string()?; - unsafe { - try_call!(raw::git_treebuilder_remove(self.raw, filename)); - } - Ok(()) - } - - /// Selectively remove entries from the tree - /// - /// Values for which the filter returns `true` will be kept. Note - /// that this behavior is different from the libgit2 C interface. - pub fn filter<F>(&mut self, mut filter: F) -> Result<(), Error> - where - F: FnMut(&TreeEntry<'_>) -> bool, - { - let mut cb: &mut FilterCb<'_> = &mut filter; - let ptr = &mut cb as *mut _; - let cb: raw::git_treebuilder_filter_cb = Some(filter_cb); - unsafe { - try_call!(raw::git_treebuilder_filter(self.raw, cb, ptr as *mut _)); - panic::check(); - } - Ok(()) - } - - /// Write the contents of the TreeBuilder as a Tree object and - /// return its Oid - pub fn write(&self) -> Result<Oid, Error> { - let mut raw = raw::git_oid { - id: [0; raw::GIT_OID_RAWSZ], - }; - unsafe { - try_call!(raw::git_treebuilder_write(&mut raw, self.raw())); - Ok(Binding::from_raw(&raw as *const _)) - } - } -} - -type FilterCb<'a> = dyn FnMut(&TreeEntry<'_>) -> bool + 'a; - -extern "C" fn filter_cb(entry: *const raw::git_tree_entry, payload: *mut c_void) -> c_int { - let ret = panic::wrap(|| unsafe { - // There's no way to return early from git_treebuilder_filter. - if panic::panicked() { - true - } else { - let entry = tree::entry_from_raw_const(entry); - let payload = payload as *mut &mut FilterCb<'_>; - (*payload)(&entry) - } - }); - if ret == Some(false) { - 1 - } else { - 0 - } -} - -impl<'repo> Binding for TreeBuilder<'repo> { - type Raw = *mut raw::git_treebuilder; - - unsafe fn from_raw(raw: *mut raw::git_treebuilder) -> TreeBuilder<'repo> { - TreeBuilder { - raw, - _marker: marker::PhantomData, - } - } - fn raw(&self) -> *mut raw::git_treebuilder { - self.raw - } -} - -impl<'repo> Drop for TreeBuilder<'repo> { - fn drop(&mut self) { - unsafe { raw::git_treebuilder_free(self.raw) } - } -} - -#[cfg(test)] -mod tests { - use crate::ObjectType; - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - - let mut builder = repo.treebuilder(None).unwrap(); - assert_eq!(builder.len(), 0); - let blob = repo.blob(b"data").unwrap(); - { - let entry = builder.insert("a", blob, 0o100644).unwrap(); - assert_eq!(entry.kind(), Some(ObjectType::Blob)); - } - builder.insert("b", blob, 0o100644).unwrap(); - assert_eq!(builder.len(), 2); - builder.remove("a").unwrap(); - assert_eq!(builder.len(), 1); - assert_eq!(builder.get("b").unwrap().unwrap().id(), blob); - builder.clear().unwrap(); - assert_eq!(builder.len(), 0); - } - - #[test] - fn write() { - let (_td, repo) = crate::test::repo_init(); - - let mut builder = repo.treebuilder(None).unwrap(); - let data = repo.blob(b"data").unwrap(); - builder.insert("name", data, 0o100644).unwrap(); - let tree = builder.write().unwrap(); - let tree = repo.find_tree(tree).unwrap(); - let entry = tree.get(0).unwrap(); - assert_eq!(entry.name(), Some("name")); - let blob = entry.to_object(&repo).unwrap(); - let blob = blob.as_blob().unwrap(); - assert_eq!(blob.content(), b"data"); - - let builder = repo.treebuilder(Some(&tree)).unwrap(); - assert_eq!(builder.len(), 1); - } - - #[test] - fn filter() { - let (_td, repo) = crate::test::repo_init(); - - let mut builder = repo.treebuilder(None).unwrap(); - let blob = repo.blob(b"data").unwrap(); - let tree = { - let head = repo.head().unwrap().peel(ObjectType::Commit).unwrap(); - let head = head.as_commit().unwrap(); - head.tree_id() - }; - builder.insert("blob", blob, 0o100644).unwrap(); - builder.insert("dir", tree, 0o040000).unwrap(); - builder.insert("dir2", tree, 0o040000).unwrap(); - - builder.filter(|_| true).unwrap(); - assert_eq!(builder.len(), 3); - builder - .filter(|e| e.kind().unwrap() != ObjectType::Blob) - .unwrap(); - assert_eq!(builder.len(), 2); - builder.filter(|_| false).unwrap(); - assert_eq!(builder.len(), 0); - } -} diff --git a/extra/git2/src/util.rs b/extra/git2/src/util.rs deleted file mode 100644 index 5f735bc00..000000000 --- a/extra/git2/src/util.rs +++ /dev/null @@ -1,342 +0,0 @@ -use libc::{c_char, c_int, size_t}; -use std::cmp::Ordering; -use std::ffi::{CString, OsStr, OsString}; -use std::iter::IntoIterator; -use std::path::{Component, Path, PathBuf}; - -use crate::{raw, Error}; - -#[doc(hidden)] -pub trait IsNull { - fn is_ptr_null(&self) -> bool; -} -impl<T> IsNull for *const T { - fn is_ptr_null(&self) -> bool { - self.is_null() - } -} -impl<T> IsNull for *mut T { - fn is_ptr_null(&self) -> bool { - self.is_null() - } -} - -#[doc(hidden)] -pub trait Binding: Sized { - type Raw; - - unsafe fn from_raw(raw: Self::Raw) -> Self; - fn raw(&self) -> Self::Raw; - - unsafe fn from_raw_opt<T>(raw: T) -> Option<Self> - where - T: Copy + IsNull, - Self: Binding<Raw = T>, - { - if raw.is_ptr_null() { - None - } else { - Some(Binding::from_raw(raw)) - } - } -} - -/// Converts an iterator of repo paths into a git2-compatible array of cstrings. -/// -/// Only use this for repo-relative paths or pathspecs. -/// -/// See `iter2cstrs` for more details. -pub fn iter2cstrs_paths<T, I>( - iter: I, -) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error> -where - T: IntoCString, - I: IntoIterator<Item = T>, -{ - let cstrs = iter - .into_iter() - .map(|i| fixup_windows_path(i.into_c_string()?)) - .collect::<Result<Vec<CString>, _>>()?; - iter2cstrs(cstrs) -} - -/// Converts an iterator of things into a git array of c-strings. -/// -/// Returns a tuple `(cstrings, pointers, git_strarray)`. The first two values -/// should not be dropped before `git_strarray`. -pub fn iter2cstrs<T, I>( - iter: I, -) -> Result<(Vec<CString>, Vec<*const c_char>, raw::git_strarray), Error> -where - T: IntoCString, - I: IntoIterator<Item = T>, -{ - let cstrs = iter - .into_iter() - .map(|i| i.into_c_string()) - .collect::<Result<Vec<CString>, _>>()?; - let ptrs = cstrs.iter().map(|i| i.as_ptr()).collect::<Vec<_>>(); - let raw = raw::git_strarray { - strings: ptrs.as_ptr() as *mut _, - count: ptrs.len() as size_t, - }; - Ok((cstrs, ptrs, raw)) -} - -#[cfg(unix)] -pub fn bytes2path(b: &[u8]) -> &Path { - use std::os::unix::prelude::*; - Path::new(OsStr::from_bytes(b)) -} -#[cfg(windows)] -pub fn bytes2path(b: &[u8]) -> &Path { - use std::str; - Path::new(str::from_utf8(b).unwrap()) -} - -/// A class of types that can be converted to C strings. -/// -/// These types are represented internally as byte slices and it is quite rare -/// for them to contain an interior 0 byte. -pub trait IntoCString { - /// Consume this container, converting it into a CString - fn into_c_string(self) -> Result<CString, Error>; -} - -impl<'a, T: IntoCString + Clone> IntoCString for &'a T { - fn into_c_string(self) -> Result<CString, Error> { - self.clone().into_c_string() - } -} - -impl<'a> IntoCString for &'a str { - fn into_c_string(self) -> Result<CString, Error> { - Ok(CString::new(self)?) - } -} - -impl IntoCString for String { - fn into_c_string(self) -> Result<CString, Error> { - Ok(CString::new(self.into_bytes())?) - } -} - -impl IntoCString for CString { - fn into_c_string(self) -> Result<CString, Error> { - Ok(self) - } -} - -impl<'a> IntoCString for &'a Path { - fn into_c_string(self) -> Result<CString, Error> { - let s: &OsStr = self.as_ref(); - s.into_c_string() - } -} - -impl IntoCString for PathBuf { - fn into_c_string(self) -> Result<CString, Error> { - let s: OsString = self.into(); - s.into_c_string() - } -} - -impl<'a> IntoCString for &'a OsStr { - fn into_c_string(self) -> Result<CString, Error> { - self.to_os_string().into_c_string() - } -} - -impl IntoCString for OsString { - #[cfg(unix)] - fn into_c_string(self) -> Result<CString, Error> { - use std::os::unix::prelude::*; - let s: &OsStr = self.as_ref(); - Ok(CString::new(s.as_bytes())?) - } - #[cfg(windows)] - fn into_c_string(self) -> Result<CString, Error> { - match self.to_str() { - Some(s) => s.into_c_string(), - None => Err(Error::from_str( - "only valid unicode paths are accepted on windows", - )), - } - } -} - -impl<'a> IntoCString for &'a [u8] { - fn into_c_string(self) -> Result<CString, Error> { - Ok(CString::new(self)?) - } -} - -impl IntoCString for Vec<u8> { - fn into_c_string(self) -> Result<CString, Error> { - Ok(CString::new(self)?) - } -} - -pub fn into_opt_c_string<S>(opt_s: Option<S>) -> Result<Option<CString>, Error> -where - S: IntoCString, -{ - match opt_s { - None => Ok(None), - Some(s) => Ok(Some(s.into_c_string()?)), - } -} - -pub fn c_cmp_to_ordering(cmp: c_int) -> Ordering { - match cmp { - 0 => Ordering::Equal, - n if n < 0 => Ordering::Less, - _ => Ordering::Greater, - } -} - -/// Converts a path to a CString that is usable by the libgit2 API. -/// -/// Checks if it is a relative path. -/// -/// On Windows, this also requires the path to be valid Unicode, and translates -/// back slashes to forward slashes. -pub fn path_to_repo_path(path: &Path) -> Result<CString, Error> { - macro_rules! err { - ($msg:literal, $path:expr) => { - return Err(Error::from_str(&format!($msg, $path.display()))) - }; - } - match path.components().next() { - None => return Err(Error::from_str("repo path should not be empty")), - Some(Component::Prefix(_)) => err!( - "repo path `{}` should be relative, not a windows prefix", - path - ), - Some(Component::RootDir) => err!("repo path `{}` should be relative", path), - Some(Component::CurDir) => err!("repo path `{}` should not start with `.`", path), - Some(Component::ParentDir) => err!("repo path `{}` should not start with `..`", path), - Some(Component::Normal(_)) => {} - } - #[cfg(windows)] - { - match path.to_str() { - None => { - return Err(Error::from_str( - "only valid unicode paths are accepted on windows", - )) - } - Some(s) => return fixup_windows_path(s), - } - } - #[cfg(not(windows))] - { - path.into_c_string() - } -} - -pub fn cstring_to_repo_path<T: IntoCString>(path: T) -> Result<CString, Error> { - fixup_windows_path(path.into_c_string()?) -} - -#[cfg(windows)] -fn fixup_windows_path<P: Into<Vec<u8>>>(path: P) -> Result<CString, Error> { - let mut bytes: Vec<u8> = path.into(); - for i in 0..bytes.len() { - if bytes[i] == b'\\' { - bytes[i] = b'/'; - } - } - Ok(CString::new(bytes)?) -} - -#[cfg(not(windows))] -fn fixup_windows_path(path: CString) -> Result<CString, Error> { - Ok(path) -} - -#[cfg(test)] -mod tests { - use super::*; - - macro_rules! assert_err { - ($path:expr, $msg:expr) => { - match path_to_repo_path(Path::new($path)) { - Ok(_) => panic!("expected `{}` to err", $path), - Err(e) => assert_eq!(e.message(), $msg), - } - }; - } - - macro_rules! assert_repo_path_ok { - ($path:expr) => { - assert_repo_path_ok!($path, $path) - }; - ($path:expr, $expect:expr) => { - assert_eq!( - path_to_repo_path(Path::new($path)), - Ok(CString::new($expect).unwrap()) - ); - }; - } - - #[test] - #[cfg(windows)] - fn path_to_repo_path_translate() { - assert_repo_path_ok!("foo"); - assert_repo_path_ok!("foo/bar"); - assert_repo_path_ok!(r"foo\bar", "foo/bar"); - assert_repo_path_ok!(r"foo\bar\", "foo/bar/"); - } - - #[test] - fn path_to_repo_path_no_weird() { - assert_err!("", "repo path should not be empty"); - assert_err!("./foo", "repo path `./foo` should not start with `.`"); - assert_err!("../foo", "repo path `../foo` should not start with `..`"); - } - - #[test] - #[cfg(not(windows))] - fn path_to_repo_path_no_absolute() { - assert_err!("/", "repo path `/` should be relative"); - assert_repo_path_ok!("foo/bar"); - } - - #[test] - #[cfg(windows)] - fn path_to_repo_path_no_absolute() { - assert_err!( - r"c:", - r"repo path `c:` should be relative, not a windows prefix" - ); - assert_err!( - r"c:\", - r"repo path `c:\` should be relative, not a windows prefix" - ); - assert_err!( - r"c:temp", - r"repo path `c:temp` should be relative, not a windows prefix" - ); - assert_err!( - r"\\?\UNC\a\b\c", - r"repo path `\\?\UNC\a\b\c` should be relative, not a windows prefix" - ); - assert_err!( - r"\\?\c:\foo", - r"repo path `\\?\c:\foo` should be relative, not a windows prefix" - ); - assert_err!( - r"\\.\COM42", - r"repo path `\\.\COM42` should be relative, not a windows prefix" - ); - assert_err!( - r"\\a\b", - r"repo path `\\a\b` should be relative, not a windows prefix" - ); - assert_err!(r"\", r"repo path `\` should be relative"); - assert_err!(r"/", r"repo path `/` should be relative"); - assert_err!(r"\foo", r"repo path `\foo` should be relative"); - assert_err!(r"/foo", r"repo path `/foo` should be relative"); - } -} diff --git a/extra/git2/src/version.rs b/extra/git2/src/version.rs deleted file mode 100644 index b5dd4fb12..000000000 --- a/extra/git2/src/version.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::raw; -use libc::c_int; -use std::fmt; - -/// Version information about libgit2 and the capabilities it supports. -pub struct Version { - major: c_int, - minor: c_int, - rev: c_int, - features: c_int, -} - -macro_rules! flag_test { - ($features:expr, $flag:expr) => { - ($features as u32 & $flag as u32) != 0 - }; -} - -impl Version { - /// Returns a [`Version`] which provides information about libgit2. - pub fn get() -> Version { - let mut v = Version { - major: 0, - minor: 0, - rev: 0, - features: 0, - }; - unsafe { - raw::git_libgit2_version(&mut v.major, &mut v.minor, &mut v.rev); - v.features = raw::git_libgit2_features(); - } - v - } - - /// Returns the version of libgit2. - /// - /// The return value is a tuple of `(major, minor, rev)` - pub fn libgit2_version(&self) -> (u32, u32, u32) { - (self.major as u32, self.minor as u32, self.rev as u32) - } - - /// Returns the version of the libgit2-sys crate. - pub fn crate_version(&self) -> &'static str { - env!("CARGO_PKG_VERSION") - } - - /// Returns true if this was built with the vendored version of libgit2. - pub fn vendored(&self) -> bool { - raw::vendored() - } - - /// Returns true if libgit2 was built thread-aware and can be safely used - /// from multiple threads. - pub fn threads(&self) -> bool { - flag_test!(self.features, raw::GIT_FEATURE_THREADS) - } - - /// Returns true if libgit2 was built with and linked against a TLS implementation. - /// - /// Custom TLS streams may still be added by the user to support HTTPS - /// regardless of this. - pub fn https(&self) -> bool { - flag_test!(self.features, raw::GIT_FEATURE_HTTPS) - } - - /// Returns true if libgit2 was built with and linked against libssh2. - /// - /// A custom transport may still be added by the user to support libssh2 - /// regardless of this. - pub fn ssh(&self) -> bool { - flag_test!(self.features, raw::GIT_FEATURE_SSH) - } - - /// Returns true if libgit2 was built with support for sub-second - /// resolution in file modification times. - pub fn nsec(&self) -> bool { - flag_test!(self.features, raw::GIT_FEATURE_NSEC) - } -} - -impl fmt::Debug for Version { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let mut f = f.debug_struct("Version"); - f.field("major", &self.major) - .field("minor", &self.minor) - .field("rev", &self.rev) - .field("crate_version", &self.crate_version()) - .field("vendored", &self.vendored()) - .field("threads", &self.threads()) - .field("https", &self.https()) - .field("ssh", &self.ssh()) - .field("nsec", &self.nsec()); - f.finish() - } -} diff --git a/extra/git2/src/worktree.rs b/extra/git2/src/worktree.rs deleted file mode 100644 index 569b639cf..000000000 --- a/extra/git2/src/worktree.rs +++ /dev/null @@ -1,331 +0,0 @@ -use crate::buf::Buf; -use crate::reference::Reference; -use crate::repo::Repository; -use crate::util::{self, Binding}; -use crate::{raw, Error}; -use std::os::raw::c_int; -use std::path::Path; -use std::ptr; -use std::str; -use std::{marker, mem}; - -/// An owned git worktree -/// -/// This structure corresponds to a `git_worktree` in libgit2. -// -pub struct Worktree { - raw: *mut raw::git_worktree, -} - -/// Options which can be used to configure how a worktree is initialized -pub struct WorktreeAddOptions<'a> { - raw: raw::git_worktree_add_options, - _marker: marker::PhantomData<Reference<'a>>, -} - -/// Options to configure how worktree pruning is performed -pub struct WorktreePruneOptions { - raw: raw::git_worktree_prune_options, -} - -/// Lock Status of a worktree -#[derive(PartialEq, Debug)] -pub enum WorktreeLockStatus { - /// Worktree is Unlocked - Unlocked, - /// Worktree is locked with the optional message - Locked(Option<String>), -} - -impl Worktree { - /// Open a worktree of a the repository - /// - /// If a repository is not the main tree but a worktree, this - /// function will look up the worktree inside the parent - /// repository and create a new `git_worktree` structure. - pub fn open_from_repository(repo: &Repository) -> Result<Worktree, Error> { - let mut raw = ptr::null_mut(); - unsafe { - try_call!(raw::git_worktree_open_from_repository(&mut raw, repo.raw())); - Ok(Binding::from_raw(raw)) - } - } - - /// Retrieves the name of the worktree - /// - /// This is the name that can be passed to repo::Repository::find_worktree - /// to reopen the worktree. This is also the name that would appear in the - /// list returned by repo::Repository::worktrees - pub fn name(&self) -> Option<&str> { - unsafe { - crate::opt_bytes(self, raw::git_worktree_name(self.raw)) - .and_then(|s| str::from_utf8(s).ok()) - } - } - - /// Retrieves the path to the worktree - /// - /// This is the path to the top-level of the source and not the path to the - /// .git file within the worktree. This path can be passed to - /// repo::Repository::open. - pub fn path(&self) -> &Path { - unsafe { - util::bytes2path(crate::opt_bytes(self, raw::git_worktree_path(self.raw)).unwrap()) - } - } - - /// Validates the worktree - /// - /// This checks that it still exists on the - /// filesystem and that the metadata is correct - pub fn validate(&self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_worktree_validate(self.raw)); - } - Ok(()) - } - - /// Locks the worktree - pub fn lock(&self, reason: Option<&str>) -> Result<(), Error> { - let reason = crate::opt_cstr(reason)?; - unsafe { - try_call!(raw::git_worktree_lock(self.raw, reason)); - } - Ok(()) - } - - /// Unlocks the worktree - pub fn unlock(&self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_worktree_unlock(self.raw)); - } - Ok(()) - } - - /// Checks if worktree is locked - pub fn is_locked(&self) -> Result<WorktreeLockStatus, Error> { - let buf = Buf::new(); - unsafe { - match try_call!(raw::git_worktree_is_locked(buf.raw(), self.raw)) { - 0 => Ok(WorktreeLockStatus::Unlocked), - _ => { - let v = buf.to_vec(); - Ok(WorktreeLockStatus::Locked(match v.len() { - 0 => None, - _ => Some(String::from_utf8(v).unwrap()), - })) - } - } - } - } - - /// Prunes the worktree - pub fn prune(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<(), Error> { - // When successful the worktree should be removed however the backing structure - // of the git_worktree should still be valid. - unsafe { - try_call!(raw::git_worktree_prune(self.raw, opts.map(|o| o.raw()))); - } - Ok(()) - } - - /// Checks if the worktree is prunable - pub fn is_prunable(&self, opts: Option<&mut WorktreePruneOptions>) -> Result<bool, Error> { - unsafe { - let rv = try_call!(raw::git_worktree_is_prunable( - self.raw, - opts.map(|o| o.raw()) - )); - Ok(rv != 0) - } - } -} - -impl<'a> WorktreeAddOptions<'a> { - /// Creates a default set of add options. - /// - /// By default this will not lock the worktree - pub fn new() -> WorktreeAddOptions<'a> { - unsafe { - let mut raw = mem::zeroed(); - assert_eq!( - raw::git_worktree_add_options_init(&mut raw, raw::GIT_WORKTREE_ADD_OPTIONS_VERSION), - 0 - ); - WorktreeAddOptions { - raw, - _marker: marker::PhantomData, - } - } - } - - /// If enabled, this will cause the newly added worktree to be locked - pub fn lock(&mut self, enabled: bool) -> &mut WorktreeAddOptions<'a> { - self.raw.lock = enabled as c_int; - self - } - - /// reference to use for the new worktree HEAD - pub fn reference( - &mut self, - reference: Option<&'a Reference<'_>>, - ) -> &mut WorktreeAddOptions<'a> { - self.raw.reference = if let Some(reference) = reference { - reference.raw() - } else { - ptr::null_mut() - }; - self - } - - /// Get a set of raw add options to be used with `git_worktree_add` - pub fn raw(&self) -> *const raw::git_worktree_add_options { - &self.raw - } -} - -impl WorktreePruneOptions { - /// Creates a default set of pruning options - /// - /// By defaults this will prune only worktrees that are no longer valid - /// unlocked and not checked out - pub fn new() -> WorktreePruneOptions { - unsafe { - let mut raw = mem::zeroed(); - assert_eq!( - raw::git_worktree_prune_options_init( - &mut raw, - raw::GIT_WORKTREE_PRUNE_OPTIONS_VERSION - ), - 0 - ); - WorktreePruneOptions { raw } - } - } - - /// Controls whether valid (still existing on the filesystem) worktrees - /// will be pruned - /// - /// Defaults to false - pub fn valid(&mut self, valid: bool) -> &mut WorktreePruneOptions { - self.flag(raw::GIT_WORKTREE_PRUNE_VALID, valid) - } - - /// Controls whether locked worktrees will be pruned - /// - /// Defaults to false - pub fn locked(&mut self, locked: bool) -> &mut WorktreePruneOptions { - self.flag(raw::GIT_WORKTREE_PRUNE_LOCKED, locked) - } - - /// Controls whether the actual working tree on the filesystem is recursively removed - /// - /// Defaults to false - pub fn working_tree(&mut self, working_tree: bool) -> &mut WorktreePruneOptions { - self.flag(raw::GIT_WORKTREE_PRUNE_WORKING_TREE, working_tree) - } - - fn flag(&mut self, flag: raw::git_worktree_prune_t, on: bool) -> &mut WorktreePruneOptions { - if on { - self.raw.flags |= flag as u32; - } else { - self.raw.flags &= !(flag as u32); - } - self - } - - /// Get a set of raw prune options to be used with `git_worktree_prune` - pub fn raw(&mut self) -> *mut raw::git_worktree_prune_options { - &mut self.raw - } -} - -impl Binding for Worktree { - type Raw = *mut raw::git_worktree; - unsafe fn from_raw(ptr: *mut raw::git_worktree) -> Worktree { - Worktree { raw: ptr } - } - fn raw(&self) -> *mut raw::git_worktree { - self.raw - } -} - -impl Drop for Worktree { - fn drop(&mut self) { - unsafe { raw::git_worktree_free(self.raw) } - } -} - -#[cfg(test)] -mod tests { - use crate::WorktreeAddOptions; - use crate::WorktreeLockStatus; - - use tempfile::TempDir; - - #[test] - fn smoke_add_no_ref() { - let (_td, repo) = crate::test::repo_init(); - - let wtdir = TempDir::new().unwrap(); - let wt_path = wtdir.path().join("tree-no-ref-dir"); - let opts = WorktreeAddOptions::new(); - - let wt = repo.worktree("tree-no-ref", &wt_path, Some(&opts)).unwrap(); - assert_eq!(wt.name(), Some("tree-no-ref")); - assert_eq!( - wt.path().canonicalize().unwrap(), - wt_path.canonicalize().unwrap() - ); - let status = wt.is_locked().unwrap(); - assert_eq!(status, WorktreeLockStatus::Unlocked); - } - - #[test] - fn smoke_add_locked() { - let (_td, repo) = crate::test::repo_init(); - - let wtdir = TempDir::new().unwrap(); - let wt_path = wtdir.path().join("locked-tree"); - let mut opts = WorktreeAddOptions::new(); - opts.lock(true); - - let wt = repo.worktree("locked-tree", &wt_path, Some(&opts)).unwrap(); - // shouldn't be able to lock a worktree that was created locked - assert!(wt.lock(Some("my reason")).is_err()); - assert_eq!(wt.name(), Some("locked-tree")); - assert_eq!( - wt.path().canonicalize().unwrap(), - wt_path.canonicalize().unwrap() - ); - assert_eq!(wt.is_locked().unwrap(), WorktreeLockStatus::Locked(None)); - assert!(wt.unlock().is_ok()); - assert!(wt.lock(Some("my reason")).is_ok()); - assert_eq!( - wt.is_locked().unwrap(), - WorktreeLockStatus::Locked(Some("my reason".to_string())) - ); - } - - #[test] - fn smoke_add_from_branch() { - let (_td, repo) = crate::test::repo_init(); - - let (wt_top, branch) = crate::test::worktrees_env_init(&repo); - let wt_path = wt_top.path().join("test"); - let mut opts = WorktreeAddOptions::new(); - let reference = branch.into_reference(); - opts.reference(Some(&reference)); - - let wt = repo - .worktree("test-worktree", &wt_path, Some(&opts)) - .unwrap(); - assert_eq!(wt.name(), Some("test-worktree")); - assert_eq!( - wt.path().canonicalize().unwrap(), - wt_path.canonicalize().unwrap() - ); - let status = wt.is_locked().unwrap(); - assert_eq!(status, WorktreeLockStatus::Unlocked); - } -} |