diff --git a/doc/man/task.1.in b/doc/man/task.1.in index bb1c55db7..1468befe0 100644 --- a/doc/man/task.1.in +++ b/doc/man/task.1.in @@ -401,15 +401,6 @@ Modifies the existing task with provided information. .B task prepend Prepends description text to an existing task. Is affected by the context. -.TP -.B task purge -Permanently removes the specified tasks from the data files. Only -tasks that are already deleted can be purged. This command has a -local-only effect and changes introduced by it are not synced. -Is affected by the context. - -Warning: causes permanent, non-revertible loss of data. - .TP .B task start Marks the specified tasks as started. Is affected by the context. @@ -1200,7 +1191,6 @@ active context. Here is a list of the commands that are affected: log prepend projects - purge start stats stop diff --git a/src/TDB2.cpp b/src/TDB2.cpp index 89c146bfe..ffee3fc81 100644 --- a/src/TDB2.cpp +++ b/src/TDB2.cpp @@ -654,13 +654,6 @@ void TDB2::modify (Task& task, bool add_to_backlog /* = true */) update (task, add_to_backlog); } -//////////////////////////////////////////////////////////////////////////////// -void TDB2::purge (Task& task) -{ - // Delete the task from completed.data - completed.purge_task (task); -} - //////////////////////////////////////////////////////////////////////////////// void TDB2::update ( Task& task, diff --git a/src/TDB2.h b/src/TDB2.h index 755538fa7..979342bbf 100644 --- a/src/TDB2.h +++ b/src/TDB2.h @@ -112,7 +112,6 @@ public: void set_location (const std::string&); void add (Task&, bool add_to_backlog = true); void modify (Task&, bool add_to_backlog = true); - void purge (Task&); void commit (); void get_changes (std::vector &); void revert (); diff --git a/src/commands/CmdPurge.cpp b/src/commands/CmdPurge.cpp index 6bf494177..c54a77515 100644 --- a/src/commands/CmdPurge.cpp +++ b/src/commands/CmdPurge.cpp @@ -27,17 +27,13 @@ #include #include #include -#include -#include -#include -#include //////////////////////////////////////////////////////////////////////////////// CmdPurge::CmdPurge () { _keyword = "purge"; _usage = "task purge"; - _description = "Removes the specified tasks from the data files. Causes permanent loss of data."; + _description = "(deprecated; does nothing)"; _read_only = false; _displays_id = false; _needs_confirm = true; @@ -49,131 +45,11 @@ CmdPurge::CmdPurge () _category = Command::Category::operation; } -//////////////////////////////////////////////////////////////////////////////// -// Purges the task, while taking care of: -// - dependencies on this task -// - child tasks -void CmdPurge::purgeTask (Task& task, int& count) -{ - Context::getContext ().tdb2.purge (task); - handleDeps (task); - handleChildren (task, count); - count++; -} - -//////////////////////////////////////////////////////////////////////////////// -// Makes sure that any task having the dependency on the task being purged -// has that dependency removed, to preserve referential integrity. -void CmdPurge::handleDeps (Task& task) -{ - std::string uuid = task.get ("uuid"); - - for (auto& blockedConst: Context::getContext ().tdb2.all_tasks ()) - { - Task& blocked = const_cast(blockedConst); - if (blocked.hasDependency (uuid)) - { - blocked.removeDependency (uuid); - Context::getContext ().tdb2.modify (blocked); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// Makes sure that with any recurrence parent are all the child tasks removed -// as well. If user chooses not to, the whole command is aborted. -void CmdPurge::handleChildren (Task& task, int& count) -{ - // If this is not a recurrence parent, we have no job here - if (!task.has ("mask")) - return; - - std::string uuid = task.get ("uuid"); - std::vector children; - - // Find all child tasks - for (auto& childConst: Context::getContext ().tdb2.all_tasks ()) - { - Task& child = const_cast (childConst); - - if (child.get ("parent") == uuid) - { - if (child.getStatus () != Task::deleted) - // In case any child task is not deleted, bail out - throw format ("Task '{1}' is a recurrence template. Its child task {2} must be deleted before it can be purged.", - task.get ("description"), - child.identifier (true)); - else - children.push_back (child); - } - } - - // If there are no children, our job is done - if (children.empty ()) - return; - - // Ask for confirmation to purge them, if needed - std::string question = format ("Task '{1}' is a recurrence template. All its {2} deleted children tasks will be purged as well. Continue?", - task.get ("description"), - children.size ()); - - if (Context::getContext ().config.getBoolean ("recurrence.confirmation") || - (Context::getContext ().config.get ("recurrence.confirmation") == "prompt" - && confirm (question))) - { - for (auto& child: children) - purgeTask (child, count); - } - else - throw std::string ("Purge operation aborted."); -} - - //////////////////////////////////////////////////////////////////////////////// int CmdPurge::execute (std::string&) { - int rc = 0; - int count = 0; - bool matched_deleted = false; - - Filter filter; - std::vector filtered; - - // Apply filter. - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); - return 1; - } - - for (auto& task : filtered) - { - // Allow purging of deleted tasks only. Hence no need to deal with: - // - unblocked tasks notifications (deleted tasks are not blocking) - // - project changes (deleted tasks not included in progress) - // It also has the nice property of being explicit - users need to - // mark tasks as deleted before purging. - if (task.getStatus () == Task::deleted) - { - // Mark that at least one deleted task matched the filter - matched_deleted = true; - - std::string question; - question = format ("Permanently remove task {1} '{2}'?", - task.identifier (true), - task.get ("description")); - - if (permission (question, filtered.size ())) - purgeTask (task, count); - } - } - - if (filtered.size () > 0 and ! matched_deleted) - Context::getContext ().footnote ("No deleted tasks specified. Maybe you forgot to delete tasks first?"); - - feedback_affected (count == 1 ? "Purged {1} task." : "Purged {1} tasks.", count); - return rc; + Context::getContext ().footnote ("As of version 3.0, this command has no effect."); + return 0; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdPurge.h b/src/commands/CmdPurge.h index 208c52f90..96db2977d 100644 --- a/src/commands/CmdPurge.h +++ b/src/commands/CmdPurge.h @@ -32,10 +32,6 @@ class CmdPurge : public Command { -private: - void purgeTask (Task& task, int& count); - void handleChildren (Task& task, int& count); - void handleDeps (Task& task); public: CmdPurge (); int execute (std::string&); diff --git a/test/purge.t b/test/purge.t deleted file mode 100755 index deded3249..000000000 --- a/test/purge.t +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -############################################################################### -# -# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included -# in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# -# https://www.opensource.org/licenses/mit-license.php -# -############################################################################### - -import sys -import os -import unittest -# Ensure python finds the local simpletap module -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from basetest import Task, TestCase - - -class TestDelete(TestCase): - def setUp(self): - self.t = Task() - - def test_add_delete_purge(self): - """Verify that add/delete/purge successfully purges a task""" - self.t("add one") - uuid = self.t("_get 1.uuid")[1].strip() - - code, out, err = self.t("1 delete", input="y\n") - self.assertIn("Deleted 1 task.", out) - - code, out, err = self.t(uuid + " purge", input="y\n") - self.assertIn("Purged 1 task.", out) - - code, out, err = self.t("uuids") - self.assertNotIn(uuid, out) - - def test_purge_remove_deps(self): - """Purge command removes broken dependency references""" - self.t("add one") - self.t("add two dep:1") - uuid = self.t("_get 1.uuid")[1].strip() - - code, out, err = self.t("1 delete", input="y\n") - self.assertIn("Deleted 1 task.", out) - - code, out, err = self.t(uuid + " purge", input="y\n") - self.assertIn("Purged 1 task.", out) - - code, out, err = self.t("uuids") - self.assertNotIn(uuid, out) - - dependencies = self.t("_get 1.depends")[1].strip() - self.assertNotIn(uuid, dependencies) - - def test_purge_children(self): - """Purge command indirectly purges child tasks""" - self.t("add one recur:daily due:yesterday") - uuid = self.t("_get 1.uuid")[1].strip() - - # A dummy call to report, so that recurrence tasks get generated - self.t("list") - - code, out, err = self.t("1 delete", input="y\ny\n") - self.assertIn("Deleted 4 tasks.", out) - - code, out, err = self.t(uuid + " purge", input="y\ny\n") - self.assertIn("Purged 4 tasks.", out) - - code, out, err = self.t("uuids") - self.assertEqual('\n', out) - - def test_purge_children_fail_pending(self): - """Purge aborts if task has pending children""" - self.t("add one recur:daily due:yesterday") - uuid = self.t("_get 1.uuid")[1].strip() - - # A dummy call to report, so that recurrence tasks get generated - self.t("list") - - code, out, err = self.t("1 delete", input="y\nn\n") - self.assertIn("Deleted 1 task.", out) - - code, out, err = self.t.runError(uuid + " purge", input="y\n") - self.assertIn("child task 1 must be deleted before", err) - - # Check that nothing was purged - code, out, err = self.t("count") - self.assertEqual('4\n', out) - - def test_purge_children_fail_confirm(self): - """Purge aborts if user does not agree with it affecting child tasks""" - self.t("add one recur:daily due:yesterday") - uuid = self.t("_get 1.uuid")[1].strip() - - # A dummy call to report, so that recurrence tasks get generated - self.t("list") - - code, out, err = self.t("1 delete", input="y\ny\n") - self.assertIn("Deleted 4 tasks.", out) - - # Do not agree with purging of the child tasks - code, out, err = self.t.runError(uuid + " purge", input="y\nn\n") - self.assertIn("Purge operation aborted.", err) - - # Check that nothing was purged - code, out, err = self.t("count") - self.assertEqual('4\n', out) - - def test_purge_children(self): - """Purge command removes dependencies on indirectly purged tasks""" - self.t("add one recur:daily due:yesterday") - uuid = self.t("_get 1.uuid")[1].strip() - - # A dummy call to report, so that recurrence tasks get generated - self.t("list") - self.t("add two dep:4") - - # Check that the dependency is present - dependencies = self.t("_get 5.depends")[1].strip() - self.assertNotEqual("", dependencies) - - code, out, err = self.t("1 delete", input="y\ny\n") - self.assertIn("Deleted 4 tasks.", out) - - code, out, err = self.t(uuid + " purge", input="y\ny\n") - self.assertIn("Purged 4 tasks.", out) - - # Make sure we are dealing with the intended task - description = self.t("_get 1.description")[1].strip() - self.assertEqual("two", description) - - # Check that the dependency was removed - dependencies = self.t("_get 1.depends")[1].strip() - self.assertEqual("", dependencies) - -if __name__ == "__main__": - from simpletap import TAPTestRunner - unittest.main(testRunner=TAPTestRunner()) - -# vim: ai sts=4 et sw=4 ft=python