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 index entries that represent a conflict of a single file.
416    pub fn conflict_get(&self, path: &Path) -> Result<IndexConflict, Error> {
417        let path = path_to_repo_path(path)?;
418        let mut ancestor = ptr::null();
419        let mut our = ptr::null();
420        let mut their = ptr::null();
421
422        unsafe {
423            try_call!(raw::git_index_conflict_get(
424                &mut ancestor,
425                &mut our,
426                &mut their,
427                self.raw,
428                path
429            ));
430
431            Ok(IndexConflict {
432                ancestor: match ancestor.is_null() {
433                    false => Some(IndexEntry::from_raw(*ancestor)),
434                    true => None,
435                },
436                our: match our.is_null() {
437                    false => Some(IndexEntry::from_raw(*our)),
438                    true => None,
439                },
440                their: match their.is_null() {
441                    false => Some(IndexEntry::from_raw(*their)),
442                    true => None,
443                },
444            })
445        }
446    }
447
448    /// Get the full path to the index file on disk.
449    ///
450    /// Returns `None` if this is an in-memory index.
451    pub fn path(&self) -> Option<&Path> {
452        unsafe { crate::opt_bytes(self, raw::git_index_path(&*self.raw)).map(util::bytes2path) }
453    }
454
455    /// Update the contents of an existing index object in memory by reading
456    /// from the hard disk.
457    ///
458    /// If force is true, this performs a "hard" read that discards in-memory
459    /// changes and always reloads the on-disk index data. If there is no
460    /// on-disk version, the index will be cleared.
461    ///
462    /// If force is false, this does a "soft" read that reloads the index data
463    /// from disk only if it has changed since the last time it was loaded.
464    /// Purely in-memory index data will be untouched. Be aware: if there are
465    /// changes on disk, unwritten in-memory changes are discarded.
466    pub fn read(&mut self, force: bool) -> Result<(), Error> {
467        unsafe {
468            try_call!(raw::git_index_read(self.raw, force));
469        }
470        Ok(())
471    }
472
473    /// Read a tree into the index file with stats
474    ///
475    /// The current index contents will be replaced by the specified tree.
476    pub fn read_tree(&mut self, tree: &Tree<'_>) -> Result<(), Error> {
477        unsafe {
478            try_call!(raw::git_index_read_tree(self.raw, &*tree.raw()));
479        }
480        Ok(())
481    }
482
483    /// Remove an entry from the index
484    pub fn remove(&mut self, path: &Path, stage: i32) -> Result<(), Error> {
485        let path = path_to_repo_path(path)?;
486        unsafe {
487            try_call!(raw::git_index_remove(self.raw, path, stage as c_int));
488        }
489        Ok(())
490    }
491
492    /// Remove an index entry corresponding to a file on disk.
493    ///
494    /// The file path must be relative to the repository's working folder. It
495    /// may exist.
496    ///
497    /// If this file currently is the result of a merge conflict, this file will
498    /// no longer be marked as conflicting. The data about the conflict will be
499    /// moved to the "resolve undo" (REUC) section.
500    pub fn remove_path(&mut self, path: &Path) -> Result<(), Error> {
501        let path = path_to_repo_path(path)?;
502        unsafe {
503            try_call!(raw::git_index_remove_bypath(self.raw, path));
504        }
505        Ok(())
506    }
507
508    /// Remove all entries from the index under a given directory.
509    pub fn remove_dir(&mut self, path: &Path, stage: i32) -> Result<(), Error> {
510        let path = path_to_repo_path(path)?;
511        unsafe {
512            try_call!(raw::git_index_remove_directory(
513                self.raw,
514                path,
515                stage as c_int
516            ));
517        }
518        Ok(())
519    }
520
521    /// Removes the index entries that represent a conflict of a single file.
522    pub fn conflict_remove(&mut self, path: &Path) -> Result<(), Error> {
523        let path = path_to_repo_path(path)?;
524        unsafe {
525            try_call!(raw::git_index_conflict_remove(self.raw, path));
526        }
527        Ok(())
528    }
529
530    /// Remove all matching index entries.
531    ///
532    /// If you provide a callback function, it will be invoked on each matching
533    /// item in the index immediately before it is removed. Return 0 to remove
534    /// the item, > 0 to skip the item, and < 0 to abort the scan.
535    pub fn remove_all<T, I>(
536        &mut self,
537        pathspecs: I,
538        mut cb: Option<&mut IndexMatchedPath<'_>>,
539    ) -> Result<(), Error>
540    where
541        T: IntoCString,
542        I: IntoIterator<Item = T>,
543    {
544        let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
545        let ptr = cb.as_mut();
546        let callback = ptr
547            .as_ref()
548            .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
549        unsafe {
550            try_call!(raw::git_index_remove_all(
551                self.raw,
552                &raw_strarray,
553                callback,
554                ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
555            ));
556        }
557        Ok(())
558    }
559
560    /// Update all index entries to match the working directory
561    ///
562    /// This method will fail in bare index instances.
563    ///
564    /// This scans the existing index entries and synchronizes them with the
565    /// working directory, deleting them if the corresponding working directory
566    /// file no longer exists otherwise updating the information (including
567    /// adding the latest version of file to the ODB if needed).
568    ///
569    /// If you provide a callback function, it will be invoked on each matching
570    /// item in the index immediately before it is updated (either refreshed or
571    /// removed depending on working directory state). Return 0 to proceed with
572    /// updating the item, > 0 to skip the item, and < 0 to abort the scan.
573    pub fn update_all<T, I>(
574        &mut self,
575        pathspecs: I,
576        mut cb: Option<&mut IndexMatchedPath<'_>>,
577    ) -> Result<(), Error>
578    where
579        T: IntoCString,
580        I: IntoIterator<Item = T>,
581    {
582        let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
583        let ptr = cb.as_mut();
584        let callback = ptr
585            .as_ref()
586            .map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
587        unsafe {
588            try_call!(raw::git_index_update_all(
589                self.raw,
590                &raw_strarray,
591                callback,
592                ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
593            ));
594        }
595        Ok(())
596    }
597
598    /// Write an existing index object from memory back to disk using an atomic
599    /// file lock.
600    pub fn write(&mut self) -> Result<(), Error> {
601        unsafe {
602            try_call!(raw::git_index_write(self.raw));
603        }
604        Ok(())
605    }
606
607    /// Write the index as a tree.
608    ///
609    /// This method will scan the index and write a representation of its
610    /// current state back to disk; it recursively creates tree objects for each
611    /// of the subtrees stored in the index, but only returns the OID of the
612    /// root tree. This is the OID that can be used e.g. to create a commit.
613    ///
614    /// The index instance cannot be bare, and needs to be associated to an
615    /// existing repository.
616    ///
617    /// The index must not contain any file in conflict.
618    pub fn write_tree(&mut self) -> Result<Oid, Error> {
619        let mut raw = raw::git_oid {
620            id: [0; raw::GIT_OID_RAWSZ],
621        };
622        unsafe {
623            try_call!(raw::git_index_write_tree(&mut raw, self.raw));
624            Ok(Binding::from_raw(&raw as *const _))
625        }
626    }
627
628    /// Write the index as a tree to the given repository
629    ///
630    /// This is the same as `write_tree` except that the destination repository
631    /// can be chosen.
632    pub fn write_tree_to(&mut self, repo: &Repository) -> Result<Oid, Error> {
633        let mut raw = raw::git_oid {
634            id: [0; raw::GIT_OID_RAWSZ],
635        };
636        unsafe {
637            try_call!(raw::git_index_write_tree_to(&mut raw, self.raw, repo.raw()));
638            Ok(Binding::from_raw(&raw as *const _))
639        }
640    }
641
642    /// Find the first position of any entries matching a prefix.
643    ///
644    /// To find the first position of a path inside a given folder, suffix the prefix with a '/'.
645    pub fn find_prefix<T: IntoCString>(&self, prefix: T) -> Result<usize, Error> {
646        let mut at_pos: size_t = 0;
647        let entry_path = prefix.into_c_string()?;
648        unsafe {
649            try_call!(raw::git_index_find_prefix(
650                &mut at_pos,
651                self.raw,
652                entry_path
653            ));
654            Ok(at_pos)
655        }
656    }
657}
658
659impl IndexEntry {
660    /// Create a raw index entry.
661    ///
662    /// The returned `raw::git_index_entry` contains a pointer to a `CString` path, which is also
663    /// returned because it's lifetime must exceed the lifetime of the `raw::git_index_entry`.
664    pub(crate) unsafe fn to_raw(&self) -> Result<(raw::git_index_entry, CString), Error> {
665        let path = CString::new(&self.path[..])?;
666
667        // libgit2 encodes the length of the path in the lower bits of the
668        // `flags` entry, so mask those out and recalculate here to ensure we
669        // don't corrupt anything.
670        let mut flags = self.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;
671
672        if self.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
673            flags |= self.path.len() as u16;
674        } else {
675            flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
676        }
677
678        unsafe {
679            let raw = raw::git_index_entry {
680                dev: self.dev,
681                ino: self.ino,
682                mode: self.mode,
683                uid: self.uid,
684                gid: self.gid,
685                file_size: self.file_size,
686                id: *self.id.raw(),
687                flags,
688                flags_extended: self.flags_extended,
689                path: path.as_ptr(),
690                mtime: raw::git_index_time {
691                    seconds: self.mtime.seconds(),
692                    nanoseconds: self.mtime.nanoseconds(),
693                },
694                ctime: raw::git_index_time {
695                    seconds: self.ctime.seconds(),
696                    nanoseconds: self.ctime.nanoseconds(),
697                },
698            };
699
700            Ok((raw, path))
701        }
702    }
703}
704
705impl Binding for Index {
706    type Raw = *mut raw::git_index;
707    unsafe fn from_raw(raw: *mut raw::git_index) -> Index {
708        Index { raw }
709    }
710    fn raw(&self) -> *mut raw::git_index {
711        self.raw
712    }
713}
714
715impl<'index> Binding for IndexConflicts<'index> {
716    type Raw = *mut raw::git_index_conflict_iterator;
717
718    unsafe fn from_raw(raw: *mut raw::git_index_conflict_iterator) -> IndexConflicts<'index> {
719        IndexConflicts {
720            conflict_iter: raw,
721            _marker: marker::PhantomData,
722        }
723    }
724    fn raw(&self) -> *mut raw::git_index_conflict_iterator {
725        self.conflict_iter
726    }
727}
728
729extern "C" fn index_matched_path_cb(
730    path: *const c_char,
731    matched_pathspec: *const c_char,
732    payload: *mut c_void,
733) -> c_int {
734    unsafe {
735        let path = CStr::from_ptr(path).to_bytes();
736        let matched_pathspec = CStr::from_ptr(matched_pathspec).to_bytes();
737
738        panic::wrap(|| {
739            let payload = payload as *mut &mut IndexMatchedPath<'_>;
740            (*payload)(util::bytes2path(path), matched_pathspec) as c_int
741        })
742        .unwrap_or(-1)
743    }
744}
745
746impl Drop for Index {
747    fn drop(&mut self) {
748        unsafe { raw::git_index_free(self.raw) }
749    }
750}
751
752impl<'index> Drop for IndexConflicts<'index> {
753    fn drop(&mut self) {
754        unsafe { raw::git_index_conflict_iterator_free(self.conflict_iter) }
755    }
756}
757
758impl<'index> Iterator for IndexEntries<'index> {
759    type Item = IndexEntry;
760    fn next(&mut self) -> Option<IndexEntry> {
761        self.range.next().map(|i| self.index.get(i).unwrap())
762    }
763}
764
765impl<'index> Iterator for IndexConflicts<'index> {
766    type Item = Result<IndexConflict, Error>;
767    fn next(&mut self) -> Option<Result<IndexConflict, Error>> {
768        let mut ancestor = ptr::null();
769        let mut our = ptr::null();
770        let mut their = ptr::null();
771        unsafe {
772            try_call_iter!(raw::git_index_conflict_next(
773                &mut ancestor,
774                &mut our,
775                &mut their,
776                self.conflict_iter
777            ));
778            Some(Ok(IndexConflict {
779                ancestor: match ancestor.is_null() {
780                    false => Some(IndexEntry::from_raw(*ancestor)),
781                    true => None,
782                },
783                our: match our.is_null() {
784                    false => Some(IndexEntry::from_raw(*our)),
785                    true => None,
786                },
787                their: match their.is_null() {
788                    false => Some(IndexEntry::from_raw(*their)),
789                    true => None,
790                },
791            }))
792        }
793    }
794}
795
796impl Binding for IndexEntry {
797    type Raw = raw::git_index_entry;
798
799    unsafe fn from_raw(raw: raw::git_index_entry) -> IndexEntry {
800        let raw::git_index_entry {
801            ctime,
802            mtime,
803            dev,
804            ino,
805            mode,
806            uid,
807            gid,
808            file_size,
809            id,
810            flags,
811            flags_extended,
812            path,
813        } = raw;
814
815        // libgit2 encodes the length of the path in the lower bits of `flags`,
816        // but if the length exceeds the number of bits then the path is
817        // nul-terminated.
818        let mut pathlen = (flags & raw::GIT_INDEX_ENTRY_NAMEMASK) as usize;
819        if pathlen == raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
820            pathlen = CStr::from_ptr(path).to_bytes().len();
821        }
822
823        let path = slice::from_raw_parts(path as *const u8, pathlen);
824
825        IndexEntry {
826            dev,
827            ino,
828            mode,
829            uid,
830            gid,
831            file_size,
832            id: Binding::from_raw(&id as *const _),
833            flags,
834            flags_extended,
835            path: path.to_vec(),
836            mtime: Binding::from_raw(mtime),
837            ctime: Binding::from_raw(ctime),
838        }
839    }
840
841    fn raw(&self) -> raw::git_index_entry {
842        // not implemented, may require a CString in storage
843        panic!()
844    }
845}
846
847#[cfg(test)]
848mod tests {
849    use std::fs::{self, File};
850    use std::path::Path;
851    use tempfile::TempDir;
852
853    use crate::{ErrorCode, Index, IndexEntry, IndexTime, Oid, Repository, ResetType};
854
855    #[test]
856    fn smoke() {
857        let mut index = Index::new().unwrap();
858        assert!(index.add_path(&Path::new(".")).is_err());
859        index.clear().unwrap();
860        assert_eq!(index.len(), 0);
861        assert!(index.get(0).is_none());
862        assert!(index.path().is_none());
863        assert!(index.read(true).is_err());
864    }
865
866    #[test]
867    fn smoke_from_repo() {
868        let (_td, repo) = crate::test::repo_init();
869        let mut index = repo.index().unwrap();
870        assert_eq!(
871            index.path().map(|s| s.to_path_buf()),
872            Some(repo.path().join("index"))
873        );
874        Index::open(&repo.path().join("index")).unwrap();
875
876        index.clear().unwrap();
877        index.read(true).unwrap();
878        index.write().unwrap();
879        index.write_tree().unwrap();
880        index.write_tree_to(&repo).unwrap();
881    }
882
883    #[test]
884    fn add_all() {
885        let (_td, repo) = crate::test::repo_init();
886        let mut index = repo.index().unwrap();
887
888        let root = repo.path().parent().unwrap();
889        fs::create_dir(&root.join("foo")).unwrap();
890        File::create(&root.join("foo/bar")).unwrap();
891        let mut called = false;
892        index
893            .add_all(
894                ["foo"].iter(),
895                crate::IndexAddOption::DEFAULT,
896                Some(&mut |a: &Path, b: &[u8]| {
897                    assert!(!called);
898                    called = true;
899                    assert_eq!(b, b"foo");
900                    assert_eq!(a, Path::new("foo/bar"));
901                    0
902                }),
903            )
904            .unwrap();
905        assert!(called);
906
907        called = false;
908        index
909            .remove_all(
910                ["."].iter(),
911                Some(&mut |a: &Path, b: &[u8]| {
912                    assert!(!called);
913                    called = true;
914                    assert_eq!(b, b".");
915                    assert_eq!(a, Path::new("foo/bar"));
916                    0
917                }),
918            )
919            .unwrap();
920        assert!(called);
921    }
922
923    #[test]
924    fn smoke_add() {
925        let (_td, repo) = crate::test::repo_init();
926        let mut index = repo.index().unwrap();
927
928        let root = repo.path().parent().unwrap();
929        fs::create_dir(&root.join("foo")).unwrap();
930        File::create(&root.join("foo/bar")).unwrap();
931        index.add_path(Path::new("foo/bar")).unwrap();
932        index.write().unwrap();
933        assert_eq!(index.iter().count(), 1);
934
935        // Make sure we can use this repo somewhere else now.
936        let id = index.write_tree().unwrap();
937        let tree = repo.find_tree(id).unwrap();
938        let sig = repo.signature().unwrap();
939        let id = repo.refname_to_id("HEAD").unwrap();
940        let parent = repo.find_commit(id).unwrap();
941        let commit = repo
942            .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
943            .unwrap();
944        let obj = repo.find_object(commit, None).unwrap();
945        repo.reset(&obj, ResetType::Hard, None).unwrap();
946
947        let td2 = TempDir::new().unwrap();
948        let url = crate::test::path2url(&root);
949        let repo = Repository::clone(&url, td2.path()).unwrap();
950        let obj = repo.find_object(commit, None).unwrap();
951        repo.reset(&obj, ResetType::Hard, None).unwrap();
952    }
953
954    #[test]
955    fn add_then_read() {
956        let mut index = Index::new().unwrap();
957        let mut e = entry();
958        e.path = b"foobar".to_vec();
959        index.add(&e).unwrap();
960        let e = index.get(0).unwrap();
961        assert_eq!(e.path.len(), 6);
962    }
963
964    #[test]
965    fn add_then_find() {
966        let mut index = Index::new().unwrap();
967        let mut e = entry();
968        e.path = b"foo/bar".to_vec();
969        index.add(&e).unwrap();
970        let mut e = entry();
971        e.path = b"foo2/bar".to_vec();
972        index.add(&e).unwrap();
973        assert_eq!(index.get(0).unwrap().path, b"foo/bar");
974        assert_eq!(
975            index.get_path(Path::new("foo/bar"), 0).unwrap().path,
976            b"foo/bar"
977        );
978        assert_eq!(index.find_prefix(Path::new("foo2/")), Ok(1));
979        assert_eq!(
980            index.find_prefix(Path::new("empty/")).unwrap_err().code(),
981            ErrorCode::NotFound
982        );
983    }
984
985    #[test]
986    fn add_frombuffer_then_read() {
987        let (_td, repo) = crate::test::repo_init();
988        let mut index = repo.index().unwrap();
989
990        let mut e = entry();
991        e.path = b"foobar".to_vec();
992        let content = b"the contents";
993        index.add_frombuffer(&e, content).unwrap();
994        let e = index.get(0).unwrap();
995        assert_eq!(e.path.len(), 6);
996
997        let b = repo.find_blob(e.id).unwrap();
998        assert_eq!(b.content(), content);
999    }
1000
1001    fn entry() -> IndexEntry {
1002        IndexEntry {
1003            ctime: IndexTime::new(0, 0),
1004            mtime: IndexTime::new(0, 0),
1005            dev: 0,
1006            ino: 0,
1007            mode: 0o100644,
1008            uid: 0,
1009            gid: 0,
1010            file_size: 0,
1011            id: Oid::from_bytes(&[0; 20]).unwrap(),
1012            flags: 0,
1013            flags_extended: 0,
1014            path: Vec::new(),
1015        }
1016    }
1017}