- Fixed bug #1022, where dependencies were note released when a blocking task
  was completed (thanks to Arkady Grudzinsky).
- The Task object now caches ::is_blocked and ::is_blocking Booleans that are
  determined on pending.data load.
- Simplified and sped up color rule processing using cached values, reducing
  the number of map lookups, and removed loop invariants when the rules are
  not defined.
- Simplified urgency calculations given the cached values for blocked/blocking.
- On load, pending.data is scanned for accurate blocked/blocking status
  determination.
- Obsoleted and removed complex single-task dependency calculations.
- Sped up 'nag' processing by using cached values..
- Modified the 'show' command to consider color.blocking to be valid.
- Added default config value for color.blocking, and included it in the
  precedence list ahead of blocked, as it is more important.
- Updated taskrc.5 man page to include the new color.blocking rule, and its
  place in the rule precedence.
This commit is contained in:
Paul Beckingham 2012-07-09 01:18:11 -04:00
parent 02053f7300
commit 79e2c591f1
14 changed files with 182 additions and 178 deletions

View file

@ -98,6 +98,8 @@ Bugs
that lack description or entry date (thanks to Nicholas Rabenau).
+ Fixed bug #1017, which exported invalid JSON when there were no tasks (thanks
to Nicholas Rabenau).
+ Fixed bug #1022, where dependencies were note released when a blocking task
was completed (thanks to Arkady Grudzinsky).
+ Fixed bug #1023, which applied default.project and default.priority during
modification (thanks to Christoph Lange).

9
NEWS
View file

@ -1,5 +1,5 @@
New Features in taskwarrior 2.0.1
New Features in taskwarrior 2.1.0
- The new 'project.indented' format is available and used in the 'projects'
and 'summary' commands.
@ -12,18 +12,19 @@ New Features in taskwarrior 2.0.1
Please refer to the ChangeLog file for full details. There are too many to
list here.
New commands in taskwarrior 2.0.1
New commands in taskwarrior 2.1.0
- New 'ready' report that lists tasks ready for work, sorted by urgency.
- New 'udas' command shows UDA details and warnings.
- New '_udas' helper command lists UDA names for completion purposes.
New configuration options in taskwarrior 2.0.1
New configuration options in taskwarrior 2.1.0
- urgency.scheduled.coefficient
- color.scheduled
- color.blocking
Newly deprecated features in taskwarrior 2.0.1
Newly deprecated features in taskwarrior 2.1.0
- None

View file

@ -741,6 +741,9 @@ Task is started, therefore active.
.B color.scheduled
Task is scheduled, therefore ready for work.
.br
.B color.blocking
Task is blocking another in a dependency.
.br
.B color.blocked
Task is blocked by a dependency.
.br
@ -935,7 +938,7 @@ Colors the output of the merge command.
.RE
.TP
.B rule.precedence.color=due.today,active,blocked,overdue,due,scheduled,keyword.,project.,tag.,recurring,pri,tagged,completed,deleted
.B rule.precedence.color=due.today,active,blocking,blocked,overdue,due,scheduled,keyword.,project.,tag.,recurring,pri,tagged,completed,deleted
.RS
This setting specifies the precedence of the color rules, from highest to
lowest. Note that the prefix 'color.' is omitted (for brevity), and that any

View file

@ -207,6 +207,7 @@ std::string Config::_defaults =
"color.pri.L=rgb245 # Color of priority:L tasks\n"
"color.tagged=rgb031 # Color of tagged tasks\n"
"color.blocked=white on color8 # Color of blocked tasks\n"
"color.blocking=white on color7 # Color of blocking tasks\n"
"#color.completed=on blue # Color of completed tasks\n"
"#color.deleted=on blue # Color of deleted tasks\n"
#else
@ -267,7 +268,7 @@ std::string Config::_defaults =
"# Here is the rule precedence order, highest to lowest.\n"
"# Note that these are just the color rule names, without the leading 'color.'\n"
"# and any trailing '.value'.\n"
"rule.precedence.color=due.today,active,blocked,overdue,due,scheduled,keyword.,project.,tag.,recurring,pri.,tagged,completed,deleted\n"
"rule.precedence.color=due.today,active,blocking,blocked,overdue,due,scheduled,keyword.,project.,tag.,recurring,pri.,tagged,completed,deleted\n"
"\n"
"# Shadow file support\n"
"#shadow.file=/tmp/shadow.txt # Location of shadow file\n"

View file

@ -67,6 +67,7 @@ TF2::TF2 ()
, _loaded_tasks (false)
, _loaded_lines (false)
, _has_ids (false)
, _auto_dep_scan (false)
{
}
@ -312,6 +313,9 @@ void TF2::load_tasks ()
}
}
if (_auto_dep_scan)
dependency_scan ();
_loaded_tasks = true;
}
@ -382,6 +386,12 @@ void TF2::has_ids ()
_has_ids = true;
}
////////////////////////////////////////////////////////////////////////////////
void TF2::auto_dep_scan ()
{
_auto_dep_scan = true;
}
////////////////////////////////////////////////////////////////////////////////
// Completely wipe it all clean.
void TF2::clear ()
@ -391,9 +401,10 @@ void TF2::clear ()
_loaded_tasks = false;
_loaded_lines = false;
// Note that the actual file name, and _has_ids are deliberately not cleared.
// Note that these are deliberately not cleared.
//_file._data = "";
//_has_ids = false;
//_auto_dep_scan = false;
_tasks.clear ();
_added_tasks.clear ();
@ -404,6 +415,49 @@ void TF2::clear ()
_U2I.clear ();
}
////////////////////////////////////////////////////////////////////////////////
// For any task that has depenencies, follow the chain of dependencies until the
// end. Along the way, update the Task::is_blocked and Task::is_blocking data
// cache.
void TF2::dependency_scan ()
{
// Iterate and modify TDB2 in-place. Don't do this at home.
std::vector <Task>::iterator left;
for (left = _tasks.begin ();
left != _tasks.end ();
++left)
{
if (left->has ("depends"))
{
std::vector <std::string> deps;
left->getDependencies (deps);
std::vector <std::string>::iterator d;
for (d = deps.begin (); d != deps.end (); ++d)
{
std::vector <Task>::iterator right;
for (right = _tasks.begin ();
right != _tasks.end ();
++right)
{
if (right->get ("uuid") == *d)
{
Task::status status = right->getStatus ();
if (status != Task::completed &&
status != Task::deleted)
{
left->is_blocked = true;
right->is_blocking = true;
}
break;
}
}
}
}
}
}
////////////////////////////////////////////////////////////////////////////////
const std::string TF2::dump ()
{
@ -454,6 +508,10 @@ TDB2::TDB2 ()
{
// Mark the pending file as the only one that has ID numbers.
pending.has_ids ();
// Indicate that dependencies should be automatically scanned on startup,
// setting Task::is_blocked and Task::is_blocking accordingly.
pending.auto_dep_scan ();
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -66,15 +66,20 @@ public:
int id (const std::string&);
void has_ids ();
void auto_dep_scan ();
void clear ();
const std::string dump ();
private:
void dependency_scan ();
public:
bool _read_only;
bool _dirty;
bool _loaded_tasks;
bool _loaded_lines;
bool _has_ids;
bool _auto_dep_scan;
std::vector <Task> _tasks;
std::vector <Task> _added_tasks;
std::vector <Task> _modified_tasks;

View file

@ -100,6 +100,8 @@ Task::Task ()
: id (0)
, urgency_value (0.0)
, recalc_urgency (true)
, is_blocked (false)
, is_blocking (false)
{
}
@ -119,6 +121,8 @@ Task& Task::operator= (const Task& other)
id = other.id;
urgency_value = other.urgency_value;
recalc_urgency = other.recalc_urgency;
is_blocked = other.is_blocked;
is_blocking = other.is_blocking;
}
return *this;
@ -1409,22 +1413,8 @@ float Task::urgency_waiting () const
// A task is blocked only if the task it depends upon is pending/waiting.
float Task::urgency_blocked () const
{
if (has ("depends"))
{
std::vector <std::string> deps;
getDependencies (deps);
std::vector <std::string>::iterator d;
for (d = deps.begin (); d != deps.end (); ++d)
{
Task t;
if (context.tdb2.get (*d, t) &&
(t.getStatus () == Task::pending || t.getStatus () == Task::waiting))
{
if (is_blocked)
return 1.0;
}
}
}
return 0.0;
}
@ -1524,7 +1514,7 @@ float Task::urgency_age () const
////////////////////////////////////////////////////////////////////////////////
float Task::urgency_blocking () const
{
if (dependencyIsBlocking (*this))
if (is_blocking)
return 1.0;
return 0.0;

View file

@ -59,6 +59,9 @@ public:
float urgency_value;
bool recalc_urgency;
bool is_blocked;
bool is_blocking;
// Series of helper functions.
static status textToStatus (const std::string&);
static std::string statusToText (status);

View file

@ -85,6 +85,7 @@ int CmdShow::execute (std::string& output)
" color.active"
" color.alternate"
" color.blocked"
" color.blocking"
" color.burndown.done"
" color.burndown.pending"
" color.burndown.started"

View file

@ -39,28 +39,6 @@
extern Context context;
////////////////////////////////////////////////////////////////////////////////
// A task is blocked if it depends on tasks that are pending or waiting.
//
// 1 --> 2(pending) = blocked
// 3 --> 4(completed) = not blocked any more
bool dependencyIsBlocked (const Task& task)
{
std::string depends = task.get ("depends");
if (depends != "")
{
const std::vector <Task>& all = context.tdb2.pending.get_tasks ();
std::vector <Task>::const_iterator it;
for (it = all.begin (); it != all.end (); ++it)
if ((it->getStatus () == Task::pending ||
it->getStatus () == Task::waiting) &&
depends.find (it->get ("uuid")) != std::string::npos)
return true;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
void dependencyGetBlocked (const Task& task, std::vector <Task>& blocked)
{
@ -76,26 +54,6 @@ void dependencyGetBlocked (const Task& task, std::vector <Task>& blocked)
blocked.push_back (*it);
}
////////////////////////////////////////////////////////////////////////////////
// To be a blocking task, there must be at least one other task that depends on
// this task, that is either pending or waiting.
bool dependencyIsBlocking (const Task& task)
{
std::string uuid = task.get ("uuid");
const std::vector <Task>& all = context.tdb2.pending.get_tasks ();
std::vector <Task>::const_iterator it;
for (it = all.begin (); it != all.end (); ++it)
if ((it->getStatus () == Task::pending ||
it->getStatus () == Task::waiting) &&
it->has ("depends") &&
it->get ("depends").find (uuid) != std::string::npos)
return true;
return false;
}
////////////////////////////////////////////////////////////////////////////////
void dependencyGetBlocking (const Task& task, std::vector <Task>& blocking)
{

View file

@ -59,9 +59,7 @@ std::string colorizeError (const std::string&);
std::string colorizeDebug (const std::string&);
// dependency.cpp
bool dependencyIsBlocked (const Task&);
void dependencyGetBlocked (const Task&, std::vector <Task>&);
bool dependencyIsBlocking (const Task&);
void dependencyGetBlocking (const Task&, std::vector <Task>&);
bool dependencyIsCircular (const Task&);
void dependencyChainOnComplete (Task&);

View file

@ -510,7 +510,7 @@ bool nag (Task& task)
if (pri == 'M' && !overdue && !high ) return false;
if (pri == 'L' && !overdue && !high && !medium ) return false;
if (pri == ' ' && !overdue && !high && !medium && !low ) return false;
if (dependencyIsBlocking (task) && !dependencyIsBlocked (task)) return false;
if (task.is_blocking && !task.is_blocked ) return false;
// All the excuses are made, all that remains is to nag the user.
context.footnote (nagMessage);

View file

@ -27,7 +27,6 @@
#define L10N // Localization complete.
#include <iostream>
#include <stdlib.h>
#include <Context.h>
#include <Date.h>
@ -84,85 +83,80 @@ void initializeColorRules ()
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeBlocked (Task& task, const std::string& rule, Color& c)
static void colorizeBlocked (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (dependencyIsBlocked (task))
c.blend (gsColor[rule]);
if (task.is_blocked)
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeTagged (Task& task, const std::string& rule, Color& c)
static void colorizeBlocking (Task& task, const Color& base, Color& c)
{
if (task.is_blocking)
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeTagged (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.getTagCount ())
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizePriorityL (Task& task, const std::string& rule, Color& c)
static void colorizePriorityL (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.get ("priority") == "L")
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizePriorityM (Task& task, const std::string& rule, Color& c)
static void colorizePriorityM (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.get ("priority") == "M")
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizePriorityH (Task& task, const std::string& rule, Color& c)
static void colorizePriorityH (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.get ("priority") == "H")
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizePriorityNone (Task& task, const std::string& rule, Color& c)
static void colorizePriorityNone (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.get ("priority") == "")
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeActive (Task& task, const std::string& rule, Color& c)
static void colorizeActive (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.has ("start") &&
!task.has ("end"))
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeScheduled (Task& task, const std::string& rule, Color& c)
static void colorizeScheduled (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.has ("scheduled") &&
Date (task.get_date ("scheduled")) <= now)
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeTag (Task& task, const std::string& rule, Color& c)
static void colorizeTag (Task& task, const std::string& rule, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.hasTag (rule.substr (10)))
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeProject (Task& task, const std::string& rule, Color& c)
static void colorizeProject (Task& task, const std::string& rule, const Color& base, Color& c)
{
if (!gsColor[rule].nontrivial ())
return;
// Observe the case sensitivity setting.
bool sensitive = context.config.getBoolean ("search.case.sensitive");
@ -172,38 +166,33 @@ static void colorizeProject (Task& task, const std::string& rule, Color& c)
// Match project names leftmost.
if (rule_trunc.length () <= project.length ())
if (compare (rule_trunc, project.substr (0, rule_trunc.length ()), sensitive))
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeProjectNone (Task& task, const std::string& rule, Color& c)
static void colorizeProjectNone (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.get ("project") == "")
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeTagNone (Task& task, const std::string& rule, Color& c)
static void colorizeTagNone (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.getTagCount () == 0)
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeKeyword (Task& task, const std::string& rule, Color& c)
static void colorizeKeyword (Task& task, const std::string& rule, const Color& base, Color& c)
{
if (!gsColor[rule].nontrivial ())
return;
// Observe the case sensitivity setting.
bool sensitive = context.config.getBoolean ("search.case.sensitive");
// The easiest thing to check is the description, because it is just one
// attribute.
if (find (task.get ("description"), rule.substr (14), sensitive) != std::string::npos)
c.blend (gsColor[rule]);
c.blend (base);
// Failing the description check, look at all annotations, returning on the
// first match.
@ -215,7 +204,7 @@ static void colorizeKeyword (Task& task, const std::string& rule, Color& c)
if (it->first.substr (0, 11) == "annotation_" &&
find (it->second, rule.substr (14), sensitive) != std::string::npos)
{
c.blend (gsColor[rule]);
c.blend (base);
return;
}
}
@ -223,11 +212,8 @@ static void colorizeKeyword (Task& task, const std::string& rule, Color& c)
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeDue (Task& task, const std::string& rule, Color& c)
static void colorizeDue (Task& task, const Color& base, Color& c)
{
if (!gsColor[rule].nontrivial ())
return;
Task::status status = task.getStatus ();
if (task.has ("due") &&
@ -235,16 +221,13 @@ static void colorizeDue (Task& task, const std::string& rule, Color& c)
status != Task::deleted)
{
if (getDueState (task.get ("due")) == 1)
c.blend (gsColor[rule]);
c.blend (base);
}
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeDueToday (Task& task, const std::string& rule, Color& c)
static void colorizeDueToday (Task& task, const Color& base, Color& c)
{
if (!gsColor[rule].nontrivial ())
return;
Task::status status = task.getStatus ();
if (task.has ("due") &&
@ -252,16 +235,13 @@ static void colorizeDueToday (Task& task, const std::string& rule, Color& c)
status != Task::deleted)
{
if (getDueState (task.get ("due")) == 2)
c.blend (gsColor[rule]);
c.blend (base);
}
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeOverdue (Task& task, const std::string& rule, Color& c)
static void colorizeOverdue (Task& task, const Color& base, Color& c)
{
if (!gsColor[rule].nontrivial ())
return;
Task::status status = task.getStatus ();
if (task.has ("due") &&
@ -269,32 +249,29 @@ static void colorizeOverdue (Task& task, const std::string& rule, Color& c)
status != Task::deleted)
{
if (getDueState (task.get ("due")) == 3)
c.blend (gsColor[rule]);
c.blend (base);
}
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeRecurring (Task& task, const std::string& rule, Color& c)
static void colorizeRecurring (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.has ("recur"))
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeCompleted (Task& task, const std::string& rule, Color& c)
static void colorizeCompleted (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.getStatus () == Task::completed)
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
static void colorizeDeleted (Task& task, const std::string& rule, Color& c)
static void colorizeDeleted (Task& task, const Color& base, Color& c)
{
if (gsColor[rule].nontrivial ())
if (task.getStatus () == Task::completed)
c.blend (gsColor[rule]);
c.blend (base);
}
////////////////////////////////////////////////////////////////////////////////
@ -314,27 +291,32 @@ void autoColorize (Task& task, Color& c)
std::vector <std::string>::reverse_iterator r;
for (r = gsPrecedence.rbegin (); r != gsPrecedence.rend (); ++r)
{
if (*r == "color.blocked") colorizeBlocked (task, *r, c);
else if (*r == "color.tagged") colorizeTagged (task, *r, c);
else if (*r == "color.pri.L") colorizePriorityL (task, *r, c);
else if (*r == "color.pri.M") colorizePriorityM (task, *r, c);
else if (*r == "color.pri.H") colorizePriorityH (task, *r, c);
else if (*r == "color.pri.none") colorizePriorityNone (task, *r, c);
else if (*r == "color.active") colorizeActive (task, *r, c);
else if (*r == "color.scheduled") colorizeScheduled (task, *r, c);
else if (*r == "color.project.none") colorizeProjectNone (task, *r, c);
else if (*r == "color.tag.none") colorizeTagNone (task, *r, c);
else if (*r == "color.due") colorizeDue (task, *r, c);
else if (*r == "color.due.today") colorizeDueToday (task, *r, c);
else if (*r == "color.overdue") colorizeOverdue (task, *r, c);
else if (*r == "color.recurring") colorizeRecurring (task, *r, c);
else if (*r == "color.completed") colorizeCompleted (task, *r, c);
else if (*r == "color.deleted") colorizeDeleted (task, *r, c);
Color base = gsColor[*r];
if (base.nontrivial ())
{
if (*r == "color.blocked") colorizeBlocked (task, base, c);
else if (*r == "color.blocking") colorizeBlocking (task, base, c);
else if (*r == "color.tagged") colorizeTagged (task, base, c);
else if (*r == "color.pri.L") colorizePriorityL (task, base, c);
else if (*r == "color.pri.M") colorizePriorityM (task, base, c);
else if (*r == "color.pri.H") colorizePriorityH (task, base, c);
else if (*r == "color.pri.none") colorizePriorityNone (task, base, c);
else if (*r == "color.active") colorizeActive (task, base, c);
else if (*r == "color.scheduled") colorizeScheduled (task, base, c);
else if (*r == "color.project.none") colorizeProjectNone (task, base, c);
else if (*r == "color.tag.none") colorizeTagNone (task, base, c);
else if (*r == "color.due") colorizeDue (task, base, c);
else if (*r == "color.due.today") colorizeDueToday (task, base, c);
else if (*r == "color.overdue") colorizeOverdue (task, base, c);
else if (*r == "color.recurring") colorizeRecurring (task, base, c);
else if (*r == "color.completed") colorizeCompleted (task, base, c);
else if (*r == "color.deleted") colorizeDeleted (task, base, c);
// Wildcards
else if (r->substr (0, 10) == "color.tag.") colorizeTag (task, *r, c);
else if (r->substr (0, 14) == "color.project.") colorizeProject (task, *r, c);
else if (r->substr (0, 14) == "color.keyword.") colorizeKeyword (task, *r, c);
else if (r->substr (0, 10) == "color.tag.") colorizeTag (task, *r, base, c);
else if (r->substr (0, 14) == "color.project.") colorizeProject (task, *r, base, c);
else if (r->substr (0, 14) == "color.keyword.") colorizeKeyword (task, *r, base, c);
}
}
}

View file

@ -38,6 +38,7 @@ if (open my $fh, '>', 'special.rc')
"color.alternate=\n",
"color.tagged=\n",
"color.pri.H=\n",
"color.completed=\n",
"nag=NAG\n",
"_forcecolor=1\n";
close $fh;
@ -47,6 +48,7 @@ if (open my $fh, '>', 'special.rc')
# Prove that +nocolor suppresses all color for a task.
qx{../src/task rc:special.rc add should have no red +nocolor priority:H 2>&1};
qx{../src/task rc:special.rc add should be red +nonag 2>&1};
my $output = qx{../src/task rc:special.rc ls 2>&1};
like ($output, qr/\s1\s+H\s+should have no red/, 'no red in first task due to +nocolor');
like ($output, qr/\033\[31mshould be red\s+\033\[0m/, 'red in second task');