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