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