mirror of
				https://git.proxmox.com/git/rustc
				synced 2025-10-31 20:00:20 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			930 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			930 lines
		
	
	
		
			32 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| 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(),
 | ||
|         }
 | ||
|     }
 | ||
| }
 | 
