diff --git a/ChangeLog b/ChangeLog index f2e399a99..eae86e7ef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -256,6 +256,8 @@ + Fixed bug #917, which mis-encoded quotes (thanks to Uli Martens). + Fixed bug #929, which corrected argument handling for aliases (thanks to Uli Martens). + + Fixed bug #932, which fixed change propagation for recurring tasks (thanks to + Jennifer Cormier). # Untracked Bugs, biggest first. + Fixed bug that required the '%YAML' prologue in a YAML import. diff --git a/src/TDB2.cpp b/src/TDB2.cpp index 21d03e014..58811ee41 100644 --- a/src/TDB2.cpp +++ b/src/TDB2.cpp @@ -1500,12 +1500,17 @@ void TDB2::revert () // moves them to the completed.data file. Returns a count of tasks moved. // Now reverts expired waiting tasks to pending. // Now cleans up dangling dependencies. +// +// Possible scenarios: +// - task in pending that needs to be in completed +// - task in completed that needs to be in pending +// - waiting task in pending that needs to be un-waited int TDB2::gc () { context.timer_gc.start (); unsigned long load_start = context.timer_load.total (); - // Allowed as a temporary override. + // Allowed as an override, but not recommended. if (context.config.getBoolean ("gc")) { std::vector pending_tasks = pending.get_tasks (); @@ -1755,6 +1760,36 @@ const std::vector TDB2::siblings (Task& task) return results; } +//////////////////////////////////////////////////////////////////////////////// +const std::vector TDB2::children (Task& task) +{ + std::vector results; + std::string parent = task.get ("uuid"); + + // First load and scan pending. + if (! pending._loaded_tasks) + pending.load_tasks (); + + std::vector ::iterator i; + for (i = pending._tasks.begin (); i != pending._tasks.end (); ++i) + { + // Do not include self in results. + if (i->id != task.id) + { + // Do not include completed or deleted tasks. + if (i->getStatus () != Task::completed && + i->getStatus () != Task::deleted) + { + // If task has the same parent, it is a sibling. + if (i->get ("parent") == parent) + results.push_back (*i); + } + } + } + + return results; +} + //////////////////////////////////////////////////////////////////////////////// std::string TDB2::uuid (int id) { diff --git a/src/TDB2.h b/src/TDB2.h index 2404c79c3..56d2685cc 100644 --- a/src/TDB2.h +++ b/src/TDB2.h @@ -110,6 +110,7 @@ public: bool get (int, Task&); bool get (const std::string&, Task&); const std::vector siblings (Task&); + const std::vector children (Task&); // ID <--> UUID mapping. std::string uuid (int); diff --git a/src/commands/CmdModify.cpp b/src/commands/CmdModify.cpp index 7ad9dc5ee..ea745c2ad 100644 --- a/src/commands/CmdModify.cpp +++ b/src/commands/CmdModify.cpp @@ -110,7 +110,7 @@ int CmdModify::execute (std::string& output) dependencyChainOnModify (before, *task); context.footnote (onProjectChange (before, *task)); - // Delete siblings. + // Task potentially has siblings - modify them. if (task->has ("parent")) { std::vector siblings = context.tdb2.siblings (*task); @@ -131,6 +131,28 @@ int CmdModify::execute (std::string& output) } } } + + // Task potentially has child tasks - modify them. + else if (task->get ("status") == "recurring") + { + std::vector children = context.tdb2.children (*task); + if (children.size () && + confirm (STRING_CMD_MODIFY_RECUR)) + { + std::vector ::iterator child; + for (child = children.begin (); child != children.end (); ++child) + { + Task alternate (*child); + modify_task_description_replace (*child, modifications); + updateRecurrenceMask (*child); + context.tdb2.modify (*child); + dependencyChainOnModify (alternate, *child); + context.footnote (onProjectChange (alternate, *child)); + ++count; + feedback_affected (STRING_CMD_MODIFY_TASK_R, *child); + } + } + } } else { diff --git a/test/bug.360.t b/test/bug.360.t index 7db9aad17..261c36522 100755 --- a/test/bug.360.t +++ b/test/bug.360.t @@ -45,7 +45,7 @@ qx{../src/task rc:bug.rc ls}; # Result: trying to add the project generates an error about removing # recurrence from a task. -my $output = qx{../src/task rc:bug.rc 1 modify project:bar}; +my $output = qx{echo '-- y' | ../src/task rc:bug.rc 1 modify project:bar}; unlike ($output, qr/You cannot remove the recurrence from a recurring task./ms, 'No recurrence removal error'); # Now try to generate the error above via regular means - ie, is it actually diff --git a/test/bug.932.t b/test/bug.932.t new file mode 100755 index 000000000..fa0bd382b --- /dev/null +++ b/test/bug.932.t @@ -0,0 +1,74 @@ +#! /usr/bin/perl +################################################################################ +## taskwarrior - a command line task list manager. +## +## Copyright 2006-2012, 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. +## +## http://www.opensource.org/licenses/mit-license.php +## +################################################################################ + +use strict; +use warnings; +use Test::More tests => 11; + +# Create the rc file. +if (open my $fh, '>', 'bug.rc') +{ + print $fh "data.location=.\n", + "confirmation=off\n"; + close $fh; + ok (-r 'bug.rc', 'Created bug.rc'); +} + +# Bug 932: Modifying recurring task's recurrence period - strange outcome +# - add a recurring task with multiple child tasks +# - modify a child task and test for propagation +# - modify the parent task and test for propagation +qx{../src/task rc:bug.rc add R due:yesterday recur:daily}; +my $output = qx{../src/task rc:bug.rc list}; +like ($output, qr/2.+R/ms, 'Found child 0'); +like ($output, qr/3.+R/ms, 'Found child 1'); +like ($output, qr/4.+R/ms, 'Found child 2'); + +qx{echo '-- y' | ../src/task rc:bug.rc 2 mod project:P}; +$output = qx{../src/task rc:bug.rc list}; +like ($output, qr/2\s+P.+R/ms, 'Found modified child 0'); +like ($output, qr/3\s+P.+R/ms, 'Found modified child 1 (propagated from 0)'); +like ($output, qr/4\s+P.+R/ms, 'Found modified child 2 (propagated from 0)'); + +qx{echo '-- y' | ../src/task rc:bug.rc 1 mod priority:H}; +$output = qx{../src/task rc:bug.rc list}; +like ($output, qr/2\s+P.+H.+R/ms, 'Found modified child 0 (propagated from parent'); +like ($output, qr/3\s+P.+H.+R/ms, 'Found modified child 1 (propagated from parent)'); +like ($output, qr/4\s+P.+H.+R/ms, 'Found modified child 2 (propagated from parent)'); + +# Cleanup. +unlink qw(pending.data completed.data undo.data backlog.data synch.key bug.rc); +ok (! -r 'pending.data' && + ! -r 'completed.data' && + ! -r 'undo.data' && + ! -r 'backlog.data' && + ! -r 'synch.key' && + ! -r 'bug.rc', 'Cleanup'); + +exit 0; + diff --git a/test/dependencies.t b/test/dependencies.t index e3ebaafe2..888620cee 100755 --- a/test/dependencies.t +++ b/test/dependencies.t @@ -122,7 +122,7 @@ qx{../src/task rc:dep.rc add Six recurring due:tomorrow recur:daily}; # [20] qx{../src/task rc:dep.rc ls}; # To force handleRecurrence call. -$output = qx{../src/task rc:dep.rc 6 modify dep:5}; +$output = qx{echo '-- y' | ../src/task rc:dep.rc 6 modify dep:5}; like ($output, qr/Modified \d+ task/, 'dependencies - recurring task depending on another task'); # [21]