History

feat: `file::Transaction::rollback()` allows to explicitly roll back a pending change. (#595)

As opposed to dropping the Transaction, this method allows to obtain all edits that would have been applied.

Sebastian Thiel committed 3 years ago

Commit e86e1591 parent 5f7fe692 files · +53 −0Browse files

Unified · Split

2 files
  1. git-ref/src/store/file/transaction/prepare.rs +14 −0
  2. git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs +39 −0
git-ref/src/store/file/transaction/prepare.rs+14−0
@@ -357,6 +357,20 @@ impl<'s, 'p> Transaction<'s, 'p> { · 356 unmodified lines
357357 self.updates = Some(updates);
358358 Ok(self)
359359 }
360+
361+ /// Rollback all intermediate state and return the `RefEdits` as we know them thus far.
362+ ///
363+ /// Note that they have been altered compared to what was initially provided as they have
364+ /// been split and know about their current state on disk.
365+ ///
366+ /// # Note
367+ ///
368+ /// A rollback happens automatically as this instance is dropped as well.
369+ pub fn rollback(self) -> Vec<RefEdit> {
370+ self.updates
371+ .map(|updates| updates.into_iter().map(|u| u.update).collect())
372+ .unwrap_or_default()
373+ }
360374 }
361375
362376 fn possibly_adjust_name_for_prefixes(name: &FullNameRef) -> Option<FullName> {
git-ref/tests/file/transaction/prepare_and_commit/create_or_update.rs+39−0
@@ -68,6 +68,45 @@ fn reference_with_equally_named_empty_or_non_empty_directory_already_in_place_ca · 67 unmodified lines
6868 Ok(())
6969 }
7070
71+#[test]
72+fn intermediate_directories_are_removed_on_rollback() -> crate::Result {
73+ for explicit_rollback in [false, true] {
74+ let (dir, store) = empty_store()?;
75+
76+ let create_at = |name: &str| RefEdit {
77+ change: Change::Update {
78+ log: LogChange::default(),
79+ expected: PreviousValue::MustNotExist,
80+ new: Target::Peeled(git_hash::Kind::Sha1.null()),
81+ },
82+ name: name.try_into().expect("valid"),
83+ deref: false,
84+ };
85+
86+ let transaction = store.transaction().prepare(
87+ [create_at("refs/heads/a/b/ref"), create_at("refs/heads/a/c/ref")],
88+ Fail::Immediately,
89+ Fail::Immediately,
90+ )?;
91+
92+ assert!(
93+ dir.path().join("refs/heads/a/b").exists(),
94+ "lock files have been created in their place to avoid concurrent modification"
95+ );
96+ assert!(dir.path().join("refs/heads/a/c").exists());
97+
98+ if explicit_rollback {
99+ transaction.rollback();
100+ } else {
101+ drop(transaction);
102+ }
103+
104+ assert!(!dir.path().join("refs/heads").exists());
105+ assert!(!dir.path().join("refs").exists(), "we go all in right now and also remove the refs directory. 'git' might not do that, but it's not a problem either");
106+ }
107+ Ok(())
108+}
109+
71110 #[test]
72111 fn reference_with_old_value_must_exist_when_creating_it() -> crate::Result {
73112 let (_keep, store) = empty_store()?;