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