1 use std::ffi::{CStr, CString};
2 use std::marker;
3 use std::ops::Range;
4 use std::path::Path;
5 use std::ptr;
6 use std::slice;
7 
8 use libc::{c_char, c_int, c_uint, c_void, size_t};
9 
10 use crate::util::{self, path_to_repo_path, Binding};
11 use crate::IntoCString;
12 use crate::{panic, raw, Error, IndexAddOption, IndexTime, Oid, Repository, Tree};
13 
14 /// A structure to represent a git [index][1]
15 ///
16 /// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
17 pub struct Index {
18     raw: *mut raw::git_index,
19 }
20 
21 /// An iterator over the entries in an index
22 pub struct IndexEntries<'index> {
23     range: Range<usize>,
24     index: &'index Index,
25 }
26 
27 /// An iterator over the conflicting entries in an index
28 pub struct IndexConflicts<'index> {
29     conflict_iter: *mut raw::git_index_conflict_iterator,
30     _marker: marker::PhantomData<&'index Index>,
31 }
32 
33 /// A structure to represent the information returned when a conflict is detected in an index entry
34 pub struct IndexConflict {
35     /// The ancestor index entry of the two conflicting index entries
36     pub ancestor: Option<IndexEntry>,
37     /// The index entry originating from the user's copy of the repository.
38     /// Its contents conflict with 'their' index entry
39     pub our: Option<IndexEntry>,
40     /// The index entry originating from the external repository.
41     /// Its contents conflict with 'our' index entry
42     pub their: Option<IndexEntry>,
43 }
44 
45 /// A callback function to filter index matches.
46 ///
47 /// Used by `Index::{add_all,remove_all,update_all}`.  The first argument is the
48 /// path, and the second is the patchspec that matched it.  Return 0 to confirm
49 /// the operation on the item, > 0 to skip the item, and < 0 to abort the scan.
50 pub type IndexMatchedPath<'a> = dyn FnMut(&Path, &[u8]) -> i32 + 'a;
51 
52 /// A structure to represent an entry or a file inside of an index.
53 ///
54 /// All fields of an entry are public for modification and inspection. This is
55 /// also how a new index entry is created.
56 #[allow(missing_docs)]
57 #[derive(Debug)]
58 pub struct IndexEntry {
59     pub ctime: IndexTime,
60     pub mtime: IndexTime,
61     pub dev: u32,
62     pub ino: u32,
63     pub mode: u32,
64     pub uid: u32,
65     pub gid: u32,
66     pub file_size: u32,
67     pub id: Oid,
68     pub flags: u16,
69     pub flags_extended: u16,
70 
71     /// The path of this index entry as a byte vector. Regardless of the
72     /// current platform, the directory separator is an ASCII forward slash
73     /// (`0x2F`). There are no terminating or internal NUL characters, and no
74     /// trailing slashes. Most of the time, paths will be valid utf-8 — but
75     /// not always. For more information on the path storage format, see
76     /// [these git docs][git-index-docs]. Note that libgit2 will take care of
77     /// handling the prefix compression mentioned there.
78     ///
79     /// [git-index-docs]: https://github.com/git/git/blob/a08a83db2bf27f015bec9a435f6d73e223c21c5e/Documentation/technical/index-format.txt#L107-L124
80     ///
81     /// You can turn this value into a `std::ffi::CString` with
82     /// `CString::new(&entry.path[..]).unwrap()`. To turn a reference into a
83     /// `&std::path::Path`, see the `bytes2path()` function in the private,
84     /// internal `util` module in this crate’s source code.
85     pub path: Vec<u8>,
86 }
87 
88 impl Index {
89     /// Creates a new in-memory index.
90     ///
91     /// This index object cannot be read/written to the filesystem, but may be
92     /// used to perform in-memory index operations.
new() -> Result<Index, Error>93     pub fn new() -> Result<Index, Error> {
94         crate::init();
95         let mut raw = ptr::null_mut();
96         unsafe {
97             try_call!(raw::git_index_new(&mut raw));
98             Ok(Binding::from_raw(raw))
99         }
100     }
101 
102     /// Create a new bare Git index object as a memory representation of the Git
103     /// index file in 'index_path', without a repository to back it.
104     ///
105     /// Since there is no ODB or working directory behind this index, any Index
106     /// methods which rely on these (e.g. add_path) will fail.
107     ///
108     /// If you need an index attached to a repository, use the `index()` method
109     /// on `Repository`.
open(index_path: &Path) -> Result<Index, Error>110     pub fn open(index_path: &Path) -> Result<Index, Error> {
111         crate::init();
112         let mut raw = ptr::null_mut();
113         // Normal file path OK (does not need Windows conversion).
114         let index_path = index_path.into_c_string()?;
115         unsafe {
116             try_call!(raw::git_index_open(&mut raw, index_path));
117             Ok(Binding::from_raw(raw))
118         }
119     }
120 
121     /// Get index on-disk version.
122     ///
123     /// Valid return values are 2, 3, or 4.  If 3 is returned, an index
124     /// with version 2 may be written instead, if the extension data in
125     /// version 3 is not necessary.
version(&self) -> u32126     pub fn version(&self) -> u32 {
127         unsafe { raw::git_index_version(self.raw) }
128     }
129 
130     /// Set index on-disk version.
131     ///
132     /// Valid values are 2, 3, or 4.  If 2 is given, git_index_write may
133     /// write an index with version 3 instead, if necessary to accurately
134     /// represent the index.
set_version(&mut self, version: u32) -> Result<(), Error>135     pub fn set_version(&mut self, version: u32) -> Result<(), Error> {
136         unsafe {
137             try_call!(raw::git_index_set_version(self.raw, version));
138         }
139         Ok(())
140     }
141 
142     /// Add or update an index entry from an in-memory struct
143     ///
144     /// If a previous index entry exists that has the same path and stage as the
145     /// given 'source_entry', it will be replaced. Otherwise, the 'source_entry'
146     /// will be added.
add(&mut self, entry: &IndexEntry) -> Result<(), Error>147     pub fn add(&mut self, entry: &IndexEntry) -> Result<(), Error> {
148         let path = CString::new(&entry.path[..])?;
149 
150         // libgit2 encodes the length of the path in the lower bits of the
151         // `flags` entry, so mask those out and recalculate here to ensure we
152         // don't corrupt anything.
153         let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;
154 
155         if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
156             flags |= entry.path.len() as u16;
157         } else {
158             flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
159         }
160 
161         unsafe {
162             let raw = raw::git_index_entry {
163                 dev: entry.dev,
164                 ino: entry.ino,
165                 mode: entry.mode,
166                 uid: entry.uid,
167                 gid: entry.gid,
168                 file_size: entry.file_size,
169                 id: *entry.id.raw(),
170                 flags,
171                 flags_extended: entry.flags_extended,
172                 path: path.as_ptr(),
173                 mtime: raw::git_index_time {
174                     seconds: entry.mtime.seconds(),
175                     nanoseconds: entry.mtime.nanoseconds(),
176                 },
177                 ctime: raw::git_index_time {
178                     seconds: entry.ctime.seconds(),
179                     nanoseconds: entry.ctime.nanoseconds(),
180                 },
181             };
182             try_call!(raw::git_index_add(self.raw, &raw));
183             Ok(())
184         }
185     }
186 
187     /// Add or update an index entry from a buffer in memory
188     ///
189     /// This method will create a blob in the repository that owns the index and
190     /// then add the index entry to the index. The path of the entry represents
191     /// the position of the blob relative to the repository's root folder.
192     ///
193     /// If a previous index entry exists that has the same path as the given
194     /// 'entry', it will be replaced. Otherwise, the 'entry' will be added.
195     /// The id and the file_size of the 'entry' are updated with the real value
196     /// of the blob.
197     ///
198     /// This forces the file to be added to the index, not looking at gitignore
199     /// rules.
200     ///
201     /// If this file currently is the result of a merge conflict, this file will
202     /// no longer be marked as conflicting. The data about the conflict will be
203     /// moved to the "resolve undo" (REUC) section.
add_frombuffer(&mut self, entry: &IndexEntry, data: &[u8]) -> Result<(), Error>204     pub fn add_frombuffer(&mut self, entry: &IndexEntry, data: &[u8]) -> Result<(), Error> {
205         let path = CString::new(&entry.path[..])?;
206 
207         // libgit2 encodes the length of the path in the lower bits of the
208         // `flags` entry, so mask those out and recalculate here to ensure we
209         // don't corrupt anything.
210         let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;
211 
212         if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
213             flags |= entry.path.len() as u16;
214         } else {
215             flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
216         }
217 
218         unsafe {
219             let raw = raw::git_index_entry {
220                 dev: entry.dev,
221                 ino: entry.ino,
222                 mode: entry.mode,
223                 uid: entry.uid,
224                 gid: entry.gid,
225                 file_size: entry.file_size,
226                 id: *entry.id.raw(),
227                 flags,
228                 flags_extended: entry.flags_extended,
229                 path: path.as_ptr(),
230                 mtime: raw::git_index_time {
231                     seconds: entry.mtime.seconds(),
232                     nanoseconds: entry.mtime.nanoseconds(),
233                 },
234                 ctime: raw::git_index_time {
235                     seconds: entry.ctime.seconds(),
236                     nanoseconds: entry.ctime.nanoseconds(),
237                 },
238             };
239 
240             let ptr = data.as_ptr() as *const c_void;
241             let len = data.len() as size_t;
242             try_call!(raw::git_index_add_frombuffer(self.raw, &raw, ptr, len));
243             Ok(())
244         }
245     }
246 
247     /// Add or update an index entry from a file on disk
248     ///
249     /// The file path must be relative to the repository's working folder and
250     /// must be readable.
251     ///
252     /// This method will fail in bare index instances.
253     ///
254     /// This forces the file to be added to the index, not looking at gitignore
255     /// rules.
256     ///
257     /// If this file currently is the result of a merge conflict, this file will
258     /// no longer be marked as conflicting. The data about the conflict will be
259     /// moved to the "resolve undo" (REUC) section.
add_path(&mut self, path: &Path) -> Result<(), Error>260     pub fn add_path(&mut self, path: &Path) -> Result<(), Error> {
261         let posix_path = path_to_repo_path(path)?;
262         unsafe {
263             try_call!(raw::git_index_add_bypath(self.raw, posix_path));
264             Ok(())
265         }
266     }
267 
268     /// Add or update index entries matching files in the working directory.
269     ///
270     /// This method will fail in bare index instances.
271     ///
272     /// The `pathspecs` are a list of file names or shell glob patterns that
273     /// will matched against files in the repository's working directory. Each
274     /// file that matches will be added to the index (either updating an
275     /// existing entry or adding a new entry). You can disable glob expansion
276     /// and force exact matching with the `AddDisablePathspecMatch` flag.
277     ///
278     /// Files that are ignored will be skipped (unlike `add_path`). If a file is
279     /// already tracked in the index, then it will be updated even if it is
280     /// ignored. Pass the `AddForce` flag to skip the checking of ignore rules.
281     ///
282     /// To emulate `git add -A` and generate an error if the pathspec contains
283     /// the exact path of an ignored file (when not using `AddForce`), add the
284     /// `AddCheckPathspec` flag. This checks that each entry in `pathspecs`
285     /// that is an exact match to a filename on disk is either not ignored or
286     /// already in the index. If this check fails, the function will return
287     /// an error.
288     ///
289     /// To emulate `git add -A` with the "dry-run" option, just use a callback
290     /// function that always returns a positive value. See below for details.
291     ///
292     /// If any files are currently the result of a merge conflict, those files
293     /// will no longer be marked as conflicting. The data about the conflicts
294     /// will be moved to the "resolve undo" (REUC) section.
295     ///
296     /// If you provide a callback function, it will be invoked on each matching
297     /// item in the working directory immediately before it is added to /
298     /// updated in the index. Returning zero will add the item to the index,
299     /// greater than zero will skip the item, and less than zero will abort the
300     /// scan an return an error to the caller.
301     ///
302     /// # Example
303     ///
304     /// Emulate `git add *`:
305     ///
306     /// ```no_run
307     /// use git2::{Index, IndexAddOption, Repository};
308     ///
309     /// let repo = Repository::open("/path/to/a/repo").expect("failed to open");
310     /// let mut index = repo.index().expect("cannot get the Index file");
311     /// index.add_all(["*"].iter(), IndexAddOption::DEFAULT, None);
312     /// index.write();
313     /// ```
add_all<T, I>( &mut self, pathspecs: I, flag: IndexAddOption, mut cb: Option<&mut IndexMatchedPath<'_>>, ) -> Result<(), Error> where T: IntoCString, I: IntoIterator<Item = T>,314     pub fn add_all<T, I>(
315         &mut self,
316         pathspecs: I,
317         flag: IndexAddOption,
318         mut cb: Option<&mut IndexMatchedPath<'_>>,
319     ) -> Result<(), Error>
320     where
321         T: IntoCString,
322         I: IntoIterator<Item = T>,
323     {
324         let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
325         let ptr = cb.as_mut();
326         let callback = ptr
327             .as_ref()
328             .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
329         unsafe {
330             try_call!(raw::git_index_add_all(
331                 self.raw,
332                 &raw_strarray,
333                 flag.bits() as c_uint,
334                 callback,
335                 ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
336             ));
337         }
338         Ok(())
339     }
340 
341     /// Clear the contents (all the entries) of an index object.
342     ///
343     /// This clears the index object in memory; changes must be explicitly
344     /// written to disk for them to take effect persistently via `write_*`.
clear(&mut self) -> Result<(), Error>345     pub fn clear(&mut self) -> Result<(), Error> {
346         unsafe {
347             try_call!(raw::git_index_clear(self.raw));
348         }
349         Ok(())
350     }
351 
352     /// Get the count of entries currently in the index
len(&self) -> usize353     pub fn len(&self) -> usize {
354         unsafe { raw::git_index_entrycount(&*self.raw) as usize }
355     }
356 
357     /// Return `true` is there is no entry in the index
is_empty(&self) -> bool358     pub fn is_empty(&self) -> bool {
359         self.len() == 0
360     }
361 
362     /// Get one of the entries in the index by its position.
get(&self, n: usize) -> Option<IndexEntry>363     pub fn get(&self, n: usize) -> Option<IndexEntry> {
364         unsafe {
365             let ptr = raw::git_index_get_byindex(self.raw, n as size_t);
366             if ptr.is_null() {
367                 None
368             } else {
369                 Some(Binding::from_raw(*ptr))
370             }
371         }
372     }
373 
374     /// Get an iterator over the entries in this index.
iter(&self) -> IndexEntries<'_>375     pub fn iter(&self) -> IndexEntries<'_> {
376         IndexEntries {
377             range: 0..self.len(),
378             index: self,
379         }
380     }
381 
382     /// Get an iterator over the index entries that have conflicts
conflicts(&self) -> Result<IndexConflicts<'_>, Error>383     pub fn conflicts(&self) -> Result<IndexConflicts<'_>, Error> {
384         crate::init();
385         let mut conflict_iter = ptr::null_mut();
386         unsafe {
387             try_call!(raw::git_index_conflict_iterator_new(
388                 &mut conflict_iter,
389                 self.raw
390             ));
391             Ok(Binding::from_raw(conflict_iter))
392         }
393     }
394 
395     /// Get one of the entries in the index by its path.
get_path(&self, path: &Path, stage: i32) -> Option<IndexEntry>396     pub fn get_path(&self, path: &Path, stage: i32) -> Option<IndexEntry> {
397         let path = path_to_repo_path(path).unwrap();
398         unsafe {
399             let ptr = call!(raw::git_index_get_bypath(self.raw, path, stage as c_int));
400             if ptr.is_null() {
401                 None
402             } else {
403                 Some(Binding::from_raw(*ptr))
404             }
405         }
406     }
407 
408     /// Does this index have conflicts?
409     ///
410     /// Returns `true` if the index contains conflicts, `false` if it does not.
has_conflicts(&self) -> bool411     pub fn has_conflicts(&self) -> bool {
412         unsafe { raw::git_index_has_conflicts(self.raw) == 1 }
413     }
414 
415     /// Get the full path to the index file on disk.
416     ///
417     /// Returns `None` if this is an in-memory index.
path(&self) -> Option<&Path>418     pub fn path(&self) -> Option<&Path> {
419         unsafe { crate::opt_bytes(self, raw::git_index_path(&*self.raw)).map(util::bytes2path) }
420     }
421 
422     /// Update the contents of an existing index object in memory by reading
423     /// from the hard disk.
424     ///
425     /// If force is true, this performs a "hard" read that discards in-memory
426     /// changes and always reloads the on-disk index data. If there is no
427     /// on-disk version, the index will be cleared.
428     ///
429     /// If force is false, this does a "soft" read that reloads the index data
430     /// from disk only if it has changed since the last time it was loaded.
431     /// Purely in-memory index data will be untouched. Be aware: if there are
432     /// changes on disk, unwritten in-memory changes are discarded.
read(&mut self, force: bool) -> Result<(), Error>433     pub fn read(&mut self, force: bool) -> Result<(), Error> {
434         unsafe {
435             try_call!(raw::git_index_read(self.raw, force));
436         }
437         Ok(())
438     }
439 
440     /// Read a tree into the index file with stats
441     ///
442     /// The current index contents will be replaced by the specified tree.
read_tree(&mut self, tree: &Tree<'_>) -> Result<(), Error>443     pub fn read_tree(&mut self, tree: &Tree<'_>) -> Result<(), Error> {
444         unsafe {
445             try_call!(raw::git_index_read_tree(self.raw, &*tree.raw()));
446         }
447         Ok(())
448     }
449 
450     /// Remove an entry from the index
remove(&mut self, path: &Path, stage: i32) -> Result<(), Error>451     pub fn remove(&mut self, path: &Path, stage: i32) -> Result<(), Error> {
452         let path = path_to_repo_path(path)?;
453         unsafe {
454             try_call!(raw::git_index_remove(self.raw, path, stage as c_int));
455         }
456         Ok(())
457     }
458 
459     /// Remove an index entry corresponding to a file on disk.
460     ///
461     /// The file path must be relative to the repository's working folder. It
462     /// may exist.
463     ///
464     /// If this file currently is the result of a merge conflict, this file will
465     /// no longer be marked as conflicting. The data about the conflict will be
466     /// moved to the "resolve undo" (REUC) section.
remove_path(&mut self, path: &Path) -> Result<(), Error>467     pub fn remove_path(&mut self, path: &Path) -> Result<(), Error> {
468         let path = path_to_repo_path(path)?;
469         unsafe {
470             try_call!(raw::git_index_remove_bypath(self.raw, path));
471         }
472         Ok(())
473     }
474 
475     /// Remove all entries from the index under a given directory.
remove_dir(&mut self, path: &Path, stage: i32) -> Result<(), Error>476     pub fn remove_dir(&mut self, path: &Path, stage: i32) -> Result<(), Error> {
477         let path = path_to_repo_path(path)?;
478         unsafe {
479             try_call!(raw::git_index_remove_directory(
480                 self.raw,
481                 path,
482                 stage as c_int
483             ));
484         }
485         Ok(())
486     }
487 
488     /// Remove all matching index entries.
489     ///
490     /// If you provide a callback function, it will be invoked on each matching
491     /// item in the index immediately before it is removed. Return 0 to remove
492     /// the item, > 0 to skip the item, and < 0 to abort the scan.
remove_all<T, I>( &mut self, pathspecs: I, mut cb: Option<&mut IndexMatchedPath<'_>>, ) -> Result<(), Error> where T: IntoCString, I: IntoIterator<Item = T>,493     pub fn remove_all<T, I>(
494         &mut self,
495         pathspecs: I,
496         mut cb: Option<&mut IndexMatchedPath<'_>>,
497     ) -> Result<(), Error>
498     where
499         T: IntoCString,
500         I: IntoIterator<Item = T>,
501     {
502         let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
503         let ptr = cb.as_mut();
504         let callback = ptr
505             .as_ref()
506             .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
507         unsafe {
508             try_call!(raw::git_index_remove_all(
509                 self.raw,
510                 &raw_strarray,
511                 callback,
512                 ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
513             ));
514         }
515         Ok(())
516     }
517 
518     /// Update all index entries to match the working directory
519     ///
520     /// This method will fail in bare index instances.
521     ///
522     /// This scans the existing index entries and synchronizes them with the
523     /// working directory, deleting them if the corresponding working directory
524     /// file no longer exists otherwise updating the information (including
525     /// adding the latest version of file to the ODB if needed).
526     ///
527     /// If you provide a callback function, it will be invoked on each matching
528     /// item in the index immediately before it is updated (either refreshed or
529     /// removed depending on working directory state). Return 0 to proceed with
530     /// updating the item, > 0 to skip the item, and < 0 to abort the scan.
update_all<T, I>( &mut self, pathspecs: I, mut cb: Option<&mut IndexMatchedPath<'_>>, ) -> Result<(), Error> where T: IntoCString, I: IntoIterator<Item = T>,531     pub fn update_all<T, I>(
532         &mut self,
533         pathspecs: I,
534         mut cb: Option<&mut IndexMatchedPath<'_>>,
535     ) -> Result<(), Error>
536     where
537         T: IntoCString,
538         I: IntoIterator<Item = T>,
539     {
540         let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
541         let ptr = cb.as_mut();
542         let callback = ptr
543             .as_ref()
544             .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
545         unsafe {
546             try_call!(raw::git_index_update_all(
547                 self.raw,
548                 &raw_strarray,
549                 callback,
550                 ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
551             ));
552         }
553         Ok(())
554     }
555 
556     /// Write an existing index object from memory back to disk using an atomic
557     /// file lock.
write(&mut self) -> Result<(), Error>558     pub fn write(&mut self) -> Result<(), Error> {
559         unsafe {
560             try_call!(raw::git_index_write(self.raw));
561         }
562         Ok(())
563     }
564 
565     /// Write the index as a tree.
566     ///
567     /// This method will scan the index and write a representation of its
568     /// current state back to disk; it recursively creates tree objects for each
569     /// of the subtrees stored in the index, but only returns the OID of the
570     /// root tree. This is the OID that can be used e.g. to create a commit.
571     ///
572     /// The index instance cannot be bare, and needs to be associated to an
573     /// existing repository.
574     ///
575     /// The index must not contain any file in conflict.
write_tree(&mut self) -> Result<Oid, Error>576     pub fn write_tree(&mut self) -> Result<Oid, Error> {
577         let mut raw = raw::git_oid {
578             id: [0; raw::GIT_OID_RAWSZ],
579         };
580         unsafe {
581             try_call!(raw::git_index_write_tree(&mut raw, self.raw));
582             Ok(Binding::from_raw(&raw as *const _))
583         }
584     }
585 
586     /// Write the index as a tree to the given repository
587     ///
588     /// This is the same as `write_tree` except that the destination repository
589     /// can be chosen.
write_tree_to(&mut self, repo: &Repository) -> Result<Oid, Error>590     pub fn write_tree_to(&mut self, repo: &Repository) -> Result<Oid, Error> {
591         let mut raw = raw::git_oid {
592             id: [0; raw::GIT_OID_RAWSZ],
593         };
594         unsafe {
595             try_call!(raw::git_index_write_tree_to(&mut raw, self.raw, repo.raw()));
596             Ok(Binding::from_raw(&raw as *const _))
597         }
598     }
599 }
600 
601 impl Binding for Index {
602     type Raw = *mut raw::git_index;
from_raw(raw: *mut raw::git_index) -> Index603     unsafe fn from_raw(raw: *mut raw::git_index) -> Index {
604         Index { raw }
605     }
raw(&self) -> *mut raw::git_index606     fn raw(&self) -> *mut raw::git_index {
607         self.raw
608     }
609 }
610 
611 impl<'index> Binding for IndexConflicts<'index> {
612     type Raw = *mut raw::git_index_conflict_iterator;
613 
from_raw(raw: *mut raw::git_index_conflict_iterator) -> IndexConflicts<'index>614     unsafe fn from_raw(raw: *mut raw::git_index_conflict_iterator) -> IndexConflicts<'index> {
615         IndexConflicts {
616             conflict_iter: raw,
617             _marker: marker::PhantomData,
618         }
619     }
raw(&self) -> *mut raw::git_index_conflict_iterator620     fn raw(&self) -> *mut raw::git_index_conflict_iterator {
621         self.conflict_iter
622     }
623 }
624 
index_matched_path_cb( path: *const c_char, matched_pathspec: *const c_char, payload: *mut c_void, ) -> c_int625 extern "C" fn index_matched_path_cb(
626     path: *const c_char,
627     matched_pathspec: *const c_char,
628     payload: *mut c_void,
629 ) -> c_int {
630     unsafe {
631         let path = CStr::from_ptr(path).to_bytes();
632         let matched_pathspec = CStr::from_ptr(matched_pathspec).to_bytes();
633 
634         panic::wrap(|| {
635             let payload = payload as *mut &mut IndexMatchedPath<'_>;
636             (*payload)(util::bytes2path(path), matched_pathspec) as c_int
637         })
638         .unwrap_or(-1)
639     }
640 }
641 
642 impl Drop for Index {
drop(&mut self)643     fn drop(&mut self) {
644         unsafe { raw::git_index_free(self.raw) }
645     }
646 }
647 
648 impl<'index> Drop for IndexConflicts<'index> {
drop(&mut self)649     fn drop(&mut self) {
650         unsafe { raw::git_index_conflict_iterator_free(self.conflict_iter) }
651     }
652 }
653 
654 impl<'index> Iterator for IndexEntries<'index> {
655     type Item = IndexEntry;
next(&mut self) -> Option<IndexEntry>656     fn next(&mut self) -> Option<IndexEntry> {
657         self.range.next().map(|i| self.index.get(i).unwrap())
658     }
659 }
660 
661 impl<'index> Iterator for IndexConflicts<'index> {
662     type Item = Result<IndexConflict, Error>;
next(&mut self) -> Option<Result<IndexConflict, Error>>663     fn next(&mut self) -> Option<Result<IndexConflict, Error>> {
664         let mut ancestor = ptr::null();
665         let mut our = ptr::null();
666         let mut their = ptr::null();
667         unsafe {
668             try_call_iter!(raw::git_index_conflict_next(
669                 &mut ancestor,
670                 &mut our,
671                 &mut their,
672                 self.conflict_iter
673             ));
674             Some(Ok(IndexConflict {
675                 ancestor: match ancestor.is_null() {
676                     false => Some(IndexEntry::from_raw(*ancestor)),
677                     true => None,
678                 },
679                 our: match our.is_null() {
680                     false => Some(IndexEntry::from_raw(*our)),
681                     true => None,
682                 },
683                 their: match their.is_null() {
684                     false => Some(IndexEntry::from_raw(*their)),
685                     true => None,
686                 },
687             }))
688         }
689     }
690 }
691 
692 impl Binding for IndexEntry {
693     type Raw = raw::git_index_entry;
694 
from_raw(raw: raw::git_index_entry) -> IndexEntry695     unsafe fn from_raw(raw: raw::git_index_entry) -> IndexEntry {
696         let raw::git_index_entry {
697             ctime,
698             mtime,
699             dev,
700             ino,
701             mode,
702             uid,
703             gid,
704             file_size,
705             id,
706             flags,
707             flags_extended,
708             path,
709         } = raw;
710 
711         // libgit2 encodes the length of the path in the lower bits of `flags`,
712         // but if the length exceeds the number of bits then the path is
713         // nul-terminated.
714         let mut pathlen = (flags & raw::GIT_INDEX_ENTRY_NAMEMASK) as usize;
715         if pathlen == raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
716             pathlen = CStr::from_ptr(path).to_bytes().len();
717         }
718 
719         let path = slice::from_raw_parts(path as *const u8, pathlen);
720 
721         IndexEntry {
722             dev,
723             ino,
724             mode,
725             uid,
726             gid,
727             file_size,
728             id: Binding::from_raw(&id as *const _),
729             flags,
730             flags_extended,
731             path: path.to_vec(),
732             mtime: Binding::from_raw(mtime),
733             ctime: Binding::from_raw(ctime),
734         }
735     }
736 
raw(&self) -> raw::git_index_entry737     fn raw(&self) -> raw::git_index_entry {
738         // not implemented, may require a CString in storage
739         panic!()
740     }
741 }
742 
743 #[cfg(test)]
744 mod tests {
745     use std::fs::{self, File};
746     use std::path::Path;
747     use tempfile::TempDir;
748 
749     use crate::{Index, IndexEntry, IndexTime, Oid, Repository, ResetType};
750 
751     #[test]
smoke()752     fn smoke() {
753         let mut index = Index::new().unwrap();
754         assert!(index.add_path(&Path::new(".")).is_err());
755         index.clear().unwrap();
756         assert_eq!(index.len(), 0);
757         assert!(index.get(0).is_none());
758         assert!(index.path().is_none());
759         assert!(index.read(true).is_err());
760     }
761 
762     #[test]
smoke_from_repo()763     fn smoke_from_repo() {
764         let (_td, repo) = crate::test::repo_init();
765         let mut index = repo.index().unwrap();
766         assert_eq!(
767             index.path().map(|s| s.to_path_buf()),
768             Some(repo.path().join("index"))
769         );
770         Index::open(&repo.path().join("index")).unwrap();
771 
772         index.clear().unwrap();
773         index.read(true).unwrap();
774         index.write().unwrap();
775         index.write_tree().unwrap();
776         index.write_tree_to(&repo).unwrap();
777     }
778 
779     #[test]
add_all()780     fn add_all() {
781         let (_td, repo) = crate::test::repo_init();
782         let mut index = repo.index().unwrap();
783 
784         let root = repo.path().parent().unwrap();
785         fs::create_dir(&root.join("foo")).unwrap();
786         File::create(&root.join("foo/bar")).unwrap();
787         let mut called = false;
788         index
789             .add_all(
790                 ["foo"].iter(),
791                 crate::IndexAddOption::DEFAULT,
792                 Some(&mut |a: &Path, b: &[u8]| {
793                     assert!(!called);
794                     called = true;
795                     assert_eq!(b, b"foo");
796                     assert_eq!(a, Path::new("foo/bar"));
797                     0
798                 }),
799             )
800             .unwrap();
801         assert!(called);
802 
803         called = false;
804         index
805             .remove_all(
806                 ["."].iter(),
807                 Some(&mut |a: &Path, b: &[u8]| {
808                     assert!(!called);
809                     called = true;
810                     assert_eq!(b, b".");
811                     assert_eq!(a, Path::new("foo/bar"));
812                     0
813                 }),
814             )
815             .unwrap();
816         assert!(called);
817     }
818 
819     #[test]
smoke_add()820     fn smoke_add() {
821         let (_td, repo) = crate::test::repo_init();
822         let mut index = repo.index().unwrap();
823 
824         let root = repo.path().parent().unwrap();
825         fs::create_dir(&root.join("foo")).unwrap();
826         File::create(&root.join("foo/bar")).unwrap();
827         index.add_path(Path::new("foo/bar")).unwrap();
828         index.write().unwrap();
829         assert_eq!(index.iter().count(), 1);
830 
831         // Make sure we can use this repo somewhere else now.
832         let id = index.write_tree().unwrap();
833         let tree = repo.find_tree(id).unwrap();
834         let sig = repo.signature().unwrap();
835         let id = repo.refname_to_id("HEAD").unwrap();
836         let parent = repo.find_commit(id).unwrap();
837         let commit = repo
838             .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
839             .unwrap();
840         let obj = repo.find_object(commit, None).unwrap();
841         repo.reset(&obj, ResetType::Hard, None).unwrap();
842 
843         let td2 = TempDir::new().unwrap();
844         let url = crate::test::path2url(&root);
845         let repo = Repository::clone(&url, td2.path()).unwrap();
846         let obj = repo.find_object(commit, None).unwrap();
847         repo.reset(&obj, ResetType::Hard, None).unwrap();
848     }
849 
850     #[test]
add_then_read()851     fn add_then_read() {
852         let mut index = Index::new().unwrap();
853         assert!(index.add(&entry()).is_err());
854 
855         let mut index = Index::new().unwrap();
856         let mut e = entry();
857         e.path = b"foobar".to_vec();
858         index.add(&e).unwrap();
859         let e = index.get(0).unwrap();
860         assert_eq!(e.path.len(), 6);
861     }
862 
863     #[test]
add_frombuffer_then_read()864     fn add_frombuffer_then_read() {
865         let (_td, repo) = crate::test::repo_init();
866         let mut index = repo.index().unwrap();
867 
868         let mut e = entry();
869         e.path = b"foobar".to_vec();
870         let content = b"the contents";
871         index.add_frombuffer(&e, content).unwrap();
872         let e = index.get(0).unwrap();
873         assert_eq!(e.path.len(), 6);
874 
875         let b = repo.find_blob(e.id).unwrap();
876         assert_eq!(b.content(), content);
877     }
878 
entry() -> IndexEntry879     fn entry() -> IndexEntry {
880         IndexEntry {
881             ctime: IndexTime::new(0, 0),
882             mtime: IndexTime::new(0, 0),
883             dev: 0,
884             ino: 0,
885             mode: 0o100644,
886             uid: 0,
887             gid: 0,
888             file_size: 0,
889             id: Oid::from_bytes(&[0; 20]).unwrap(),
890             flags: 0,
891             flags_extended: 0,
892             path: Vec::new(),
893         }
894     }
895 }
896