mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Undo
- Stubbed the undo implementation in TB2::revert (undo as a name is already taken), and in the process lose the capability. - Add detection of <modifications> when running the 'undo' command and generate an error.
This commit is contained in:
parent
94ce784f33
commit
c1f33a23d3
4 changed files with 346 additions and 4 deletions
339
src/TDB2.cpp
339
src/TDB2.cpp
|
@ -567,6 +567,345 @@ void TDB2::synch ()
|
|||
context.timer_synch.stop ();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void TDB2::revert ()
|
||||
{
|
||||
/*
|
||||
Directory location (context.config.get ("data.location"));
|
||||
|
||||
std::string undoFile = location._data + "/undo.data";
|
||||
std::string pendingFile = location._data + "/pending.data";
|
||||
std::string completedFile = location._data + "/completed.data";
|
||||
|
||||
// load undo.data
|
||||
std::vector <std::string> u;
|
||||
File::read (undoFile, u);
|
||||
|
||||
if (u.size () < 3)
|
||||
throw std::string ("There are no recorded transactions to undo.");
|
||||
|
||||
// pop last tx
|
||||
u.pop_back (); // separator.
|
||||
|
||||
std::string current = u.back ().substr (4);
|
||||
u.pop_back ();
|
||||
|
||||
std::string prior;
|
||||
std::string when;
|
||||
if (u.back ().substr (0, 5) == "time ")
|
||||
{
|
||||
when = u.back ().substr (5);
|
||||
u.pop_back ();
|
||||
prior = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
prior = u.back ().substr (4);
|
||||
u.pop_back ();
|
||||
when = u.back ().substr (5);
|
||||
u.pop_back ();
|
||||
}
|
||||
|
||||
Date lastChange (atoi (when.c_str ()));
|
||||
|
||||
// Set the colors.
|
||||
Color color_red (context.color () ? context.config.get ("color.undo.before") : "");
|
||||
Color color_green (context.color () ? context.config.get ("color.undo.after") : "");
|
||||
|
||||
if (context.config.get ("undo.style") == "side")
|
||||
{
|
||||
std::cout << "\n"
|
||||
<< "The last modification was made "
|
||||
<< lastChange.toString ()
|
||||
<< "\n";
|
||||
|
||||
// Attributes are all there is, so figure the different attribute names
|
||||
// between before and after.
|
||||
ViewText view;
|
||||
view.width (context.getWidth ());
|
||||
view.intraPadding (2);
|
||||
view.add (Column::factory ("string", ""));
|
||||
view.add (Column::factory ("string", "Prior Values"));
|
||||
view.add (Column::factory ("string", "Current Values"));
|
||||
|
||||
Task after (current);
|
||||
|
||||
if (prior != "")
|
||||
{
|
||||
Task before (prior);
|
||||
|
||||
std::vector <std::string> beforeAtts;
|
||||
foreach (att, before)
|
||||
beforeAtts.push_back (att->first);
|
||||
|
||||
std::vector <std::string> afterAtts;
|
||||
foreach (att, after)
|
||||
afterAtts.push_back (att->first);
|
||||
|
||||
std::vector <std::string> beforeOnly;
|
||||
std::vector <std::string> afterOnly;
|
||||
listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly);
|
||||
|
||||
int row;
|
||||
foreach (name, beforeOnly)
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, *name);
|
||||
view.set (row, 1, renderAttribute (*name, before.get (*name)), color_red);
|
||||
}
|
||||
|
||||
foreach (name, before)
|
||||
{
|
||||
std::string priorValue = before.get (name->first);
|
||||
std::string currentValue = after.get (name->first);
|
||||
|
||||
if (currentValue != "")
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, name->first);
|
||||
view.set (row, 1, renderAttribute (name->first, priorValue),
|
||||
(priorValue != currentValue ? color_red : Color ()));
|
||||
view.set (row, 2, renderAttribute (name->first, currentValue),
|
||||
(priorValue != currentValue ? color_green : Color ()));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (name, afterOnly)
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, *name);
|
||||
view.set (row, 2, renderAttribute (*name, after.get (*name)), color_green);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int row;
|
||||
foreach (name, after)
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, name->first);
|
||||
view.set (row, 2, renderAttribute (name->first, after.get (name->first)), color_green);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n"
|
||||
<< view.render ()
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
// This style looks like this:
|
||||
// --- before 2009-07-04 00:00:25.000000000 +0200
|
||||
// +++ after 2009-07-04 00:00:45.000000000 +0200
|
||||
//
|
||||
// - name: old // att deleted
|
||||
// + name:
|
||||
//
|
||||
// - name: old // att changed
|
||||
// + name: new
|
||||
//
|
||||
// - name:
|
||||
// + name: new // att added
|
||||
//
|
||||
else if (context.config.get ("undo.style") == "diff")
|
||||
{
|
||||
// Create reference tasks.
|
||||
Task before;
|
||||
if (prior != "")
|
||||
before.parse (prior);
|
||||
|
||||
Task after (current);
|
||||
|
||||
// Generate table header.
|
||||
ViewText view;
|
||||
view.width (context.getWidth ());
|
||||
view.intraPadding (2);
|
||||
view.add (Column::factory ("string", ""));
|
||||
view.add (Column::factory ("string", ""));
|
||||
|
||||
int row = view.addRow ();
|
||||
view.set (row, 0, "--- previous state", color_red);
|
||||
view.set (row, 1, "Undo will restore this state", color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, "+++ current state ", color_green); // Note trailing space.
|
||||
view.set (row, 1, "Change made " + lastChange.toString (context.config.get ("dateformat")), color_green);
|
||||
|
||||
view.addRow ();
|
||||
|
||||
// Add rows to table showing diffs.
|
||||
std::vector <std::string> all = context.getColumns ();
|
||||
|
||||
// Now factor in the annotation attributes.
|
||||
Task::iterator it;
|
||||
for (it = before.begin (); it != before.end (); ++it)
|
||||
if (it->first.substr (0, 11) == "annotation_")
|
||||
all.push_back (it->first);
|
||||
|
||||
for (it = after.begin (); it != after.end (); ++it)
|
||||
if (it->first.substr (0, 11) == "annotation_")
|
||||
all.push_back (it->first);
|
||||
|
||||
// Now render all the attributes.
|
||||
std::sort (all.begin (), all.end ());
|
||||
|
||||
std::string before_att;
|
||||
std::string after_att;
|
||||
std::string last_att;
|
||||
foreach (a, all)
|
||||
{
|
||||
if (*a != last_att) // Skip duplicates.
|
||||
{
|
||||
last_att = *a;
|
||||
|
||||
before_att = before.get (*a);
|
||||
after_att = after.get (*a);
|
||||
|
||||
// Don't report different uuid.
|
||||
// Show nothing if values are the unchanged.
|
||||
if (*a == "uuid" ||
|
||||
before_att == after_att)
|
||||
{
|
||||
// Show nothing - no point displaying that which did not change.
|
||||
|
||||
// row = view.addRow ();
|
||||
// view.set (row, 0, *a + ":");
|
||||
// view.set (row, 1, before_att);
|
||||
}
|
||||
|
||||
// Attribute deleted.
|
||||
else if (before_att != "" && after_att == "")
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, "-" + *a + ":", color_red);
|
||||
view.set (row, 1, before_att, color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, "+" + *a + ":", color_green);
|
||||
}
|
||||
|
||||
// Attribute added.
|
||||
else if (before_att == "" && after_att != "")
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, "-" + *a + ":", color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, "+" + *a + ":", color_green);
|
||||
view.set (row, 1, after_att, color_green);
|
||||
}
|
||||
|
||||
// Attribute changed.
|
||||
else
|
||||
{
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, "-" + *a + ":", color_red);
|
||||
view.set (row, 1, before_att, color_red);
|
||||
|
||||
row = view.addRow ();
|
||||
view.set (row, 0, "+" + *a + ":", color_green);
|
||||
view.set (row, 1, after_att, color_green);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "\n"
|
||||
<< view.render ()
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
// Output displayed, now confirm.
|
||||
if (context.config.getBoolean ("confirmation") &&
|
||||
!confirm ("The undo command is not reversible. Are you sure you want to revert to the previous state?"))
|
||||
{
|
||||
std::cout << "No changes made.\n";
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract identifying uuid.
|
||||
std::string uuid;
|
||||
std::string::size_type uuidAtt = current.find ("uuid:\"");
|
||||
if (uuidAtt != std::string::npos)
|
||||
uuid = current.substr (uuidAtt, 43); // 43 = uuid:"..."
|
||||
else
|
||||
throw std::string ("Cannot locate UUID in task to undo.");
|
||||
|
||||
// load pending.data
|
||||
std::vector <std::string> p;
|
||||
File::read (pendingFile, p);
|
||||
|
||||
// is 'current' in pending?
|
||||
foreach (task, p)
|
||||
{
|
||||
if (task->find (uuid) != std::string::npos)
|
||||
{
|
||||
context.debug ("TDB::undo - task found in pending.data");
|
||||
|
||||
// Either revert if there was a prior state, or remove the task.
|
||||
if (prior != "")
|
||||
{
|
||||
*task = prior;
|
||||
std::cout << "Modified task reverted.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
p.erase (task);
|
||||
std::cout << "Task removed.\n";
|
||||
}
|
||||
|
||||
// Rewrite files.
|
||||
File::write (pendingFile, p);
|
||||
File::write (undoFile, u);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// load completed.data
|
||||
std::vector <std::string> c;
|
||||
File::read (completedFile, c);
|
||||
|
||||
// is 'current' in completed?
|
||||
foreach (task, c)
|
||||
{
|
||||
if (task->find (uuid) != std::string::npos)
|
||||
{
|
||||
context.debug ("TDB::undo - task found in completed.data");
|
||||
|
||||
// If task now belongs back in pending.data
|
||||
if (prior.find ("status:\"pending\"") != std::string::npos ||
|
||||
prior.find ("status:\"waiting\"") != std::string::npos ||
|
||||
prior.find ("status:\"recurring\"") != std::string::npos)
|
||||
{
|
||||
c.erase (task);
|
||||
p.push_back (prior);
|
||||
File::write (completedFile, c);
|
||||
File::write (pendingFile, p);
|
||||
File::write (undoFile, u);
|
||||
std::cout << "Modified task reverted.\n";
|
||||
context.debug ("TDB::undo - task belongs in pending.data");
|
||||
}
|
||||
else
|
||||
{
|
||||
*task = prior;
|
||||
File::write (completedFile, c);
|
||||
File::write (undoFile, u);
|
||||
std::cout << "Modified task reverted.\n";
|
||||
context.debug ("TDB::undo - task belongs in completed.data");
|
||||
}
|
||||
|
||||
std::cout << "Undo complete.\n";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Perhaps user hand-edited the data files?
|
||||
// Perhaps the task was in completed.data, which was still in file format 3?
|
||||
std::cout << "Task with UUID "
|
||||
<< uuid.substr (6, 36)
|
||||
<< " not found in data.\n"
|
||||
<< "No undo possible.\n";
|
||||
*/
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Scans the pending tasks for any that are completed or deleted, and if so,
|
||||
// moves them to the completed.data file. Returns a count of tasks moved.
|
||||
|
|
|
@ -97,6 +97,7 @@ public:
|
|||
void modify (const Task&);
|
||||
void commit ();
|
||||
void synch ();
|
||||
void revert ();
|
||||
int gc ();
|
||||
int next_id ();
|
||||
|
||||
|
|
|
@ -46,11 +46,12 @@ CmdUndo::CmdUndo ()
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
int CmdUndo::execute (std::string& output)
|
||||
{
|
||||
// TODO Detect attemps to modify the task.
|
||||
// Detect attemps to modify the task.
|
||||
if (context.a3.extract_modifications ().size () > 0)
|
||||
throw STRING_CMD_UNDO_MODS;
|
||||
|
||||
context.tdb.lock (context.config.getBoolean ("locking"));
|
||||
context.tdb.undo ();
|
||||
context.tdb.unlock ();
|
||||
context.tdb2.revert ();
|
||||
context.tdb2.commit ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -203,6 +203,7 @@
|
|||
#define STRING_CMD_INFO_MODIFICATION "Modification"
|
||||
#define STRING_CMD_INFO_TOTAL_ACTIVE "Total active time"
|
||||
#define STRING_CMD_UNDO_USAGE "Reverts the most recent change to a task."
|
||||
#define STRING_CMD_UNDO_MODS "The undo command does not allow further task modification."
|
||||
#define STRING_CMD_STATS_USAGE "Shows task database statistics."
|
||||
#define STRING_CMD_STATS_CATEGORY "Category"
|
||||
#define STRING_CMD_STATS_DATA "Data"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue