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
14pub struct Index {
18 raw: *mut raw::git_index,
19}
20
21pub struct IndexEntries<'index> {
23 range: Range<usize>,
24 index: &'index Index,
25}
26
27pub struct IndexConflicts<'index> {
29 conflict_iter: *mut raw::git_index_conflict_iterator,
30 _marker: marker::PhantomData<&'index Index>,
31}
32
33pub struct IndexConflict {
35 pub ancestor: Option<IndexEntry>,
37 pub our: Option<IndexEntry>,
40 pub their: Option<IndexEntry>,
43}
44
45pub type IndexMatchedPath<'a> = dyn FnMut(&Path, &[u8]) -> i32 + 'a;
51
52#[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 pub path: Vec<u8>,
86}
87
88impl Index {
89 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 pub fn open(index_path: &Path) -> Result<Index, Error> {
111 crate::init();
112 let mut raw = ptr::null_mut();
113 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 pub fn version(&self) -> u32 {
127 unsafe { raw::git_index_version(self.raw) }
128 }
129
130 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 pub fn add(&mut self, entry: &IndexEntry) -> Result<(), Error> {
148 let path = CString::new(&entry.path[..])?;
149
150 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 pub fn add_frombuffer(&mut self, entry: &IndexEntry, data: &[u8]) -> Result<(), Error> {
205 let path = CString::new(&entry.path[..])?;
206
207 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 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 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 pub fn clear(&mut self) -> Result<(), Error> {
346 unsafe {
347 try_call!(raw::git_index_clear(self.raw));
348 }
349 Ok(())
350 }
351
352 pub fn len(&self) -> usize {
354 unsafe { raw::git_index_entrycount(&*self.raw) as usize }
355 }
356
357 pub fn is_empty(&self) -> bool {
359 self.len() == 0
360 }
361
362 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 pub fn iter(&self) -> IndexEntries<'_> {
376 IndexEntries {
377 range: 0..self.len(),
378 index: self,
379 }
380 }
381
382 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 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 pub fn has_conflicts(&self) -> bool {
412 unsafe { raw::git_index_has_conflicts(self.raw) == 1 }
413 }
414
415 pub fn path(&self) -> Option<&Path> {
419 unsafe { crate::opt_bytes(self, raw::git_index_path(&*self.raw)).map(util::bytes2path) }
420 }
421
422 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 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 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 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 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 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 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 pub fn write(&mut self) -> Result<(), Error> {
559 unsafe {
560 try_call!(raw::git_index_write(self.raw));
561 }
562 Ok(())
563 }
564
565 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 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 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 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 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 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}