Transaction: allow writing a transaction to the OpStore without publishing it

It can be useful to write an operation to the `OpStore` without also
making it visible when you load the repo. I had planned to add that
functionality at least for hooks, so the hooks can be run commands
with `jj --at-op=<operation>` and decide whether to publish the
operation. However, the immediate goal is to let us rewrite
`op_heads_store::merge_op_heads()` to use the usual `Transaction`
API. That needs to be able to just write the operation without
publishing it, since the publishing step takes a long, which
`op_heads_store::merge_op_heads()` (its caller, actually) has already
taken.
This commit is contained in:
Martin von Zweigbergk 2021-03-12 15:46:06 -08:00
parent 337b15c98d
commit 27293829d6
2 changed files with 76 additions and 4 deletions

View file

@ -15,6 +15,7 @@
use crate::commit::Commit;
use crate::evolution::MutableEvolution;
use crate::index::MutableIndex;
use crate::op_heads_store::OpHeadsStore;
use crate::op_store;
use crate::operation::Operation;
use crate::repo::{MutableRepo, ReadonlyRepo, RepoRef};
@ -120,7 +121,15 @@ impl<'r> Transaction<'r> {
mut_repo.set_view(data);
}
pub fn commit(mut self) -> Operation {
/// Writes the transaction to the operation store and publishes it.
pub fn commit(self) -> Operation {
self.write().publish()
}
/// Writes the transaction to the operation store, but does not publish it.
/// That means that a repo can be loaded at the operation, but the
/// operation will not be seen when loading the repo at head.
pub fn write(mut self) -> UnpublishedOperation {
let mut_repo = Arc::try_unwrap(self.repo.take().unwrap()).ok().unwrap();
let base_repo = mut_repo.base_repo();
let index_store = base_repo.index_store();
@ -130,9 +139,8 @@ impl<'r> Transaction<'r> {
index_store
.associate_file_with_operation(&index, operation.id())
.unwrap();
base_repo.op_heads_store().update_op_heads(&operation);
self.closed = true;
operation
UnpublishedOperation::new(base_repo.op_heads_store().clone(), operation)
}
pub fn discard(mut self) {
@ -140,10 +148,53 @@ impl<'r> Transaction<'r> {
}
}
impl<'r> Drop for Transaction<'r> {
impl Drop for Transaction<'_> {
fn drop(&mut self) {
if !std::thread::panicking() {
debug_assert!(self.closed, "Transaction was dropped without being closed.");
}
}
}
pub struct UnpublishedOperation {
op_heads_store: Arc<OpHeadsStore>,
operation: Option<Operation>,
closed: bool,
}
impl UnpublishedOperation {
fn new(op_heads_store: Arc<OpHeadsStore>, operation: Operation) -> Self {
UnpublishedOperation {
op_heads_store,
operation: Some(operation),
closed: false,
}
}
pub fn operation(&self) -> &Operation {
self.operation.as_ref().unwrap()
}
pub fn publish(mut self) -> Operation {
let operation = self.operation.take().unwrap();
self.op_heads_store.update_op_heads(&operation);
self.closed = true;
operation
}
pub fn leave_unpublished(mut self) -> Operation {
self.closed = true;
self.operation.take().unwrap()
}
}
impl Drop for UnpublishedOperation {
fn drop(&mut self) {
if !std::thread::panicking() {
debug_assert!(
self.closed,
"UnpublishedOperation was dropped without being closed."
);
}
}
}

View file

@ -27,6 +27,27 @@ fn list_dir(dir: &Path) -> Vec<String> {
.collect()
}
#[test_case(false ; "local store")]
#[test_case(true ; "git store")]
fn test_unpublished_operation(use_git: bool) {
// Test that the operation doesn't get published until that's requested.
let settings = testutils::user_settings();
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
let op_heads_dir = repo.repo_path().join("op_heads");
let op_id0 = repo.view().op_id().clone();
assert_eq!(list_dir(&op_heads_dir), vec![repo.view().op_id().hex()]);
let mut tx1 = repo.start_transaction("transaction 1");
testutils::create_random_commit(&settings, &repo).write_to_transaction(&mut tx1);
let unpublished_op = tx1.write();
let op_id1 = unpublished_op.operation().id().clone();
assert_ne!(op_id1, op_id0);
assert_eq!(list_dir(&op_heads_dir), vec![op_id0.hex()]);
unpublished_op.publish();
assert_eq!(list_dir(&op_heads_dir), vec![op_id1.hex()]);
}
#[test_case(false ; "local store")]
#[test_case(true ; "git store")]
fn test_consecutive_operations(use_git: bool) {