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 #[derive(Debug)]
58 pub 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
88 impl 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.
new() -> Result<Index, Error>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`.
open(index_path: &Path) -> Result<Index, Error>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.
version(&self) -> u32126 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.
set_version(&mut self, version: u32) -> Result<(), Error>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.
add(&mut self, entry: &IndexEntry) -> Result<(), Error>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.
add_frombuffer(&mut self, entry: &IndexEntry, data: &[u8]) -> Result<(), Error>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.
add_path(&mut self, path: &Path) -> Result<(), Error>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 /// ```
add_all<T, I>( &mut self, pathspecs: I, flag: IndexAddOption, mut cb: Option<&mut IndexMatchedPath<'_>>, ) -> Result<(), Error> where T: IntoCString, I: IntoIterator<Item = T>,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_*`.
clear(&mut self) -> Result<(), Error>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
len(&self) -> usize353 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
is_empty(&self) -> bool358 pub fn is_empty(&self) -> bool {
359 self.len() == 0
360 }
361
362 /// Get one of the entries in the index by its position.
get(&self, n: usize) -> Option<IndexEntry>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.
iter(&self) -> IndexEntries<'_>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
conflicts(&self) -> Result<IndexConflicts<'_>, Error>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.
get_path(&self, path: &Path, stage: i32) -> Option<IndexEntry>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.
has_conflicts(&self) -> bool411 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.
path(&self) -> Option<&Path>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.
read(&mut self, force: bool) -> Result<(), Error>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.
read_tree(&mut self, tree: &Tree<'_>) -> Result<(), Error>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
remove(&mut self, path: &Path, stage: i32) -> Result<(), Error>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.
remove_path(&mut self, path: &Path) -> Result<(), Error>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.
remove_dir(&mut self, path: &Path, stage: i32) -> Result<(), Error>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.
remove_all<T, I>( &mut self, pathspecs: I, mut cb: Option<&mut IndexMatchedPath<'_>>, ) -> Result<(), Error> where T: IntoCString, I: IntoIterator<Item = T>,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.
update_all<T, I>( &mut self, pathspecs: I, mut cb: Option<&mut IndexMatchedPath<'_>>, ) -> Result<(), Error> where T: IntoCString, I: IntoIterator<Item = T>,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.
write(&mut self) -> Result<(), Error>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.
write_tree(&mut self) -> Result<Oid, Error>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.
write_tree_to(&mut self, repo: &Repository) -> Result<Oid, Error>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
601 impl Binding for Index {
602 type Raw = *mut raw::git_index;
from_raw(raw: *mut raw::git_index) -> Index603 unsafe fn from_raw(raw: *mut raw::git_index) -> Index {
604 Index { raw }
605 }
raw(&self) -> *mut raw::git_index606 fn raw(&self) -> *mut raw::git_index {
607 self.raw
608 }
609 }
610
611 impl<'index> Binding for IndexConflicts<'index> {
612 type Raw = *mut raw::git_index_conflict_iterator;
613
from_raw(raw: *mut raw::git_index_conflict_iterator) -> IndexConflicts<'index>614 unsafe fn from_raw(raw: *mut raw::git_index_conflict_iterator) -> IndexConflicts<'index> {
615 IndexConflicts {
616 conflict_iter: raw,
617 _marker: marker::PhantomData,
618 }
619 }
raw(&self) -> *mut raw::git_index_conflict_iterator620 fn raw(&self) -> *mut raw::git_index_conflict_iterator {
621 self.conflict_iter
622 }
623 }
624
index_matched_path_cb( path: *const c_char, matched_pathspec: *const c_char, payload: *mut c_void, ) -> c_int625 extern "C" fn index_matched_path_cb(
626 path: *const c_char,
627 matched_pathspec: *const c_char,
628 payload: *mut c_void,
629 ) -> c_int {
630 unsafe {
631 let path = CStr::from_ptr(path).to_bytes();
632 let matched_pathspec = CStr::from_ptr(matched_pathspec).to_bytes();
633
634 panic::wrap(|| {
635 let payload = payload as *mut &mut IndexMatchedPath<'_>;
636 (*payload)(util::bytes2path(path), matched_pathspec) as c_int
637 })
638 .unwrap_or(-1)
639 }
640 }
641
642 impl Drop for Index {
drop(&mut self)643 fn drop(&mut self) {
644 unsafe { raw::git_index_free(self.raw) }
645 }
646 }
647
648 impl<'index> Drop for IndexConflicts<'index> {
drop(&mut self)649 fn drop(&mut self) {
650 unsafe { raw::git_index_conflict_iterator_free(self.conflict_iter) }
651 }
652 }
653
654 impl<'index> Iterator for IndexEntries<'index> {
655 type Item = IndexEntry;
next(&mut self) -> Option<IndexEntry>656 fn next(&mut self) -> Option<IndexEntry> {
657 self.range.next().map(|i| self.index.get(i).unwrap())
658 }
659 }
660
661 impl<'index> Iterator for IndexConflicts<'index> {
662 type Item = Result<IndexConflict, Error>;
next(&mut self) -> Option<Result<IndexConflict, Error>>663 fn next(&mut self) -> Option<Result<IndexConflict, Error>> {
664 let mut ancestor = ptr::null();
665 let mut our = ptr::null();
666 let mut their = ptr::null();
667 unsafe {
668 try_call_iter!(raw::git_index_conflict_next(
669 &mut ancestor,
670 &mut our,
671 &mut their,
672 self.conflict_iter
673 ));
674 Some(Ok(IndexConflict {
675 ancestor: match ancestor.is_null() {
676 false => Some(IndexEntry::from_raw(*ancestor)),
677 true => None,
678 },
679 our: match our.is_null() {
680 false => Some(IndexEntry::from_raw(*our)),
681 true => None,
682 },
683 their: match their.is_null() {
684 false => Some(IndexEntry::from_raw(*their)),
685 true => None,
686 },
687 }))
688 }
689 }
690 }
691
692 impl Binding for IndexEntry {
693 type Raw = raw::git_index_entry;
694
from_raw(raw: raw::git_index_entry) -> IndexEntry695 unsafe fn from_raw(raw: raw::git_index_entry) -> IndexEntry {
696 let raw::git_index_entry {
697 ctime,
698 mtime,
699 dev,
700 ino,
701 mode,
702 uid,
703 gid,
704 file_size,
705 id,
706 flags,
707 flags_extended,
708 path,
709 } = raw;
710
711 // libgit2 encodes the length of the path in the lower bits of `flags`,
712 // but if the length exceeds the number of bits then the path is
713 // nul-terminated.
714 let mut pathlen = (flags & raw::GIT_INDEX_ENTRY_NAMEMASK) as usize;
715 if pathlen == raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
716 pathlen = CStr::from_ptr(path).to_bytes().len();
717 }
718
719 let path = slice::from_raw_parts(path as *const u8, pathlen);
720
721 IndexEntry {
722 dev,
723 ino,
724 mode,
725 uid,
726 gid,
727 file_size,
728 id: Binding::from_raw(&id as *const _),
729 flags,
730 flags_extended,
731 path: path.to_vec(),
732 mtime: Binding::from_raw(mtime),
733 ctime: Binding::from_raw(ctime),
734 }
735 }
736
raw(&self) -> raw::git_index_entry737 fn raw(&self) -> raw::git_index_entry {
738 // not implemented, may require a CString in storage
739 panic!()
740 }
741 }
742
743 #[cfg(test)]
744 mod tests {
745 use std::fs::{self, File};
746 use std::path::Path;
747 use tempfile::TempDir;
748
749 use crate::{Index, IndexEntry, IndexTime, Oid, Repository, ResetType};
750
751 #[test]
smoke()752 fn smoke() {
753 let mut index = Index::new().unwrap();
754 assert!(index.add_path(&Path::new(".")).is_err());
755 index.clear().unwrap();
756 assert_eq!(index.len(), 0);
757 assert!(index.get(0).is_none());
758 assert!(index.path().is_none());
759 assert!(index.read(true).is_err());
760 }
761
762 #[test]
smoke_from_repo()763 fn smoke_from_repo() {
764 let (_td, repo) = crate::test::repo_init();
765 let mut index = repo.index().unwrap();
766 assert_eq!(
767 index.path().map(|s| s.to_path_buf()),
768 Some(repo.path().join("index"))
769 );
770 Index::open(&repo.path().join("index")).unwrap();
771
772 index.clear().unwrap();
773 index.read(true).unwrap();
774 index.write().unwrap();
775 index.write_tree().unwrap();
776 index.write_tree_to(&repo).unwrap();
777 }
778
779 #[test]
add_all()780 fn add_all() {
781 let (_td, repo) = crate::test::repo_init();
782 let mut index = repo.index().unwrap();
783
784 let root = repo.path().parent().unwrap();
785 fs::create_dir(&root.join("foo")).unwrap();
786 File::create(&root.join("foo/bar")).unwrap();
787 let mut called = false;
788 index
789 .add_all(
790 ["foo"].iter(),
791 crate::IndexAddOption::DEFAULT,
792 Some(&mut |a: &Path, b: &[u8]| {
793 assert!(!called);
794 called = true;
795 assert_eq!(b, b"foo");
796 assert_eq!(a, Path::new("foo/bar"));
797 0
798 }),
799 )
800 .unwrap();
801 assert!(called);
802
803 called = false;
804 index
805 .remove_all(
806 ["."].iter(),
807 Some(&mut |a: &Path, b: &[u8]| {
808 assert!(!called);
809 called = true;
810 assert_eq!(b, b".");
811 assert_eq!(a, Path::new("foo/bar"));
812 0
813 }),
814 )
815 .unwrap();
816 assert!(called);
817 }
818
819 #[test]
smoke_add()820 fn smoke_add() {
821 let (_td, repo) = crate::test::repo_init();
822 let mut index = repo.index().unwrap();
823
824 let root = repo.path().parent().unwrap();
825 fs::create_dir(&root.join("foo")).unwrap();
826 File::create(&root.join("foo/bar")).unwrap();
827 index.add_path(Path::new("foo/bar")).unwrap();
828 index.write().unwrap();
829 assert_eq!(index.iter().count(), 1);
830
831 // Make sure we can use this repo somewhere else now.
832 let id = index.write_tree().unwrap();
833 let tree = repo.find_tree(id).unwrap();
834 let sig = repo.signature().unwrap();
835 let id = repo.refname_to_id("HEAD").unwrap();
836 let parent = repo.find_commit(id).unwrap();
837 let commit = repo
838 .commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
839 .unwrap();
840 let obj = repo.find_object(commit, None).unwrap();
841 repo.reset(&obj, ResetType::Hard, None).unwrap();
842
843 let td2 = TempDir::new().unwrap();
844 let url = crate::test::path2url(&root);
845 let repo = Repository::clone(&url, td2.path()).unwrap();
846 let obj = repo.find_object(commit, None).unwrap();
847 repo.reset(&obj, ResetType::Hard, None).unwrap();
848 }
849
850 #[test]
add_then_read()851 fn add_then_read() {
852 let mut index = Index::new().unwrap();
853 assert!(index.add(&entry()).is_err());
854
855 let mut index = Index::new().unwrap();
856 let mut e = entry();
857 e.path = b"foobar".to_vec();
858 index.add(&e).unwrap();
859 let e = index.get(0).unwrap();
860 assert_eq!(e.path.len(), 6);
861 }
862
863 #[test]
add_frombuffer_then_read()864 fn add_frombuffer_then_read() {
865 let (_td, repo) = crate::test::repo_init();
866 let mut index = repo.index().unwrap();
867
868 let mut e = entry();
869 e.path = b"foobar".to_vec();
870 let content = b"the contents";
871 index.add_frombuffer(&e, content).unwrap();
872 let e = index.get(0).unwrap();
873 assert_eq!(e.path.len(), 6);
874
875 let b = repo.find_blob(e.id).unwrap();
876 assert_eq!(b.content(), content);
877 }
878
entry() -> IndexEntry879 fn entry() -> IndexEntry {
880 IndexEntry {
881 ctime: IndexTime::new(0, 0),
882 mtime: IndexTime::new(0, 0),
883 dev: 0,
884 ino: 0,
885 mode: 0o100644,
886 uid: 0,
887 gid: 0,
888 file_size: 0,
889 id: Oid::from_bytes(&[0; 20]).unwrap(),
890 flags: 0,
891 flags_extended: 0,
892 path: Vec::new(),
893 }
894 }
895 }
896