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