Commit 8ca31100 authored by Bram Schoenmakers's avatar Bram Schoenmakers

Merge remote-tracking branch 'mruwek/revert-subcmds' into style-fixes

parents 0ffb6869 bbd03ee4
...@@ -21,6 +21,8 @@ from datetime import date ...@@ -21,6 +21,8 @@ from datetime import date
from glob import glob from glob import glob
from uuid import uuid4 from uuid import uuid4
from freezegun import freeze_time
from topydo.commands.AddCommand import AddCommand from topydo.commands.AddCommand import AddCommand
from topydo.commands.ArchiveCommand import ArchiveCommand from topydo.commands.ArchiveCommand import ArchiveCommand
from topydo.commands.DeleteCommand import DeleteCommand from topydo.commands.DeleteCommand import DeleteCommand
...@@ -33,7 +35,33 @@ from topydo.lib.TodoList import TodoList ...@@ -33,7 +35,33 @@ from topydo.lib.TodoList import TodoList
from .command_testcase import CommandTest from .command_testcase import CommandTest
# We're searching for 'mock'
# 'mock' was added as 'unittest.mock' in Python 3.3, but PyPy 3 is based on Python 3.2
# pylint: disable=no-name-in-module
try:
from unittest import mock
except ImportError:
import mock
class BackupSimulator(object):
def __init__(self, p_todolist, p_archive, p_timestamp, p_label):
self.backup = ChangeSet(p_todolist, p_archive, p_label)
self.backup.timestamp = p_timestamp
def save(self, p_todolist):
self.backup.save(p_todolist)
def command_executer(p_cmd, p_args, p_todolist, p_archive=None, *params):
command = p_cmd(p_args, p_todolist, *params)
command.execute()
if p_archive:
archive_command = ArchiveCommand(p_todolist, p_archive)
archive_command.execute()
@freeze_time('2015, 11, 06')
class RevertCommandTest(CommandTest): class RevertCommandTest(CommandTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
...@@ -58,14 +86,8 @@ class RevertCommandTest(CommandTest): ...@@ -58,14 +86,8 @@ class RevertCommandTest(CommandTest):
self.archive = TodoList([]) self.archive = TodoList([])
def test_revert01(self): def test_revert01(self):
backup = ChangeSet(p_label=['do 1']) backup = BackupSimulator(self.todolist, self.archive, '1', ['do 1'])
backup.add_todolist(self.todolist) command_executer(DoCommand, ["1"], self.todolist, self.archive, self.out, self.error, None)
backup.add_archive(self.archive)
backup.timestamp = '1'
command = DoCommand(["1"], self.todolist, self.out, self.error, None)
command.execute()
archive_command = ArchiveCommand(self.todolist, self.archive)
archive_command.execute()
self.archive_file.write(self.archive.print_todos()) self.archive_file.write(self.archive.print_todos())
backup.save(self.todolist) backup.save(self.todolist)
...@@ -78,26 +100,22 @@ class RevertCommandTest(CommandTest): ...@@ -78,26 +100,22 @@ class RevertCommandTest(CommandTest):
result = TodoList(self.archive_file.read()).print_todos() result = TodoList(self.archive_file.read()).print_todos()
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: do 1\n")) self.assertTrue(self.output.endswith("Reverted to state before: do 1\n"))
self.assertEqual(result, "") self.assertEqual(result, "")
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz") self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz")
def test_revert02(self): def test_revert02(self):
backup = ChangeSet(self.todolist, self.archive, ['do 1']) backup = BackupSimulator(self.todolist, self.archive, '1', ['do 1'])
backup.timestamp = '1' command_executer(DoCommand, ["1"], self.todolist, self.archive, self.out, self.error, None)
command1 = DoCommand(["1"], self.todolist, self.out, self.error, None)
command1.execute()
archive_command1 = ArchiveCommand(self.todolist, self.archive)
archive_command1.execute()
self.archive_file.write(self.archive.print_todos()) self.archive_file.write(self.archive.print_todos())
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['do Bar']) # Use add_todolist and add_archive to also cover them
backup = ChangeSet(p_label=['do Bar'])
backup.add_todolist(self.todolist)
backup.add_archive(self.archive)
backup.timestamp = '2' backup.timestamp = '2'
command2 = DoCommand(["Bar"], self.todolist, self.out, self.error, None) command_executer(DoCommand, ["Bar"], self.todolist, self.archive, self.out, self.error, None)
command2.execute()
archive_command2 = ArchiveCommand(self.todolist, self.archive)
archive_command2.execute()
self.archive_file.write(self.archive.print_todos()) self.archive_file.write(self.archive.print_todos())
backup.save(self.todolist) backup.save(self.todolist)
...@@ -110,7 +128,7 @@ class RevertCommandTest(CommandTest): ...@@ -110,7 +128,7 @@ class RevertCommandTest(CommandTest):
result = TodoList(self.archive_file.read()).print_todos() result = TodoList(self.archive_file.read()).print_todos()
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: do Bar\n")) self.assertTrue(self.output.endswith("Reverted to state before: do Bar\n"))
self.assertEqual(result, "x {} Foo".format(self.today)) self.assertEqual(result, "x {} Foo".format(self.today))
self.assertEqual(self.todolist.print_todos(), "Bar\nBaz") self.assertEqual(self.todolist.print_todos(), "Bar\nBaz")
...@@ -121,51 +139,39 @@ class RevertCommandTest(CommandTest): ...@@ -121,51 +139,39 @@ class RevertCommandTest(CommandTest):
self.assertEqual(self.errors, "No backup was found for the current state of {}\n".format(config().todotxt())) self.assertEqual(self.errors, "No backup was found for the current state of {}\n".format(config().todotxt()))
def test_revert04(self): @mock.patch('topydo.lib.Config._Config.archive')
def test_revert04(self, mock_archive):
""" Test trimming of the backup_file """ """ Test trimming of the backup_file """
backup = ChangeSet(self.todolist, self.archive, ['add One']) mock_archive.return_value = '' # test for empty archive setting
backup.timestamp = '1' backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command1 = AddCommand(["One"], self.todolist, self.out, self.error, None) command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
command1.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Two']) backup = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
backup.timestamp = '2' command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
command2 = AddCommand(["Two"], self.todolist, self.out, self.error, None)
command2.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Three']) backup = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
backup.timestamp = '3' command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
command3 = AddCommand(["Three"], self.todolist, self.out, self.error, None)
command3.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Four']) backup = BackupSimulator(self.todolist, self.archive, '4', ['add Four'])
backup.timestamp = '4' command_executer(AddCommand, ["Four"], self.todolist, None, self.out, self.error, None)
command4 = AddCommand(["Four"], self.todolist, self.out, self.error, None)
command4.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Five']) backup = BackupSimulator(self.todolist, self.archive, '5', ['add Five'])
backup.timestamp = '5' command_executer(AddCommand, ["Five"], self.todolist, None, self.out, self.error, None)
command5 = AddCommand(["Five"], self.todolist, self.out, self.error, None)
command5.execute()
backup.save(self.todolist) backup.save(self.todolist)
result = len(ChangeSet().backup_dict.keys()) result = len(ChangeSet().backup_dict.keys())
self.assertEqual(result, 6) self.assertEqual(result, 6)
backup = ChangeSet(self.todolist, self.archive, ['add Six']) backup = BackupSimulator(self.todolist, self.archive, '6', ['add Six'])
backup.timestamp = '6' command_executer(AddCommand, ["Six"], self.todolist, None, self.out, self.error, None)
command6 = AddCommand(["Six"], self.todolist, self.out, self.error, None)
command6.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Seven']) backup = BackupSimulator(self.todolist, self.archive, '7', ['add Seven'])
backup.timestamp = '7' command_executer(AddCommand, ["Seven"], self.todolist, None, self.out, self.error, None)
command7 = AddCommand(["Seven"], self.todolist, self.out, self.error, None)
command7.execute()
backup.save(self.todolist) backup.save(self.todolist)
result = len(ChangeSet().backup_dict.keys()) result = len(ChangeSet().backup_dict.keys())
...@@ -183,94 +189,76 @@ class RevertCommandTest(CommandTest): ...@@ -183,94 +189,76 @@ class RevertCommandTest(CommandTest):
self.assertEqual(len(changesets), 4) self.assertEqual(len(changesets), 4)
self.assertEqual(result, []) self.assertEqual(result, [])
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add Seven\n")) self.assertTrue(self.output.endswith("Reverted to state before: add Seven\n"))
def test_revert05(self): def test_revert05(self):
""" Test for possible backup collisions """ """ Test for possible backup collisions """
backup = ChangeSet(self.todolist, self.archive, ['add One']) backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
backup.timestamp = '1' command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
command1 = AddCommand(["One"], self.todolist, self.out, self.error, None)
command1.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Two']) backup = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
backup.timestamp = '2' command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
command2 = AddCommand(["Two"], self.todolist, self.out, self.error, None)
command2.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Three']) backup = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
backup.timestamp = '3' command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
command3 = AddCommand(["Three"], self.todolist, self.out, self.error, None)
command3.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['delete Three']) backup = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
backup.timestamp = '4' command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
command4 = DeleteCommand(["Three"], self.todolist, self.out, self.error, None)
command4.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Four']) backup = BackupSimulator(self.todolist, self.archive, '5', ['add Four'])
backup.timestamp = '5' command_executer(AddCommand, ["Four"], self.todolist, None, self.out, self.error, None)
command4 = AddCommand(["Four"], self.todolist, self.out, self.error, None)
command4.execute()
backup.save(self.todolist) backup.save(self.todolist)
revert_command = RevertCommand([], self.todolist, self.out, self.error, None) revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute() revert_command.execute()
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add Four\n")) self.assertTrue(self.output.endswith("Reverted to state before: add Four\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One\n{t} Two".format(t=self.today)) self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One\n{t} Two".format(t=self.today))
revert_command = RevertCommand([], self.todolist, self.out, self.error, None) revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute() revert_command.execute()
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: delete Three\n")) self.assertTrue(self.output.endswith("Reverted to state before: delete Three\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One\n{t} Two\n{t} Three".format(t=self.today)) self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One\n{t} Two\n{t} Three".format(t=self.today))
revert_command = RevertCommand([], self.todolist, self.out, self.error, None) revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute() revert_command.execute()
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add Three\n")) self.assertTrue(self.output.endswith("Reverted to state before: add Three\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One\n{t} Two".format(t=self.today)) self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One\n{t} Two".format(t=self.today))
revert_command = RevertCommand([], self.todolist, self.out, self.error, None) revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute() revert_command.execute()
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add Two\n")) self.assertTrue(self.output.endswith("Reverted to state before: add Two\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One".format(t=self.today)) self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n{t} One".format(t=self.today))
revert_command = RevertCommand([], self.todolist, self.out, self.error, None) revert_command = RevertCommand([], self.todolist, self.out, self.error, None)
revert_command.execute() revert_command.execute()
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Successfully reverted: add One\n")) self.assertTrue(self.output.endswith("Reverted to state before: add One\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz") self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz")
def test_revert06(self): def test_revert06(self):
""" Test attempt of deletion with non-existing backup key""" """ Test attempt of deletion with non-existing backup key"""
backup = ChangeSet(self.todolist, self.archive, ['add One']) backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
backup.timestamp = '1' command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
command1 = AddCommand(["One"], self.todolist, self.out, self.error, None)
command1.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Two']) backup = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
backup.timestamp = '2' command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
command2 = AddCommand(["Two"], self.todolist, self.out, self.error, None)
command2.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['add Three']) backup = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
backup.timestamp = '3' command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
command3 = AddCommand(["Three"], self.todolist, self.out, self.error, None)
command3.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet(self.todolist, self.archive, ['delete Three']) backup = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
backup.timestamp = '4' command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
command4 = DeleteCommand(["Three"], self.todolist, self.out, self.error, None)
command4.execute()
backup.save(self.todolist) backup.save(self.todolist)
backup = ChangeSet() backup = ChangeSet()
...@@ -289,8 +277,7 @@ class RevertCommandTest(CommandTest): ...@@ -289,8 +277,7 @@ class RevertCommandTest(CommandTest):
""" Test backup when no archive file is set """ """ Test backup when no archive file is set """
backup = ChangeSet(self.todolist, None, ['add One']) backup = ChangeSet(self.todolist, None, ['add One'])
backup.timestamp = '1' backup.timestamp = '1'
command1 = AddCommand(["One"], self.todolist, self.out, self.error, None) command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
command1.execute()
backup.save(self.todolist) backup.save(self.todolist)
changesets = list(backup.backup_dict.keys()) changesets = list(backup.backup_dict.keys())
...@@ -299,6 +286,99 @@ class RevertCommandTest(CommandTest): ...@@ -299,6 +286,99 @@ class RevertCommandTest(CommandTest):
self.assertEqual(len(changesets), 1) self.assertEqual(len(changesets), 1)
self.assertEqual(self.errors, "") self.assertEqual(self.errors, "")
def test_revert_ls(self):
backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '5', ['add Four'])
command_executer(AddCommand, ["Four"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
command_executer(RevertCommand, ['ls'], self.todolist, None, self.out, self.error, None)
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith(""" 1| 1970-01-01 00:00:05 | add Four
2| 1970-01-01 00:00:04 | delete Three
3| 1970-01-01 00:00:03 | add Three
4| 1970-01-01 00:00:02 | add Two
5| 1970-01-01 00:00:01 | add One\n"""))
def test_revert_08(self):
backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '5', ['add Four'])
command_executer(AddCommand, ["Four"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
command_executer(RevertCommand, ['3'], self.todolist, None, self.out, self.error, None)
self.assertEqual(self.errors, "")
self.assertTrue(self.output.endswith("Reverted to state before: add Three\n"))
self.assertEqual(self.todolist.print_todos(), "Foo\nBar\nBaz\n2015-11-06 One\n2015-11-06 Two")
def test_revert_invalid(self):
""" Test invalid input for revert. """
command_executer(RevertCommand, ["foo"], self.todolist, None, self.out, self.error, None)
command_executer(RevertCommand, ["ls", "foo"], self.todolist, None, self.out, self.error, None)
command_executer(RevertCommand, ["1", "foo"], self.todolist, None, self.out, self.error, None)
usage_text = RevertCommand([], self.todolist).usage() + '\n'
self.assertEqual(self.errors, usage_text*3)
def test_revert_out_of_range(self):
command_executer(RevertCommand, ["158"], self.todolist, None, self.out, self.error, None)
self.assertEqual(self.errors, "Specified index is out range\n")
def test_revert_no_todolist(self):
""" Test attempt of revert with todolist missing """
backup = BackupSimulator(self.todolist, self.archive, '1', ['add One'])
command_executer(AddCommand, ["One"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '2', ['add Two'])
command_executer(AddCommand, ["Two"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '3', ['add Three'])
command_executer(AddCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
backup = BackupSimulator(self.todolist, self.archive, '4', ['delete Three'])
command_executer(DeleteCommand, ["Three"], self.todolist, None, self.out, self.error, None)
backup.save(self.todolist)
command_executer(RevertCommand, ['1'], None, None, self.out, self.error, None)
result = len(ChangeSet().backup_dict.keys())
self.assertEqual(result, 4)
def test_backup_config01(self): def test_backup_config01(self):
config(p_overrides={('topydo', 'backup_count'): '1'}) config(p_overrides={('topydo', 'backup_count'): '1'})
......
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <bram@topydo.org> # Copyright (C) 2014 - 2017 Bram Schoenmakers <bram@topydo.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -14,44 +14,119 @@ ...@@ -14,44 +14,119 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import arrow
from topydo.lib import TodoFile, TodoList from topydo.lib import TodoFile, TodoList
from topydo.lib.ChangeSet import ChangeSet from topydo.lib.ChangeSet import ChangeSet
from topydo.lib.Command import Command from topydo.lib.Command import Command, InvalidCommandArgument
from topydo.lib.Config import config from topydo.lib.Config import config
class RevertCommand(Command): class RevertCommand(Command):
def __init__(self, p_args, p_todolist, #pragma: no branch def __init__(self, p_args, p_todolist, # pragma: no branch
p_out=lambda a: None, p_out=lambda a: None,
p_err=lambda a: None, p_err=lambda a: None,
p_prompt=lambda a: None): p_prompt=lambda a: None):
super().__init__(p_args, p_todolist, p_out, p_err, super().__init__(p_args, p_todolist, p_out, p_err, p_prompt)
p_prompt)
self._backup = None
self._archive_file = None
self._archive = None
def execute(self): def execute(self):
if not super().execute(): if not super().execute():
return False return False
archive_file = TodoFile.TodoFile(config().archive()) self._backup = ChangeSet()
archive = TodoList.TodoList(archive_file.read()) archive_path = config().archive()
if archive_path:
self._archive_file = TodoFile.TodoFile(config().archive())
self._archive = TodoList.TodoList(self._archive_file.read())
if len(self.args) > 1:
self.error(self.usage())
else:
try:
arg = self.argument(0)
self._handle_args(arg)
except InvalidCommandArgument:
try:
self._revert_last()
except (ValueError, KeyError):
self.error('No backup was found for the current state of '
+ config().todotxt())
self._backup.close()
def _revert(self, p_timestamp=None):
self._backup.read_backup(self.todolist, p_timestamp)
self._backup.apply(self.todolist, self._archive)
last_change = ChangeSet() if self._archive:
self._archive_file.write(self._archive.print_todos())
self.out("Reverted to state before: " + self._backup.label)
def _revert_last(self):
self._revert()
self._backup.delete()
def _revert_to_specific(self, p_position):
timestamps = [timestamp for timestamp, _ in self._backup]
position = int(p_position) - 1 # numbering in UI starts with 1
try: try:
last_change.get_backup(self.todolist) timestamp = timestamps[position]
last_change.apply(self.todolist, archive) self._revert(timestamp)
archive_file.write(archive.print_todos()) for timestamp in timestamps[:position + 1]:
last_change.delete() self._backup.read_backup(p_timestamp=timestamp)
self._backup.delete()
except IndexError:
self.error('Specified index is out range')
self.out("Successfully reverted: " + last_change.label) def _handle_args(self, p_arg):
except (ValueError, KeyError): try:
self.error('No backup was found for the current state of ' + config().todotxt()) if p_arg == 'ls':
self._handle_ls()
elif p_arg.isdigit():
self._revert_to_specific(p_arg)
else:
raise InvalidCommandArgument
except InvalidCommandArgument:
self.error(self.usage())
last_change.close() def _handle_ls(self):
num = 1
for timestamp, change in self._backup:
label = change[2]
time = arrow.get(timestamp).format('YYYY-MM-DD HH:mm:ss')
self.out('{0: >3}| {1} | {2}'.format(str(num), time, label))
num += 1
def usage(self): def usage(self):
return """Synopsis: revert""" return """Synopsis:
revert [ls]
revert [NUMBER]"""
def help(self): def help(self):
return """Reverts the last command.""" return """\
Reverts last commands.
* ls : Lists all backups ordered and numbered chronologically (starting
with 1 for latest backup).
* [NUMBER] : revert to specific point in history specified by NUMBER.
Output example for `revert ls`:
1 | 1970-01-01 00:00:02 | add Baz
2 | 1970-01-01 00:00:01 | add Bar
3 | 1970-01-01 00:00:00 | add Foo
In such example executing `revert 2` will revert todo and archive files to the
state before execution of `add Bar`.
* `revert` without any further arguments will revert to the latest backup
available, provided that this backup matches current state of the todo file.
Topydo will refuse to revert, if any changes to todo file were made by
external application after the latest backup. To force a `revert` action use
it with a NUMBER.\
"""
# Topydo - A todo.txt client written in Python. # Topydo - A todo.txt client written in Python.
# Copyright (C) 2014 - 2015 Bram Schoenmakers <bram@topydo.org> # Copyright (C) 2014 - 2017 Bram Schoenmakers <bram@topydo.org>
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
...@@ -46,7 +46,7 @@ class ChangeSet(object): ...@@ -46,7 +46,7 @@ class ChangeSet(object):
def __init__(self, p_todolist=None, p_archive=None, p_label=None): def __init__(self, p_todolist=None, p_archive=None, p_label=None):
self.todolist = deepcopy(p_todolist) self.todolist = deepcopy(p_todolist)
self.archive = deepcopy(p_archive) self.archive = deepcopy(p_archive)
self.timestamp = str(int(time.time())) self.timestamp = str(time.time())
self.label = ' '.join(p_label if p_label else []) self.label = ' '.join(p_label if p_label else [])
try: try:
...@@ -56,6 +56,11 @@ class ChangeSet(object): ...@@ -56,6 +56,11 @@ class ChangeSet(object):
self._read() self._read()
def __iter__(self):
items = {key: self.backup_dict[key]
for key in self.backup_dict if key != 'index'}.items()
return iter(sorted(items, reverse=True))
def _read(self): def _read(self):
""" """
Reads backup file from json_file property and sets backup_dict property Reads backup file from json_file property and sets backup_dict property
...@@ -158,15 +163,18 @@ class ChangeSet(object): ...@@ -158,15 +163,18 @@ class ChangeSet(object):
for changeset in index[backup_limit:]: for changeset in index[backup_limit:]:
self.delete(changeset[0], p_write=False) self.delete(changeset[0], p_write=False)
def get_backup(self, p_todolist): def read_backup(self, p_todolist=None, p_timestamp=None):
""" """
Retrieves a backup for p_todolist from backup file and sets todolist, Retrieves a backup for p_timestamp or p_todolist (if p_timestamp is not
archive and label attributes to appropriate data from it. specified) from backup file and sets timestamp, todolist, archive and
label attributes to appropriate data from it.
""" """
if not p_timestamp:
change_hash = hash_todolist(p_todolist) change_hash = hash_todolist(p_todolist)
index = self._get_index() index = self._get_index()
self.timestamp = index[[change[1] for change in index].index(change_hash)][0] self.timestamp = index[[change[1] for change in index].index(change_hash)][0]
else:
self.timestamp = p_timestamp
d = self.backup_dict[self.timestamp] d = self.backup_dict[self.timestamp]
...@@ -176,10 +184,10 @@ class ChangeSet(object): ...@@ -176,10 +184,10 @@ class ChangeSet(object):
def apply(self, p_todolist, p_archive): def apply(self, p_todolist, p_archive):
""" Applies backup on supplied p_todolist. """ """ Applies backup on supplied p_todolist. """
if self.todolist: if self.todolist and p_todolist:
p_todolist.replace(self.todolist.todos()) p_todolist.replace(self.todolist.todos())
if self.archive: if self.archive and p_archive:
p_archive.replace(self.archive.todos()) p_archive.replace(self.archive.todos())
def close(self): def close(self):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment