[go: up one dir, main page]

git2/
index.rs

1use std::ffi::{CStr, CString};
2use std::marker;
3use std::ops::Range;
4use std::path::Path;
5use std::ptr;
6use std::slice;
7
8use libc::{c_char, c_int, c_uint, c_void, size_t};
9
10use crate::util::{self, path_to_repo_path, Binding};
11use crate::IntoCString;
12use 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
17pub struct Index {
18    raw: *mut raw::git_index,
19}
20
21/// An iterator over the entries in an index
22pub struct IndexEntries<'index> {
23    range: Range<usize>,
24    index: &'index Index,
25}
26
27/// An iterator over the conflicting entries in an index
28pub 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
34pub 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 pathspec that matched it.  Return 0 to confirm
49/// the operation on the item, > 0 to skip the item, and < 0 to abort the scan.
50pub 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)]
58pub 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
88impl 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.
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`.
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.
126    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.
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.
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.
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.
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    /// ```
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_*`.
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
353    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
358    pub fn is_empty(&self) -> bool {
359        self.len() == 0
360    }
361
362    /// Get one of the entries in the index by its position.
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.
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
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.
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.
411    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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.
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    /// Find the first position of any entries matching a prefix.
601    ///
602    /// To find the first position of a path inside a given folder, suffix the prefix with a '/'.
603    pub fn find_prefix<T: IntoCString>(&self, prefix: T) -> Result<usize, Error> {
604        let mut at_pos: size_t = 0;
605        let entry_path = prefix.into_c_string()?;
606        unsafe {
607            try_call!(raw::git_index_find_prefix(
608                &mut at_pos,
609                self.raw,
610                entry_path
611            ));
612            Ok(at_pos)
613        }
614    }
615}
616
617impl Binding for Index {
618    type Raw = *mut raw::git_index;
619    unsafe fn from_raw(raw: *mut raw::git_index) -> Index {
620        Index { raw }
621    }
622    fn raw(&self) -> *mut raw::git_index {
623        self.raw
624    }
625}
626
627impl<'index> Binding for IndexConflicts<'index> {
628    type Raw = *mut raw::git_index_conflict_iterator;
629
630    unsafe fn from_raw(raw: *mut raw::git_index_conflict_iterator) -> IndexConflicts<'index> {
631        IndexConflicts {
632            conflict_iter: raw,
633            _marker: marker::PhantomData,
634        }
635    }
636    fn raw(&self) -> *mut raw::git_index_conflict_iterator {
637        self.conflict_iter
638    }
639}
640
641extern "C" fn index_matched_path_cb(
642    path: *const c_char,
643    matched_pathspec: *const c_char,
644    payload: *mut c_void,
645) -> c_int {
646    unsafe {
647        let path = CStr::from_ptr(path).to_bytes();
648        let matched_pathspec = CStr::from_ptr(matched_pathspec).to_bytes();
649
650        panic::wrap(|| {
651            let payload = payload as *mut &mut IndexMatchedPath<'_>;
652            (*payload)(util::bytes2path(path), matched_pathspec) as c_int
653        })
654        .unwrap_or(-1)
655    }
656}
657
658impl Drop for Index {
659    fn drop(&mut self) {
660        unsafe { raw::git_index_free(self.raw) }
661    }
662}
663
664impl<'index> Drop for IndexConflicts<'index> {
665    fn drop(&mut self) {
666        unsafe { raw::git_index_conflict_iterator_free(self.conflict_iter) }
667    }
668}
669
670impl<'index> Iterator for IndexEntries<'index> {
671    type Item = IndexEntry;
672    fn next(&mut self) -> Option<IndexEntry> {
673        self.range.next().map(|i| self.index.get(i).unwrap())
674    }
675}
676
677impl<'index> Iterator for IndexConflicts<'index> {
678    type Item = Result<IndexConflict, Error>;
679    fn next(&mut self) -> Option<Result<IndexConflict, Error>> {
680        let mut ancestor = ptr::null();
681        let mut our = ptr::null();
682        let mut their = ptr::null();
683        unsafe {
684            try_call_iter!(raw::git_index_conflict_next(
685                &mut ancestor,
686                &mut our,
687                &mut their,
688                self.conflict_iter
689            ));
690            Some(Ok(IndexConflict {
691                ancestor: match ancestor.is_null() {
692                    false => Some(IndexEntry::from_raw(*ancestor)),
693                    true => None,
694                },
695                our: match our.is_null() {
696                    false => Some(IndexEntry::from_raw(*our)),
697                    true => None,
698                },
699                their: match their.is_null() {
700                    false => Some(IndexEntry::from_raw(*their)),
701                    true => None,
702                },
703            }))
704        }
705    }
706}
707
708impl Binding for IndexEntry {
709    type Raw = raw::git_index_entry;
710
711    unsafe fn from_raw(raw: raw::git_index_entry) -> IndexEntry {
712        let raw::git_index_entry {
713            ctime,
714            mtime,
715            dev,
716            ino,
717            mode,
718            uid,
719            gid,
720            file_size,
721            id,
722            flags,
723            flags_extended,
724            path,
725        } = raw;
726
727        // libgit2 encodes the length of the path in the lower bits of `flags`,
728        // but if the length exceeds the number of bits then the path is
729        // nul-terminated.
730        let mut pathlen = (flags & raw::GIT_INDEX_ENTRY_NAMEMASK) as usize;
731        if pathlen == raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
732            pathlen = CStr::from_ptr(path).to_bytes().len();
733        }
734
735        let path = slice::from_raw_parts(path as *const u8, pathlen);
736
737        IndexEntry {
738            dev,
739            ino,
740            mode,
741            uid,
742            gid,
743            file_size,
744            id: Binding::from_raw(&id as *const _),
745            flags,
746            flags_extended,
747            path: path.to_vec(),
748            mtime: Binding::from_raw(mtime),
749            ctime: Binding::from_raw(ctime),
750        }
751    }
752
753    fn raw(&self) -> raw::git_index_entry {
754        // not implemented, may require a CString in storage
755        panic!()
756    }
757}
758
759#[cfg(test)]
760mod tests {
761    use std::fs::{self, File};
762    use std::path::Path;
763    use tempfile::TempDir;
764
765    use crate::{ErrorCode, Index, IndexEntry, IndexTime, Oid, Repository, ResetType};
766
767    #[test]
768    fn smoke() {
769        let mut index = Index::new().unwrap();
770        assert!(index.add_path(&Path::new(".")).is_err());
771        index.clear().unwrap();
772        assert_eq!(index.len(), 0);
773        assert!(index.get(0).is_none());
774        assert!(index.path().is_none());
775        assert!(index.read(true).is_err());
776    }
777
778    #[test]
779    fn smoke_from_repo() {
780        let (_td, repo) = crate::test::repo_init();
781        let mut index = repo.index().unwrap();
782        assert_eq!(
783            index.path().map(|s| s.to_path_buf()),
784            Some(repo.path().join("index"))
785        );
786        Index::open(&repo.path().join("index")).unwrap();
787
788        index.clear().unwrap();
789        index.read(true).unwrap();
790        index.write().unwrap();
791        index.write_tree().unwrap();
792        index.write_tree_to(&repo).unwrap();
793    }
794
795    #[test]
796    fn add_all() {
797        let (_td, repo) = crate::test::repo_init();
798        let mut index = repo.index().unwrap();
799
800        let root = repo.path().parent().unwrap();
801        fs::create_dir(&root.join("foo")).unwrap();
802        File::create(&root.join("foo/bar")).unwrap();
803        let mut called = false;
804        index
805            .add_all(
806                ["foo"].iter(),
807                crate::IndexAddOption::DEFAULT,
808                Some(&mut |a: &Path, b: &[u8]| {
809                    assert!(!called);
810                    called = true;
811                    assert_eq!(b, b"foo");
812                    assert_eq!(a, Path::new("foo/bar"));
813                    0
814                }),
815            )
816            .unwrap();
817        assert!(called);
818
819        called = false;
820        index
821            .remove_all(
822                ["."].iter(),
823                Some(&mut |a: &Path, b: &[u8]| {
824                    assert!(!called);
825                    called = true;
826                    assert_eq!(b, b".");
827                    assert_eq!(a, Path::new("foo/bar"));
828                    0
829                }),
830            )
831            .unwrap();
832        assert!(called);
833    }
834
835    #[test]
836    fn smoke_add() {
837        let (_td, repo) = crate::test::repo_init();
838        let mut index = repo.index().unwrap();
839
840        let root = repo.path().parent().unwrap();
841        fs::create_dir(&root.join("foo")).unwrap();
842        File::create(&root.join("foo/bar")).unwrap();
843        index.add_path(Path::new("foo/bar")).unwrap();
844        index.write().unwrap();
845        assert_eq!(index.iter().count(), 1);
846
847        // Make sure we can use this repo somewhere else now.
848        let id = index.write_tree().unwrap();
849        let tree = repo.find_tree(id).unwrap();
850        let sig = repo.signature().unwrap();
851        let id = repo.refname_to_id("HEAD").unwrap();
852        let parent = repo.find_commit(id).unwrap();
853        let commit = repo
854            .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
855            .unwrap();
856        let obj = repo.find_object(commit, None).unwrap();
857        repo.reset(&obj, ResetType::Hard, None).unwrap();
858
859        let td2 = TempDir::new().unwrap();
860        let url = crate::test::path2url(&root);
861        let repo = Repository::clone(&url, td2.path()).unwrap();
862        let obj = repo.find_object(commit, None).unwrap();
863        repo.reset(&obj, ResetType::Hard, None).unwrap();
864    }
865
866    #[test]
867    fn add_then_read() {
868        let mut index = Index::new().unwrap();
869        let mut e = entry();
870        e.path = b"foobar".to_vec();
871        index.add(&e).unwrap();
872        let e = index.get(0).unwrap();
873        assert_eq!(e.path.len(), 6);
874    }
875
876    #[test]
877    fn add_then_find() {
878        let mut index = Index::new().unwrap();
879        let mut e = entry();
880        e.path = b"foo/bar".to_vec();
881        index.add(&e).unwrap();
882        let mut e = entry();
883        e.path = b"foo2/bar".to_vec();
884        index.add(&e).unwrap();
885        assert_eq!(index.get(0).unwrap().path, b"foo/bar");
886        assert_eq!(
887            index.get_path(Path::new("foo/bar"), 0).unwrap().path,
888            b"foo/bar"
889        );
890        assert_eq!(index.find_prefix(Path::new("foo2/")), Ok(1));
891        assert_eq!(
892            index.find_prefix(Path::new("empty/")).unwrap_err().code(),
893            ErrorCode::NotFound
894        );
895    }
896
897    #[test]
898    fn add_frombuffer_then_read() {
899        let (_td, repo) = crate::test::repo_init();
900        let mut index = repo.index().unwrap();
901
902        let mut e = entry();
903        e.path = b"foobar".to_vec();
904        let content = b"the contents";
905        index.add_frombuffer(&e, content).unwrap();
906        let e = index.get(0).unwrap();
907        assert_eq!(e.path.len(), 6);
908
909        let b = repo.find_blob(e.id).unwrap();
910        assert_eq!(b.content(), content);
911    }
912
913    fn entry() -> IndexEntry {
914        IndexEntry {
915            ctime: IndexTime::new(0, 0),
916            mtime: IndexTime::new(0, 0),
917            dev: 0,
918            ino: 0,
919            mode: 0o100644,
920            uid: 0,
921            gid: 0,
922            file_size: 0,
923            id: Oid::from_bytes(&[0; 20]).unwrap(),
924            flags: 0,
925            flags_extended: 0,
926            path: Vec::new(),
927        }
928    }
929}