git2/
branch.rs

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
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
15pub struct Branch<'repo> {
16    inner: Reference<'repo>,
17}
18
19/// An iterator over the branches inside of a repository.
20pub struct Branches<'repo> {
21    raw: *mut raw::git_branch_iterator,
22    _marker: marker::PhantomData<References<'repo>>,
23}
24
25impl<'repo> Branch<'repo> {
26    /// Creates Branch type from a Reference
27    pub fn wrap(reference: Reference<'_>) -> Branch<'_> {
28        Branch { inner: reference }
29    }
30
31    /// Ensure the branch name is well-formed.
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
43    pub fn get(&self) -> &Reference<'repo> {
44        &self.inner
45    }
46
47    /// Gain mutable access to the reference that is this branch
48    pub fn get_mut(&mut self) -> &mut Reference<'repo> {
49        &mut self.inner
50    }
51
52    /// Take ownership of the underlying reference.
53    pub fn into_reference(self) -> Reference<'repo> {
54        self.inner
55    }
56
57    /// Delete an existing branch reference.
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.
66    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.
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.
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.
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.
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.
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
127impl<'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.
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
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}