Files
taskwarrior-2.x/taskchampion/src/taskdb/undo.rs
2021-12-21 01:12:30 +00:00

118 lines
3.0 KiB
Rust

use super::apply;
use crate::storage::{ReplicaOp, StorageTxn};
use log::{debug, trace};
/// Undo local operations until an UndoPoint.
pub(super) fn undo(txn: &mut dyn StorageTxn) -> anyhow::Result<bool> {
let mut applied = false;
let mut popped = false;
let mut local_ops = txn.operations()?;
while let Some(op) = local_ops.pop() {
popped = true;
if op == ReplicaOp::UndoPoint {
break;
}
debug!("Reversing operation {:?}", op);
let rev_ops = op.reverse_ops();
for op in rev_ops {
trace!("Applying reversed operation {:?}", op);
apply::apply_op(txn, &op)?;
applied = true;
}
}
if popped {
txn.set_operations(local_ops)?;
txn.commit()?;
}
Ok(applied)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::server::SyncOp;
use crate::taskdb::TaskDb;
use chrono::Utc;
use pretty_assertions::assert_eq;
use uuid::Uuid;
#[test]
fn test_apply_create() -> anyhow::Result<()> {
let mut db = TaskDb::new_inmemory();
let uuid1 = Uuid::new_v4();
let uuid2 = Uuid::new_v4();
let timestamp = Utc::now();
// apply a few ops, capture the DB state, make an undo point, and then apply a few more
// ops.
db.apply(SyncOp::Create { uuid: uuid1 })?;
db.apply(SyncOp::Update {
uuid: uuid1,
property: "prop".into(),
value: Some("v1".into()),
timestamp,
})?;
db.apply(SyncOp::Create { uuid: uuid2 })?;
db.apply(SyncOp::Update {
uuid: uuid2,
property: "prop".into(),
value: Some("v2".into()),
timestamp,
})?;
db.apply(SyncOp::Update {
uuid: uuid2,
property: "prop2".into(),
value: Some("v3".into()),
timestamp,
})?;
let db_state = db.sorted_tasks();
db.add_undo_point()?;
db.apply(SyncOp::Delete { uuid: uuid1 })?;
db.apply(SyncOp::Update {
uuid: uuid2,
property: "prop".into(),
value: None,
timestamp,
})?;
db.apply(SyncOp::Update {
uuid: uuid2,
property: "prop2".into(),
value: Some("new-value".into()),
timestamp,
})?;
assert_eq!(db.operations().len(), 9);
{
let mut txn = db.storage.txn()?;
assert!(undo(txn.as_mut())?);
}
// undo took db back to the snapshot
assert_eq!(db.operations().len(), 5);
assert_eq!(db.sorted_tasks(), db_state);
{
let mut txn = db.storage.txn()?;
assert!(undo(txn.as_mut())?);
}
// empty db
assert_eq!(db.operations().len(), 0);
assert_eq!(db.sorted_tasks(), vec![]);
{
let mut txn = db.storage.txn()?;
// nothing left to undo, so undo() returns false
assert!(!undo(txn.as_mut())?);
}
Ok(())
}
}