summaryrefslogtreecommitdiffstats
path: root/extra/git2/src/index.rs
diff options
context:
space:
mode:
Diffstat (limited to 'extra/git2/src/index.rs')
-rw-r--r--extra/git2/src/index.rs929
1 files changed, 929 insertions, 0 deletions
diff --git a/extra/git2/src/index.rs b/extra/git2/src/index.rs
new file mode 100644
index 000000000..0291d3cb9
--- /dev/null
+++ b/extra/git2/src/index.rs
@@ -0,0 +1,929 @@
+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(),
+ }
+ }
+}