summaryrefslogtreecommitdiffstats
path: root/extra/git2/src/transaction.rs
diff options
context:
space:
mode:
Diffstat (limited to 'extra/git2/src/transaction.rs')
-rw-r--r--extra/git2/src/transaction.rs285
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)
- ))
- }
-}