1 use std::ffi::CString; 2 use std::marker; 3 use std::ptr; 4 use std::str; 5 6 use {raw, Error, Reference, BranchType, References}; 7 use util::Binding; 8 9 /// A structure to represent a git [branch][1] 10 /// 11 /// A branch is currently just a wrapper to an underlying `Reference`. The 12 /// reference can be accessed through the `get` and `unwrap` methods. 13 /// 14 /// [1]: http://git-scm.com/book/en/Git-Branching-What-a-Branch-Is 15 pub struct Branch<'repo> { 16 inner: Reference<'repo>, 17 } 18 19 /// An iterator over the branches inside of a repository. 20 pub struct Branches<'repo> { 21 raw: *mut raw::git_branch_iterator, 22 _marker: marker::PhantomData<References<'repo>>, 23 } 24 25 impl<'repo> Branch<'repo> { 26 /// Creates Branch type from a Reference wrap(reference: Reference) -> Branch27 pub fn wrap(reference: Reference) -> Branch { Branch { inner: reference } } 28 29 /// Gain access to the reference that is this branch get(&self) -> &Reference<'repo>30 pub fn get(&self) -> &Reference<'repo> { &self.inner } 31 32 /// Take ownership of the underlying reference. into_reference(self) -> Reference<'repo>33 pub fn into_reference(self) -> Reference<'repo> { self.inner } 34 35 /// Delete an existing branch reference. delete(&mut self) -> Result<(), Error>36 pub fn delete(&mut self) -> Result<(), Error> { 37 unsafe { try_call!(raw::git_branch_delete(self.get().raw())); } 38 Ok(()) 39 } 40 41 /// Determine if the current local branch is pointed at by HEAD. is_head(&self) -> bool42 pub fn is_head(&self) -> bool { 43 unsafe { raw::git_branch_is_head(&*self.get().raw()) == 1 } 44 } 45 46 /// Move/rename an existing local branch reference. rename(&mut self, new_branch_name: &str, force: bool) -> Result<Branch<'repo>, Error>47 pub fn rename(&mut self, new_branch_name: &str, force: bool) 48 -> Result<Branch<'repo>, Error> { 49 let mut ret = ptr::null_mut(); 50 let new_branch_name = try!(CString::new(new_branch_name)); 51 unsafe { 52 try_call!(raw::git_branch_move(&mut ret, self.get().raw(), 53 new_branch_name, force)); 54 Ok(Branch::wrap(Binding::from_raw(ret))) 55 } 56 } 57 58 /// Return the name of the given local or remote branch. 59 /// 60 /// May return `Ok(None)` if the name is not valid utf-8. name(&self) -> Result<Option<&str>, Error>61 pub fn name(&self) -> Result<Option<&str>, Error> { 62 self.name_bytes().map(|s| str::from_utf8(s).ok()) 63 } 64 65 /// Return the name of the given local or remote branch. name_bytes(&self) -> Result<&[u8], Error>66 pub fn name_bytes(&self) -> Result<&[u8], Error> { 67 let mut ret = ptr::null(); 68 unsafe { 69 try_call!(raw::git_branch_name(&mut ret, &*self.get().raw())); 70 Ok(::opt_bytes(self, ret).unwrap()) 71 } 72 } 73 74 /// Return the reference supporting the remote tracking branch, given a 75 /// local branch reference. upstream<'a>(&'a self) -> Result<Branch<'a>, Error>76 pub fn upstream<'a>(&'a self) -> Result<Branch<'a>, Error> { 77 let mut ret = ptr::null_mut(); 78 unsafe { 79 try_call!(raw::git_branch_upstream(&mut ret, &*self.get().raw())); 80 Ok(Branch::wrap(Binding::from_raw(ret))) 81 } 82 } 83 84 /// Set the upstream configuration for a given local branch. 85 /// 86 /// If `None` is specified, then the upstream branch is unset. The name 87 /// provided is the name of the branch to set as upstream. set_upstream(&mut self, upstream_name: Option<&str>) -> Result<(), Error>88 pub fn set_upstream(&mut self, 89 upstream_name: Option<&str>) -> Result<(), Error> { 90 let upstream_name = try!(::opt_cstr(upstream_name)); 91 unsafe { 92 try_call!(raw::git_branch_set_upstream(self.get().raw(), 93 upstream_name)); 94 Ok(()) 95 } 96 } 97 } 98 99 impl<'repo> Branches<'repo> { 100 /// Creates a new iterator from the raw pointer given. 101 /// 102 /// This function is unsafe as it is not guaranteed that `raw` is a valid 103 /// pointer. from_raw(raw: *mut raw::git_branch_iterator) -> Branches<'repo>104 pub unsafe fn from_raw(raw: *mut raw::git_branch_iterator) 105 -> Branches<'repo> { 106 Branches { 107 raw: raw, 108 _marker: marker::PhantomData, 109 } 110 } 111 } 112 113 impl<'repo> Iterator for Branches<'repo> { 114 type Item = Result<(Branch<'repo>, BranchType), Error>; next(&mut self) -> Option<Result<(Branch<'repo>, BranchType), Error>>115 fn next(&mut self) -> Option<Result<(Branch<'repo>, BranchType), Error>> { 116 let mut ret = ptr::null_mut(); 117 let mut typ = raw::GIT_BRANCH_LOCAL; 118 unsafe { 119 try_call_iter!(raw::git_branch_next(&mut ret, &mut typ, self.raw)); 120 let typ = match typ { 121 raw::GIT_BRANCH_LOCAL => BranchType::Local, 122 raw::GIT_BRANCH_REMOTE => BranchType::Remote, 123 n => panic!("unexected branch type: {}", n), 124 }; 125 Some(Ok((Branch::wrap(Binding::from_raw(ret)), typ))) 126 } 127 } 128 } 129 130 impl<'repo> Drop for Branches<'repo> { drop(&mut self)131 fn drop(&mut self) { 132 unsafe { raw::git_branch_iterator_free(self.raw) } 133 } 134 } 135 136 #[cfg(test)] 137 mod tests { 138 use BranchType; 139 140 #[test] smoke()141 fn smoke() { 142 let (_td, repo) = ::test::repo_init(); 143 let head = repo.head().unwrap(); 144 let target = head.target().unwrap(); 145 let commit = repo.find_commit(target).unwrap(); 146 147 let mut b1 = repo.branch("foo", &commit, false).unwrap(); 148 assert!(!b1.is_head()); 149 repo.branch("foo2", &commit, false).unwrap(); 150 151 assert_eq!(repo.branches(None).unwrap().count(), 3); 152 repo.find_branch("foo", BranchType::Local).unwrap(); 153 let mut b1 = b1.rename("bar", false).unwrap(); 154 assert_eq!(b1.name().unwrap(), Some("bar")); 155 assert!(b1.upstream().is_err()); 156 b1.set_upstream(Some("master")).unwrap(); 157 b1.upstream().unwrap(); 158 b1.set_upstream(None).unwrap(); 159 160 b1.delete().unwrap(); 161 } 162 } 163