1 use std::ffi::CString; 2 use std::marker; 3 use std::ptr; 4 use std::str; 5 6 use crate::util::Binding; 7 use crate::{raw, BranchType, Error, Reference, References}; 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 `into_reference` 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<'_>) -> Branch<'_>27 pub fn wrap(reference: Reference<'_>) -> Branch<'_> { 28 Branch { inner: reference } 29 } 30 31 /// Ensure the branch name is well-formed. name_is_valid(name: &str) -> Result<bool, Error>32 pub fn name_is_valid(name: &str) -> Result<bool, Error> { 33 crate::init(); 34 let name = CString::new(name)?; 35 let mut valid: libc::c_int = 0; 36 unsafe { 37 try_call!(raw::git_branch_name_is_valid(&mut valid, name.as_ptr())); 38 } 39 Ok(valid == 1) 40 } 41 42 /// Gain access to the reference that is this branch get(&self) -> &Reference<'repo>43 pub fn get(&self) -> &Reference<'repo> { 44 &self.inner 45 } 46 47 /// Gain mutable access to the reference that is this branch get_mut(&mut self) -> &mut Reference<'repo>48 pub fn get_mut(&mut self) -> &mut Reference<'repo> { 49 &mut self.inner 50 } 51 52 /// Take ownership of the underlying reference. into_reference(self) -> Reference<'repo>53 pub fn into_reference(self) -> Reference<'repo> { 54 self.inner 55 } 56 57 /// Delete an existing branch reference. delete(&mut self) -> Result<(), Error>58 pub fn delete(&mut self) -> Result<(), Error> { 59 unsafe { 60 try_call!(raw::git_branch_delete(self.get().raw())); 61 } 62 Ok(()) 63 } 64 65 /// Determine if the current local branch is pointed at by HEAD. is_head(&self) -> bool66 pub fn is_head(&self) -> bool { 67 unsafe { raw::git_branch_is_head(&*self.get().raw()) == 1 } 68 } 69 70 /// Move/rename an existing local branch reference. rename(&mut self, new_branch_name: &str, force: bool) -> Result<Branch<'repo>, Error>71 pub fn rename(&mut self, new_branch_name: &str, force: bool) -> Result<Branch<'repo>, Error> { 72 let mut ret = ptr::null_mut(); 73 let new_branch_name = CString::new(new_branch_name)?; 74 unsafe { 75 try_call!(raw::git_branch_move( 76 &mut ret, 77 self.get().raw(), 78 new_branch_name, 79 force 80 )); 81 Ok(Branch::wrap(Binding::from_raw(ret))) 82 } 83 } 84 85 /// Return the name of the given local or remote branch. 86 /// 87 /// May return `Ok(None)` if the name is not valid utf-8. name(&self) -> Result<Option<&str>, Error>88 pub fn name(&self) -> Result<Option<&str>, Error> { 89 self.name_bytes().map(|s| str::from_utf8(s).ok()) 90 } 91 92 /// Return the name of the given local or remote branch. name_bytes(&self) -> Result<&[u8], Error>93 pub fn name_bytes(&self) -> Result<&[u8], Error> { 94 let mut ret = ptr::null(); 95 unsafe { 96 try_call!(raw::git_branch_name(&mut ret, &*self.get().raw())); 97 Ok(crate::opt_bytes(self, ret).unwrap()) 98 } 99 } 100 101 /// Return the reference supporting the remote tracking branch, given a 102 /// local branch reference. upstream(&self) -> Result<Branch<'repo>, Error>103 pub fn upstream(&self) -> Result<Branch<'repo>, Error> { 104 let mut ret = ptr::null_mut(); 105 unsafe { 106 try_call!(raw::git_branch_upstream(&mut ret, &*self.get().raw())); 107 Ok(Branch::wrap(Binding::from_raw(ret))) 108 } 109 } 110 111 /// Set the upstream configuration for a given local branch. 112 /// 113 /// If `None` is specified, then the upstream branch is unset. The name 114 /// provided is the name of the branch to set as upstream. set_upstream(&mut self, upstream_name: Option<&str>) -> Result<(), Error>115 pub fn set_upstream(&mut self, upstream_name: Option<&str>) -> Result<(), Error> { 116 let upstream_name = crate::opt_cstr(upstream_name)?; 117 unsafe { 118 try_call!(raw::git_branch_set_upstream( 119 self.get().raw(), 120 upstream_name 121 )); 122 Ok(()) 123 } 124 } 125 } 126 127 impl<'repo> Branches<'repo> { 128 /// Creates a new iterator from the raw pointer given. 129 /// 130 /// This function is unsafe as it is not guaranteed that `raw` is a valid 131 /// pointer. from_raw(raw: *mut raw::git_branch_iterator) -> Branches<'repo>132 pub unsafe fn from_raw(raw: *mut raw::git_branch_iterator) -> Branches<'repo> { 133 Branches { 134 raw, 135 _marker: marker::PhantomData, 136 } 137 } 138 } 139 140 impl<'repo> Iterator for Branches<'repo> { 141 type Item = Result<(Branch<'repo>, BranchType), Error>; next(&mut self) -> Option<Result<(Branch<'repo>, BranchType), Error>>142 fn next(&mut self) -> Option<Result<(Branch<'repo>, BranchType), Error>> { 143 let mut ret = ptr::null_mut(); 144 let mut typ = raw::GIT_BRANCH_LOCAL; 145 unsafe { 146 try_call_iter!(raw::git_branch_next(&mut ret, &mut typ, self.raw)); 147 let typ = match typ { 148 raw::GIT_BRANCH_LOCAL => BranchType::Local, 149 raw::GIT_BRANCH_REMOTE => BranchType::Remote, 150 n => panic!("unexected branch type: {}", n), 151 }; 152 Some(Ok((Branch::wrap(Binding::from_raw(ret)), typ))) 153 } 154 } 155 } 156 157 impl<'repo> Drop for Branches<'repo> { drop(&mut self)158 fn drop(&mut self) { 159 unsafe { raw::git_branch_iterator_free(self.raw) } 160 } 161 } 162 163 #[cfg(test)] 164 mod tests { 165 use crate::{Branch, BranchType}; 166 167 #[test] smoke()168 fn smoke() { 169 let (_td, repo) = crate::test::repo_init(); 170 let head = repo.head().unwrap(); 171 let target = head.target().unwrap(); 172 let commit = repo.find_commit(target).unwrap(); 173 174 let mut b1 = repo.branch("foo", &commit, false).unwrap(); 175 assert!(!b1.is_head()); 176 repo.branch("foo2", &commit, false).unwrap(); 177 178 assert_eq!(repo.branches(None).unwrap().count(), 3); 179 repo.find_branch("foo", BranchType::Local).unwrap(); 180 let mut b1 = b1.rename("bar", false).unwrap(); 181 assert_eq!(b1.name().unwrap(), Some("bar")); 182 assert!(b1.upstream().is_err()); 183 b1.set_upstream(Some("main")).unwrap(); 184 b1.upstream().unwrap(); 185 b1.set_upstream(None).unwrap(); 186 187 b1.delete().unwrap(); 188 } 189 190 #[test] name_is_valid()191 fn name_is_valid() { 192 assert!(Branch::name_is_valid("foo").unwrap()); 193 assert!(!Branch::name_is_valid("").unwrap()); 194 assert!(!Branch::name_is_valid("with spaces").unwrap()); 195 assert!(!Branch::name_is_valid("~tilde").unwrap()); 196 } 197 } 198