| 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 patchspec 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 _)) |
| } |
| } |
| } |
| |
| 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::{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(); |
| assert!(index.add(&entry()).is_err()); |
| |
| 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_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(), |
| } |
| } |
| } |