1use std::ffi::CString;
2use std::marker;
3use std::ptr;
4use std::str;
5
6use crate::util::Binding;
7use crate::{raw, BranchType, Error, Reference, References};
8
9pub struct Branch<'repo> {
16 inner: Reference<'repo>,
17}
18
19pub struct Branches<'repo> {
21 raw: *mut raw::git_branch_iterator,
22 _marker: marker::PhantomData<References<'repo>>,
23}
24
25impl<'repo> Branch<'repo> {
26 pub fn wrap(reference: Reference<'_>) -> Branch<'_> {
28 Branch { inner: reference }
29 }
30
31 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 pub fn get(&self) -> &Reference<'repo> {
44 &self.inner
45 }
46
47 pub fn get_mut(&mut self) -> &mut Reference<'repo> {
49 &mut self.inner
50 }
51
52 pub fn into_reference(self) -> Reference<'repo> {
54 self.inner
55 }
56
57 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 pub fn is_head(&self) -> bool {
67 unsafe { raw::git_branch_is_head(&*self.get().raw()) == 1 }
68 }
69
70 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 pub fn name(&self) -> Result<Option<&str>, Error> {
89 self.name_bytes().map(|s| str::from_utf8(s).ok())
90 }
91
92 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 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 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
127impl<'repo> Branches<'repo> {
128 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
140impl<'repo> Iterator for Branches<'repo> {
141 type Item = 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
157impl<'repo> Drop for Branches<'repo> {
158 fn drop(&mut self) {
159 unsafe { raw::git_branch_iterator_free(self.raw) }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use crate::{Branch, BranchType};
166
167 #[test]
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]
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}