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