use std::ffi::{CStr, OsString, CString};
use std::ops::Range;
use std::path::Path;
use std::ptr;
use std::slice;
use libc::{c_int, c_uint, size_t, c_void, c_char};
use {raw, panic, Repository, Error, Tree, Oid, IndexAddOption, IndexTime};
use IntoCString;
use util::{self, Binding};
pub struct Index {
raw: *mut raw::git_index,
}
pub struct IndexEntries<'index> {
range: Range<usize>,
index: &'index Index,
}
pub type IndexMatchedPath<'a> = 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> {
::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> {
::init();
let mut raw = ptr::null_mut();
let index_path = try!(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 = try!(CString::new(&entry.path[..]));
let mut flags = entry.flags & !raw::GIT_IDXENTRY_NAMEMASK;
if entry.path.len() < raw::GIT_IDXENTRY_NAMEMASK as usize {
flags |= entry.path.len() as u16;
} else {
flags |= raw::GIT_IDXENTRY_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 = try!(CString::new(&entry.path[..]));
let mut flags = entry.flags & !raw::GIT_IDXENTRY_NAMEMASK;
if entry.path.len() < raw::GIT_IDXENTRY_NAMEMASK as usize {
flags |= entry.path.len() as u16;
} else {
flags |= raw::GIT_IDXENTRY_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 mut posix_path = OsString::new();
for (i, comp) in path.components().enumerate() {
if i != 0 { posix_path.push("/"); }
posix_path.push(comp.as_os_str());
}
let posix_path = try!(posix_path.into_c_string());
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) = try!(::util::iter2cstrs(pathspecs));
let ptr = cb.as_mut();
let callback = ptr.as_ref().map(|_| {
index_matched_path_cb as raw::git_index_matched_path_cb
});
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 get_path(&self, path: &Path, stage: i32) -> Option<IndexEntry> {
let path = path.into_c_string().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 {
::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 = try!(path.into_c_string());
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 = try!(path.into_c_string());
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 = try!(path.into_c_string());
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) = try!(::util::iter2cstrs(pathspecs));
let ptr = cb.as_mut();
let callback = ptr.as_ref().map(|_| {
index_matched_path_cb as raw::git_index_matched_path_cb
});
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) = try!(::util::iter2cstrs(pathspecs));
let ptr = cb.as_mut();
let callback = ptr.as_ref().map(|_| {
index_matched_path_cb as raw::git_index_matched_path_cb
});
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 }
}
extern 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> Iterator for IndexEntries<'index> {
type Item = IndexEntry;
fn next(&mut self) -> Option<IndexEntry> {
self.range.next().map(|i| self.index.get(i).unwrap())
}
}
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_IDXENTRY_NAMEMASK) as usize;
if pathlen == raw::GIT_IDXENTRY_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 tempdir::TempDir;
use {Index, IndexEntry, Repository, ResetType, Oid, IndexTime};
#[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) = ::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) = ::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(), ::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) = ::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("git").unwrap();
let url = ::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) = ::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(),
}
}
}