show undo diff (#3213)
Exposes undo operations via the C API, and uses those to show a (new, differently formatted) diff before committing the undo.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use crate::depmap::DependencyMap;
|
||||
use crate::errors::Result;
|
||||
use crate::server::{Server, SyncOp};
|
||||
use crate::storage::{Storage, TaskMap};
|
||||
use crate::storage::{ReplicaOp, Storage, TaskMap};
|
||||
use crate::task::{Status, Task};
|
||||
use crate::taskdb::TaskDb;
|
||||
use crate::workingset::WorkingSet;
|
||||
@@ -236,10 +236,15 @@ impl Replica {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Undo local operations until the most recent UndoPoint, returning false if there are no
|
||||
/// Return undo local operations until the most recent UndoPoint, returning an empty Vec if there are no
|
||||
/// local operations to undo.
|
||||
pub fn undo(&mut self) -> Result<bool> {
|
||||
self.taskdb.undo()
|
||||
pub fn get_undo_ops(&mut self) -> Result<Vec<ReplicaOp>> {
|
||||
self.taskdb.get_undo_ops()
|
||||
}
|
||||
|
||||
/// Undo local operations in storage, returning a boolean indicating success.
|
||||
pub fn commit_undo_ops(&mut self, undo_ops: Vec<ReplicaOp>) -> Result<bool> {
|
||||
self.taskdb.commit_undo_ops(undo_ops)
|
||||
}
|
||||
|
||||
/// Rebuild this replica's working set, based on whether tasks are pending or not. If
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::errors::Result;
|
||||
/**
|
||||
This module defines the backend storage used by [`Replica`](crate::Replica).
|
||||
It defines a [trait](crate::storage::Storage) for storage implementations, and provides a default on-disk implementation as well as an in-memory implementation for testing.
|
||||
@@ -5,7 +6,6 @@ It defines a [trait](crate::storage::Storage) for storage implementations, and p
|
||||
Typical uses of this crate do not interact directly with this module; [`StorageConfig`](crate::StorageConfig) is sufficient.
|
||||
However, users who wish to implement their own storage backends can implement the traits defined here and pass the result to [`Replica`](crate::Replica).
|
||||
*/
|
||||
use crate::errors::Result;
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use uuid::Uuid;
|
||||
mod apply;
|
||||
mod snapshot;
|
||||
mod sync;
|
||||
mod undo;
|
||||
pub mod undo;
|
||||
mod working_set;
|
||||
|
||||
/// A TaskDb is the backend for a replica. It manages the storage, operations, synchronization,
|
||||
@@ -114,11 +114,17 @@ impl TaskDb {
|
||||
sync::sync(server, txn.as_mut(), avoid_snapshots)
|
||||
}
|
||||
|
||||
/// Undo local operations until the most recent UndoPoint, returning false if there are no
|
||||
/// Return undo local operations until the most recent UndoPoint, returning an empty Vec if there are no
|
||||
/// local operations to undo.
|
||||
pub fn undo(&mut self) -> Result<bool> {
|
||||
pub fn get_undo_ops(&mut self) -> Result<Vec<ReplicaOp>> {
|
||||
let mut txn = self.storage.txn()?;
|
||||
undo::undo(txn.as_mut())
|
||||
undo::get_undo_ops(txn.as_mut())
|
||||
}
|
||||
|
||||
/// Undo local operations in storage, returning a boolean indicating success.
|
||||
pub fn commit_undo_ops(&mut self, undo_ops: Vec<ReplicaOp>) -> Result<bool> {
|
||||
let mut txn = self.storage.txn()?;
|
||||
undo::commit_undo_ops(txn.as_mut(), undo_ops)
|
||||
}
|
||||
|
||||
/// Get the number of un-synchronized operations in storage, excluding undo
|
||||
|
||||
@@ -1,19 +1,53 @@
|
||||
use super::apply;
|
||||
use crate::errors::Result;
|
||||
use crate::storage::{ReplicaOp, StorageTxn};
|
||||
use log::{debug, trace};
|
||||
use log::{debug, info, trace};
|
||||
|
||||
/// Undo local operations until an UndoPoint.
|
||||
pub(super) fn undo(txn: &mut dyn StorageTxn) -> Result<bool> {
|
||||
let mut applied = false;
|
||||
let mut popped = false;
|
||||
let mut local_ops = txn.operations()?;
|
||||
/// Local operations until the most recent UndoPoint.
|
||||
pub fn get_undo_ops(txn: &mut dyn StorageTxn) -> Result<Vec<ReplicaOp>> {
|
||||
let mut local_ops = txn.operations().unwrap();
|
||||
let mut undo_ops: Vec<ReplicaOp> = Vec::new();
|
||||
|
||||
while let Some(op) = local_ops.pop() {
|
||||
popped = true;
|
||||
if op == ReplicaOp::UndoPoint {
|
||||
break;
|
||||
}
|
||||
undo_ops.push(op);
|
||||
}
|
||||
|
||||
Ok(undo_ops)
|
||||
}
|
||||
|
||||
/// Commit operations to storage, returning a boolean indicating success.
|
||||
pub fn commit_undo_ops(txn: &mut dyn StorageTxn, mut undo_ops: Vec<ReplicaOp>) -> Result<bool> {
|
||||
let mut applied = false;
|
||||
let mut local_ops = txn.operations().unwrap();
|
||||
|
||||
// Add UndoPoint to undo_ops unless this undo will empty the operations database, in which case
|
||||
// there is no UndoPoint.
|
||||
if local_ops.len() > undo_ops.len() {
|
||||
undo_ops.push(ReplicaOp::UndoPoint);
|
||||
}
|
||||
|
||||
// Drop undo_ops iff they're the latest operations.
|
||||
// TODO Support concurrent undo by adding the reverse of undo_ops rather than popping from operations.
|
||||
let old_len = local_ops.len();
|
||||
let undo_len = undo_ops.len();
|
||||
let new_len = old_len - undo_len;
|
||||
let local_undo_ops = &local_ops[new_len..old_len];
|
||||
undo_ops.reverse();
|
||||
if local_undo_ops != undo_ops {
|
||||
info!("Undo failed: concurrent changes to the database occurred.");
|
||||
debug!(
|
||||
"local_undo_ops={:#?}\nundo_ops={:#?}",
|
||||
local_undo_ops, undo_ops
|
||||
);
|
||||
return Ok(applied);
|
||||
}
|
||||
undo_ops.reverse();
|
||||
local_ops.truncate(new_len);
|
||||
|
||||
for op in undo_ops {
|
||||
debug!("Reversing operation {:?}", op);
|
||||
let rev_ops = op.reverse_ops();
|
||||
for op in rev_ops {
|
||||
@@ -23,7 +57,7 @@ pub(super) fn undo(txn: &mut dyn StorageTxn) -> Result<bool> {
|
||||
}
|
||||
}
|
||||
|
||||
if popped {
|
||||
if undo_len != 0 {
|
||||
txn.set_operations(local_ops)?;
|
||||
txn.commit()?;
|
||||
}
|
||||
@@ -87,31 +121,33 @@ mod tests {
|
||||
timestamp,
|
||||
})?;
|
||||
|
||||
assert_eq!(db.operations().len(), 9);
|
||||
assert_eq!(db.operations().len(), 9, "{:#?}", db.operations());
|
||||
|
||||
{
|
||||
let mut txn = db.storage.txn()?;
|
||||
assert!(undo(txn.as_mut())?);
|
||||
}
|
||||
let undo_ops = get_undo_ops(db.storage.txn()?.as_mut())?;
|
||||
assert_eq!(undo_ops.len(), 3, "{:#?}", undo_ops);
|
||||
|
||||
// undo took db back to the snapshot
|
||||
assert_eq!(db.operations().len(), 5);
|
||||
assert_eq!(db.sorted_tasks(), db_state);
|
||||
assert!(commit_undo_ops(db.storage.txn()?.as_mut(), undo_ops)?);
|
||||
|
||||
{
|
||||
let mut txn = db.storage.txn()?;
|
||||
assert!(undo(txn.as_mut())?);
|
||||
}
|
||||
// Note that we've subtracted the length of undo_ops plus one for the UndoPoint.
|
||||
assert_eq!(db.operations().len(), 5, "{:#?}", db.operations());
|
||||
assert_eq!(db.sorted_tasks(), db_state, "{:#?}", db.sorted_tasks());
|
||||
|
||||
// Note that the number of undo operations is equal to the number of operations in the
|
||||
// database here because there are no UndoPoints.
|
||||
let undo_ops = get_undo_ops(db.storage.txn()?.as_mut())?;
|
||||
assert_eq!(undo_ops.len(), 5, "{:#?}", undo_ops);
|
||||
|
||||
assert!(commit_undo_ops(db.storage.txn()?.as_mut(), undo_ops)?);
|
||||
|
||||
// empty db
|
||||
assert_eq!(db.operations().len(), 0);
|
||||
assert_eq!(db.sorted_tasks(), vec![]);
|
||||
assert_eq!(db.operations().len(), 0, "{:#?}", db.operations());
|
||||
assert_eq!(db.sorted_tasks(), vec![], "{:#?}", db.sorted_tasks());
|
||||
|
||||
{
|
||||
let mut txn = db.storage.txn()?;
|
||||
// nothing left to undo, so undo() returns false
|
||||
assert!(!undo(txn.as_mut())?);
|
||||
}
|
||||
let undo_ops = get_undo_ops(db.storage.txn()?.as_mut())?;
|
||||
assert_eq!(undo_ops.len(), 0, "{:#?}", undo_ops);
|
||||
|
||||
// nothing left to undo, so commit_undo_ops() returns false
|
||||
assert!(!commit_undo_ops(db.storage.txn()?.as_mut(), undo_ops)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user