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