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