use std::ffi::{CStr, CString};
use std::marker;
use std::ops::Range;
use std::path::Path;
use std::ptr;
use std::slice;
use libc::{c_char, c_int, c_uint, c_void, size_t};
use crate::util::{self, path_to_repo_path, Binding};
use crate::IntoCString;
use crate::{panic, raw, Error, IndexAddOption, IndexTime, Oid, Repository, Tree};
pub struct Index {
raw: *mut raw::git_index,
}
pub struct IndexEntries<'index> {
range: Range<usize>,
index: &'index Index,
}
pub struct IndexConflicts<'index> {
conflict_iter: *mut raw::git_index_conflict_iterator,
_marker: marker::PhantomData<&'index Index>,
}
pub struct IndexConflict {
pub ancestor: Option<IndexEntry>,
pub our: Option<IndexEntry>,
pub their: Option<IndexEntry>,
}
pub type IndexMatchedPath<'a> = dyn FnMut(&Path, &[u8]) -> i32 + 'a;
#[allow(missing_docs)]
pub struct IndexEntry {
pub ctime: IndexTime,
pub mtime: IndexTime,
pub dev: u32,
pub ino: u32,
pub mode: u32,
pub uid: u32,
pub gid: u32,
pub file_size: u32,
pub id: Oid,
pub flags: u16,
pub flags_extended: u16,
pub path: Vec<u8>,
}
impl Index {
pub fn new() -> Result<Index, Error> {
crate::init();
let mut raw = ptr::null_mut();
unsafe {
try_call!(raw::git_index_new(&mut raw));
Ok(Binding::from_raw(raw))
}
}
pub fn open(index_path: &Path) -> Result<Index, Error> {
crate::init();
let mut raw = ptr::null_mut();
let index_path = index_path.into_c_string()?;
unsafe {
try_call!(raw::git_index_open(&mut raw, index_path));
Ok(Binding::from_raw(raw))
}
}
pub fn add(&mut self, entry: &IndexEntry) -> Result<(), Error> {
let path = CString::new(&entry.path[..])?;
let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;
if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
flags |= entry.path.len() as u16;
} else {
flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
}
unsafe {
let raw = raw::git_index_entry {
dev: entry.dev,
ino: entry.ino,
mode: entry.mode,
uid: entry.uid,
gid: entry.gid,
file_size: entry.file_size,
id: *entry.id.raw(),
flags: flags,
flags_extended: entry.flags_extended,
path: path.as_ptr(),
mtime: raw::git_index_time {
seconds: entry.mtime.seconds(),
nanoseconds: entry.mtime.nanoseconds(),
},
ctime: raw::git_index_time {
seconds: entry.ctime.seconds(),
nanoseconds: entry.ctime.nanoseconds(),
},
};
try_call!(raw::git_index_add(self.raw, &raw));
Ok(())
}
}
pub fn add_frombuffer(&mut self, entry: &IndexEntry, data: &[u8]) -> Result<(), Error> {
let path = CString::new(&entry.path[..])?;
let mut flags = entry.flags & !raw::GIT_INDEX_ENTRY_NAMEMASK;
if entry.path.len() < raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
flags |= entry.path.len() as u16;
} else {
flags |= raw::GIT_INDEX_ENTRY_NAMEMASK;
}
unsafe {
let raw = raw::git_index_entry {
dev: entry.dev,
ino: entry.ino,
mode: entry.mode,
uid: entry.uid,
gid: entry.gid,
file_size: entry.file_size,
id: *entry.id.raw(),
flags: flags,
flags_extended: entry.flags_extended,
path: path.as_ptr(),
mtime: raw::git_index_time {
seconds: entry.mtime.seconds(),
nanoseconds: entry.mtime.nanoseconds(),
},
ctime: raw::git_index_time {
seconds: entry.ctime.seconds(),
nanoseconds: entry.ctime.nanoseconds(),
},
};
let ptr = data.as_ptr() as *const c_void;
let len = data.len() as size_t;
try_call!(raw::git_index_add_frombuffer(self.raw, &raw, ptr, len));
Ok(())
}
}
pub fn add_path(&mut self, path: &Path) -> Result<(), Error> {
let posix_path = path_to_repo_path(path)?;
unsafe {
try_call!(raw::git_index_add_bypath(self.raw, posix_path));
Ok(())
}
}
pub fn add_all<T, I>(
&mut self,
pathspecs: I,
flag: IndexAddOption,
mut cb: Option<&mut IndexMatchedPath<'_>>,
) -> Result<(), Error>
where
T: IntoCString,
I: IntoIterator<Item = T>,
{
let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
let ptr = cb.as_mut();
let callback = ptr
.as_ref()
.map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
unsafe {
try_call!(raw::git_index_add_all(
self.raw,
&raw_strarray,
flag.bits() as c_uint,
callback,
ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
));
}
Ok(())
}
pub fn clear(&mut self) -> Result<(), Error> {
unsafe {
try_call!(raw::git_index_clear(self.raw));
}
Ok(())
}
pub fn len(&self) -> usize {
unsafe { raw::git_index_entrycount(&*self.raw) as usize }
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn get(&self, n: usize) -> Option<IndexEntry> {
unsafe {
let ptr = raw::git_index_get_byindex(self.raw, n as size_t);
if ptr.is_null() {
None
} else {
Some(Binding::from_raw(*ptr))
}
}
}
pub fn iter(&self) -> IndexEntries<'_> {
IndexEntries {
range: 0..self.len(),
index: self,
}
}
pub fn conflicts(&self) -> Result<IndexConflicts<'_>, Error> {
crate::init();
let mut conflict_iter = ptr::null_mut();
unsafe {
try_call!(raw::git_index_conflict_iterator_new(
&mut conflict_iter,
self.raw
));
Ok(Binding::from_raw(conflict_iter))
}
}
pub fn get_path(&self, path: &Path, stage: i32) -> Option<IndexEntry> {
let path = path_to_repo_path(path).unwrap();
unsafe {
let ptr = call!(raw::git_index_get_bypath(self.raw, path, stage as c_int));
if ptr.is_null() {
None
} else {
Some(Binding::from_raw(*ptr))
}
}
}
pub fn has_conflicts(&self) -> bool {
unsafe { raw::git_index_has_conflicts(self.raw) == 1 }
}
pub fn path(&self) -> Option<&Path> {
unsafe { crate::opt_bytes(self, raw::git_index_path(&*self.raw)).map(util::bytes2path) }
}
pub fn read(&mut self, force: bool) -> Result<(), Error> {
unsafe {
try_call!(raw::git_index_read(self.raw, force));
}
Ok(())
}
pub fn read_tree(&mut self, tree: &Tree<'_>) -> Result<(), Error> {
unsafe {
try_call!(raw::git_index_read_tree(self.raw, &*tree.raw()));
}
Ok(())
}
pub fn remove(&mut self, path: &Path, stage: i32) -> Result<(), Error> {
let path = path_to_repo_path(path)?;
unsafe {
try_call!(raw::git_index_remove(self.raw, path, stage as c_int));
}
Ok(())
}
pub fn remove_path(&mut self, path: &Path) -> Result<(), Error> {
let path = path_to_repo_path(path)?;
unsafe {
try_call!(raw::git_index_remove_bypath(self.raw, path));
}
Ok(())
}
pub fn remove_dir(&mut self, path: &Path, stage: i32) -> Result<(), Error> {
let path = path_to_repo_path(path)?;
unsafe {
try_call!(raw::git_index_remove_directory(
self.raw,
path,
stage as c_int
));
}
Ok(())
}
pub fn remove_all<T, I>(
&mut self,
pathspecs: I,
mut cb: Option<&mut IndexMatchedPath<'_>>,
) -> Result<(), Error>
where
T: IntoCString,
I: IntoIterator<Item = T>,
{
let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
let ptr = cb.as_mut();
let callback = ptr
.as_ref()
.map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
unsafe {
try_call!(raw::git_index_remove_all(
self.raw,
&raw_strarray,
callback,
ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
));
}
Ok(())
}
pub fn update_all<T, I>(
&mut self,
pathspecs: I,
mut cb: Option<&mut IndexMatchedPath<'_>>,
) -> Result<(), Error>
where
T: IntoCString,
I: IntoIterator<Item = T>,
{
let (_a, _b, raw_strarray) = crate::util::iter2cstrs_paths(pathspecs)?;
let ptr = cb.as_mut();
let callback = ptr
.as_ref()
.map(|_| index_matched_path_cb as extern "C" fn(_, _, _) -> _);
unsafe {
try_call!(raw::git_index_update_all(
self.raw,
&raw_strarray,
callback,
ptr.map(|p| p as *mut _).unwrap_or(ptr::null_mut()) as *mut c_void
));
}
Ok(())
}
pub fn write(&mut self) -> Result<(), Error> {
unsafe {
try_call!(raw::git_index_write(self.raw));
}
Ok(())
}
pub fn write_tree(&mut self) -> Result<Oid, Error> {
let mut raw = raw::git_oid {
id: [0; raw::GIT_OID_RAWSZ],
};
unsafe {
try_call!(raw::git_index_write_tree(&mut raw, self.raw));
Ok(Binding::from_raw(&raw as *const _))
}
}
pub fn write_tree_to(&mut self, repo: &Repository) -> Result<Oid, Error> {
let mut raw = raw::git_oid {
id: [0; raw::GIT_OID_RAWSZ],
};
unsafe {
try_call!(raw::git_index_write_tree_to(&mut raw, self.raw, repo.raw()));
Ok(Binding::from_raw(&raw as *const _))
}
}
}
impl Binding for Index {
type Raw = *mut raw::git_index;
unsafe fn from_raw(raw: *mut raw::git_index) -> Index {
Index { raw: raw }
}
fn raw(&self) -> *mut raw::git_index {
self.raw
}
}
impl<'index> Binding for IndexConflicts<'index> {
type Raw = *mut raw::git_index_conflict_iterator;
unsafe fn from_raw(raw: *mut raw::git_index_conflict_iterator) -> IndexConflicts<'index> {
IndexConflicts {
conflict_iter: raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *mut raw::git_index_conflict_iterator {
self.conflict_iter
}
}
extern "C" fn index_matched_path_cb(
path: *const c_char,
matched_pathspec: *const c_char,
payload: *mut c_void,
) -> c_int {
unsafe {
let path = CStr::from_ptr(path).to_bytes();
let matched_pathspec = CStr::from_ptr(matched_pathspec).to_bytes();
panic::wrap(|| {
let payload = payload as *mut &mut IndexMatchedPath<'_>;
(*payload)(util::bytes2path(path), matched_pathspec) as c_int
})
.unwrap_or(-1)
}
}
impl Drop for Index {
fn drop(&mut self) {
unsafe { raw::git_index_free(self.raw) }
}
}
impl<'index> Drop for IndexConflicts<'index> {
fn drop(&mut self) {
unsafe { raw::git_index_conflict_iterator_free(self.conflict_iter) }
}
}
impl<'index> Iterator for IndexEntries<'index> {
type Item = IndexEntry;
fn next(&mut self) -> Option<IndexEntry> {
self.range.next().map(|i| self.index.get(i).unwrap())
}
}
impl<'index> Iterator for IndexConflicts<'index> {
type Item = Result<IndexConflict, Error>;
fn next(&mut self) -> Option<Result<IndexConflict, Error>> {
let mut ancestor = ptr::null();
let mut our = ptr::null();
let mut their = ptr::null();
unsafe {
try_call_iter!(raw::git_index_conflict_next(
&mut ancestor,
&mut our,
&mut their,
self.conflict_iter
));
Some(Ok(IndexConflict {
ancestor: match ancestor.is_null() {
false => Some(IndexEntry::from_raw(*ancestor)),
true => None,
},
our: match our.is_null() {
false => Some(IndexEntry::from_raw(*our)),
true => None,
},
their: match their.is_null() {
false => Some(IndexEntry::from_raw(*their)),
true => None,
},
}))
}
}
}
impl Binding for IndexEntry {
type Raw = raw::git_index_entry;
unsafe fn from_raw(raw: raw::git_index_entry) -> IndexEntry {
let raw::git_index_entry {
ctime,
mtime,
dev,
ino,
mode,
uid,
gid,
file_size,
id,
flags,
flags_extended,
path,
} = raw;
let mut pathlen = (flags & raw::GIT_INDEX_ENTRY_NAMEMASK) as usize;
if pathlen == raw::GIT_INDEX_ENTRY_NAMEMASK as usize {
pathlen = CStr::from_ptr(path).to_bytes().len();
}
let path = slice::from_raw_parts(path as *const u8, pathlen);
IndexEntry {
dev: dev,
ino: ino,
mode: mode,
uid: uid,
gid: gid,
file_size: file_size,
id: Binding::from_raw(&id as *const _),
flags: flags,
flags_extended: flags_extended,
path: path.to_vec(),
mtime: Binding::from_raw(mtime),
ctime: Binding::from_raw(ctime),
}
}
fn raw(&self) -> raw::git_index_entry {
panic!()
}
}
#[cfg(test)]
mod tests {
use std::fs::{self, File};
use std::path::Path;
use tempfile::TempDir;
use crate::{Index, IndexEntry, IndexTime, Oid, Repository, ResetType};
#[test]
fn smoke() {
let mut index = Index::new().unwrap();
assert!(index.add_path(&Path::new(".")).is_err());
index.clear().unwrap();
assert_eq!(index.len(), 0);
assert!(index.get(0).is_none());
assert!(index.path().is_none());
assert!(index.read(true).is_err());
}
#[test]
fn smoke_from_repo() {
let (_td, repo) = crate::test::repo_init();
let mut index = repo.index().unwrap();
assert_eq!(
index.path().map(|s| s.to_path_buf()),
Some(repo.path().join("index"))
);
Index::open(&repo.path().join("index")).unwrap();
index.clear().unwrap();
index.read(true).unwrap();
index.write().unwrap();
index.write_tree().unwrap();
index.write_tree_to(&repo).unwrap();
}
#[test]
fn add_all() {
let (_td, repo) = crate::test::repo_init();
let mut index = repo.index().unwrap();
let root = repo.path().parent().unwrap();
fs::create_dir(&root.join("foo")).unwrap();
File::create(&root.join("foo/bar")).unwrap();
let mut called = false;
index
.add_all(
["foo"].iter(),
crate::IndexAddOption::DEFAULT,
Some(&mut |a: &Path, b: &[u8]| {
assert!(!called);
called = true;
assert_eq!(b, b"foo");
assert_eq!(a, Path::new("foo/bar"));
0
}),
)
.unwrap();
assert!(called);
called = false;
index
.remove_all(
["."].iter(),
Some(&mut |a: &Path, b: &[u8]| {
assert!(!called);
called = true;
assert_eq!(b, b".");
assert_eq!(a, Path::new("foo/bar"));
0
}),
)
.unwrap();
assert!(called);
}
#[test]
fn smoke_add() {
let (_td, repo) = crate::test::repo_init();
let mut index = repo.index().unwrap();
let root = repo.path().parent().unwrap();
fs::create_dir(&root.join("foo")).unwrap();
File::create(&root.join("foo/bar")).unwrap();
index.add_path(Path::new("foo/bar")).unwrap();
index.write().unwrap();
assert_eq!(index.iter().count(), 1);
let id = index.write_tree().unwrap();
let tree = repo.find_tree(id).unwrap();
let sig = repo.signature().unwrap();
let id = repo.refname_to_id("HEAD").unwrap();
let parent = repo.find_commit(id).unwrap();
let commit = repo
.commit(Some("HEAD"), &sig, &sig, "commit", &tree, &[&parent])
.unwrap();
let obj = repo.find_object(commit, None).unwrap();
repo.reset(&obj, ResetType::Hard, None).unwrap();
let td2 = TempDir::new().unwrap();
let url = crate::test::path2url(&root);
let repo = Repository::clone(&url, td2.path()).unwrap();
let obj = repo.find_object(commit, None).unwrap();
repo.reset(&obj, ResetType::Hard, None).unwrap();
}
#[test]
fn add_then_read() {
let mut index = Index::new().unwrap();
assert!(index.add(&entry()).is_err());
let mut index = Index::new().unwrap();
let mut e = entry();
e.path = b"foobar".to_vec();
index.add(&e).unwrap();
let e = index.get(0).unwrap();
assert_eq!(e.path.len(), 6);
}
#[test]
fn add_frombuffer_then_read() {
let (_td, repo) = crate::test::repo_init();
let mut index = repo.index().unwrap();
let mut e = entry();
e.path = b"foobar".to_vec();
let content = b"the contents";
index.add_frombuffer(&e, content).unwrap();
let e = index.get(0).unwrap();
assert_eq!(e.path.len(), 6);
let b = repo.find_blob(e.id).unwrap();
assert_eq!(b.content(), content);
}
fn entry() -> IndexEntry {
IndexEntry {
ctime: IndexTime::new(0, 0),
mtime: IndexTime::new(0, 0),
dev: 0,
ino: 0,
mode: 0o100644,
uid: 0,
gid: 0,
file_size: 0,
id: Oid::from_bytes(&[0; 20]).unwrap(),
flags: 0,
flags_extended: 0,
path: Vec::new(),
}
}
}