use std::ffi::CString;
use std::iter::{range, Range};
use std::kinds::marker;
use std::mem;
use std::path::BytesContainer;
use std::str;
use libc::{c_char, size_t, c_uint};
use {raw, Status, DiffDelta};
pub struct StatusOptions {
raw: raw::git_status_options,
pathspec: Vec<CString>,
ptrs: Vec<*const c_char>,
}
#[derive(Copy)]
pub enum StatusShow {
Index,
Workdir,
IndexAndWorkdir,
}
pub struct Statuses<'repo> {
raw: *mut raw::git_status_list,
marker: marker::ContravariantLifetime<'repo>,
}
pub struct StatusIter<'statuses> {
statuses: &'statuses Statuses<'statuses>,
range: Range<uint>,
}
pub struct StatusEntry<'statuses> {
raw: *const raw::git_status_entry,
marker: marker::ContravariantLifetime<'statuses>,
}
impl StatusOptions {
pub fn new() -> StatusOptions {
unsafe {
let mut raw = mem::zeroed();
let r = raw::git_status_init_options(&mut raw,
raw::GIT_STATUS_OPTIONS_VERSION);
assert_eq!(r, 0);
StatusOptions {
raw: raw,
pathspec: Vec::new(),
ptrs: Vec::new(),
}
}
}
pub fn show(&mut self, show: StatusShow) -> &mut StatusOptions {
self.raw.show = match show {
StatusShow::Index => raw::GIT_STATUS_SHOW_INDEX_ONLY,
StatusShow::Workdir => raw::GIT_STATUS_SHOW_WORKDIR_ONLY,
StatusShow::IndexAndWorkdir => raw::GIT_STATUS_SHOW_INDEX_AND_WORKDIR,
};
self
}
pub fn pathspec<T>(&mut self, pathspec: T) -> &mut StatusOptions
where T: BytesContainer
{
let s = CString::from_slice(pathspec.container_as_bytes());
self.ptrs.push(s.as_ptr());
self.pathspec.push(s);
self
}
fn flag(&mut self, flag: raw::git_status_opt_t, val: bool)
-> &mut StatusOptions {
if val {
self.raw.flags |= flag as c_uint;
} else {
self.raw.flags &= !(flag as c_uint);
}
self
}
pub fn include_untracked(&mut self, include: bool) -> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNTRACKED, include)
}
pub fn include_ignored(&mut self, include: bool) -> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_INCLUDE_IGNORED, include)
}
pub fn include_unmodified(&mut self, include: bool) -> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNMODIFIED, include)
}
pub fn exclude_submodules(&mut self, exclude: bool) -> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_EXCLUDE_SUBMODULES, exclude)
}
pub fn recurse_untracked_dirs(&mut self, include: bool)
-> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS, include)
}
pub fn disable_pathspec_match(&mut self, include: bool)
-> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH, include)
}
pub fn recurse_ignored_dirs(&mut self, include: bool)
-> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_RECURSE_IGNORED_DIRS, include)
}
pub fn renames_head_to_index(&mut self, include: bool)
-> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX, include)
}
pub fn renames_index_to_workdir(&mut self, include: bool)
-> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR, include)
}
pub fn sort_case_sensitively(&mut self, include: bool)
-> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_SORT_CASE_SENSITIVELY, include)
}
pub fn sort_case_insensitively(&mut self, include: bool)
-> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_SORT_CASE_INSENSITIVELY, include)
}
pub fn renames_from_rewrites(&mut self, include: bool)
-> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_RENAMES_FROM_REWRITES, include)
}
pub fn no_refresh(&mut self, include: bool) -> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_NO_REFRESH, include)
}
pub fn update_index(&mut self, include: bool) -> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_UPDATE_INDEX, include)
}
#[allow(missing_docs)]
pub fn include_unreadable(&mut self, include: bool) -> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNREADABLE, include)
}
#[allow(missing_docs)]
pub fn include_unreadable_as_untracked(&mut self, include: bool)
-> &mut StatusOptions {
self.flag(raw::GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED, include)
}
pub unsafe fn raw(&mut self) -> *const raw::git_status_options {
self.raw.pathspec.strings = self.ptrs.as_ptr() as *mut _;
self.raw.pathspec.count = self.ptrs.len() as size_t;
&self.raw
}
}
impl<'repo> Statuses<'repo> {
pub unsafe fn from_raw(raw: *mut raw::git_status_list) -> Statuses<'repo> {
Statuses {
raw: raw,
marker: marker::ContravariantLifetime,
}
}
pub fn get(&self, index: uint) -> Option<StatusEntry> {
unsafe {
let p = raw::git_status_byindex(self.raw, index as size_t);
if p.is_null() {
None
} else {
Some(StatusEntry::from_raw(p))
}
}
}
pub fn len(&self) -> uint {
unsafe { raw::git_status_list_entrycount(self.raw) as uint }
}
pub fn iter(&self) -> StatusIter {
StatusIter {
statuses: self,
range: range(0, self.len()),
}
}
}
#[unsafe_destructor]
impl<'repo> Drop for Statuses<'repo> {
fn drop(&mut self) {
unsafe { raw::git_status_list_free(self.raw); }
}
}
impl<'a> Iterator for StatusIter<'a> {
type Item = StatusEntry<'a>;
fn next(&mut self) -> Option<StatusEntry<'a>> {
self.range.next().and_then(|i| self.statuses.get(i))
}
fn size_hint(&self) -> (uint, Option<uint>) { self.range.size_hint() }
}
impl<'a> DoubleEndedIterator for StatusIter<'a> {
fn next_back(&mut self) -> Option<StatusEntry<'a>> {
self.range.next_back().and_then(|i| self.statuses.get(i))
}
}
impl<'a> ExactSizeIterator for StatusIter<'a> {}
impl<'statuses> StatusEntry<'statuses> {
pub unsafe fn from_raw(raw: *const raw::git_status_entry)
-> StatusEntry<'statuses> {
StatusEntry {
raw: raw,
marker: marker::ContravariantLifetime,
}
}
pub fn path_bytes(&self) -> &[u8] {
unsafe {
if (*self.raw).head_to_index.is_null() {
::opt_bytes(self, (*(*self.raw).index_to_workdir).old_file.path)
} else {
::opt_bytes(self, (*(*self.raw).head_to_index).old_file.path)
}.unwrap()
}
}
pub fn path(&self) -> Option<&str> { str::from_utf8(self.path_bytes()).ok() }
pub fn status(&self) -> Status {
Status::from_bits_truncate(unsafe { (*self.raw).status as u32 })
}
pub fn head_to_index(&self) -> Option<DiffDelta<'statuses>> {
unsafe {
let p = (*self.raw).head_to_index;
if p.is_null() {
None
} else {
Some(DiffDelta::from_raw(p))
}
}
}
pub fn index_to_workdir(&self) -> Option<DiffDelta<'statuses>> {
unsafe {
let p = (*self.raw).index_to_workdir;
if p.is_null() {
None
} else {
Some(DiffDelta::from_raw(p))
}
}
}
}
#[cfg(test)]
mod tests {
use std::io::File;
#[test]
fn smoke() {
let (td, repo) = ::test::repo_init();
assert_eq!(repo.statuses(None).unwrap().len(), 0);
File::create(&td.path().join("foo")).unwrap();
let statuses = repo.statuses(None).unwrap();
assert_eq!(statuses.iter().count(), 1);
let status = statuses.iter().next().unwrap();
assert_eq!(status.path(), Some("foo"));
assert!(status.status().contains(::STATUS_WT_NEW));
assert!(!status.status().contains(::STATUS_INDEX_NEW));
assert!(status.head_to_index().is_none());
let diff = status.index_to_workdir().unwrap();
assert_eq!(diff.old_file().path_bytes().unwrap(), b"foo");
assert_eq!(diff.new_file().path_bytes().unwrap(), b"foo");
}
}