use std::ffi::CString;
use std::kinds::marker;
use std::mem;
use std::path::BytesContainer;
use std::slice;
use std::str;
use libc;
use {raw, Direction, Error, Refspec, Oid};
use {Signature, Push, RemoteCallbacks, Progress};
pub struct Remote<'repo, 'cb> {
raw: *mut raw::git_remote,
marker: marker::ContravariantLifetime<'repo>,
callbacks: Option<&'cb mut RemoteCallbacks<'cb>>,
}
pub struct Refspecs<'remote, 'cb: 'remote> {
cur: uint,
cnt: uint,
remote: &'remote Remote<'remote, 'cb>,
}
pub struct RemoteHead<'remote> {
raw: *const raw::git_remote_head,
marker: marker::ContravariantLifetime<'remote>,
}
impl<'repo, 'cb> Remote<'repo, 'cb> {
pub unsafe fn from_raw(raw: *mut raw::git_remote) -> Remote<'repo, 'cb> {
Remote {
raw: raw,
marker: marker::ContravariantLifetime,
callbacks: None,
}
}
pub fn is_valid_name(remote_name: &str) -> bool {
::init();
let remote_name = CString::from_slice(remote_name.as_bytes());
unsafe { raw::git_remote_is_valid_name(remote_name.as_ptr()) == 1 }
}
pub fn name(&self) -> Option<&str> {
self.name_bytes().and_then(|s| str::from_utf8(s).ok())
}
pub fn name_bytes(&self) -> Option<&[u8]> {
unsafe { ::opt_bytes(self, raw::git_remote_name(&*self.raw)) }
}
pub fn url(&self) -> Option<&str> {
str::from_utf8(self.url_bytes()).ok()
}
pub fn url_bytes(&self) -> &[u8] {
unsafe { ::opt_bytes(self, raw::git_remote_url(&*self.raw)).unwrap() }
}
pub fn pushurl(&self) -> Option<&str> {
self.pushurl_bytes().and_then(|s| str::from_utf8(s).ok())
}
pub fn pushurl_bytes(&self) -> Option<&[u8]> {
unsafe { ::opt_bytes(self, raw::git_remote_pushurl(&*self.raw)) }
}
pub fn connect(&mut self, dir: Direction) -> Result<(), Error> {
unsafe {
try!(self.set_raw_callbacks());
try_call!(raw::git_remote_connect(self.raw, dir));
}
Ok(())
}
pub fn connected(&mut self) -> bool {
unsafe { raw::git_remote_connected(self.raw) == 1 }
}
pub fn disconnect(&mut self) {
unsafe { raw::git_remote_disconnect(self.raw) }
}
pub fn save(&self) -> Result<(), Error> {
unsafe { try_call!(raw::git_remote_save(&*self.raw)); }
Ok(())
}
pub fn add_fetch(&mut self, spec: &str) -> Result<(), Error> {
let spec = CString::from_slice(spec.as_bytes());
unsafe {
try_call!(raw::git_remote_add_fetch(self.raw, spec));
}
Ok(())
}
pub fn add_push(&mut self, spec: &str) -> Result<(), Error> {
let spec = CString::from_slice(spec.as_bytes());
unsafe {
try_call!(raw::git_remote_add_push(self.raw, spec));
}
Ok(())
}
pub fn set_url(&mut self, url: &str) -> Result<(), Error> {
let url = CString::from_slice(url.as_bytes());
unsafe {
try_call!(raw::git_remote_set_url(self.raw, url));
}
Ok(())
}
pub fn set_pushurl(&mut self, pushurl: Option<&str>) -> Result<(), Error> {
let pushurl = pushurl.map(|s| CString::from_slice(s.as_bytes()));
unsafe {
try_call!(raw::git_remote_set_pushurl(self.raw, pushurl));
}
Ok(())
}
pub fn set_update_fetchhead(&mut self, update: bool) {
unsafe {
raw::git_remote_set_update_fetchhead(self.raw, update as libc::c_int)
}
}
pub fn set_fetch_refspecs<T, I>(&mut self, i: I) -> Result<(), Error>
where T: BytesContainer, I: Iterator<Item=T>
{
let (_a, _b, mut arr) = ::util::iter2cstrs(i);
unsafe {
try_call!(raw::git_remote_set_fetch_refspecs(self.raw, &mut arr));
}
Ok(())
}
pub fn set_push_refspecs<T, I>(&mut self, i: I) -> Result<(), Error>
where T: BytesContainer, I: Iterator<Item=T>
{
let (_a, _b, mut arr) = ::util::iter2cstrs(i);
unsafe {
try_call!(raw::git_remote_set_push_refspecs(self.raw, &mut arr));
}
Ok(())
}
pub fn clear_refspecs(&mut self) {
unsafe { raw::git_remote_clear_refspecs(self.raw) }
}
pub fn download(&mut self) -> Result<(), Error> {
unsafe {
try!(self.set_raw_callbacks());
try_call!(raw::git_remote_download(self.raw, 0 as *const _));
}
Ok(())
}
pub fn refspecs<'a>(&'a self) -> Refspecs<'a, 'cb> {
let cnt = unsafe { raw::git_remote_refspec_count(&*self.raw) as uint };
Refspecs { cur: 0, cnt: cnt, remote: self }
}
pub fn fetch(&mut self,
refspecs: &[&str],
signature: Option<&Signature>,
msg: Option<&str>) -> Result<(), Error> {
let (_a, _b, arr) = ::util::iter2cstrs(refspecs.iter());
let msg = msg.map(|s| CString::from_slice(s.as_bytes()));
unsafe {
try!(self.set_raw_callbacks());
try_call!(raw::git_remote_fetch(self.raw,
&arr,
&*signature.map(|s| s.raw())
.unwrap_or(0 as *mut _),
msg));
}
Ok(())
}
pub fn update_tips(&mut self, signature: Option<&Signature>,
msg: Option<&str>) -> Result<(), Error> {
let msg = msg.map(|s| CString::from_slice(s.as_bytes()));
unsafe {
try_call!(raw::git_remote_update_tips(self.raw,
&*signature.map(|s| s.raw())
.unwrap_or(0 as *mut _),
msg));
}
Ok(())
}
pub fn update_fetchhead(&mut self) -> Result<(), Error> {
unsafe { try_call!(raw::git_remote_update_fetchhead(self.raw)); }
Ok(())
}
pub fn push(&mut self) -> Result<Push, Error> {
let mut ret = 0 as *mut raw::git_push;
try!(self.set_raw_callbacks());
unsafe {
try_call!(raw::git_push_new(&mut ret, self.raw));
Ok(Push::from_raw(ret))
}
}
pub fn set_callbacks(&mut self, callbacks: &'cb mut RemoteCallbacks<'cb>) {
self.callbacks = Some(callbacks);
}
fn set_raw_callbacks(&mut self) -> Result<(), Error> {
match self.callbacks {
Some(ref mut cbs) => unsafe {
let raw = cbs.raw();
try_call!(raw::git_remote_set_callbacks(self.raw, &raw));
},
None => {}
}
Ok(())
}
pub fn stats(&self) -> Progress {
unsafe {
Progress::from_raw(raw::git_remote_stats(self.raw))
}
}
pub fn list(&self) -> Result<&[RemoteHead], Error> {
let mut size = 0;
let mut base = 0 as *mut _;
unsafe {
try_call!(raw::git_remote_ls(&mut base, &mut size, self.raw));
assert_eq!(mem::size_of::<RemoteHead>(),
mem::size_of::<*const raw::git_remote_head>());
let base = base as *const _;
let slice = slice::from_raw_buf(&base, size as uint);
Ok(mem::transmute::<&[*const raw::git_remote_head],
&[RemoteHead]>(slice))
}
}
}
impl<'a, 'b> Iterator for Refspecs<'a, 'b> {
type Item = Refspec<'a>;
fn next(&mut self) -> Option<Refspec<'a>> {
if self.cur == self.cnt { return None }
let ret = unsafe {
let ptr = raw::git_remote_get_refspec(&*self.remote.raw,
self.cur as libc::size_t);
assert!(!ptr.is_null());
Refspec::from_raw(ptr)
};
self.cur += 1;
Some(ret)
}
}
impl<'a, 'b> Clone for Remote<'a, 'b> {
fn clone(&self) -> Remote<'a, 'b> {
let mut ret = 0 as *mut raw::git_remote;
let rc = unsafe { call!(raw::git_remote_dup(&mut ret, self.raw)) };
assert_eq!(rc, 0);
Remote {
raw: ret,
marker: marker::ContravariantLifetime,
callbacks: None,
}
}
}
#[unsafe_destructor]
impl<'a, 'b> Drop for Remote<'a, 'b> {
fn drop(&mut self) {
unsafe { raw::git_remote_free(self.raw) }
}
}
#[allow(missing_docs)] impl<'remote> RemoteHead<'remote> {
pub fn is_local(&self) -> bool {
unsafe { (*self.raw).local != 0 }
}
pub fn oid(&self) -> Oid { unsafe { Oid::from_raw(&(*self.raw).oid) } }
pub fn loid(&self) -> Oid { unsafe { Oid::from_raw(&(*self.raw).loid) } }
pub fn name(&self) -> &str {
let b = unsafe { ::opt_bytes(self, (*self.raw).name).unwrap() };
str::from_utf8(b).unwrap()
}
pub fn symref_target(&self) -> Option<&str> {
let b = unsafe { ::opt_bytes(self, (*self.raw).symref_target) };
b.map(|b| str::from_utf8(b).unwrap())
}
}
#[cfg(test)]
mod tests {
use std::io::TempDir;
use std::cell::Cell;
use url::Url;
use {Repository, Remote, RemoteCallbacks, Direction};
#[test]
fn smoke() {
let (td, repo) = ::test::repo_init();
repo.remote("origin", "/path/to/nowhere").unwrap();
drop(repo);
let repo = Repository::init(td.path()).unwrap();
let origin = repo.find_remote("origin").unwrap();
assert_eq!(origin.name(), Some("origin"));
assert_eq!(origin.url(), Some("/path/to/nowhere"));
}
#[test]
fn create_remote() {
let td = TempDir::new("test").unwrap();
let remote = td.path().join("remote");
Repository::init_bare(&remote).unwrap();
let (_td, repo) = ::test::repo_init();
let url = if cfg!(unix) {
format!("file://{}", remote.display())
} else {
format!("file:///{}", remote.display().to_string()
.as_slice().replace("\\", "/"))
};
let mut origin = repo.remote("origin", url.as_slice()).unwrap();
assert_eq!(origin.name(), Some("origin"));
assert_eq!(origin.url(), Some(url.as_slice()));
assert_eq!(origin.pushurl(), None);
{
let mut specs = origin.refspecs();
let spec = specs.next().unwrap();
assert!(specs.next().is_none());
assert_eq!(spec.str(), Some("+refs/heads/*:refs/remotes/origin/*"));
assert_eq!(spec.dst(), Some("refs/remotes/origin/*"));
assert_eq!(spec.src(), Some("refs/heads/*"));
assert!(spec.is_force());
}
{
let remotes = repo.remotes().unwrap();
assert_eq!(remotes.len(), 1);
assert_eq!(remotes.get(0), Some("origin"));
assert_eq!(remotes.iter().count(), 1);
assert_eq!(remotes.iter().next().unwrap(), Some("origin"));
}
origin.connect(Direction::Push).unwrap();
assert!(origin.connected());
origin.disconnect();
origin.connect(Direction::Fetch).unwrap();
assert!(origin.connected());
origin.download().unwrap();
origin.disconnect();
origin.save().unwrap();
origin.add_fetch("foo").unwrap();
origin.add_fetch("bar").unwrap();
origin.clear_refspecs();
origin.set_fetch_refspecs(["foo"].iter().map(|a| *a)).unwrap();
origin.set_push_refspecs(["foo"].iter().map(|a| *a)).unwrap();
let sig = repo.signature().unwrap();
origin.fetch(&[], Some(&sig), None).unwrap();
origin.fetch(&[], None, Some("foo")).unwrap();
origin.update_tips(Some(&sig), None).unwrap();
origin.update_tips(None, Some("foo")).unwrap();
}
#[test]
fn rename_remote() {
let (_td, repo) = ::test::repo_init();
repo.remote("origin", "foo").unwrap();
repo.remote_rename("origin", "foo").unwrap();
repo.remote_delete("foo").unwrap();
}
#[test]
fn create_remote_anonymous() {
let td = TempDir::new("test").unwrap();
let repo = Repository::init(td.path()).unwrap();
let origin = repo.remote_anonymous("/path/to/nowhere",
Some("master")).unwrap();
assert_eq!(origin.name(), None);
drop(origin.clone());
}
#[test]
fn is_valid() {
assert!(Remote::is_valid_name("foobar"));
assert!(!Remote::is_valid_name("\x01"));
}
#[test]
fn transfer_cb() {
let (td, _repo) = ::test::repo_init();
let td2 = TempDir::new("git").unwrap();
let url = Url::from_file_path(td.path()).unwrap();
let url = url.to_string();
let repo = Repository::init(td2.path()).unwrap();
let mut origin = repo.remote("origin", url.as_slice()).unwrap();
let progress_hit = Cell::new(false);
let mut callbacks = RemoteCallbacks::new();
callbacks.transfer_progress(|_progress| {
progress_hit.set(true);
true
});
origin.set_callbacks(&mut callbacks);
origin.fetch(&[], None, None).unwrap();
assert!(progress_hit.get());
}
}