diff options
Diffstat (limited to 'extra/git2/src/transaction.rs')
-rw-r--r-- | extra/git2/src/transaction.rs | 285 |
1 files changed, 0 insertions, 285 deletions
diff --git a/extra/git2/src/transaction.rs b/extra/git2/src/transaction.rs deleted file mode 100644 index 4f661f1d4..000000000 --- a/extra/git2/src/transaction.rs +++ /dev/null @@ -1,285 +0,0 @@ -use std::ffi::CString; -use std::marker; - -use crate::{raw, util::Binding, Error, Oid, Reflog, Repository, Signature}; - -/// A structure representing a transactional update of a repository's references. -/// -/// Transactions work by locking loose refs for as long as the [`Transaction`] -/// is held, and committing all changes to disk when [`Transaction::commit`] is -/// called. Note that committing is not atomic: if an operation fails, the -/// transaction aborts, but previous successful operations are not rolled back. -pub struct Transaction<'repo> { - raw: *mut raw::git_transaction, - _marker: marker::PhantomData<&'repo Repository>, -} - -impl Drop for Transaction<'_> { - fn drop(&mut self) { - unsafe { raw::git_transaction_free(self.raw) } - } -} - -impl<'repo> Binding for Transaction<'repo> { - type Raw = *mut raw::git_transaction; - - unsafe fn from_raw(ptr: *mut raw::git_transaction) -> Transaction<'repo> { - Transaction { - raw: ptr, - _marker: marker::PhantomData, - } - } - - fn raw(&self) -> *mut raw::git_transaction { - self.raw - } -} - -impl<'repo> Transaction<'repo> { - /// Lock the specified reference by name. - pub fn lock_ref(&mut self, refname: &str) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - unsafe { - try_call!(raw::git_transaction_lock_ref(self.raw, refname)); - } - - Ok(()) - } - - /// Set the target of the specified reference. - /// - /// The reference must have been locked via `lock_ref`. - /// - /// If `reflog_signature` is `None`, the [`Signature`] is read from the - /// repository config. - pub fn set_target( - &mut self, - refname: &str, - target: Oid, - reflog_signature: Option<&Signature<'_>>, - reflog_message: &str, - ) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - let reflog_message = CString::new(reflog_message).unwrap(); - unsafe { - try_call!(raw::git_transaction_set_target( - self.raw, - refname, - target.raw(), - reflog_signature.map(|s| s.raw()), - reflog_message - )); - } - - Ok(()) - } - - /// Set the target of the specified symbolic reference. - /// - /// The reference must have been locked via `lock_ref`. - /// - /// If `reflog_signature` is `None`, the [`Signature`] is read from the - /// repository config. - pub fn set_symbolic_target( - &mut self, - refname: &str, - target: &str, - reflog_signature: Option<&Signature<'_>>, - reflog_message: &str, - ) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - let target = CString::new(target).unwrap(); - let reflog_message = CString::new(reflog_message).unwrap(); - unsafe { - try_call!(raw::git_transaction_set_symbolic_target( - self.raw, - refname, - target, - reflog_signature.map(|s| s.raw()), - reflog_message - )); - } - - Ok(()) - } - - /// Add a [`Reflog`] to the transaction. - /// - /// This commit the in-memory [`Reflog`] to disk when the transaction commits. - /// Note that atomicity is **not* guaranteed: if the transaction fails to - /// modify `refname`, the reflog may still have been committed to disk. - /// - /// If this is combined with setting the target, that update won't be - /// written to the log (i.e. the `reflog_signature` and `reflog_message` - /// parameters will be ignored). - pub fn set_reflog(&mut self, refname: &str, reflog: Reflog) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - unsafe { - try_call!(raw::git_transaction_set_reflog( - self.raw, - refname, - reflog.raw() - )); - } - - Ok(()) - } - - /// Remove a reference. - /// - /// The reference must have been locked via `lock_ref`. - pub fn remove(&mut self, refname: &str) -> Result<(), Error> { - let refname = CString::new(refname).unwrap(); - unsafe { - try_call!(raw::git_transaction_remove(self.raw, refname)); - } - - Ok(()) - } - - /// Commit the changes from the transaction. - /// - /// The updates will be made one by one, and the first failure will stop the - /// processing. - pub fn commit(self) -> Result<(), Error> { - unsafe { - try_call!(raw::git_transaction_commit(self.raw)); - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use crate::{Error, ErrorClass, ErrorCode, Oid, Repository}; - - #[test] - fn smoke() { - let (_td, repo) = crate::test::repo_init(); - - let mut tx = t!(repo.transaction()); - - t!(tx.lock_ref("refs/heads/main")); - t!(tx.lock_ref("refs/heads/next")); - - t!(tx.set_target("refs/heads/main", Oid::zero(), None, "set main to zero")); - t!(tx.set_symbolic_target( - "refs/heads/next", - "refs/heads/main", - None, - "set next to main", - )); - - t!(tx.commit()); - - assert_eq!(repo.refname_to_id("refs/heads/main").unwrap(), Oid::zero()); - assert_eq!( - repo.find_reference("refs/heads/next") - .unwrap() - .symbolic_target() - .unwrap(), - "refs/heads/main" - ); - } - - #[test] - fn locks_same_repo_handle() { - let (_td, repo) = crate::test::repo_init(); - - let mut tx1 = t!(repo.transaction()); - t!(tx1.lock_ref("refs/heads/seen")); - - let mut tx2 = t!(repo.transaction()); - assert!(matches!(tx2.lock_ref("refs/heads/seen"), Err(e) if e.code() == ErrorCode::Locked)) - } - - #[test] - fn locks_across_repo_handles() { - let (td, repo1) = crate::test::repo_init(); - let repo2 = t!(Repository::open(&td)); - - let mut tx1 = t!(repo1.transaction()); - t!(tx1.lock_ref("refs/heads/seen")); - - let mut tx2 = t!(repo2.transaction()); - assert!(matches!(tx2.lock_ref("refs/heads/seen"), Err(e) if e.code() == ErrorCode::Locked)) - } - - #[test] - fn drop_unlocks() { - let (_td, repo) = crate::test::repo_init(); - - let mut tx = t!(repo.transaction()); - t!(tx.lock_ref("refs/heads/seen")); - drop(tx); - - let mut tx2 = t!(repo.transaction()); - t!(tx2.lock_ref("refs/heads/seen")) - } - - #[test] - fn commit_unlocks() { - let (_td, repo) = crate::test::repo_init(); - - let mut tx = t!(repo.transaction()); - t!(tx.lock_ref("refs/heads/seen")); - t!(tx.commit()); - - let mut tx2 = t!(repo.transaction()); - t!(tx2.lock_ref("refs/heads/seen")); - } - - #[test] - fn prevents_non_transactional_updates() { - let (_td, repo) = crate::test::repo_init(); - let head = t!(repo.refname_to_id("HEAD")); - - let mut tx = t!(repo.transaction()); - t!(tx.lock_ref("refs/heads/seen")); - - assert!(matches!( - repo.reference("refs/heads/seen", head, true, "competing with lock"), - Err(e) if e.code() == ErrorCode::Locked - )); - } - - #[test] - fn remove() { - let (_td, repo) = crate::test::repo_init(); - let head = t!(repo.refname_to_id("HEAD")); - let next = "refs/heads/next"; - - t!(repo.reference( - next, - head, - true, - "refs/heads/next@{0}: branch: Created from HEAD" - )); - - { - let mut tx = t!(repo.transaction()); - t!(tx.lock_ref(next)); - t!(tx.remove(next)); - t!(tx.commit()); - } - assert!(matches!(repo.refname_to_id(next), Err(e) if e.code() == ErrorCode::NotFound)) - } - - #[test] - fn must_lock_ref() { - let (_td, repo) = crate::test::repo_init(); - - // 🤷 - fn is_not_locked_err(e: &Error) -> bool { - e.code() == ErrorCode::NotFound - && e.class() == ErrorClass::Reference - && e.message() == "the specified reference is not locked" - } - - let mut tx = t!(repo.transaction()); - assert!(matches!( - tx.set_target("refs/heads/main", Oid::zero(), None, "set main to zero"), - Err(e) if is_not_locked_err(&e) - )) - } -} |