summaryrefslogtreecommitdiffstats
path: root/extra/git2/src/branch.rs
diff options
context:
space:
mode:
Diffstat (limited to 'extra/git2/src/branch.rs')
-rw-r--r--extra/git2/src/branch.rs197
1 files changed, 197 insertions, 0 deletions
diff --git a/extra/git2/src/branch.rs b/extra/git2/src/branch.rs
new file mode 100644
index 000000000..e1eba99c2
--- /dev/null
+++ b/extra/git2/src/branch.rs
@@ -0,0 +1,197 @@
+use std::ffi::CString;
+use std::marker;
+use std::ptr;
+use std::str;
+
+use crate::util::Binding;
+use crate::{raw, BranchType, Error, Reference, References};
+
+/// A structure to represent a git [branch][1]
+///
+/// A branch is currently just a wrapper to an underlying `Reference`. The
+/// reference can be accessed through the `get` and `into_reference` methods.
+///
+/// [1]: http://git-scm.com/book/en/Git-Branching-What-a-Branch-Is
+pub struct Branch<'repo> {
+ inner: Reference<'repo>,
+}
+
+/// An iterator over the branches inside of a repository.
+pub struct Branches<'repo> {
+ raw: *mut raw::git_branch_iterator,
+ _marker: marker::PhantomData<References<'repo>>,
+}
+
+impl<'repo> Branch<'repo> {
+ /// Creates Branch type from a Reference
+ pub fn wrap(reference: Reference<'_>) -> Branch<'_> {
+ Branch { inner: reference }
+ }
+
+ /// Ensure the branch name is well-formed.
+ pub fn name_is_valid(name: &str) -> Result<bool, Error> {
+ crate::init();
+ let name = CString::new(name)?;
+ let mut valid: libc::c_int = 0;
+ unsafe {
+ try_call!(raw::git_branch_name_is_valid(&mut valid, name.as_ptr()));
+ }
+ Ok(valid == 1)
+ }
+
+ /// Gain access to the reference that is this branch
+ pub fn get(&self) -> &Reference<'repo> {
+ &self.inner
+ }
+
+ /// Gain mutable access to the reference that is this branch
+ pub fn get_mut(&mut self) -> &mut Reference<'repo> {
+ &mut self.inner
+ }
+
+ /// Take ownership of the underlying reference.
+ pub fn into_reference(self) -> Reference<'repo> {
+ self.inner
+ }
+
+ /// Delete an existing branch reference.
+ pub fn delete(&mut self) -> Result<(), Error> {
+ unsafe {
+ try_call!(raw::git_branch_delete(self.get().raw()));
+ }
+ Ok(())
+ }
+
+ /// Determine if the current local branch is pointed at by HEAD.
+ pub fn is_head(&self) -> bool {
+ unsafe { raw::git_branch_is_head(&*self.get().raw()) == 1 }
+ }
+
+ /// Move/rename an existing local branch reference.
+ pub fn rename(&mut self, new_branch_name: &str, force: bool) -> Result<Branch<'repo>, Error> {
+ let mut ret = ptr::null_mut();
+ let new_branch_name = CString::new(new_branch_name)?;
+ unsafe {
+ try_call!(raw::git_branch_move(
+ &mut ret,
+ self.get().raw(),
+ new_branch_name,
+ force
+ ));
+ Ok(Branch::wrap(Binding::from_raw(ret)))
+ }
+ }
+
+ /// Return the name of the given local or remote branch.
+ ///
+ /// May return `Ok(None)` if the name is not valid utf-8.
+ pub fn name(&self) -> Result<Option<&str>, Error> {
+ self.name_bytes().map(|s| str::from_utf8(s).ok())
+ }
+
+ /// Return the name of the given local or remote branch.
+ pub fn name_bytes(&self) -> Result<&[u8], Error> {
+ let mut ret = ptr::null();
+ unsafe {
+ try_call!(raw::git_branch_name(&mut ret, &*self.get().raw()));
+ Ok(crate::opt_bytes(self, ret).unwrap())
+ }
+ }
+
+ /// Return the reference supporting the remote tracking branch, given a
+ /// local branch reference.
+ pub fn upstream(&self) -> Result<Branch<'repo>, Error> {
+ let mut ret = ptr::null_mut();
+ unsafe {
+ try_call!(raw::git_branch_upstream(&mut ret, &*self.get().raw()));
+ Ok(Branch::wrap(Binding::from_raw(ret)))
+ }
+ }
+
+ /// Set the upstream configuration for a given local branch.
+ ///
+ /// If `None` is specified, then the upstream branch is unset. The name
+ /// provided is the name of the branch to set as upstream.
+ pub fn set_upstream(&mut self, upstream_name: Option<&str>) -> Result<(), Error> {
+ let upstream_name = crate::opt_cstr(upstream_name)?;
+ unsafe {
+ try_call!(raw::git_branch_set_upstream(
+ self.get().raw(),
+ upstream_name
+ ));
+ Ok(())
+ }
+ }
+}
+
+impl<'repo> Branches<'repo> {
+ /// Creates a new iterator from the raw pointer given.
+ ///
+ /// This function is unsafe as it is not guaranteed that `raw` is a valid
+ /// pointer.
+ pub unsafe fn from_raw(raw: *mut raw::git_branch_iterator) -> Branches<'repo> {
+ Branches {
+ raw,
+ _marker: marker::PhantomData,
+ }
+ }
+}
+
+impl<'repo> Iterator for Branches<'repo> {
+ type Item = Result<(Branch<'repo>, BranchType), Error>;
+ fn next(&mut self) -> Option<Result<(Branch<'repo>, BranchType), Error>> {
+ let mut ret = ptr::null_mut();
+ let mut typ = raw::GIT_BRANCH_LOCAL;
+ unsafe {
+ try_call_iter!(raw::git_branch_next(&mut ret, &mut typ, self.raw));
+ let typ = match typ {
+ raw::GIT_BRANCH_LOCAL => BranchType::Local,
+ raw::GIT_BRANCH_REMOTE => BranchType::Remote,
+ n => panic!("unexected branch type: {}", n),
+ };
+ Some(Ok((Branch::wrap(Binding::from_raw(ret)), typ)))
+ }
+ }
+}
+
+impl<'repo> Drop for Branches<'repo> {
+ fn drop(&mut self) {
+ unsafe { raw::git_branch_iterator_free(self.raw) }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{Branch, BranchType};
+
+ #[test]
+ fn smoke() {
+ let (_td, repo) = crate::test::repo_init();
+ let head = repo.head().unwrap();
+ let target = head.target().unwrap();
+ let commit = repo.find_commit(target).unwrap();
+
+ let mut b1 = repo.branch("foo", &commit, false).unwrap();
+ assert!(!b1.is_head());
+ repo.branch("foo2", &commit, false).unwrap();
+
+ assert_eq!(repo.branches(None).unwrap().count(), 3);
+ repo.find_branch("foo", BranchType::Local).unwrap();
+ let mut b1 = b1.rename("bar", false).unwrap();
+ assert_eq!(b1.name().unwrap(), Some("bar"));
+ assert!(b1.upstream().is_err());
+ b1.set_upstream(Some("main")).unwrap();
+ b1.upstream().unwrap();
+ b1.set_upstream(None).unwrap();
+
+ b1.delete().unwrap();
+ }
+
+ #[test]
+ fn name_is_valid() {
+ assert!(Branch::name_is_valid("foo").unwrap());
+ assert!(!Branch::name_is_valid("").unwrap());
+ assert!(!Branch::name_is_valid("with spaces").unwrap());
+ assert!(!Branch::name_is_valid("~tilde").unwrap());
+ }
+}